diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-01-25 18:31:31 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-01-25 18:31:31 +0000 |
commit | 9243becbedbb6a1592208051f8fa2b090dcc5e7d (patch) | |
tree | 607c2a862ec3f4399b8766383f6f8e04c4aa43b4 /src | |
parent | 9e40b6e9e1bc67f2d856adb837ee64dd0e25b717 (diff) | |
parent | 3c48d3c83fc21dbc0841f9210f04bdb073d73cd1 (diff) | |
download | rneovim-9243becbedbb6a1592208051f8fa2b090dcc5e7d.tar.gz rneovim-9243becbedbb6a1592208051f8fa2b090dcc5e7d.tar.bz2 rneovim-9243becbedbb6a1592208051f8fa2b090dcc5e7d.zip |
Merge remote-tracking branch 'upstream/master' into usermarksusermarks
Diffstat (limited to 'src')
537 files changed, 78535 insertions, 68610 deletions
diff --git a/src/Doxyfile b/src/Doxyfile deleted file mode 100644 index e085e4e198..0000000000 --- a/src/Doxyfile +++ /dev/null @@ -1,2566 +0,0 @@ -# Doxyfile 1.9.0 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the configuration -# file that follow. The default is UTF-8 which is also the encoding used for all -# text before the first occurrence of this tag. Doxygen uses libiconv (or the -# iconv built into libc) for the transcoding. See -# https://www.gnu.org/software/libiconv/ for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = Neovim - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = - -# With the PROJECT_LOGO tag one can specify a logo or an icon that is included -# in the documentation. The maximum height of the logo should not exceed 55 -# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy -# the logo to the output directory. - -PROJECT_LOGO = contrib/doxygen/logo-devdoc.png - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = build/doxygen - -# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - -ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - -# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = NO - -# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line -# such as -# /*************** -# as being the beginning of a Javadoc-style comment "banner". If set to NO, the -# Javadoc-style will behave just like regular comments and it will not be -# interpreted by doxygen. -# The default value is: NO. - -JAVADOC_BANNER = NO - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# By default Python docstrings are displayed as preformatted text and doxygen's -# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the -# doxygen's special commands can be used and the contents of the docstring -# documentation blocks is shown as doxygen documentation. -# The default value is: YES. - -PYTHON_DOCSTRING = YES - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new -# page for each member. If set to NO, the documentation of a member will be part -# of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines (in the resulting output). You can put ^^ in the value part of an -# alias to insert a newline as if a physical newline was in the original file. -# When you need a literal { or } or , in the value part of an alias you have to -# escape them by means of a backslash (\), this can lead to conflicts with the -# commands \{ and \} for these it is advised to use the version @{ and @} or use -# a double escape (\\{ and \\}) - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = YES - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice -# sources only. Doxygen will then generate output that is more tailored for that -# language. For instance, namespaces will be presented as modules, types will be -# separated into more groups, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_SLICE = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, -# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: -# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser -# tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files). For instance to make doxygen treat .inc files -# as Fortran files (default is PHP), and .f files as C (default is Fortran), -# use: inc=Fortran f=C. -# -# Note: For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. When specifying no_extension you should add -# * to the FILE_PATTERNS. -# -# Note see also the list of default file extension mappings. - -EXTENSION_MAPPING = lua=C - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See https://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = YES - -# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up -# to that level are automatically included in the table of contents, even if -# they do not have an id attribute. -# Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 5. -# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. - -TOC_INCLUDE_HEADINGS = 5 - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by putting a % sign in front of the word or -# globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# If one adds a struct or class to a group and this option is enabled, then also -# any nested class or struct is added to the same group. By default this option -# is disabled and one has to add nested compounds explicitly via \ingroup. -# The default value is: NO. - -GROUP_NESTED_COMPOUNDS = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = NO - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use -# during processing. When set to 0 doxygen will based this on the number of -# cores available in the system. You can set it explicitly to a value larger -# than 0 to get more control over the balance between CPU load and processing -# speed. At this moment only the input processing can be done using multiple -# threads. Since this is still an experimental feature the default is set to 1, -# which efficively disables parallel processing. Please report any issues you -# encounter. Generating dot graphs in parallel is controlled by the -# DOT_NUM_THREADS setting. -# Minimum value: 0, maximum value: 32, default value: 1. - -NUM_PROC_THREADS = 1 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual -# methods of a class will be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIV_VIRTUAL = NO - -# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO, -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. If set to YES, local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO, only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If this flag is set to YES, the name of an unnamed parameter in a declaration -# will be determined by the corresponding definition. By default unnamed -# parameters remain unnamed in the output. -# The default value is: YES. - -RESOLVE_UNNAMED_PARAMS = YES - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO, these classes will be included in the various overviews. This option -# has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# declarations. If set to NO, these declarations will be included in the -# documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO, these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = NO - -# With the correct setting of option CASE_SENSE_NAMES doxygen will better be -# able to match the capabilities of the underlying filesystem. In case the -# filesystem is case sensitive (i.e. it supports files in the same directory -# whose names only differ in casing), the option must be set to YES to properly -# deal with such files in case they appear in the input. For filesystems that -# are not case sensitive the option should be be set to NO to properly deal with -# output files written for symbols that only differ in casing, such as for two -# classes, one named CLASS and the other named Class, and to also support -# references to files without having to specify the exact matching casing. On -# Windows (including Cygwin) and MacOS, users should typically set this option -# to NO, whereas on Linux or other Unix flavors it should typically be set to -# YES. -# The default value is: system dependent. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES, the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will -# append additional text to a page's title, such as Class Reference. If set to -# YES the compound reference will be hidden. -# The default value is: NO. - -HIDE_COMPOUND_REFERENCE= NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO, the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo -# list. This list is created by putting \todo commands in the documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test -# list. This list is created by putting \test commands in the documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if <section_label> ... \endif and \cond <section_label> -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES, the -# list will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = YES - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = YES - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = YES - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO, doxygen will only warn about wrong or incomplete -# parameter documentation, but not about the absence of documentation. If -# EXTRACT_ALL is set to YES then this flag will automatically be disabled. -# The default value is: NO. - -WARN_NO_PARAMDOC = NO - -# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS -# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but -# at the end of the doxygen process doxygen will return with a non-zero status. -# Possible values are: NO, YES and FAIL_ON_WARNINGS. -# The default value is: NO. - -WARN_AS_ERROR = NO - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING -# Note: If this tag is empty the current directory is searched. - -INPUT = src/ - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: -# https://www.gnu.org/software/libiconv/) for the list of possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# read by doxygen. -# -# Note the list of default checked file patterns might differ from the list of -# default file extension mappings. -# -# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, -# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, -# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), -# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, -# *.ucf, *.qsf and *.ice. - -FILE_PATTERNS = *.h \ - *.c \ - *.lua - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# <filter> <input-file> -# -# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. -# -# Note that for custom extensions or not directly supported extensions you also -# need to set EXTENSION_MAPPING for the extension otherwise the files are not -# properly processed by doxygen. - -FILTER_PATTERNS = *.lua=scripts/lua2dox_filter - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# entity all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see https://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = contrib/doxygen/header.html - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = contrib/doxygen/footer.html - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = contrib/doxygen/customdoxygen.css - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefore more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = contrib/doxygen/extra.css - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the style sheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# https://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = YES - -# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML -# documentation will contain a main index with vertical navigation menus that -# are dynamically created via JavaScript. If disabled, the navigation index will -# consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have JavaScript, -# like the Qt help browser. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_MENUS = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: -# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To -# create a documentation set, doxygen will generate a Makefile in the HTML -# output directory. Running make will produce the docset in that directory and -# running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy -# genXcode/_index.html for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: -# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler (hhc.exe). If non-empty, -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the main .chm file (NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated -# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location (absolute path -# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to -# run qhelpgenerator on the generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg -# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see -# https://inkscape.org) to generate formulas as SVG images instead of PNGs for -# the HTML output. These images will generally look nicer at scaled resolutions. -# Possible values are: png (the default) and svg (looks nicer but requires the -# pdf2svg or inkscape tool). -# The default value is: png. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FORMULA_FORMAT = png - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands -# to create new LaTeX commands to be used in formulas as building blocks. See -# the section "Including formulas" for details. - -FORMULA_MACROFILE = - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side JavaScript for the rendering -# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: -# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use <access key> + S -# (what the <access key> is depends on the OS and browser, but it is typically -# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down -# key> to jump into the search results window, the results can be navigated -# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel -# the search. The filter options can be selected when the cursor is inside the -# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> -# to select a filter and <Enter> or <escape> to activate or cancel the filter -# option. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -SEARCHENGINE = YES - -# When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using JavaScript. There -# are two flavors of web server based searching depending on the EXTERNAL_SEARCH -# setting. When disabled, doxygen will generate a PHP script for searching and -# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing -# and searching needs to be provided by external tools. See the section -# "External Indexing and Searching" for details. -# The default value is: NO. -# This tag requires that the tag SEARCHENGINE is set to YES. - -SERVER_BASED_SEARCH = NO - -# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP -# script for searching. Instead the search results are written to an XML file -# which needs to be processed by an external indexer. Doxygen will invoke an -# external search engine pointed to by the SEARCHENGINE_URL option to obtain the -# search results. -# -# Doxygen ships with an example indexer (doxyindexer) and search engine -# (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: -# https://xapian.org/). -# -# See the section "External Indexing and Searching" for details. -# The default value is: NO. -# This tag requires that the tag SEARCHENGINE is set to YES. - -EXTERNAL_SEARCH = NO - -# The SEARCHENGINE_URL should point to a search engine hosted by a web server -# which will return the search results when EXTERNAL_SEARCH is enabled. -# -# Doxygen ships with an example indexer (doxyindexer) and search engine -# (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: -# https://xapian.org/). See the section "External Indexing and Searching" for -# details. -# This tag requires that the tag SEARCHENGINE is set to YES. - -SEARCHENGINE_URL = - -# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed -# search data is written to a file for indexing by an external tool. With the -# SEARCHDATA_FILE tag the name of this file can be specified. -# The default file is: searchdata.xml. -# This tag requires that the tag SEARCHENGINE is set to YES. - -SEARCHDATA_FILE = searchdata.xml - -# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the -# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is -# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple -# projects and redirect the results back to the right project. -# This tag requires that the tag SEARCHENGINE is set to YES. - -EXTERNAL_SEARCH_ID = - -# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen -# projects other than the one defined by this configuration file, but that are -# all added to the same external search index. Each project needs to have a -# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of -# to a relative location where the documentation can be found. The format is: -# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... -# This tag requires that the tag SEARCHENGINE is set to YES. - -EXTRA_SEARCH_MAPPINGS = - -#--------------------------------------------------------------------------- -# Configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. -# The default value is: YES. - -GENERATE_LATEX = YES - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: latex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. -# -# Note that when not enabling USE_PDFLATEX the default is latex when enabling -# USE_PDFLATEX the default is pdflatex and when in the later case latex is -# chosen this is overwritten by pdflatex. For specific output languages the -# default can have been set differently, this depends on the implementation of -# the output language. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate -# index for LaTeX. -# Note: This tag is used in the Makefile / make.bat. -# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file -# (.tex). -# The default file is: makeindex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -MAKEINDEX_CMD_NAME = makeindex - -# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to -# generate index for LaTeX. In case there is no backslash (\) as first character -# it will be automatically added in the LaTeX code. -# Note: This tag is used in the generated output file (.tex). -# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. -# The default value is: makeindex. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_MAKEINDEX_CMD = makeindex - -# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX -# documents. This may be useful for small projects and may help to save some -# trees in general. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used by the -# printer. -# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x -# 14 inches) and executive (7.25 x 10.5 inches). -# The default value is: a4. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -PAPER_TYPE = a4 - -# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names -# that should be included in the LaTeX output. The package can be specified just -# by its name or with the correct syntax as to be used with the LaTeX -# \usepackage command. To get the times font for instance you can specify : -# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} -# To use the option intlimits with the amsmath package you can specify: -# EXTRA_PACKAGES=[intlimits]{amsmath} -# If left blank no extra packages will be included. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the -# generated LaTeX document. The header should contain everything until the first -# chapter. If it is left blank doxygen will generate a standard header. See -# section "Doxygen usage" for information on how to let doxygen write the -# default header to a separate file. -# -# Note: Only use a user-defined header if you know what you are doing! The -# following commands have a special meaning inside the header: $title, -# $datetime, $date, $doxygenversion, $projectname, $projectnumber, -# $projectbrief, $projectlogo. Doxygen will replace $title with the empty -# string, for the replacement values of the other commands the user is referred -# to HTML_HEADER. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_HEADER = - -# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the -# generated LaTeX document. The footer should contain everything after the last -# chapter. If it is left blank doxygen will generate a standard footer. See -# LATEX_HEADER for more information on how to generate a default footer and what -# special commands can be used inside the footer. -# -# Note: Only use a user-defined footer if you know what you are doing! -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_FOOTER = - -# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# LaTeX style sheets that are included after the standard style sheets created -# by doxygen. Using this option one can overrule certain style aspects. Doxygen -# will copy the style sheet files to the output directory. -# Note: The order of the extra style sheet files is of importance (e.g. the last -# style sheet in the list overrules the setting of the previous ones in the -# list). -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EXTRA_STYLESHEET = - -# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the LATEX_OUTPUT output -# directory. Note that the files will be copied as-is; there are no commands or -# markers available. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EXTRA_FILES = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is -# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will -# contain links (just like the HTML output) instead of page references. This -# makes the output suitable for online browsing using a PDF viewer. -# The default value is: YES. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -PDF_HYPERLINKS = YES - -# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as -# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX -# files. Set this option to YES, to get a higher quality PDF documentation. -# -# See also section LATEX_CMD_NAME for selecting the engine. -# The default value is: YES. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -USE_PDFLATEX = YES - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode -# command to the generated LaTeX files. This will instruct LaTeX to keep running -# if errors occur, instead of asking the user for help. This option is also used -# when generating formulas in HTML. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_BATCHMODE = NO - -# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the -# index chapters (such as File Index, Compound Index, etc.) in the output. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_HIDE_INDICES = NO - -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - -# The LATEX_BIB_STYLE tag can be used to specify the style to use for the -# bibliography, e.g. plainnat, or ieeetr. See -# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. -# The default value is: plain. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_BIB_STYLE = plain - -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - -# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) -# path from which the emoji images will be read. If a relative path is entered, -# it will be relative to the LATEX_OUTPUT directory. If left blank the -# LATEX_OUTPUT directory will be used. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_EMOJI_DIRECTORY = - -#--------------------------------------------------------------------------- -# Configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The -# RTF output is optimized for Word 97 and may not look too pretty with other RTF -# readers/editors. -# The default value is: NO. - -GENERATE_RTF = NO - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: rtf. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF -# documents. This may be useful for small projects and may help to save some -# trees in general. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will -# contain hyperlink fields. The RTF file will contain links (just like the HTML -# output) instead of page references. This makes the output suitable for online -# browsing using Word or some other Word compatible readers that support those -# fields. -# -# Note: WordPad (write) and others do not support links. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_HYPERLINKS = NO - -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# configuration file, i.e. a series of assignments. You only have to provide -# replacements, missing definitions are set to their default value. -# -# See also section "Doxygen usage" for information on how to generate the -# default style sheet that doxygen normally uses. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an RTF document. Syntax is -# similar to doxygen's configuration file. A template extensions file can be -# generated using doxygen -e rtf extensionFile. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_EXTENSIONS_FILE = - -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_SOURCE_CODE = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the man page output -#--------------------------------------------------------------------------- - -# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for -# classes and files. -# The default value is: NO. - -GENERATE_MAN = NO - -# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. A directory man3 will be created inside the directory specified by -# MAN_OUTPUT. -# The default directory is: man. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_OUTPUT = man - -# The MAN_EXTENSION tag determines the extension that is added to the generated -# man pages. In case the manual section does not start with a number, the number -# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is -# optional. -# The default value is: .3. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_EXTENSION = .3 - -# The MAN_SUBDIR tag determines the name of the directory created within -# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by -# MAN_EXTENSION with the initial . removed. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_SUBDIR = - -# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it -# will generate one additional man file for each entity documented in the real -# man page(s). These additional files only source the real man page, but without -# them the man command would be unable to find the correct page. -# The default value is: NO. -# This tag requires that the tag GENERATE_MAN is set to YES. - -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the XML output -#--------------------------------------------------------------------------- - -# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that -# captures the structure of the code including all documentation. -# The default value is: NO. - -GENERATE_XML = NO - -# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: xml. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_OUTPUT = xml - -# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program -# listings (including syntax highlighting and cross-referencing information) to -# the XML output. Note that enabling this will significantly increase the size -# of the XML output. -# The default value is: YES. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_PROGRAMLISTING = YES - -# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include -# namespace members in file scope as well, matching the HTML output. -# The default value is: NO. -# This tag requires that the tag GENERATE_XML is set to YES. - -XML_NS_MEMB_FILE_SCOPE = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the DOCBOOK output -#--------------------------------------------------------------------------- - -# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files -# that can be used to generate PDF. -# The default value is: NO. - -GENERATE_DOCBOOK = NO - -# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in -# front of it. -# The default directory is: docbook. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_OUTPUT = docbook - -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - -#--------------------------------------------------------------------------- -# Configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - -# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an -# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures -# the structure of the code including all documentation. Note that this feature -# is still experimental and incomplete at the moment. -# The default value is: NO. - -GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# Configuration options related to the Perl module output -#--------------------------------------------------------------------------- - -# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module -# file that captures the structure of the code including all documentation. -# -# Note that this feature is still experimental and incomplete at the moment. -# The default value is: NO. - -GENERATE_PERLMOD = NO - -# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary -# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI -# output from the Perl module output. -# The default value is: NO. -# This tag requires that the tag GENERATE_PERLMOD is set to YES. - -PERLMOD_LATEX = NO - -# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely -# formatted so it can be parsed by a human reader. This is useful if you want to -# understand what is going on. On the other hand, if this tag is set to NO, the -# size of the Perl module output will be much smaller and Perl will parse it -# just the same. -# The default value is: YES. -# This tag requires that the tag GENERATE_PERLMOD is set to YES. - -PERLMOD_PRETTY = YES - -# The names of the make variables in the generated doxyrules.make file are -# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful -# so different doxyrules.make files included by the same Makefile don't -# overwrite each other's variables. -# This tag requires that the tag GENERATE_PERLMOD is set to YES. - -PERLMOD_MAKEVAR_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- - -# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all -# C-preprocessor directives found in the sources and include files. -# The default value is: YES. - -ENABLE_PREPROCESSING = YES - -# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names -# in the source code. If set to NO, only conditional compilation will be -# performed. Macro expansion can be done in a controlled way by setting -# EXPAND_ONLY_PREDEF to YES. -# The default value is: NO. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -MACRO_EXPANSION = NO - -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then -# the macro expansion is limited to the macros specified with the PREDEFINED and -# EXPAND_AS_DEFINED tags. -# The default value is: NO. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -EXPAND_ONLY_PREDEF = NO - -# If the SEARCH_INCLUDES tag is set to YES, the include files in the -# INCLUDE_PATH will be searched if a #include is found. -# The default value is: YES. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -SEARCH_INCLUDES = YES - -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by the -# preprocessor. -# This tag requires that the tag SEARCH_INCLUDES is set to YES. - -INCLUDE_PATH = - -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will be -# used. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -INCLUDE_FILE_PATTERNS = - -# The PREDEFINED tag can be used to specify one or more macro names that are -# defined before the preprocessor is started (similar to the -D option of e.g. -# gcc). The argument of the tag is a list of macros of the form: name or -# name=definition (no spaces). If the definition and the "=" are omitted, "=1" -# is assumed. To prevent a macro definition from being undefined via #undef or -# recursively expanded use the := operator instead of the = operator. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -PREDEFINED = - -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this -# tag can be used to specify a list of macro names that should be expanded. The -# macro definition that is found in the sources will be used. Use the PREDEFINED -# tag if you want to use a different macro definition that overrules the -# definition found in the source code. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will -# remove all references to function-like macros that are alone on a line, have -# an all uppercase name, and do not end with a semicolon. Such function macros -# are typically used for boiler-plate code, and will confuse the parser if not -# removed. -# The default value is: YES. -# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. - -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration options related to external references -#--------------------------------------------------------------------------- - -# The TAGFILES tag can be used to specify one or more tag files. For each tag -# file the location of the external documentation should be added. The format of -# a tag file without this location is as follows: -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where loc1 and loc2 can be relative or absolute paths or URLs. See the -# section "Linking to external documentation" for more information about the use -# of tag files. -# Note: Each tag file must have a unique name (where the name does NOT include -# the path). If a tag file is not located in the directory in which doxygen is -# run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create a -# tag file that is based on the input files it reads. See section "Linking to -# external documentation" for more information about the usage of tag files. - -GENERATE_TAGFILE = - -# If the ALLEXTERNALS tag is set to YES, all external class will be listed in -# the class index. If set to NO, only the inherited external classes will be -# listed. -# The default value is: NO. - -ALLEXTERNALS = NO - -# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will be -# listed. -# The default value is: YES. - -EXTERNAL_GROUPS = YES - -# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in -# the related pages index. If set to NO, only the current project's pages will -# be listed. -# The default value is: YES. - -EXTERNAL_PAGES = YES - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - -# You can include diagrams made with dia in doxygen documentation. Doxygen will -# then run dia to produce the diagram and insert it in the documentation. The -# DIA_PATH tag allows you to specify the directory where the dia binary resides. -# If left empty dia is assumed to be found in the default search path. - -DIA_PATH = - -# If set to YES the inheritance and collaboration graphs will hide inheritance -# and usage relations if the target is undocumented or is not a class. -# The default value is: YES. - -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz (see: -# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent -# Bell Labs. The other options in this section have no effect if this option is -# set to NO -# The default value is: NO. - -HAVE_DOT = NO - -# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed -# to run in parallel. When set to 0 doxygen will base this on the number of -# processors available in the system. You can set it explicitly to a value -# larger than 0 to get control over the balance between CPU load and processing -# speed. -# Minimum value: 0, maximum value: 32, default value: 0. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_NUM_THREADS = 0 - -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTNAME = Helvetica - -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTSIZE = 10 - -# By default doxygen will tell dot to use the default font as specified with -# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set -# the path where dot can find it using this tag. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTPATH = - -# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for -# each documented class showing the direct and indirect inheritance relations. -# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a -# graph for each documented class showing the direct and indirect implementation -# dependencies (inheritance, containment, and class references variables) of the -# class with other documented classes. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -COLLABORATION_GRAPH = YES - -# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for -# groups, showing the direct groups dependencies. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -GROUP_GRAPHS = YES - -# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling -# Language. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -UML_LOOK = NO - -# If the UML_LOOK tag is enabled, the fields and methods are shown inside the -# class node. If there are many fields or methods and many nodes the graph may -# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the -# number of items for each type to make the size more manageable. Set this to 0 -# for no limit. Note that the threshold may be exceeded by 50% before the limit -# is enforced. So when you set the threshold to 10, up to 15 fields may appear, -# but if the number exceeds 15, the total amount of fields shown is limited to -# 10. -# Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag UML_LOOK is set to YES. - -UML_LIMIT_NUM_FIELDS = 10 - -# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and -# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS -# tag is set to YES, doxygen will add type and arguments for attributes and -# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen -# will not generate fields with class member information in the UML graphs. The -# class diagrams will look similar to the default class diagrams but using UML -# notation for the relationships. -# Possible values are: NO, YES and NONE. -# The default value is: NO. -# This tag requires that the tag UML_LOOK is set to YES. - -DOT_UML_DETAILS = NO - -# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters -# to display on a single line. If the actual line length exceeds this threshold -# significantly it will wrapped across multiple lines. Some heuristics are apply -# to avoid ugly line breaks. -# Minimum value: 0, maximum value: 1000, default value: 17. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_WRAP_THRESHOLD = 17 - -# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and -# collaboration graphs will show the relations between templates and their -# instances. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -TEMPLATE_RELATIONS = NO - -# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to -# YES then doxygen will generate a graph for each documented file showing the -# direct and indirect include dependencies of the file with other documented -# files. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -INCLUDE_GRAPH = YES - -# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are -# set to YES then doxygen will generate a graph for each documented file showing -# the direct and indirect include dependencies of the file with other documented -# files. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH tag is set to YES then doxygen will generate a call -# dependency graph for every global function or class method. -# -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable call graphs for selected -# functions only using the \callgraph command. Disabling a call graph can be -# accomplished by means of the command \hidecallgraph. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -CALL_GRAPH = NO - -# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller -# dependency graph for every global function or class method. -# -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable caller graphs for selected -# functions only using the \callergraph command. Disabling a caller graph can be -# accomplished by means of the command \hidecallergraph. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -CALLER_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical -# hierarchy of all classes instead of a textual one. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -GRAPHICAL_HIERARCHY = YES - -# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the -# dependencies a directory has on other directories in a graphical way. The -# dependency relations are determined by the #include relations between the -# files in the directories. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -DIRECTORY_GRAPH = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. For an explanation of the image formats see the section -# output formats in the documentation of the dot tool (Graphviz (see: -# http://www.graphviz.org/)). -# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order -# to make the SVG files visible in IE 9+ (other browsers do not have this -# requirement). -# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, -# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and -# png:gdiplus:gdiplus. -# The default value is: png. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_IMAGE_FORMAT = png - -# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to -# enable generation of interactive SVG images that allow zooming and panning. -# -# Note that this requires a modern browser other than Internet Explorer. Tested -# and working are Firefox, Chrome, Safari, and Opera. -# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make -# the SVG files visible. Older versions of IE do not have SVG support. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -INTERACTIVE_SVG = NO - -# The DOT_PATH tag can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found in the path. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_PATH = - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the \dotfile -# command). -# This tag requires that the tag HAVE_DOT is set to YES. - -DOTFILE_DIRS = - -# The MSCFILE_DIRS tag can be used to specify one or more directories that -# contain msc files that are included in the documentation (see the \mscfile -# command). - -MSCFILE_DIRS = - -# The DIAFILE_DIRS tag can be used to specify one or more directories that -# contain dia files that are included in the documentation (see the \diafile -# command). - -DIAFILE_DIRS = - -# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the -# path where java can find the plantuml.jar file. If left blank, it is assumed -# PlantUML is not used or called during a preprocessing step. Doxygen will -# generate a warning when it encounters a \startuml command in this case and -# will not generate output for the diagram. - -PLANTUML_JAR_PATH = - -# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a -# configuration file for plantuml. - -PLANTUML_CFG_FILE = - -# When using plantuml, the specified paths are searched for files specified by -# the !include statement in a plantuml block. - -PLANTUML_INCLUDE_PATH = - -# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes -# that will be shown in the graph. If the number of nodes in a graph becomes -# larger than this value, doxygen will truncate the graph, which is visualized -# by representing a node as a red box. Note that doxygen if the number of direct -# children of the root node in a graph is already larger than -# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that -# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. -# Minimum value: 0, maximum value: 10000, default value: 50. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_GRAPH_MAX_NODES = 50 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs -# generated by dot. A depth value of 3 means that only nodes reachable from the -# root by following a path via at most 3 edges will be shown. Nodes that lay -# further from the root node will be omitted. Note that setting this option to 1 -# or 2 may greatly reduce the computation time needed for large code bases. Also -# note that the size of a graph can be further restricted by -# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. -# Minimum value: 0, maximum value: 1000, default value: 0. -# This tag requires that the tag HAVE_DOT is set to YES. - -MAX_DOT_GRAPH_DEPTH = 0 - -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - -# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) support -# this, this feature is disabled by default. -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_MULTI_TARGETS = YES - -# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page -# explaining the meaning of the various boxes and arrows in the dot generated -# graphs. -# The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate -# files that are used to generate the various graphs. -# -# Note: This setting is not only used for dot files but also for msc and -# plantuml temporary files. -# The default value is: YES. - -DOT_CLEANUP = YES diff --git a/src/clint.py b/src/clint.py index 28f6031a57..155f5f2ce5 100755 --- a/src/clint.py +++ b/src/clint.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 # +# https://github.com/cpplint/cpplint +# # Copyright (c) 2009 Google Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -29,15 +31,9 @@ """Lints C files in the Neovim source tree. -The goal of this script is to identify places in the code that *may* -be in non-compliance with Neovim style. It does not attempt to fix -up these problems -- the point is to educate. It does also not -attempt to find all problems, or to ensure that everything it does -find is legitimately a problem. - -In particular, we can get very confused by /* and // inside strings! -We do a small hack, which is to ignore //'s with "'s after them on the -same line, but it is far from perfect (in either direction). +This can get very confused by /* and // inside strings! We do a small hack, +which is to ignore //'s with "'s after them on the same line, but it is far +from perfect (in either direction). """ @@ -61,25 +57,10 @@ Syntax: clint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...] <file> [file] ... The style guidelines this tries to follow are those in - http://neovim.io/develop/style-guide.xml - - Note: This is Google's cpplint.py modified for use with the Neovim project, - which follows the Google C++ coding convention except with the following - modifications: - - * Function names are lower_case. - * Struct and enum names that are not typedef-ed are struct lower_case and - enum lower_case. - * The opening brace for functions appear on the next line. - * All control structures must always use braces. - - Neovim is a C project. As a result, for .c and .h files, the following rules - are suppressed: + https://neovim.io/doc/user/dev_style.html#dev-style - * [whitespace/braces] { should almost always be at the end of the previous - line - * [build/include] Include the directory when naming .h files - * [runtime/int] Use int16_t/int64_t/etc, rather than the C type. + Note: This is Google's https://github.com/cpplint/cpplint modified for use + with the Neovim project. Every problem is given a confidence score from 1-5, with 5 meaning we are certain of the problem, and 1 meaning it could be a legitimate construct. @@ -176,7 +157,6 @@ _ERROR_CATEGORIES = [ 'build/printf_format', 'build/storage_class', 'readability/bool', - 'readability/braces', 'readability/multiline_comment', 'readability/multiline_string', 'readability/nul', @@ -202,7 +182,7 @@ _ERROR_CATEGORIES = [ 'whitespace/cast', ] -# The default state of the category filter. This is overrided by the --filter= +# The default state of the category filter. This is overridden by the --filter= # flag. By default all errors are on, so only add here categories that should be # off by default (i.e., categories that must be enabled by the --filter= flags). # All entries here should start with a '-' or '+', as in the --filter= flag. @@ -1941,13 +1921,6 @@ def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0): error(filename, linenum, 'whitespace/indent', 2, 'End of the inner expression should have ' 'the same indent as start') - else: - if (pos != depth_line_starts[depth][0] + 4 - and not (depth_line_starts[depth][1] == '{' - and pos == depth_line_starts[depth][0] + 2)): - if depth not in ignore_error_levels: - error(filename, linenum, 'whitespace/indent', 2, - 'Inner expression indentation should be 4') else: if (pos != level_starts[depth][0] + 1 + (level_starts[depth][2] == '{')): @@ -2331,213 +2304,36 @@ def CheckBraces(filename, clean_lines, linenum, error): prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] if (not Search(r'[,;:}{(]\s*$', prevline) and not Match(r'\s*#', prevline)): - error(filename, linenum, 'whitespace/braces', 4, - '{ should almost always be at the end' - ' of the previous line') + return # Brace must appear after function signature, but on the *next* line if Match(r'^(?:\w+(?: ?\*+)? )+\w+\(', line): pos = line.find('(') - (endline, end_linenum, endpos) = CloseExpression( - clean_lines, linenum, pos) + (endline, end_linenum, _) = CloseExpression(clean_lines, linenum, pos) if endline.endswith('{'): - error(filename, end_linenum, 'readability/braces', 5, - 'Brace starting function body must be placed on its own line') - else: - func_start_linenum = end_linenum + 1 - while not clean_lines.lines[func_start_linenum] == "{": - attrline = Match( - r'^((?!# *define).*?)' - r'(?:FUNC_ATTR|FUNC_API|REAL_FATTR)_\w+' - r'(?:\(\d+(, \d+)*\))?', - clean_lines.lines[func_start_linenum], - ) - if attrline: - if len(attrline.group(1)) != 2: - error(filename, func_start_linenum, - 'whitespace/indent', 5, - 'Function attribute line should have 2-space ' - 'indent') - - func_start_linenum += 1 - else: - func_start = clean_lines.lines[func_start_linenum] - if not func_start.startswith('enum ') and func_start.endswith('{'): - error(filename, func_start_linenum, - 'readability/braces', 5, - 'Brace starting function body must be placed ' - 'after the function signature') - break - - # An else clause should be on the same line as the preceding closing brace. - # If there is no preceding closing brace, there should be one. - if Match(r'\s*else\s*', line): - prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if Match(r'\s*}\s*$', prevline): - error(filename, linenum, 'whitespace/newline', 4, - 'An else should appear on the same line as the preceding }') - else: - error(filename, linenum, 'readability/braces', 5, - 'An else should always have braces before it') - - # If should always have a brace - for blockstart in ('if', 'while', 'for'): - if Match(r'\s*{0}(?!\w)[^{{]*$'.format(blockstart), line): - pos = line.find(blockstart) - pos = line.find('(', pos) - if pos > 0: - (endline, _, endpos) = CloseExpression( - clean_lines, linenum, pos) - if endline[endpos:].find('{') == -1: - error(filename, linenum, 'readability/braces', 5, - '{} should always use braces'.format(blockstart)) - - # If braces come on one side of an else, they should be on both. - # However, we have to worry about "else if" that spans multiple lines! - if Search(r'}\s*else[^{]*$', line) or Match(r'[^}]*else\s*{', line): - if Search(r'}\s*else if([^{]*)$', line): # could be multi-line if - # find the ( after the if - pos = line.find('else if') - pos = line.find('(', pos) - if pos > 0: - (endline, _, endpos) = CloseExpression( - clean_lines, linenum, pos) - # must be brace after if - if endline[endpos:].find('{') == -1: - error(filename, linenum, 'readability/braces', 5, - 'If an else has a brace on one side,' - ' it should have it on both') - else: # common case: else not followed by a multi-line if - error(filename, linenum, 'readability/braces', 5, - 'If an else has a brace on one side,' - ' it should have it on both') - - # Likewise, an else should never have the else clause on the same line - if Search(r'\belse [^\s{]', line) and not Search(r'\belse if\b', line): - error(filename, linenum, 'whitespace/newline', 4, - 'Else clause should never be on same line as else (use 2 lines)') - - # In the same way, a do/while should never be on one line - if Match(r'\s*do [^\s{]', line): - error(filename, linenum, 'whitespace/newline', 4, - 'do/while clauses should not be on a single line') - - # Block bodies should not be followed by a semicolon. Due to C++11 - # brace initialization, there are more places where semicolons are - # required than not, so we use a whitelist approach to check these - # rather than a blacklist. These are the places where "};" should - # be replaced by just "}": - # 1. Some flavor of block following closing parenthesis: - # for (;;) {}; - # while (...) {}; - # switch (...) {}; - # Function(...) {}; - # if (...) {}; - # if (...) else if (...) {}; - # - # 2. else block: - # if (...) else {}; - # - # 3. const member function: - # Function(...) const {}; - # - # 4. Block following some statement: - # x = 42; - # {}; - # - # 5. Block at the beginning of a function: - # Function(...) { - # {}; - # } - # - # Note that naively checking for the preceding "{" will also match - # braces inside multi-dimensional arrays, but this is fine since - # that expression will not contain semicolons. - # - # 6. Block following another block: - # while (true) {} - # {}; - # - # 7. End of namespaces: - # namespace {}; - # - # These semicolons seems far more common than other kinds of - # redundant semicolons, possibly due to people converting classes - # to namespaces. For now we do not warn for this case. - # - # Try matching case 1 first. - match = Match(r'^(.*\)\s*)\{', line) - if match: - # Matched closing parenthesis (case 1). Check the token before the - # matching opening parenthesis, and don't warn if it looks like a - # macro. This avoids these false positives: - # - macro that defines a base class - # - multi-line macro that defines a base class - # - macro that defines the whole class-head - # - # But we still issue warnings for macros that we know are safe to - # warn, specifically: - # - TEST, TEST_F, TEST_P, MATCHER, MATCHER_P - # - TYPED_TEST - # - INTERFACE_DEF - # - EXCLUSIVE_LOCKS_REQUIRED, SHARED_LOCKS_REQUIRED, LOCKS_EXCLUDED: - # - # We implement a whitelist of safe macros instead of a blacklist of - # unsafe macros, even though the latter appears less frequently in - # google code and would have been easier to implement. This is because - # the downside for getting the whitelist wrong means some extra - # semicolons, while the downside for getting the blacklist wrong - # would result in compile errors. - # - # In addition to macros, we also don't want to warn on compound - # literals. - closing_brace_pos = match.group(1).rfind(')') - opening_parenthesis = ReverseCloseExpression( - clean_lines, linenum, closing_brace_pos) - if opening_parenthesis[2] > -1: - line_prefix = opening_parenthesis[0][0:opening_parenthesis[2]] - macro = Search(r'\b([A-Z_]+)\s*$', line_prefix) - if ((macro and - macro.group(1) not in ( - 'TEST', 'TEST_F', 'MATCHER', 'MATCHER_P', 'TYPED_TEST', - 'EXCLUSIVE_LOCKS_REQUIRED', 'SHARED_LOCKS_REQUIRED', - 'LOCKS_EXCLUDED', 'INTERFACE_DEF')) or - Search(r'\s+=\s*$', line_prefix) or - Search(r'^\s*return\s*$', line_prefix)): - match = None - - else: - # Try matching cases 2-3. - match = Match(r'^(.*(?:else|\)\s*const)\s*)\{', line) - if not match: - # Try matching cases 4-6. These are always matched on separate - # lines. - # - # Note that we can't simply concatenate the previous line to the - # current line and do a single match, otherwise we may output - # duplicate warnings for the blank line case: - # if (cond) { - # // blank line - # } - prevline = GetPreviousNonBlankLine(clean_lines, linenum)[0] - if prevline and Search(r'[;{}]\s*$', prevline): - match = Match(r'^(\s*)\{', line) - - # Check matching closing brace - if match: - (endline, endlinenum, endpos) = CloseExpression( - clean_lines, linenum, len(match.group(1))) - if endpos > -1 and Match(r'^\s*;', endline[endpos:]): - # Current {} pair is eligible for semicolon check, and we have found - # the redundant semicolon, output warning here. - # - # Note: because we are scanning forward for opening braces, and - # outputting warnings for the matching closing brace, if there are - # nested blocks with trailing semicolons, we will get the error - # messages in reversed order. - error(filename, endlinenum, 'readability/braces', 4, - "You don't need a ; after a }") + return + func_start_linenum = end_linenum + 1 + while not clean_lines.lines[func_start_linenum] == "{": + attrline = Match( + r'^((?!# *define).*?)' + r'(?:FUNC_ATTR|FUNC_API|REAL_FATTR)_\w+' + r'(?:\(\d+(, \d+)*\))?', + clean_lines.lines[func_start_linenum], + ) + if attrline: + if len(attrline.group(1)) != 2: + error(filename, func_start_linenum, + 'whitespace/indent', 5, + 'Function attribute line should have 2-space ' + 'indent') + + func_start_linenum += 1 + else: + func_start = clean_lines.lines[func_start_linenum] + if not func_start.startswith('enum ') and func_start.endswith('{'): + return + break def CheckStyle(filename, clean_lines, linenum, error): """Checks rules from the 'C++ style rules' section of cppguide.html. @@ -2573,23 +2369,10 @@ def CheckStyle(filename, clean_lines, linenum, error): # if(match(prev, " +for \\(")) complain = 0; # if(prevodd && match(prevprev, " +for \\(")) complain = 0; initial_spaces = 0 - cleansed_line = clean_lines.elided[linenum] while initial_spaces < len(line) and line[initial_spaces] == ' ': initial_spaces += 1 - if (cleansed_line.count(';') > 1 and - # for loops are allowed two ;'s (and may run over two lines). - cleansed_line.find('for') == -1 and - (GetPreviousNonBlankLine(clean_lines, linenum)[0].find('for') == -1 or - GetPreviousNonBlankLine(clean_lines, linenum)[0].find(';') != -1) and - # It's ok to have many commands in a switch case that fits in 1 line - not ((cleansed_line.find('case ') != -1 or - cleansed_line.find('default:') != -1) and - cleansed_line.find('break;') != -1)): - error(filename, linenum, 'whitespace/newline', 0, - 'More than one command on the same line') - # Some more style checks CheckBraces(filename, clean_lines, linenum, error) CheckSpacing(filename, clean_lines, linenum, error) @@ -2707,7 +2490,7 @@ def CheckLanguage(filename, clean_lines, linenum, error): if match: error(filename, linenum, 'runtime/printf', 4, 'Use xstrlcat or snprintf instead of %s' % match.group(1)) - if not Search(r'eval/typval\.[ch]$', filename): + if not Search(r'eval/typval\.[ch]$|eval/typval_defs\.h$', filename): match = Search(r'(?:\.|->)' r'(?:lv_(?:first|last|refcount|len|watch|idx(?:_item)?' r'|copylist|lock)' @@ -2720,8 +2503,7 @@ def CheckLanguage(filename, clean_lines, linenum, error): # Check for suspicious usage of "if" like # } if (a == b) { if Search(r'\}\s*if\s*\(', line): - error(filename, linenum, 'readability/braces', 4, - 'Did you mean "else if"? If not, start a new line for "if".') + return # Check for potential format string bugs like printf(foo). # We constrain the pattern not to pick things like DocidForPrintf(foo). diff --git a/src/nvim/lib/kbtree.h b/src/klib/kbtree.h index 99f79952d7..99f79952d7 100644 --- a/src/nvim/lib/kbtree.h +++ b/src/klib/kbtree.h diff --git a/src/nvim/lib/khash.h b/src/klib/khash.h index 57a41f9c13..57a41f9c13 100644 --- a/src/nvim/lib/khash.h +++ b/src/klib/khash.h diff --git a/src/nvim/lib/klist.h b/src/klib/klist.h index a9abbc6dc2..a9abbc6dc2 100644 --- a/src/nvim/lib/klist.h +++ b/src/klib/klist.h diff --git a/src/nvim/lib/kvec.h b/src/klib/kvec.h index b5b3adf7d2..f6674a0adf 100644 --- a/src/nvim/lib/kvec.h +++ b/src/klib/kvec.h @@ -111,7 +111,7 @@ (v).size = (v).size + len; \ } while (0) -#define kv_concat(v, str) kv_concat_len(v, str, STRLEN(str)) +#define kv_concat(v, str) kv_concat_len(v, str, strlen(str)) #define kv_splice(v1, v0) kv_concat_len(v1, (v0).items, (v0).size) #define kv_pushp(v) \ diff --git a/src/man/nvim.1 b/src/man/nvim.1 index 9f35014ee8..c32bdeadc6 100644 --- a/src/man/nvim.1 +++ b/src/man/nvim.1 @@ -122,9 +122,6 @@ modifications. .It Fl b Binary mode. .Ic ":help edit-binary" -.It Fl l -Lisp mode. -Sets the 'lisp' and 'showmatch' options. .It Fl A Arabic mode. Sets the 'arabic' option. @@ -144,7 +141,7 @@ is specified, append messages to instead of printing them. .Ic ":help 'verbose'" .It Fl D -Debug mode for VimL (Vim script). +Vimscript debug mode. Started when executing the first command from a script. :help debug-mode .It Fl n @@ -200,7 +197,8 @@ Skip loading plugins. Implied by .Cm -u NONE . .It Fl -clean -Mimic a fresh install of Nvim. Skip loading non-builtin plugins and shada (viminfo) file. +Start Nvim with "factory defaults" (no user config and plugins, no shada). +.Ic ":help --clean" .It Fl o Ns Op Ar N Open .Ar N @@ -267,10 +265,26 @@ but execute before processing any vimrc. Up to 10 instances of these can be used independently from instances of .Fl c . +.It Fl l Ar script Op Ar args +Execute Lua +.Ar script +with optional +.Op Ar args +after processing any preceding Nvim startup arguments. +All +.Op Ar args +are treated as script arguments and are passed literally to Lua, that is, +.Fl l +stops processing of Nvim arguments. +.Ic ":help -l" .It Fl S Op Ar session -Source +Execute +.Ar session +after the first file argument has been read. If .Ar session -after the first file argument has been read. +filename ends with +.Pa .lua +it is executed as Lua instead of Vimscript. Equivalent to .Cm -c \(dqsource session\(dq . .Ar session diff --git a/src/mpack/mpack_core.c b/src/mpack/mpack_core.c index 4ee67a032a..3424f444b9 100644 --- a/src/mpack/mpack_core.c +++ b/src/mpack/mpack_core.c @@ -173,6 +173,9 @@ MPACK_API int mpack_write(mpack_tokbuf_t *tokbuf, char **buf, size_t *buflen, int mpack_rtoken(const char **buf, size_t *buflen, mpack_token_t *tok) { + if (*buflen == 0) { + return MPACK_EOF; + } unsigned char t = ADVANCE(buf, buflen); if (t < 0x80) { /* positive fixint */ diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 635833748d..2361210e59 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -1,25 +1,337 @@ -option(USE_GCOV "Enable gcov support" OFF) +add_library(main_lib INTERFACE) +add_executable(nvim main.c) + +add_library(libuv_lib INTERFACE) +find_package(LibUV 1.28.0 REQUIRED) +target_include_directories(libuv_lib SYSTEM BEFORE INTERFACE ${LIBUV_INCLUDE_DIRS}) +target_link_libraries(libuv_lib INTERFACE ${LIBUV_LIBRARIES}) + +find_package(Msgpack 1.0.0 REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${MSGPACK_INCLUDE_DIRS}) +target_link_libraries(main_lib INTERFACE ${MSGPACK_LIBRARIES}) + +find_package(LibLUV 1.43.0 REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBLUV_INCLUDE_DIRS}) +# Use "luv" as imported library, to work around CMake using "-lluv" for +# "luv.so". #10407 +add_library(luv UNKNOWN IMPORTED) +set_target_properties(luv PROPERTIES IMPORTED_LOCATION ${LIBLUV_LIBRARIES}) +target_link_libraries(main_lib INTERFACE luv) + +find_package(TreeSitter REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${TreeSitter_INCLUDE_DIRS}) +target_link_libraries(main_lib INTERFACE ${TreeSitter_LIBRARIES}) + +find_package(UNIBILIUM 2.0 REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${UNIBILIUM_INCLUDE_DIRS}) +target_link_libraries(main_lib INTERFACE ${UNIBILIUM_LIBRARIES}) + +find_package(LibTermkey 0.22 REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBTERMKEY_INCLUDE_DIRS}) +target_link_libraries(main_lib INTERFACE ${LIBTERMKEY_LIBRARIES}) + +find_package(LIBVTERM 0.3 REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBVTERM_INCLUDE_DIRS}) +target_link_libraries(main_lib INTERFACE ${LIBVTERM_LIBRARIES}) + +find_package(Iconv REQUIRED) +target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${Iconv_INCLUDE_DIRS}) +target_link_libraries(main_lib INTERFACE ${Iconv_LIBRARIES}) + +option(ENABLE_LIBINTL "enable libintl" ON) +if(ENABLE_LIBINTL) + # LibIntl (not Intl) selects our FindLibIntl.cmake script. #8464 + find_package(LibIntl REQUIRED) + target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LibIntl_INCLUDE_DIRS}) + if (LibIntl_FOUND) + target_link_libraries(main_lib INTERFACE ${LibIntl_LIBRARY}) + endif() +endif() + +# The unit test lib requires LuaJIT; it will be skipped if LuaJIT is missing. +option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF) +if(PREFER_LUA) + find_package(Lua 5.1 EXACT REQUIRED) + target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LUA_INCLUDE_DIR}) + target_link_libraries(main_lib INTERFACE ${LUA_LIBRARIES}) + # Passive (not REQUIRED): if LUAJIT_FOUND is not set, nvim-test is skipped. + find_package(LuaJit) +else() + find_package(LuaJit REQUIRED) + target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LUAJIT_INCLUDE_DIRS}) + target_link_libraries(main_lib INTERFACE ${LUAJIT_LIBRARIES}) +endif() + +option(ENABLE_IWYU "Run include-what-you-use with the compiler." OFF) +if(ENABLE_IWYU) + find_program(IWYU_PRG NAMES include-what-you-use iwyu) + if(NOT IWYU_PRG) + message(FATAL_ERROR "ENABLE_IWYU is ON but include-what-you-use is not found!") + endif() + + set(iwyu_flags "${IWYU_PRG};") + string(APPEND iwyu_flags "-Xiwyu;--no_default_mappings;") + string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/mapping.imp;") + string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/gcc.libc.imp;") + string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/gcc.symbols.imp") + + set_target_properties(nvim PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_flags}") + target_compile_definitions(main_lib INTERFACE EXITFREE) +endif() + +if(MSVC) + # TODO(dundargoc): bump warning level + target_compile_options(main_lib INTERFACE -W2) + + # Disable warnings that give too many false positives. + target_compile_options(main_lib INTERFACE -wd4311 -wd4146) + target_compile_definitions(main_lib INTERFACE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE) + + target_sources(main_lib INTERFACE ${CMAKE_CURRENT_LIST_DIR}/os/nvim.manifest) +else() + target_compile_options(main_lib INTERFACE -Wall -Wextra -pedantic -Wno-unused-parameter + -Wstrict-prototypes -std=gnu99 -Wshadow -Wconversion + -Wdouble-promotion + -Wmissing-noreturn + -Wmissing-format-attribute + -Wmissing-prototypes) +endif() + +# On FreeBSD 64 math.h uses unguarded C11 extension, which taints clang +# 3.4.1 used there. +if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND CMAKE_C_COMPILER_ID MATCHES "Clang") + target_compile_options(main_lib INTERFACE -Wno-c11-extensions) +endif() + +check_c_compiler_flag(-Wimplicit-fallthrough HAVE_WIMPLICIT_FALLTHROUGH_FLAG) +if(HAVE_WIMPLICIT_FALLTHROUGH_FLAG) + target_compile_options(main_lib INTERFACE -Wimplicit-fallthrough) +endif() + +option(ENABLE_COMPILER_SUGGESTIONS "Enable -Wsuggest compiler warnings" OFF) +if(ENABLE_COMPILER_SUGGESTIONS) + # Clang doesn't have -Wsuggest-attribute so check for each one. + check_c_compiler_flag(-Wsuggest-attribute=pure HAVE_WSUGGEST_ATTRIBUTE_PURE) + if(HAVE_WSUGGEST_ATTRIBUTE_PURE) + target_compile_options(main_lib INTERFACE -Wsuggest-attribute=pure) + endif() + + check_c_compiler_flag(-Wsuggest-attribute=const HAVE_WSUGGEST_ATTRIBUTE_CONST) + if(HAVE_WSUGGEST_ATTRIBUTE_CONST) + target_compile_options(main_lib INTERFACE -Wsuggest-attribute=const) + endif() + + check_c_compiler_flag(-Wsuggest-attribute=malloc HAVE_WSUGGEST_ATTRIBUTE_MALLOC) + if(HAVE_WSUGGEST_ATTRIBUTE_MALLOC) + target_compile_options(main_lib INTERFACE -Wsuggest-attribute=malloc) + endif() + + check_c_compiler_flag(-Wsuggest-attribute=cold HAVE_WSUGGEST_ATTRIBUTE_COLD) + if(HAVE_WSUGGEST_ATTRIBUTE_COLD) + target_compile_options(main_lib INTERFACE -Wsuggest-attribute=cold) + endif() +endif() + +if(MINGW) + # Use POSIX compatible stdio in Mingw + target_compile_definitions(main_lib INTERFACE __USE_MINGW_ANSI_STDIO) +endif() +if(WIN32) + # Windows Vista is the minimum supported version + target_compile_definitions(main_lib INTERFACE _WIN32_WINNT=0x0600 MSWIN) +endif() + +# OpenBSD's GCC (4.2.1) doesn't have -Wvla +check_c_compiler_flag(-Wvla HAS_WVLA_FLAG) +if(HAS_WVLA_FLAG) + target_compile_options(main_lib INTERFACE -Wvla) +endif() + +check_c_compiler_flag(-fno-common HAVE_FNO_COMMON) +if (HAVE_FNO_COMMON) + target_compile_options(main_lib INTERFACE -fno-common) +endif() + +check_c_compiler_flag(-fdiagnostics-color=auto HAS_DIAG_COLOR_FLAG) +if(HAS_DIAG_COLOR_FLAG) + if(CMAKE_GENERATOR MATCHES "Ninja") + target_compile_options(main_lib INTERFACE -fdiagnostics-color=always) + else() + target_compile_options(main_lib INTERFACE -fdiagnostics-color=auto) + endif() +endif() + +option(CI_BUILD "CI, extra flags will be set" OFF) +if(CI_BUILD) + message(STATUS "CI build enabled") + if(MSVC) + target_compile_options(main_lib INTERFACE -WX) + else() + target_compile_options(main_lib INTERFACE -Werror) + if(DEFINED ENV{BUILD_UCHAR}) + # Get some test coverage for unsigned char + target_compile_options(main_lib INTERFACE -funsigned-char) + endif() + endif() +endif() + +list(APPEND CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}") +list(APPEND CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}") +check_c_source_compiles(" +#include <unibilium.h> + +int +main(void) +{ + unibi_str_from_var(unibi_var_from_str(\"\")); + return unibi_num_from_var(unibi_var_from_num(0)); +} +" UNIBI_HAS_VAR_FROM) +list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIRS}") +list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARIES}") +if(UNIBI_HAS_VAR_FROM) + target_compile_definitions(main_lib INTERFACE NVIM_UNIBI_HAS_VAR_FROM) +endif() + +list(APPEND CMAKE_REQUIRED_INCLUDES "${MSGPACK_INCLUDE_DIRS}") +check_c_source_compiles(" +#include <msgpack.h> + +int +main(void) +{ + return MSGPACK_OBJECT_FLOAT32; +} +" MSGPACK_HAS_FLOAT32) +list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${MSGPACK_INCLUDE_DIRS}") +if(MSGPACK_HAS_FLOAT32) + target_compile_definitions(main_lib INTERFACE NVIM_MSGPACK_HAS_FLOAT32) +endif() + +list(APPEND CMAKE_REQUIRED_INCLUDES "${TreeSitter_INCLUDE_DIRS}") +list(APPEND CMAKE_REQUIRED_LIBRARIES "${TreeSitter_LIBRARIES}") +check_c_source_compiles(" +#include <tree_sitter/api.h> +int +main(void) +{ + TSQueryCursor *cursor = ts_query_cursor_new(); + ts_query_cursor_set_match_limit(cursor, 32); + return 0; +} +" TS_HAS_SET_MATCH_LIMIT) +if(TS_HAS_SET_MATCH_LIMIT) + target_compile_definitions(main_lib INTERFACE NVIM_TS_HAS_SET_MATCH_LIMIT) +endif() +check_c_source_compiles(" +#include <stdlib.h> +#include <tree_sitter/api.h> +int +main(void) +{ + ts_set_allocator(malloc, calloc, realloc, free); + return 0; +} +" TS_HAS_SET_ALLOCATOR) +if(TS_HAS_SET_ALLOCATOR) + target_compile_definitions(main_lib INTERFACE NVIM_TS_HAS_SET_ALLOCATOR) +endif() +list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${TreeSitter_INCLUDE_DIRS}") +list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${TreeSitter_LIBRARIES}") + +# Include <string.h> because some toolchains define _FORTIFY_SOURCE=2 in +# internal header files, which should in turn be #included by <string.h>. +check_c_source_compiles(" +#include <string.h> + +#if defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE > 1 +#error \"_FORTIFY_SOURCE > 1\" +#endif +int +main(void) +{ + return 0; +} +" HAS_ACCEPTABLE_FORTIFY) + +if(NOT HAS_ACCEPTABLE_FORTIFY) + message(STATUS "Unsupported _FORTIFY_SOURCE found, forcing _FORTIFY_SOURCE=1") + # Extract possible prefix to _FORTIFY_SOURCE (e.g. -Wp,-D_FORTIFY_SOURCE). + string(REGEX MATCH "[^\ ]+-D_FORTIFY_SOURCE" _FORTIFY_SOURCE_PREFIX "${CMAKE_C_FLAGS}") + string(REPLACE "-D_FORTIFY_SOURCE" "" _FORTIFY_SOURCE_PREFIX "${_FORTIFY_SOURCE_PREFIX}" ) + if(NOT _FORTIFY_SOURCE_PREFIX STREQUAL "") + message(STATUS "Detected _FORTIFY_SOURCE Prefix=${_FORTIFY_SOURCE_PREFIX}") + endif() + # -U in add_definitions doesn't end up in the correct spot, so we add it to + # the flags variable instead. + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_FORTIFY_SOURCE_PREFIX}-U_FORTIFY_SOURCE ${_FORTIFY_SOURCE_PREFIX}-D_FORTIFY_SOURCE=1") +endif() + +target_compile_definitions(main_lib INTERFACE INCLUDE_GENERATED_DECLARATIONS) + +# Remove --sort-common from linker flags, as this seems to cause bugs (see #2641, #3374). +# TODO: Figure out the root cause. +if(CMAKE_EXE_LINKER_FLAGS MATCHES "--sort-common" OR + CMAKE_SHARED_LINKER_FLAGS MATCHES "--sort-common" OR + CMAKE_MODULE_LINKER_FLAGS MATCHES "--sort-common") + message(STATUS "Removing --sort-common from linker flags") + string(REGEX REPLACE ",--sort-common(=[^,]+)?" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + string(REGEX REPLACE ",--sort-common(=[^,]+)?" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") + string(REGEX REPLACE ",--sort-common(=[^,]+)?" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") + + # If no linker flags remain for a -Wl argument, remove it. + # '-Wl$' will match LDFLAGS="-Wl,--sort-common", + # '-Wl ' will match LDFLAGS="-Wl,--sort-common -Wl,..." + string(REGEX REPLACE "-Wl($| )" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") + string(REGEX REPLACE "-Wl($| )" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}") + string(REGEX REPLACE "-Wl($| )" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}") +endif() + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + if(CMAKE_SYSTEM_NAME STREQUAL "SunOS") + target_link_libraries(nvim PRIVATE -Wl,--no-undefined -lsocket) + elseif(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(nvim PRIVATE -Wl,--no-undefined) + endif() + + # For O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW flags on older systems + # (pre POSIX.1-2008: glibc 2.11 and earlier). #4042 + # For ptsname(). #6743 + target_compile_definitions(main_lib INTERFACE _GNU_SOURCE) +endif() +option(USE_GCOV "Enable gcov support" OFF) if(USE_GCOV) if(CLANG_TSAN) # GCOV and TSAN results in false data race reports message(FATAL_ERROR "USE_GCOV cannot be used with CLANG_TSAN") endif() message(STATUS "Enabling gcov support") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") - add_definitions(-DUSE_GCOV) + target_compile_options(main_lib INTERFACE --coverage) + target_link_libraries(main_lib INTERFACE --coverage) + target_compile_definitions(main_lib INTERFACE USE_GCOV) endif() if(WIN32) if(MINGW) # Enable wmain - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -municode") + target_link_libraries(nvim PRIVATE -municode) endif() elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreServices") - set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -framework CoreServices") + target_link_libraries(nvim PRIVATE "-framework CoreServices") +endif() + +if(UNIX) + # -fstack-protector breaks non Unix builds even in Mingw-w64 + check_c_compiler_flag(-fstack-protector-strong HAS_FSTACK_PROTECTOR_STRONG_FLAG) + check_c_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR_FLAG) + if(HAS_FSTACK_PROTECTOR_STRONG_FLAG) + target_compile_options(main_lib INTERFACE -fstack-protector-strong) + target_link_libraries(main_lib INTERFACE -fstack-protector-strong) + elseif(HAS_FSTACK_PROTECTOR_FLAG) + target_compile_options(main_lib INTERFACE -fstack-protector --param ssp-buffer-size=4) + target_link_libraries(main_lib INTERFACE -fstack-protector --param ssp-buffer-size=4) + endif() endif() set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators) @@ -36,10 +348,8 @@ 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_CLIENT ${GENERATED_DIR}/ui_events_client.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) @@ -68,36 +378,26 @@ set(LUA_FILETYPE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/filetype.lu set(LUA_INIT_PACKAGES_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_init_packages.lua) set(LUA_KEYMAP_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/keymap.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") -set(LINT_PRG ${PROJECT_SOURCE_DIR}/src/clint.py) -set(DOWNLOAD_SCRIPT ${PROJECT_SOURCE_DIR}/cmake/Download.cmake) -set(LINT_SUPPRESSES_ROOT ${PROJECT_BINARY_DIR}/errors) -set(LINT_SUPPRESSES_URL "${LINT_SUPPRESS_URL_BASE}/errors.tar.gz") -set(LINT_SUPPRESSES_ARCHIVE ${LINT_SUPPRESSES_ROOT}/errors.tar.gz) -set(LINT_SUPPRESSES_TOUCH_FILE "${TOUCHES_DIR}/unpacked-clint-errors-archive") -set(CLINT_REPORT_PATH ${LINT_SUPPRESSES_ROOT}/src/home/runner/work/doc/doc/gh-pages/reports/clint) glob_wrapper(UNICODE_FILES ${UNICODE_DIR}/*.txt) glob_wrapper(API_HEADERS api/*.h) list(REMOVE_ITEM API_HEADERS ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h) glob_wrapper(MSGPACK_RPC_HEADERS msgpack_rpc/*.h) -include_directories(${GENERATED_DIR}) -include_directories(${CACHED_GENERATED_DIR}) -include_directories(${GENERATED_INCLUDES_DIR}) +target_include_directories(main_lib INTERFACE ${GENERATED_DIR}) +target_include_directories(main_lib INTERFACE ${CACHED_GENERATED_DIR}) +target_include_directories(main_lib INTERFACE ${GENERATED_INCLUDES_DIR}) +target_include_directories(main_lib INTERFACE "${PROJECT_BINARY_DIR}/cmake.config") +target_include_directories(main_lib INTERFACE "${PROJECT_SOURCE_DIR}/src") file(MAKE_DIRECTORY ${TOUCHES_DIR}) file(MAKE_DIRECTORY ${GENERATED_DIR}) file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}) -file(MAKE_DIRECTORY ${LINT_SUPPRESSES_ROOT}) -file(MAKE_DIRECTORY ${LINT_SUPPRESSES_ROOT}/src) glob_wrapper(NVIM_SOURCES *.c) glob_wrapper(NVIM_HEADERS *.h) -glob_wrapper(EXTERNAL_SOURCES ../xdiff/*.c ../mpack/*.c ../cjson/*.c) -glob_wrapper(EXTERNAL_HEADERS ../xdiff/*.h ../mpack/*.h ../cjson/*.h) +glob_wrapper(EXTERNAL_SOURCES ../xdiff/*.c ../mpack/*.c ../cjson/*.c ../klib/*.c) +glob_wrapper(EXTERNAL_HEADERS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h) foreach(subdir os @@ -111,9 +411,6 @@ foreach(subdir viml viml/parser ) - if(${subdir} MATCHES "tui" AND NOT FEAT_TUI) - continue() - endif() file(MAKE_DIRECTORY ${GENERATED_DIR}/${subdir}) file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/${subdir}) @@ -123,8 +420,6 @@ foreach(subdir list(APPEND NVIM_HEADERS ${headers}) endforeach() -glob_wrapper(UNIT_TEST_FIXTURES ${PROJECT_SOURCE_DIR}/test/unit/fixtures/*.c) - # Sort file lists to ensure generated files are created in the same order from # build to build. list(SORT NVIM_SOURCES) @@ -156,29 +451,32 @@ endforeach() list(REMOVE_ITEM NVIM_SOURCES ${to_remove}) -if(NOT MSVC) - # xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306 +# xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306 +if(MSVC) set_source_files_properties( - ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion") + ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} /wd4090 /wd4244") +else() + set_source_files_properties( + ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion -Wno-strict-prototypes") endif() if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$") - add_definitions(-DMIN_LOG_LEVEL=${MIN_LOG_LEVEL}) + target_compile_definitions(main_lib INTERFACE MIN_LOG_LEVEL=${MIN_LOG_LEVEL}) endif() -if(DEBUG OR CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) - add_definitions(-DEXITFREE) +if(CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) + target_compile_definitions(main_lib INTERFACE EXITFREE) endif() -get_directory_property(gen_cdefs COMPILE_DEFINITIONS) -foreach(gen_cdef ${gen_cdefs} DO_NOT_DEFINE_EMPTY_ATTRIBUTES) +get_target_property(prop main_lib INTERFACE_COMPILE_DEFINITIONS) +foreach(gen_cdef DO_NOT_DEFINE_EMPTY_ATTRIBUTES ${prop}) if(NOT ${gen_cdef} MATCHES "INCLUDE_GENERATED_DECLARATIONS") list(APPEND gen_cflags "-D${gen_cdef}") endif() endforeach() -get_directory_property(gen_includes INCLUDE_DIRECTORIES) -foreach(gen_include ${gen_includes} ${LUA_PREFERRED_INCLUDE_DIRS}) +get_target_property(prop main_lib INTERFACE_INCLUDE_DIRECTORIES) +foreach(gen_include ${prop}) list(APPEND gen_cflags "-I${gen_include}") endforeach() if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_OSX_SYSROOT) @@ -190,14 +488,6 @@ separate_arguments(C_FLAGS_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS}) separate_arguments(C_FLAGS_${build_type}_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS_${build_type}}) set(gen_cflags ${gen_cflags} ${C_FLAGS_${build_type}_ARRAY} ${C_FLAGS_ARRAY}) -function(get_preproc_output varname iname) - if(MSVC) - set(${varname} /P /Fi${iname} /nologo PARENT_SCOPE) - else() - set(${varname} -E -o ${iname} PARENT_SCOPE) - endif() -endfunction() - set(NVIM_VERSION_GIT_H ${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef_git.h) add_custom_target(update_version_stamp COMMAND ${CMAKE_COMMAND} @@ -206,9 +496,8 @@ add_custom_target(update_version_stamp -DNVIM_VERSION_PATCH=${NVIM_VERSION_PATCH} -DNVIM_VERSION_PRERELEASE=${NVIM_VERSION_PRERELEASE} -DOUTPUT=${NVIM_VERSION_GIT_H} - -DCMAKE_MESSAGE_LOG_LEVEL=${CMAKE_MESSAGE_LOG_LEVEL} + -DNVIM_SOURCE_DIR=${CMAKE_SOURCE_DIR} -P ${PROJECT_SOURCE_DIR}/cmake/GenerateVersion.cmake - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} BYPRODUCTS ${NVIM_VERSION_GIT_H}) # NVIM_GENERATED_FOR_HEADERS: generated headers to be included in headers @@ -221,7 +510,6 @@ foreach(sfile ${NVIM_SOURCES} ${GENERATED_API_DISPATCH} "${GENERATED_UI_EVENTS_CALL}" "${GENERATED_UI_EVENTS_REMOTE}" - "${GENERATED_UI_EVENTS_BRIDGE}" "${GENERATED_KEYSETS}" "${GENERATED_UI_EVENTS_CLIENT}" ) @@ -240,7 +528,11 @@ foreach(sfile ${NVIM_SOURCES} set(gf_h_h "${GENERATED_INCLUDES_DIR}/${r}.h.generated.h") set(gf_i "${GENERATED_DIR}/${r}.i") - get_preproc_output(PREPROC_OUTPUT ${gf_i}) + if(MSVC) + set(PREPROC_OUTPUT /P /Fi${gf_i} /nologo) + else() + set(PREPROC_OUTPUT -E -o ${gf_i}) + endif() set(depends "${HEADER_GENERATOR}" "${sfile}") if("${f}" STREQUAL "version.c") @@ -271,11 +563,11 @@ add_custom_command(OUTPUT ${GENERATED_UNICODE_TABLES} add_custom_command( OUTPUT ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA} ${API_METADATA} ${LUA_API_C_BINDINGS} - COMMAND ${LUA_PRG} ${API_DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} - ${GENERATED_API_DISPATCH} - ${GENERATED_FUNCS_METADATA} ${API_METADATA} - ${LUA_API_C_BINDINGS} - ${API_HEADERS} + COMMAND ${LUA_GEN_PRG} ${API_DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} + ${GENERATED_API_DISPATCH} + ${GENERATED_FUNCS_METADATA} ${API_METADATA} + ${LUA_API_C_BINDINGS} + ${API_HEADERS} DEPENDS ${API_HEADERS} ${MSGPACK_RPC_HEADERS} @@ -316,20 +608,16 @@ list(APPEND NVIM_GENERATED_SOURCES ) add_custom_command( - OUTPUT ${GENERATED_UI_EVENTS} - ${GENERATED_UI_EVENTS_CALL} + OUTPUT ${GENERATED_UI_EVENTS_CALL} ${GENERATED_UI_EVENTS_REMOTE} - ${GENERATED_UI_EVENTS_BRIDGE} ${GENERATED_UI_EVENTS_METADATA} ${GENERATED_UI_EVENTS_CLIENT} - 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} - ${GENERATED_UI_EVENTS_CLIENT} + COMMAND ${LUA_GEN_PRG} ${API_UI_EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h + ${GENERATED_UI_EVENTS_CALL} + ${GENERATED_UI_EVENTS_REMOTE} + ${GENERATED_UI_EVENTS_METADATA} + ${GENERATED_UI_EVENTS_CLIENT} DEPENDS ${API_UI_EVENTS_GENERATOR} ${GENERATOR_C_GRAMMAR} @@ -396,76 +684,45 @@ foreach(hfile ${NVIM_GENERATED_FOR_HEADERS}) endif() endforeach() -# Our dependencies come first. - if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") - list(APPEND NVIM_LINK_LIBRARIES pthread c++abi) -endif() - -if (LibIntl_FOUND) - list(APPEND NVIM_LINK_LIBRARIES ${LibIntl_LIBRARY}) -endif() - -if(Iconv_LIBRARIES) - list(APPEND NVIM_LINK_LIBRARIES ${Iconv_LIBRARIES}) + target_link_libraries(main_lib INTERFACE pthread c++abi) endif() if(WIN32) - list(APPEND NVIM_LINK_LIBRARIES netapi32) + target_link_libraries(main_lib INTERFACE netapi32) endif() -# Use "luv" as imported library, to work around CMake using "-lluv" for -# "luv.so". #10407 -add_library(luv UNKNOWN IMPORTED) -set_property(TARGET luv PROPERTY IMPORTED_LOCATION ${LIBLUV_LIBRARIES}) - -# Put these last on the link line, since multiple things may depend on them. -list(APPEND NVIM_LINK_LIBRARIES - luv - ${LIBUV_LIBRARIES} - ${MSGPACK_LIBRARIES} - ${LIBVTERM_LIBRARIES} - ${LIBTERMKEY_LIBRARIES} - ${UNIBILIUM_LIBRARIES} - ${UTF8PROC_LIBRARIES} - ${TreeSitter_LIBRARIES} - ${CMAKE_THREAD_LIBS_INIT} -) - if(UNIX) - list(APPEND NVIM_LINK_LIBRARIES - m) + target_link_libraries(main_lib INTERFACE m) if (NOT CMAKE_SYSTEM_NAME STREQUAL "SunOS") - list(APPEND NVIM_LINK_LIBRARIES - util) + target_link_libraries(main_lib INTERFACE util) endif() endif() -set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUA_PREFERRED_LIBRARIES}) +target_sources(nvim PRIVATE ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} + ${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS} + ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS}) + +set_target_properties(nvim + PROPERTIES + EXPORT_COMPILE_COMMANDS ON + ENABLE_EXPORTS TRUE) -# Add IPO flags (for LTO), or error if CMake does not know the flags. #8654 -if(POLICY CMP0069) - cmake_policy(SET CMP0069 NEW) +if(${CMAKE_VERSION} VERSION_LESS 3.20) + set(CMAKE_EXPORT_COMPILE_COMMANDS ON) endif() -add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS} - ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS}) -target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) +target_link_libraries(nvim PRIVATE main_lib PUBLIC libuv_lib) install_helper(TARGETS nvim) if(MSVC) install(FILES $<TARGET_PDB_FILE:nvim> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) endif() -set_property(TARGET nvim APPEND PROPERTY - INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) -set_property(TARGET nvim PROPERTY ENABLE_EXPORTS TRUE) - -if(ENABLE_LTO AND (POLICY CMP0069)) +if(ENABLE_LTO) include(CheckIPOSupported) check_ipo_supported(RESULT IPO_SUPPORTED) if(IPO_SUPPORTED AND (NOT CMAKE_BUILD_TYPE MATCHES Debug)) - set_property(TARGET nvim PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + set_target_properties(nvim PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) endif() endif() @@ -537,8 +794,8 @@ if(WIN32) translations/qt_uk.qm D3Dcompiler_47.dll libEGL.dll - libgcc_s_dw2-1.dll - libGLESV2.dll + libgcc_s_seh-1.dll + libGLESv2.dll libstdc++-6.dll libwinpthread-1.dll nvim-qt.exe @@ -569,7 +826,10 @@ file(MAKE_DIRECTORY ${BINARY_LIB_DIR}) # install treesitter parser if bundled if(EXISTS ${DEPS_PREFIX}/lib/nvim/parser) - file(COPY ${DEPS_PREFIX}/lib/nvim/parser DESTINATION ${BINARY_LIB_DIR}) + glob_wrapper(TREESITTER_PARSERS ${DEPS_PREFIX}/lib/nvim/parser/*) + foreach(parser_lib ${TREESITTER_PARSERS}) + file(COPY ${parser_lib} DESTINATION ${BINARY_LIB_DIR}/parser) + endforeach() endif() install(DIRECTORY ${BINARY_LIB_DIR} @@ -584,8 +844,6 @@ add_library( ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS} ) -set_property(TARGET libnvim APPEND PROPERTY - INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) if(MSVC) set(LIBNVIM_NAME libnvim) else() @@ -594,15 +852,15 @@ endif() set_target_properties( libnvim PROPERTIES - POSITION_INDEPENDENT_CODE ON OUTPUT_NAME ${LIBNVIM_NAME} ) -target_compile_options(libnvim PRIVATE -DMAKE_LIB) +target_compile_definitions(libnvim PRIVATE MAKE_LIB) +target_link_libraries(libnvim PRIVATE main_lib PUBLIC libuv_lib) if(NOT LUAJIT_FOUND) message(STATUS "luajit not found, skipping nvim-test (unit tests) target") else() - set(NVIM_TEST_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUAJIT_LIBRARIES}) + glob_wrapper(UNIT_TEST_FIXTURES ${PROJECT_SOURCE_DIR}/test/unit/fixtures/*.c) add_library( nvim-test MODULE @@ -612,49 +870,41 @@ else() ${EXTERNAL_SOURCES} ${EXTERNAL_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 - ) - target_compile_options(nvim-test PRIVATE -DUNIT_TESTING) + target_link_libraries(nvim-test PRIVATE ${LUAJIT_LIBRARIES} main_lib PUBLIC libuv_lib) + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(nvim-test PRIVATE "-framework CoreServices") + endif() + target_include_directories(nvim-test PRIVATE ${LUAJIT_INCLUDE_DIRS}) + target_compile_definitions(nvim-test PRIVATE UNIT_TESTING) endif() if(CLANG_ASAN_UBSAN) message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.") - check_c_compiler_flag(-fno-sanitize-recover=all SANITIZE_RECOVER_ALL) - if(SANITIZE_RECOVER_ALL) - if(CI_BUILD) - # Try to recover from all sanitize issues so we get reports about all failures - set(SANITIZE_RECOVER -fsanitize-recover=all) # Clang 3.6+ - else() - set(SANITIZE_RECOVER -fno-sanitize-recover=all) # Clang 3.6+ - endif() + 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) else() - if(CI_BUILD) - # Try to recover from all sanitize issues so we get reports about all failures - set(SANITIZE_RECOVER -fsanitize-recover) # Clang 3.5- - else() - set(SANITIZE_RECOVER -fno-sanitize-recover) # Clang 3.5- - endif() + target_compile_options(nvim PRIVATE -fno-sanitize-recover=all) endif() - set_property(TARGET nvim APPEND PROPERTY COMPILE_OPTIONS ${SANITIZE_RECOVER} -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist) - set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=address -fsanitize=undefined ") + target_compile_options(nvim PRIVATE + -fno-omit-frame-pointer + -fno-optimize-sibling-calls + -fsanitize=address + -fsanitize=undefined + -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist) + target_link_libraries(nvim PRIVATE -fsanitize=address -fsanitize=undefined) elseif(CLANG_MSAN) message(STATUS "Enabling Clang memory sanitizer for nvim.") - set_property(TARGET nvim APPEND PROPERTY COMPILE_OPTIONS -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer -fno-optimize-sibling-calls) - set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=memory -fsanitize-memory-track-origins ") + target_compile_options(nvim 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) elseif(CLANG_TSAN) message(STATUS "Enabling Clang thread sanitizer for nvim.") - set_property(TARGET nvim APPEND PROPERTY COMPILE_OPTIONS -fsanitize=thread) - set_property(TARGET nvim APPEND PROPERTY COMPILE_OPTIONS -fPIE) - set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=thread ") + target_compile_options(nvim PRIVATE -fsanitize=thread -fPIE) + target_link_libraries(nvim PRIVATE -fsanitize=thread) endif() function(get_test_target prefix sfile relative_path_var target_var) @@ -697,10 +947,7 @@ foreach(hfile ${NVIM_HEADERS}) ${texe} EXCLUDE_FROM_ALL ${tsource} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_HEADERS}) - set_property( - TARGET ${texe} - APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS} - ) + target_link_libraries(${texe} PRIVATE main_lib) set_target_properties(${texe} PROPERTIES FOLDER test) list(FIND NO_SINGLE_CHECK_HEADERS "${relative_path}" hfile_exclude_idx) @@ -711,55 +958,19 @@ foreach(hfile ${NVIM_HEADERS}) endforeach() add_custom_target(check-single-includes DEPENDS ${HEADER_CHECK_TARGETS}) -function(add_download output url allow_failure) - add_custom_command( - OUTPUT "${output}" - COMMAND - ${CMAKE_COMMAND} - -DURL=${url} -DFILE=${output} - -DALLOW_FAILURE=${allow_failure} - -P ${DOWNLOAD_SCRIPT} - DEPENDS ${DOWNLOAD_SCRIPT} - ) -endfunction() - -add_download(${LINT_SUPPRESSES_ARCHIVE} ${LINT_SUPPRESSES_URL} off) - -add_custom_command( - OUTPUT ${LINT_SUPPRESSES_TOUCH_FILE} - WORKING_DIRECTORY ${LINT_SUPPRESSES_ROOT}/src - COMMAND ${CMAKE_COMMAND} -E tar xfz ${LINT_SUPPRESSES_ARCHIVE} - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CLINT_REPORT_PATH} "${LINT_SUPPRESSES_ROOT}" - COMMAND ${CMAKE_COMMAND} -E touch ${LINT_SUPPRESSES_TOUCH_FILE} - DEPENDS ${LINT_SUPPRESSES_ARCHIVE} -) - -add_download(${LINT_SUPPRESS_FILE} ${LINT_SUPPRESS_URL} off) - if(CI_BUILD) set(LINT_OUTPUT_FORMAT gh_action) else() set(LINT_OUTPUT_FORMAT vs7) endif() -set(LINT_NVIM_REL_SOURCES) -foreach(sfile ${LINT_NVIM_SOURCES}) - get_test_target("" "${sfile}" r suffix) - set(suppress_file ${LINT_SUPPRESSES_ROOT}/${suffix}.json) - set(suppress_url "${LINT_SUPPRESS_URL_BASE}/${suffix}.json") - set(rsfile src/nvim/${r}) - set(touch_file "${TOUCHES_DIR}/ran-clint-${suffix}") - add_custom_command( - OUTPUT ${touch_file} - COMMAND ${LINT_PRG} --suppress-errors=${suppress_file} --output=${LINT_OUTPUT_FORMAT} ${rsfile} - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - COMMAND ${CMAKE_COMMAND} -E touch ${touch_file} - DEPENDS ${LINT_PRG} ${sfile} ${LINT_SUPPRESSES_TOUCH_FILE} - ) - list(APPEND LINT_TARGETS ${touch_file}) - list(APPEND LINT_NVIM_REL_SOURCES ${rsfile}) -endforeach() -add_custom_target(lintc DEPENDS ${LINT_TARGETS}) +add_glob_target( + TARGET lintc-clint + COMMAND ${PROJECT_SOURCE_DIR}/src/clint.py + FLAGS --output=${LINT_OUTPUT_FORMAT} + FILES ${LINT_NVIM_SOURCES} + EXCLUDE + tui/terminfo_defs.h) add_custom_target(uncrustify-version COMMAND ${CMAKE_COMMAND} @@ -767,13 +978,15 @@ add_custom_target(uncrustify-version -D CONFIG_FILE=${PROJECT_SOURCE_DIR}/src/uncrustify.cfg -P ${PROJECT_SOURCE_DIR}/cmake/CheckUncrustifyVersion.cmake) -add_glob_targets( - TARGET lintuncrustify +add_glob_target( + TARGET lintc-uncrustify COMMAND ${UNCRUSTIFY_PRG} FLAGS -c "${PROJECT_SOURCE_DIR}/src/uncrustify.cfg" -q --check - FILES ${LINT_NVIM_SOURCES} - ) -add_dependencies(lintuncrustify uncrustify-version) + FILES ${LINT_NVIM_SOURCES}) +add_dependencies(lintc-uncrustify uncrustify-version) + +add_custom_target(lintc) +add_dependencies(lintc lintc-clint lintc-uncrustify) add_custom_target(formatc COMMAND ${CMAKE_COMMAND} @@ -783,14 +996,6 @@ add_custom_target(formatc WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) add_dependencies(formatc uncrustify-version) -add_custom_target( - lintcfull - COMMAND - ${LINT_PRG} --suppress-errors=${LINT_SUPPRESS_FILE} --output=${LINT_OUTPUT_FORMAT} ${LINT_NVIM_REL_SOURCES} - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - DEPENDS ${LINT_PRG} ${LINT_NVIM_SOURCES} ${LINT_SUPPRESS_FILE} lintuncrustify -) - add_custom_target(generated-sources DEPENDS ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} diff --git a/src/nvim/README.md b/src/nvim/README.md index 91fb3ca2f6..6876227e48 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -18,6 +18,12 @@ The source files use extensions to hint about their purpose. - `*.h.generated.h` - exported functions’ declarations. - `*.c.generated.h` - static functions’ declarations. +Common structures +----------------- + +- StringBuilder +- kvec or garray.c for dynamic lists / vectors (use StringBuilder for strings) + Logs ---- @@ -391,8 +397,8 @@ implemented by libuv, the platform layer used by Nvim. 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). +receives an "arbitrary event", it normally doesn't do anything other than +update the screen). Main loop --------- diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 79ae7994f7..931363e199 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -1,8 +1,12 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> #include <stdbool.h> +#include <stdint.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include "lauxlib.h" #include "nvim/api/autocmd.h" @@ -12,7 +16,12 @@ #include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/autocmd.c.generated.h" @@ -38,7 +47,7 @@ static int64_t next_autocmd_id = 1; /// Get all autocommands that match the corresponding {opts}. /// /// These examples will get autocommands matching ALL the given criteria: -/// <pre> +/// <pre>lua /// -- Matches all criteria /// autocommands = vim.api.nvim_get_autocmds({ /// group = "MyGroup", @@ -59,6 +68,9 @@ static int64_t next_autocmd_id = 1; /// - group (string|integer): the autocommand group name or id to match against. /// - event (string|array): event or events to match against |autocmd-events|. /// - pattern (string|array): pattern or patterns to match against |autocmd-pattern|. +/// Cannot be used with {buffer} +/// - buffer: Buffer number or list of buffer numbers for buffer local autocommands +/// |autocmd-buflocal|. Cannot be used with {pattern} /// @return Array of autocommands matching the criteria, with each item /// containing the following fields: /// - id (number): the autocommand id (only when defined with the API). @@ -240,7 +252,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) assert(pattern_filters[i]); char *pat = pattern_filters[i]; - int patlen = (int)STRLEN(pat); + int patlen = (int)strlen(pat); if (aupat_is_buflocal(pat, patlen)) { aupat_normalize_buflocal_pat(pattern_buflocal, @@ -349,82 +361,49 @@ cleanup: return autocmd_list; } -/// Create an |autocommand| -/// -/// The API allows for two (mutually exclusive) types of actions to be executed when the autocommand -/// triggers: a callback function (Lua or Vimscript), or a command (like regular autocommands). -/// -/// Example using callback: -/// <pre> -/// -- Lua function -/// local myluafun = function() print("This buffer enters") end -/// -/// -- Vimscript function name (as a string) -/// local myvimfun = "g:MyVimFunction" -/// -/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { -/// pattern = {"*.c", "*.h"}, -/// callback = myluafun, -- Or myvimfun -/// }) -/// </pre> -/// -/// Lua functions receive a table with information about the autocmd event as an argument. To use -/// a function which itself accepts another (optional) parameter, wrap the function -/// in a lambda: -/// -/// <pre> -/// -- Lua function with an optional parameter. -/// -- The autocmd callback would pass a table as argument but this -/// -- function expects number|nil -/// local myluafun = function(bufnr) bufnr = bufnr or vim.api.nvim_get_current_buf() end +/// Creates an |autocommand| event handler, defined by `callback` (Lua function or Vimscript +/// function _name_ string) or `command` (Ex command string). /// +/// Example using Lua callback: +/// <pre>lua /// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { /// pattern = {"*.c", "*.h"}, -/// callback = function() myluafun() end, +/// callback = function(ev) +/// print(string.format('event fired: %s', vim.inspect(ev))) +/// end /// }) /// </pre> /// -/// Example using command: -/// <pre> +/// Example using an Ex command as the handler: +/// <pre>lua /// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { /// pattern = {"*.c", "*.h"}, /// command = "echo 'Entering a C or C++ file'", /// }) /// </pre> /// -/// Example values for pattern: -/// <pre> -/// pattern = "*.py" -/// pattern = { "*.py", "*.pyi" } -/// </pre> -/// -/// Example values for event: -/// <pre> -/// "BufWritePre" -/// {"CursorHold", "BufWritePre", "BufWritePost"} +/// Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like "$HOME" +/// and "~" must be expanded explicitly: +/// <pre>lua +/// pattern = vim.fn.expand("~") .. "/some/path/*.py" /// </pre> /// -/// @param event (string|array) The event or events to register this autocommand -/// @param opts Dictionary of autocommand options: -/// - group (string|integer) optional: the autocommand group name or -/// id to match against. -/// - pattern (string|array) optional: pattern or patterns to match -/// against |autocmd-pattern|. -/// - buffer (integer) optional: buffer number for buffer local autocommands +/// @param event (string|array) Event(s) that will trigger the handler (`callback` or `command`). +/// @param opts Options dict: +/// - group (string|integer) optional: autocommand group name or id to match against. +/// - pattern (string|array) optional: pattern(s) to match literally |autocmd-pattern|. +/// - buffer (integer) optional: buffer number for buffer-local autocommands /// |autocmd-buflocal|. Cannot be used with {pattern}. -/// - desc (string) optional: description of the autocommand. -/// - callback (function|string) optional: if a string, the name of a Vimscript function -/// to call when this autocommand is triggered. Otherwise, a Lua function which is -/// called when this autocommand is triggered. Cannot be used with {command}. Lua -/// callbacks can return true to delete the autocommand; in addition, they accept a -/// single table argument with the following keys: -/// - id: (number) the autocommand id -/// - event: (string) the name of the event that triggered the autocommand -/// |autocmd-events| -/// - group: (number|nil) the autocommand group id, if it exists -/// - match: (string) the expanded value of |<amatch>| -/// - buf: (number) the expanded value of |<abuf>| -/// - file: (string) the expanded value of |<afile>| +/// - desc (string) optional: description (for documentation and troubleshooting). +/// - callback (function|string) optional: Lua function (or Vimscript function name, if +/// string) called when the event(s) is triggered. Lua callback can return true to +/// delete the autocommand, and receives a table argument with these keys: +/// - id: (number) autocommand id +/// - event: (string) name of the triggered event |autocmd-events| +/// - group: (number|nil) autocommand group id, if any +/// - match: (string) expanded value of |<amatch>| +/// - buf: (number) expanded value of |<abuf>| +/// - file: (string) expanded value of |<afile>| /// - data: (any) arbitrary data passed to |nvim_exec_autocmds()| /// - command (string) optional: Vim command to execute on event. Cannot be used with /// {callback} @@ -433,7 +412,7 @@ cleanup: /// - nested (boolean) optional: defaults to false. Run nested /// autocommands |autocmd-nested|. /// -/// @return Integer id of the created autocommand. +/// @return Autocommand id (number) /// @see |autocommand| /// @see |nvim_del_autocmd()| Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts, @@ -454,8 +433,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc } if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) { - api_set_error(err, kErrorTypeValidation, - "cannot pass both: 'callback' and 'command' for the same autocmd"); + api_set_error(err, kErrorTypeValidation, "specify either 'callback' or 'command', not both"); goto cleanup; } else if (opts->callback.type != kObjectTypeNil) { // TODO(tjdevries): It's possible we could accept callable tables, @@ -596,7 +574,7 @@ void nvim_del_autocmd(Integer id, Error *err) } /// Clear all autocommands that match the corresponding {opts}. To delete -/// a particular autocmd, see |nvim_del_autocmd|. +/// a particular autocmd, see |nvim_del_autocmd()|. /// @param opts Parameters /// - event: (string|table) /// Examples: @@ -684,7 +662,7 @@ cleanup: /// Create or get an autocommand group |autocmd-groups|. /// /// To get an existing group id, do: -/// <pre> +/// <pre>lua /// local id = vim.api.nvim_create_augroup("MyGroup", { /// clear = false /// }) @@ -725,7 +703,7 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou /// /// To get a group id one can use |nvim_get_autocmds()|. /// -/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in +/// NOTE: behavior differs from |:augroup-delete|. When deleting a group, autocommands contained in /// this group will also be deleted and cleared. This group will no longer exist. /// @param id Integer The id of the group. /// @see |nvim_del_augroup_by_name()| @@ -743,10 +721,10 @@ void nvim_del_augroup_by_id(Integer id, Error *err) /// Delete an autocommand group by name. /// -/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in +/// NOTE: behavior differs from |:augroup-delete|. When deleting a group, autocommands contained in /// this group will also be deleted and cleared. This group will no longer exist. /// @param name String The name of the group. -/// @see |autocommand-groups| +/// @see |autocmd-groups| void nvim_del_augroup_by_name(String name, Error *err) FUNC_API_SINCE(9) { @@ -887,12 +865,12 @@ static bool check_autocmd_string_array(Array arr, char *k, Error *err) static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err) { if (v->type == kObjectTypeString) { - ADD(*array, copy_object(*v)); + ADD(*array, copy_object(*v, NULL)); } else if (v->type == kObjectTypeArray) { if (!check_autocmd_string_array(v->data.array, k, err)) { return false; } - *array = copy_array(v->data.array); + *array = copy_array(v->data.array, NULL); } else { if (required) { api_set_error(err, @@ -962,7 +940,7 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob patlen = aucmd_pattern_length(pat); } } else if (v->type == kObjectTypeArray) { - if (!check_autocmd_string_array(*patterns, "pattern", err)) { + if (!check_autocmd_string_array(v->data.array, "pattern", err)) { return false; } diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 5e90e40dd3..fe9e6077d6 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -3,25 +3,30 @@ // Some of this code was adapted from 'if_py_both.h' from the original // vim source + +#include <assert.h> #include <lauxlib.h> -#include <limits.h> #include <stdbool.h> +#include <stddef.h> #include <stdint.h> -#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" +#include "lua.h" #include "nvim/api/buffer.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/cursor.h" -#include "nvim/decoration.h" #include "nvim/drawscreen.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_docmd.h" #include "nvim/extmark.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" #include "nvim/mapping.h" #include "nvim/mark.h" @@ -29,9 +34,10 @@ #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/ops.h" +#include "nvim/pos.h" +#include "nvim/types.h" #include "nvim/undo.h" #include "nvim/vim.h" -#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/buffer.c.generated.h" @@ -78,7 +84,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// /// Example (Lua): capture buffer updates in a global `events` variable /// (use "print(vim.inspect(events))" to see its contents): -/// <pre> +/// <pre>lua /// events = {} /// vim.api.nvim_buf_attach(0, false, { /// on_lines=function(...) table.insert(events, {...}) end}) @@ -271,6 +277,7 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, Integer start, Integer end, Boolean strict_indexing, + lua_State *lstate, Error *err) FUNC_API_SINCE(1) { @@ -300,21 +307,18 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id, return rv; } - rv.size = (size_t)(end - start); - rv.items = xcalloc(rv.size, sizeof(Object)); + size_t size = (size_t)(end - start); + + init_line_array(lstate, &rv, size); - if (!buf_collect_lines(buf, rv.size, start, - (channel_id != VIML_INTERNAL_CALL), &rv, err)) { + if (!buf_collect_lines(buf, size, (linenr_T)start, 0, (channel_id != VIML_INTERNAL_CALL), &rv, + lstate, err)) { goto end; } end: if (ERROR_SET(err)) { - for (size_t i = 0; i < rv.size; i++) { - xfree(rv.items[i].data.string.data); - } - - xfree(rv.items); + api_free_array(rv); rv.items = NULL; } @@ -355,6 +359,8 @@ static bool check_string_array(Array arr, bool disallow_nl, Error *err) /// Out-of-bounds indices are clamped to the nearest valid value, unless /// `strict_indexing` is set. /// +/// @see |nvim_buf_set_text()| +/// /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer /// @param start First line index @@ -527,6 +533,8 @@ end: /// /// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines. /// +/// @see |nvim_buf_set_lines()| +/// /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer /// @param start_row First line index @@ -570,7 +578,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In char *str_at_end = NULL; // Another call to ml_get_buf() may free the line, so make a copy. - str_at_start = xstrdup((char *)ml_get_buf(buf, (linenr_T)start_row, false)); + str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row, false)); size_t len_at_start = strlen(str_at_start); if (start_col < 0 || (size_t)start_col > len_at_start) { api_set_error(err, kErrorTypeValidation, "start_col out of bounds"); @@ -578,7 +586,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In } // Another call to ml_get_buf() may free the line, so make a copy. - str_at_end = xstrdup((char *)ml_get_buf(buf, (linenr_T)end_row, false)); + str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row, false)); size_t len_at_end = strlen(str_at_end); if (end_col < 0 || (size_t)end_col > len_at_end) { api_set_error(err, kErrorTypeValidation, "end_col out of bounds"); @@ -608,7 +616,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In for (int64_t i = 1; i < end_row - start_row; i++) { int64_t lnum = start_row + i; - const char *bufline = (char *)ml_get_buf(buf, (linenr_T)lnum, false); + const char *bufline = ml_get_buf(buf, (linenr_T)lnum, false); old_byte += (bcount_t)(strlen(bufline)) + 1; } old_byte += (bcount_t)end_col + 1; @@ -786,7 +794,8 @@ early_end: ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, Integer start_row, Integer start_col, Integer end_row, Integer end_col, - Dictionary opts, Error *err) + Dictionary opts, lua_State *lstate, + Error *err) FUNC_API_SINCE(9) { Array rv = ARRAY_DICT_INIT; @@ -826,33 +835,37 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer, bool replace_nl = (channel_id != VIML_INTERNAL_CALL); + size_t size = (size_t)(end_row - start_row) + 1; + + init_line_array(lstate, &rv, size); + if (start_row == end_row) { - String line = buf_get_text(buf, start_row, start_col, end_col, replace_nl, err); + String line = buf_get_text(buf, start_row, start_col, end_col, err); if (ERROR_SET(err)) { - return rv; + goto end; } - - ADD(rv, STRING_OBJ(line)); + push_linestr(lstate, &rv, line.data, line.size, 0, replace_nl); return rv; } - rv.size = (size_t)(end_row - start_row) + 1; - rv.items = xcalloc(rv.size, sizeof(Object)); + String str = buf_get_text(buf, start_row, start_col, MAXCOL - 1, err); + + push_linestr(lstate, &rv, str.data, str.size, 0, replace_nl); - rv.items[0] = STRING_OBJ(buf_get_text(buf, start_row, start_col, MAXCOL - 1, replace_nl, err)); if (ERROR_SET(err)) { goto end; } - if (rv.size > 2) { - Array tmp = ARRAY_DICT_INIT; - tmp.items = &rv.items[1]; - if (!buf_collect_lines(buf, rv.size - 2, start_row + 1, replace_nl, &tmp, err)) { + if (size > 2) { + if (!buf_collect_lines(buf, size - 2, (linenr_T)start_row + 1, 1, replace_nl, &rv, lstate, + err)) { goto end; } } - rv.items[rv.size - 1] = STRING_OBJ(buf_get_text(buf, end_row, 0, end_col, replace_nl, err)); + str = buf_get_text(buf, end_row, 0, end_col, err); + push_linestr(lstate, &rv, str.data, str.size, (int)(size - 1), replace_nl); + if (ERROR_SET(err)) { goto end; } @@ -945,7 +958,7 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) /// @param[out] err Error details, if any /// @returns Array of |maparg()|-like dictionaries describing mappings. /// The "buffer" key holds the associated buffer handle. -ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, String mode, Error *err) +ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) FUNC_API_SINCE(3) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -954,7 +967,7 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, Stri return (Array)ARRAY_DICT_INIT; } - return keymap_array(mode, buf, channel_id == LUA_INTERNAL_CALL); + return keymap_array(mode, buf); } /// Sets a buffer-local |mapping| for the given mode. @@ -1021,7 +1034,7 @@ void nvim_buf_del_var(Buffer buffer, String name, Error *err) /// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any /// @return Buffer name -String nvim_buf_get_name(Buffer buffer, Error *err) +String nvim_buf_get_name(Buffer buffer, Arena *arena, Error *err) FUNC_API_SINCE(1) { String rv = STRING_INIT; @@ -1031,7 +1044,7 @@ String nvim_buf_get_name(Buffer buffer, Error *err) return rv; } - return cstr_to_string((char *)buf->b_ffname); + return cstr_as_string(buf->b_ffname); } /// Sets the full file name for a buffer @@ -1386,3 +1399,91 @@ static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bo index++; return index; } + +/// Initialise a string array either: +/// - on the Lua stack (as a table) (if lstate is not NULL) +/// - as an API array object (if lstate is NULL). +/// +/// @param lstate Lua state. When NULL the Array is initialized instead. +/// @param a Array to initialize +/// @param size Size of array +static inline void init_line_array(lua_State *lstate, Array *a, size_t size) +{ + if (lstate) { + lua_createtable(lstate, (int)size, 0); + } else { + a->size = size; + a->items = xcalloc(a->size, sizeof(Object)); + } +} + +/// Push a string onto either the Lua stack (as a table element) or an API array object. +/// +/// For Lua, a table of the correct size must be created first. +/// API array objects must be pre allocated. +/// +/// @param lstate Lua state. When NULL the Array is pushed to instead. +/// @param a Array to push onto when not using Lua +/// @param s String to push +/// @param len Size of string +/// @param idx 0-based index to place s +/// @param replace_nl Replace newlines ('\n') with null ('\0') +static void push_linestr(lua_State *lstate, Array *a, const char *s, size_t len, int idx, + bool replace_nl) +{ + if (lstate) { + // Vim represents NULs as NLs + if (s && replace_nl && strchr(s, '\n')) { + char *tmp = xmemdupz(s, len); + strchrsub(tmp, '\n', '\0'); + lua_pushlstring(lstate, tmp, len); + xfree(tmp); + } else { + lua_pushlstring(lstate, s, len); + } + lua_rawseti(lstate, -2, idx + 1); + } else { + String str = STRING_INIT; + if (s) { + str = cbuf_to_string(s, len); + if (replace_nl) { + // Vim represents NULs as NLs, but this may confuse clients. + strchrsub(str.data, '\n', '\0'); + } + } + + a->items[idx] = STRING_OBJ(str); + } +} + +/// Collects `n` buffer lines into array `l` and/or lua_State `lstate`, optionally replacing +/// newlines with NUL. +/// +/// @param buf Buffer to get lines from +/// @param n Number of lines to collect +/// @param replace_nl Replace newlines ("\n") with NUL +/// @param start Line number to start from +/// @param start_idx First index to push to +/// @param[out] l If not NULL, Lines are copied here +/// @param[out] lstate If not NULL, Lines are pushed into a table onto the stack +/// @param err[out] Error, if any +/// @return true unless `err` was set +bool buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool replace_nl, + Array *l, lua_State *lstate, Error *err) +{ + for (size_t i = 0; i < n; i++) { + linenr_T lnum = start + (linenr_T)i; + + if (lnum >= MAXLNUM) { + if (err != NULL) { + api_set_error(err, kErrorTypeValidation, "Line index is too high"); + } + return false; + } + + char *bufstr = ml_get_buf(buf, lnum, false); + push_linestr(lstate, l, bufstr, strlen(bufstr), start_idx + (int)i, replace_nl); + } + + return true; +} diff --git a/src/nvim/api/buffer.h b/src/nvim/api/buffer.h index 1c4a93a587..0814da63cd 100644 --- a/src/nvim/api/buffer.h +++ b/src/nvim/api/buffer.h @@ -1,7 +1,10 @@ #ifndef NVIM_API_BUFFER_H #define NVIM_API_BUFFER_H +#include <lauxlib.h> + #include "nvim/api/private/defs.h" +#include "nvim/buffer_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/buffer.h.generated.h" diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index 1323fc347b..abd265f2cf 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -1,21 +1,36 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <inttypes.h> #include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/command.h" -#include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" +#include "nvim/decoration.h" +#include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" +#include "nvim/garray.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" #include "nvim/ops.h" +#include "nvim/pos.h" #include "nvim/regexp.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/usercmd.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -31,17 +46,18 @@ /// @param[out] err Error details, if any. /// @return Dictionary containing command information, with these keys: /// - cmd: (string) Command name. -/// - range: (array) Command <range>. Can have 0-2 elements depending on how many items the -/// range contains. Has no elements if command doesn't accept a range or if -/// no range was specified, one element if only a single range item was -/// specified and two elements if both range items were specified. -/// - count: (number) Any |<count>| that was supplied to the command. -1 if command cannot -/// take a count. -/// - reg: (number) The optional command |<register>|, if specified. Empty string if not -/// specified or if command cannot take a register. +/// - range: (array) (optional) Command range (|<line1>| |<line2>|). +/// Omitted if command doesn't accept a range. +/// Otherwise, has no elements if no range was specified, one element if +/// only a single range item was specified, or two elements if both range +/// items were specified. +/// - count: (number) (optional) Command |<count>|. +/// Omitted if command cannot take a count. +/// - reg: (string) (optional) Command |<register>|. +/// Omitted if command cannot take a register. /// - bang: (boolean) Whether command contains a |<bang>| (!) modifier. /// - args: (array) Command arguments. -/// - addr: (string) Value of |:command-addr|. Uses short name. +/// - addr: (string) Value of |:command-addr|. Uses short name or "line" for -addr=lines. /// - nargs: (string) Value of |:command-nargs|. /// - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|. /// Empty if there isn't a next command. @@ -62,13 +78,14 @@ /// - browse: (boolean) |:browse|. /// - confirm: (boolean) |:confirm|. /// - hide: (boolean) |:hide|. +/// - horizontal: (boolean) |:horizontal|. /// - keepalt: (boolean) |:keepalt|. /// - keepjumps: (boolean) |:keepjumps|. /// - keepmarks: (boolean) |:keepmarks|. /// - keeppatterns: (boolean) |:keeppatterns|. /// - lockmarks: (boolean) |:lockmarks|. /// - noswapfile: (boolean) |:noswapfile|. -/// - tab: (integer) |:tab|. +/// - tab: (integer) |:tab|. -1 when omitted. /// - verbose: (integer) |:verbose|. -1 when omitted. /// - vertical: (boolean) |:vertical|. /// - split: (string) Split modifier string, is an empty string when there's no split @@ -104,7 +121,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) // Parse arguments Array args = ARRAY_DICT_INIT; - size_t length = STRLEN(ea.arg); + size_t length = strlen(ea.arg); // For nargs = 1 or '?', pass the entire argument list as a single argument, // otherwise split arguments by whitespace. @@ -141,15 +158,15 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx))); } - if ((ea.argt & EX_RANGE) && ea.addr_count > 0) { + if (ea.argt & EX_RANGE) { Array range = ARRAY_DICT_INIT; - if (ea.addr_count > 1) { - ADD(range, INTEGER_OBJ(ea.line1)); + if (ea.addr_count > 0) { + if (ea.addr_count > 1) { + ADD(range, INTEGER_OBJ(ea.line1)); + } + ADD(range, INTEGER_OBJ(ea.line2)); } - ADD(range, INTEGER_OBJ(ea.line2)); PUT(result, "range", ARRAY_OBJ(range)); - } else { - PUT(result, "range", ARRAY_OBJ(ARRAY_DICT_INIT)); } if (ea.argt & EX_COUNT) { @@ -160,14 +177,12 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) } else { PUT(result, "count", INTEGER_OBJ(0)); } - } else { - PUT(result, "count", INTEGER_OBJ(-1)); } - char reg[2]; - reg[0] = (char)ea.regname; - reg[1] = '\0'; - PUT(result, "reg", CSTR_TO_OBJ(reg)); + if (ea.argt & EX_REGSTR) { + char reg[2] = { (char)ea.regname, NUL }; + PUT(result, "reg", CSTR_TO_OBJ(reg)); + } PUT(result, "bang", BOOLEAN_OBJ(ea.forceit)); PUT(result, "args", ARRAY_OBJ(args)); @@ -238,7 +253,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(mods, "unsilent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_UNSILENT)); PUT(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX)); PUT(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOAUTOCMD)); - PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab)); + PUT(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab - 1)); PUT(mods, "verbose", INTEGER_OBJ(cmdinfo.cmdmod.cmod_verbose - 1)); PUT(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_BROWSE)); PUT(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_CONFIRM)); @@ -250,6 +265,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err) PUT(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_LOCKMARKS)); PUT(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOSWAPFILE)); PUT(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_VERT)); + PUT(mods, "horizontal", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_HOR)); const char *split; if (cmdinfo.cmdmod.cmod_split & WSP_BOT) { @@ -283,7 +299,11 @@ end: /// Unlike |nvim_command()| this command takes a structured Dictionary instead of a String. This /// allows for easier construction and manipulation of an Ex command. This also allows for things /// such as having spaces inside a command argument, expanding filenames in a command that otherwise -/// doesn't expand filenames, etc. +/// doesn't expand filenames, etc. Command arguments may also be Number, Boolean or String. +/// +/// The first argument may also be used instead of count for commands that support it in order to +/// make their usage simpler with |vim.cmd()|. For example, instead of +/// `vim.cmd.bdelete{ count = 2 }`, you may do `vim.cmd.bdelete(2)`. /// /// On execution error: fails with VimL error, updates v:errmsg. /// @@ -308,8 +328,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error char *cmdline = NULL; char *cmdname = NULL; - ArrayOf(String) args; - size_t argc = 0; + ArrayOf(String) args = ARRAY_DICT_INIT; String retv = (String)STRING_INIT; @@ -381,48 +400,70 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error if (cmd->args.type != kObjectTypeArray) { VALIDATION_ERROR("'args' must be an Array"); } - // Check if every argument is valid + + // Process all arguments. Convert non-String arguments to String and check if String arguments + // have non-whitespace characters. for (size_t i = 0; i < cmd->args.data.array.size; i++) { Object elem = cmd->args.data.array.items[i]; - if (elem.type != kObjectTypeString) { - VALIDATION_ERROR("Command argument must be a String"); - } else if (string_iswhite(elem.data.string)) { - VALIDATION_ERROR("Command argument must have non-whitespace characters"); + char *data_str; + + switch (elem.type) { + case kObjectTypeBoolean: + data_str = xcalloc(2, sizeof(char)); + data_str[0] = elem.data.boolean ? '1' : '0'; + data_str[1] = '\0'; + break; + case kObjectTypeBuffer: + case kObjectTypeWindow: + case kObjectTypeTabpage: + case kObjectTypeInteger: + data_str = xcalloc(NUMBUFLEN, sizeof(char)); + snprintf(data_str, NUMBUFLEN, "%" PRId64, elem.data.integer); + break; + case kObjectTypeString: + if (string_iswhite(elem.data.string)) { + VALIDATION_ERROR("String command argument must have at least one non-whitespace " + "character"); + } + data_str = xstrndup(elem.data.string.data, elem.data.string.size); + break; + default: + VALIDATION_ERROR("Invalid type for command argument"); + break; } + + ADD(args, STRING_OBJ(cstr_as_string(data_str))); } - argc = cmd->args.data.array.size; bool argc_valid; // Check if correct number of arguments is used. switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) { case EX_EXTRA | EX_NOSPC | EX_NEEDARG: - argc_valid = argc == 1; + argc_valid = args.size == 1; break; case EX_EXTRA | EX_NOSPC: - argc_valid = argc <= 1; + argc_valid = args.size <= 1; break; case EX_EXTRA | EX_NEEDARG: - argc_valid = argc >= 1; + argc_valid = args.size >= 1; break; case EX_EXTRA: argc_valid = true; break; default: - argc_valid = argc == 0; + argc_valid = args.size == 0; break; } if (!argc_valid) { VALIDATION_ERROR("Incorrect number of arguments supplied"); } - - args = cmd->args.data.array; } // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()` // since it only ever checks the first argument. - set_cmd_addr_type(&ea, argc > 0 ? args.items[0].data.string.data : NULL); + set_cmd_addr_type(&ea, args.size > 0 ? args.items[0].data.string.data : NULL); if (HAS_KEY(cmd->range)) { if (!(ea.argt & EX_RANGE)) { @@ -557,15 +598,17 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error } if (HAS_KEY(mods.tab)) { - if (mods.tab.type != kObjectTypeInteger || mods.tab.data.integer < 0) { - VALIDATION_ERROR("'mods.tab' must be a non-negative Integer"); + if (mods.tab.type != kObjectTypeInteger) { + VALIDATION_ERROR("'mods.tab' must be an Integer"); + } else if ((int)mods.tab.data.integer >= 0) { + // Silently ignore negative integers to allow mods.tab to be set to -1. + cmdinfo.cmdmod.cmod_tab = (int)mods.tab.data.integer + 1; } - cmdinfo.cmdmod.cmod_tab = (int)mods.tab.data.integer + 1; } if (HAS_KEY(mods.verbose)) { if (mods.verbose.type != kObjectTypeInteger) { - VALIDATION_ERROR("'mods.verbose' must be a Integer"); + VALIDATION_ERROR("'mods.verbose' must be an Integer"); } else if ((int)mods.verbose.data.integer >= 0) { // Silently ignore negative integers to allow mods.verbose to be set to -1. cmdinfo.cmdmod.cmod_verbose = (int)mods.verbose.data.integer + 1; @@ -576,6 +619,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'"); cmdinfo.cmdmod.cmod_split |= (vertical ? WSP_VERT : 0); + bool horizontal; + OBJ_TO_BOOL(horizontal, mods.horizontal, false, "'mods.horizontal'"); + cmdinfo.cmdmod.cmod_split |= (horizontal ? WSP_HOR : 0); + if (HAS_KEY(mods.split)) { if (mods.split.type != kObjectTypeString) { VALIDATION_ERROR("'mods.split' must be a String"); @@ -583,15 +630,15 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error if (*mods.split.data.string.data == NUL) { // Empty string, do nothing. - } else if (STRCMP(mods.split.data.string.data, "aboveleft") == 0 - || STRCMP(mods.split.data.string.data, "leftabove") == 0) { + } else if (strcmp(mods.split.data.string.data, "aboveleft") == 0 + || strcmp(mods.split.data.string.data, "leftabove") == 0) { cmdinfo.cmdmod.cmod_split |= WSP_ABOVE; - } else if (STRCMP(mods.split.data.string.data, "belowright") == 0 - || STRCMP(mods.split.data.string.data, "rightbelow") == 0) { + } else if (strcmp(mods.split.data.string.data, "belowright") == 0 + || strcmp(mods.split.data.string.data, "rightbelow") == 0) { cmdinfo.cmdmod.cmod_split |= WSP_BELOW; - } else if (STRCMP(mods.split.data.string.data, "topleft") == 0) { + } else if (strcmp(mods.split.data.string.data, "topleft") == 0) { cmdinfo.cmdmod.cmod_split |= WSP_TOP; - } else if (STRCMP(mods.split.data.string.data, "botright") == 0) { + } else if (strcmp(mods.split.data.string.data, "botright") == 0) { cmdinfo.cmdmod.cmod_split |= WSP_BOT; } else { VALIDATION_ERROR("Invalid value for 'mods.split'"); @@ -613,6 +660,12 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods.lockmarks, false, "'mods.lockmarks'"); OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods.noswapfile, false, "'mods.noswapfile'"); + if (cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT) { + // CMOD_ERRSILENT must imply CMOD_SILENT, otherwise apply_cmdmod() and undo_cmdmod() won't + // work properly. + cmdinfo.cmdmod.cmod_flags |= CMOD_SILENT; + } + if ((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)) { VALIDATION_ERROR("Command cannot be run in sandbox"); } @@ -620,7 +673,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error // Finally, build the command line string that will be stored inside ea.cmdlinep. // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens. - build_cmdline_str(&cmdline, &ea, &cmdinfo, args, argc); + build_cmdline_str(&cmdline, &ea, &cmdinfo, args); ea.cmdlinep = &cmdline; garray_T capture_local; @@ -676,6 +729,7 @@ clear_ga: ga_clear(&capture_local); } end: + api_free_array(args); xfree(cmdline); xfree(cmdname); xfree(ea.args); @@ -705,8 +759,9 @@ static bool string_iswhite(String str) /// Build cmdline string for command, used by `nvim_cmd()`. static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, - ArrayOf(String) args, size_t argc) + ArrayOf(String) args) { + size_t argc = args.size; StringBuilder cmdline = KV_INITIAL_VALUE; kv_resize(cmdline, 32); // Make it big enough to handle most typical commands @@ -753,6 +808,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin } while (0) CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_split & WSP_VERT, "vertical "); + CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_split & WSP_HOR, "horizontal "); CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_SANDBOX, "sandbox "); CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_NOAUTOCMD, "noautocmd "); CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_BROWSE, "browse "); @@ -791,7 +847,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin } eap->argc = argc; - eap->arglens = xcalloc(argc, sizeof(size_t)); + eap->arglens = eap->argc > 0 ? xcalloc(argc, sizeof(size_t)) : NULL; size_t argstart_idx = cmdline.size; for (size_t i = 0; i < argc; i++) { String s = args.items[i].data.string; @@ -806,7 +862,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin // Now that all the arguments are appended, use the command index and argument indices to set the // values of eap->cmd, eap->arg and eap->args. eap->cmd = cmdline.items + cmdname_idx; - eap->args = xcalloc(argc, sizeof(char *)); + eap->args = eap->argc > 0 ? xcalloc(argc, sizeof(char *)) : NULL; size_t offset = argstart_idx; for (size_t i = 0; i < argc; i++) { offset++; // Account for space @@ -823,13 +879,12 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin // Replace, :make and :grep with 'makeprg' and 'grepprg'. char *p = replace_makeprg(eap, eap->arg, cmdlinep); if (p != eap->arg) { - // If replace_makeprg modified the cmdline string, correct the argument pointers. + // If replace_makeprg() modified the cmdline string, correct the eap->arg pointer. eap->arg = p; - // We can only know the position of the first argument because the argument list can be used - // multiple times in makeprg / grepprg. - if (argc >= 1) { - eap->args[0] = p; - } + // This cannot be a user command, so eap->args will not be used. + XFREE_CLEAR(eap->args); + XFREE_CLEAR(eap->arglens); + eap->argc = 0; } } @@ -840,7 +895,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin /// {command} is the replacement text or Lua function to execute. /// /// Example: -/// <pre> +/// <pre>vim /// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {}) /// :SayHello /// Hello world! @@ -850,6 +905,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin /// @param command Replacement command to execute when this user command is executed. When called /// from Lua, the command can also be a Lua function. The function is called with a /// single table argument that contains the following keys: +/// - name: (string) Command name /// - args: (string) The args passed to the command, if any |<args>| /// - fargs: (table) The args split by unescaped whitespace (when more than one /// argument is allowed), if any |<f-args>| @@ -929,7 +985,7 @@ void nvim_buf_del_user_command(Buffer buffer, String name, Error *err) for (int i = 0; i < gap->ga_len; i++) { ucmd_T *cmd = USER_CMD_GA(gap, i); - if (!STRCMP(name.data, cmd->uc_name)) { + if (!strcmp(name.data, cmd->uc_name)) { free_ucmd(cmd); gap->ga_len -= 1; diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c index abaac07755..332e2b5fc3 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -1,7 +1,6 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <limits.h> #include <stdbool.h> #include <stdint.h> #include <stdlib.h> @@ -11,10 +10,15 @@ #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/api/vimscript.h" +#include "nvim/buffer_defs.h" +#include "nvim/decoration.h" #include "nvim/extmark.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" +#include "nvim/pos.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/deprecated.c.generated.h" @@ -190,7 +194,7 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) String rv = { .size = 0 }; index = convert_index(index); - Array slice = nvim_buf_get_lines(0, buffer, index, index + 1, true, err); + Array slice = nvim_buf_get_lines(0, buffer, index, index + 1, true, NULL, err); if (!ERROR_SET(err) && slice.size) { rv = slice.items[0].data.string; @@ -263,7 +267,7 @@ ArrayOf(String) buffer_get_line_slice(Buffer buffer, { start = convert_index(start) + !include_start; end = convert_index(end) + include_end; - return nvim_buf_get_lines(0, buffer, start, end, false, err); + return nvim_buf_get_lines(0, buffer, start, end, false, NULL, err); } /// Replaces a line range on the buffer diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 933aa85530..ab3b3485e4 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -1,20 +1,29 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> #include <stdbool.h> #include <stdint.h> -#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/decoration.h" #include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" #include "nvim/extmark.h" #include "nvim/highlight_group.h" -#include "nvim/lua/executor.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/pos.h" +#include "nvim/strings.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/extmark.c.generated.h" @@ -31,7 +40,7 @@ void api_extmark_free_all_mem(void) map_destroy(String, handle_T)(&namespace_ids); } -/// Creates a new \*namespace\* or gets an existing one. +/// Creates a new namespace or gets an existing one. \*namespace\* /// /// Namespaces are used for buffer highlights and virtual text, see /// |nvim_buf_add_highlight()| and |nvim_buf_set_extmark()|. @@ -51,13 +60,13 @@ Integer nvim_create_namespace(String name) } id = next_namespace_id++; if (name.size > 0) { - String name_alloc = copy_string(name); + String name_alloc = copy_string(name, NULL); map_put(String, handle_T)(&namespace_ids, name_alloc, id); } return (Integer)id; } -/// Gets existing, non-anonymous namespaces. +/// Gets existing, non-anonymous |namespace|s. /// /// @return dict that maps from names to namespace ids. Dictionary nvim_get_namespaces(void) @@ -87,7 +96,7 @@ const char *describe_ns(NS ns_id) } // Is the Namespace in use? -static bool ns_initialized(uint32_t ns) +bool ns_initialized(uint32_t ns) { if (ns < 1) { return false; @@ -186,7 +195,7 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict return rv; } -/// Gets the position (0-indexed) of an extmark. +/// Gets the position (0-indexed) of an |extmark|. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace id from |nvim_create_namespace()| @@ -240,31 +249,29 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return extmark_to_array(&extmark, false, details); } -/// Gets extmarks in "traversal order" from a |charwise| region defined by +/// 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) /// respectively, thus the following are equivalent: -/// -/// <pre> -/// nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) -/// nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {}) +/// <pre>lua +/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) +/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) /// </pre> /// /// If `end` is less than `start`, traversal works backwards. (Useful /// with `limit`, to get the first marks prior to a given position.) /// /// Example: -/// -/// <pre> +/// <pre>lua /// local a = vim.api /// local pos = a.nvim_win_get_cursor(0) /// local ns = a.nvim_create_namespace('my-plugin') /// -- Create new extmark at line 1, column 1. /// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, {}) /// -- Create new extmark at line 3, column 1. -/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, {}) +/// local m2 = a.nvim_buf_set_extmark(0, ns, 2, 0, {}) /// -- Get extmarks only from line 3. /// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) /// -- Get all marks in this buffer + namespace. @@ -361,7 +368,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e return rv; } -/// Creates or updates an extmark. +/// Creates or updates an |extmark|. /// /// By default a new extmark is created when no id is passed in, but it is also /// possible to create a new mark by passing in a previously unused id or move @@ -389,11 +396,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// - virt_text : virtual text to link to this mark. /// A list of [text, highlight] tuples, each representing a /// text chunk with specified highlight. `highlight` element -/// can either be a a single highlight group, or an array of +/// can either be a single highlight group, or an array of /// multiple highlight groups that will be stacked /// (highest priority last). A highlight group can be supplied /// either as a string or as an integer, the latter which -/// can be obtained using |nvim_get_hl_id_by_name|. +/// can be obtained using |nvim_get_hl_id_by_name()|. /// - virt_text_pos : position of virtual text. Possible values: /// - "eol": right after eol character (default) /// - "overlay": display over the specified column, without @@ -430,7 +437,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// column of the window, bypassing /// sign and number columns. /// -/// - ephemeral : for use with |nvim_set_decoration_provider| +/// - ephemeral : for use with |nvim_set_decoration_provider()| /// callbacks. The mark will only be used for the current /// redraw cycle, and not be permantently stored in the /// buffer. @@ -473,6 +480,8 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// When a character is supplied it is used as |:syn-cchar|. /// "hl_group" is used as highlight for the cchar if provided, /// otherwise it defaults to |hl-Conceal|. +/// - spell: boolean indicating that spell checking should be +/// performed within this extmark /// - ui_watched: boolean that indicates the mark should be drawn /// by a UI. When set, the UI will receive win_extmark events. /// Note: the mark is positioned by virt_text attributes. Can be @@ -689,7 +698,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer } if (opts->sign_text.type == kObjectTypeString) { - if (!init_sign_text((char **)&decor.sign_text, + if (!init_sign_text(&decor.sign_text, opts->sign_text.data.string.data)) { api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value"); goto error; @@ -719,6 +728,15 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer bool ephemeral = false; OPTION_TO_BOOL(ephemeral, ephemeral, false); + if (opts->spell.type == kObjectTypeNil) { + decor.spell = kNone; + } else { + bool spell = false; + OPTION_TO_BOOL(spell, spell, false); + decor.spell = spell ? kTrue : kFalse; + has_decor = true; + } + OPTION_TO_BOOL(decor.ui_watched, ui_watched, false); if (decor.ui_watched) { has_decor = true; @@ -735,7 +753,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer line = buf->b_ml.ml_line_count; } } else if (line < buf->b_ml.ml_line_count) { - len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line + 1, false)); + len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1, false)); } if (col == -1) { @@ -754,7 +772,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer if (col2 >= 0) { if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) { - len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false)); + len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line2 + 1, false)); } else if (line2 == buf->b_ml.ml_line_count) { // We are trying to add an extmark past final newline len = 0; @@ -796,7 +814,7 @@ error: return 0; } -/// Removes an extmark. +/// Removes an |extmark|. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param ns_id Namespace id from |nvim_create_namespace()| @@ -826,9 +844,8 @@ uint32_t src2ns(Integer *src_id) } if (*src_id < 0) { return (((uint32_t)1) << 31) - 1; - } else { - return (uint32_t)(*src_id); } + return (uint32_t)(*src_id); } /// Adds a highlight to buffer. @@ -912,7 +929,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In return ns_id; } -/// Clears namespaced objects (highlights, extmarks, virtual text) from +/// Clears |namespace|d objects (highlights, |extmarks|, virtual text) from /// a region. /// /// Lines are 0-indexed. |api-indexing| To clear the namespace in the entire @@ -945,13 +962,13 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, (int)line_end - 1, MAXCOL); } -/// Set or change decoration provider for a namespace +/// Set or change decoration provider for a |namespace| /// /// This is a very general purpose interface for having lua callbacks /// being triggered during the redraw code. /// -/// The expected usage is to set extmarks for the currently -/// redrawn buffer. |nvim_buf_set_extmark| can be called to add marks +/// The expected usage is to set |extmarks| for the currently +/// redrawn buffer. |nvim_buf_set_extmark()| can be called to add marks /// on a per-window or per-lines basis. Use the `ephemeral` key to only /// use the mark for the current screen redraw (the callback will be called /// again for the next redraw ). @@ -972,20 +989,21 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, /// for the moment. /// /// @param ns_id Namespace id from |nvim_create_namespace()| -/// @param opts Callbacks invoked during redraw: +/// @param opts Table of callbacks: /// - on_start: called first on each screen redraw /// ["start", tick] -/// - on_buf: called for each buffer being redrawn (before window -/// callbacks) +/// - on_buf: called for each buffer being redrawn (before +/// window callbacks) /// ["buf", bufnr, tick] -/// - on_win: called when starting to redraw a specific window. +/// - on_win: called when starting to redraw a +/// specific window. /// ["win", winid, bufnr, topline, botline_guess] -/// - on_line: called for each buffer line being redrawn. (The -/// interaction with fold lines is subject to change) +/// - on_line: called for each buffer line being redrawn. +/// (The interaction with fold lines is subject to change) /// ["win", winid, bufnr, row] /// - on_end: called at the end of a redraw cycle /// ["end", tick] -void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Error *err) +void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *opts, Error *err) FUNC_API_SINCE(7) FUNC_API_LUA_ONLY { DecorProvider *p = get_decor_provider((NS)ns_id, true); @@ -993,41 +1011,36 @@ void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts, Erro decor_provider_clear(p); // regardless of what happens, it seems good idea to redraw - redraw_all_later(NOT_VALID); // TODO(bfredl): too soon? + redraw_all_later(UPD_NOT_VALID); // TODO(bfredl): too soon? struct { const char *name; + Object *source; LuaRef *dest; } cbs[] = { - { "on_start", &p->redraw_start }, - { "on_buf", &p->redraw_buf }, - { "on_win", &p->redraw_win }, - { "on_line", &p->redraw_line }, - { "on_end", &p->redraw_end }, - { "_on_hl_def", &p->hl_def }, - { NULL, NULL }, + { "on_start", &opts->on_start, &p->redraw_start }, + { "on_buf", &opts->on_buf, &p->redraw_buf }, + { "on_win", &opts->on_win, &p->redraw_win }, + { "on_line", &opts->on_line, &p->redraw_line }, + { "on_end", &opts->on_end, &p->redraw_end }, + { "_on_hl_def", &opts->_on_hl_def, &p->hl_def }, + { "_on_spell_nav", &opts->_on_spell_nav, &p->spell_nav }, + { NULL, NULL, NULL }, }; - for (size_t i = 0; i < opts.size; i++) { - String k = opts.items[i].key; - Object *v = &opts.items[i].value; - size_t j; - for (j = 0; cbs[j].name && cbs[j].dest; j++) { - if (strequal(cbs[j].name, k.data)) { - if (v->type != kObjectTypeLuaRef) { - api_set_error(err, kErrorTypeValidation, - "%s is not a function", cbs[j].name); - goto error; - } - *(cbs[j].dest) = v->data.luaref; - v->data.luaref = LUA_NOREF; - break; - } + for (size_t i = 0; cbs[i].source && cbs[i].dest && cbs[i].name; i++) { + Object *v = cbs[i].source; + if (v->type == kObjectTypeNil) { + continue; } - if (!cbs[j].name) { - api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); + + if (v->type != kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, + "%s is not a function", cbs[i].name); goto error; } + *(cbs[i].dest) = v->data.luaref; + v->data.luaref = LUA_NOREF; } p->active = true; @@ -1038,7 +1051,7 @@ error: decor_provider_clear(p); } -/// Gets the line and column of an extmark. +/// Gets the line and column of an |extmark|. /// /// Extmarks may be queried by position, name or even special names /// in the future such as "cursor". @@ -1103,7 +1116,7 @@ static int init_sign_text(char **sign_text, char *text) { char *s; - char *endp = text + (int)STRLEN(text); + char *endp = text + (int)strlen(text); // Count cells and check for non-printable chars int cells = 0; diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h index 74802c6efb..0a627a889c 100644 --- a/src/nvim/api/extmark.h +++ b/src/nvim/api/extmark.h @@ -3,7 +3,10 @@ #include "nvim/api/private/defs.h" #include "nvim/decoration.h" +#include "nvim/macros.h" #include "nvim/map.h" +#include "nvim/map_defs.h" +#include "nvim/types.h" EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT); EXTERN handle_T next_namespace_id INIT(= 1); diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 6fad52ba75..30dcef6127 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -1,8 +1,17 @@ return { - context = { + { 'context', { "types"; - }; - set_extmark = { + }}; + { 'set_decoration_provider', { + "on_start"; + "on_buf"; + "on_win"; + "on_line"; + "on_end"; + "_on_hl_def"; + "_on_spell_nav"; + }}; + { 'set_extmark', { "id"; "end_line"; "end_row"; @@ -28,9 +37,10 @@ return { "line_hl_group"; "cursorline_hl_group"; "conceal"; + "spell"; "ui_watched"; - }; - keymap = { + }}; + { 'keymap', { "noremap"; "nowait"; "silent"; @@ -40,11 +50,11 @@ return { "callback"; "desc"; "replace_keycodes"; - }; - get_commands = { + }}; + { 'get_commands', { "builtin"; - }; - user_command = { + }}; + { 'user_command', { "addr"; "bang"; "bar"; @@ -57,8 +67,8 @@ return { "preview"; "range"; "register"; - }; - float_config = { + }}; + { 'float_config', { "row"; "col"; "width"; @@ -71,27 +81,29 @@ return { "focusable"; "zindex"; "border"; + "title"; + "title_pos"; "style"; "noautocmd"; - }; - runtime = { + }}; + { 'runtime', { "is_lua"; "do_source"; - }; - eval_statusline = { + }}; + { 'eval_statusline', { "winid"; "maxwidth"; "fillchar"; "highlights"; "use_winbar"; "use_tabline"; - }; - option = { + }}; + { 'option', { "scope"; "win"; "buf"; - }; - highlight = { + }}; + { 'highlight', { "bold"; "standout"; "strikethrough"; @@ -102,6 +114,7 @@ return { "underdashed"; "italic"; "reverse"; + "altfont"; "nocombine"; "default"; "cterm"; @@ -114,8 +127,10 @@ return { "global_link"; "fallback"; "blend"; - }; - highlight_cterm = { + "fg_indexed"; + "bg_indexed"; + }}; + { 'highlight_cterm', { "bold"; "standout"; "strikethrough"; @@ -126,16 +141,17 @@ return { "underdashed"; "italic"; "reverse"; + "altfont"; "nocombine"; - }; + }}; -- Autocmds - clear_autocmds = { + { 'clear_autocmds', { "buffer"; "event"; "group"; "pattern"; - }; - create_autocmd = { + }}; + { 'create_autocmd', { "buffer"; "callback"; "command"; @@ -144,24 +160,24 @@ return { "nested"; "once"; "pattern"; - }; - exec_autocmds = { + }}; + { 'exec_autocmds', { "buffer"; "group"; "modeline"; "pattern"; "data"; - }; - get_autocmds = { + }}; + { 'get_autocmds', { "event"; "group"; "pattern"; "buffer"; - }; - create_augroup = { + }}; + { 'create_augroup', { "clear"; - }; - cmd = { + }}; + { 'cmd', { "cmd"; "range"; "count"; @@ -173,12 +189,12 @@ return { "nargs"; "addr"; "nextcmd"; - }; - cmd_magic = { + }}; + { 'cmd_magic', { "file"; "bar"; - }; - cmd_mods = { + }}; + { 'cmd_mods', { "silent"; "emsg_silent"; "unsilent"; @@ -188,6 +204,7 @@ return { "browse"; "confirm"; "hide"; + "horizontal"; "keepalt"; "keepjumps"; "keepmarks"; @@ -198,13 +215,15 @@ return { "verbose"; "vertical"; "split"; - }; - cmd_mods_filter = { + }}; + { 'cmd_mods_filter', { "pattern"; "force"; - }; - cmd_opts = { + }}; + { 'cmd_opts', { "output"; - }; + }}; + { 'echo_opts', { + "verbose"; + }}; } - diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index 867584dd71..bfcb99754f 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -1,20 +1,21 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <assert.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> -#include <stddef.h> -#include <stdlib.h> #include <string.h> #include "nvim/api/options.h" -#include "nvim/api/private/converter.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/autocmd.h" -#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" +#include "nvim/eval/window.h" +#include "nvim/globals.h" +#include "nvim/memory.h" #include "nvim/option.h" -#include "nvim/option_defs.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -147,7 +148,7 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) /// @param name Option name /// @param value New option value /// @param opts Optional parameters -/// - scope: One of 'global' or 'local'. Analogous to +/// - scope: One of "global" or "local". Analogous to /// |:setglobal| and |:setlocal|, respectively. /// - win: |window-ID|. Used for setting window local option. /// - buf: Buffer number. Used for setting buffer local option. @@ -202,7 +203,7 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error /// Gets the option information for all options. /// /// The dictionary has the full option names as keys and option metadata -/// dictionaries as detailed at |nvim_get_option_info|. +/// dictionaries as detailed at |nvim_get_option_info()|. /// /// @return dictionary of all options Dictionary nvim_get_all_options_info(Error *err) @@ -256,7 +257,7 @@ void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err) /// @param name Option name /// @param[out] err Error details, if any /// @return Option value (global) -Object nvim_get_option(String name, Error *err) +Object nvim_get_option(String name, Arena *arena, Error *err) FUNC_API_SINCE(1) { return get_option_from(NULL, SREQ_GLOBAL, name, err); @@ -268,7 +269,7 @@ Object nvim_get_option(String name, Error *err) /// @param name Option name /// @param[out] err Error details, if any /// @return Option value -Object nvim_buf_get_option(Buffer buffer, String name, Error *err) +Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err) FUNC_API_SINCE(1) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -306,7 +307,7 @@ void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object /// @param name Option name /// @param[out] err Error details, if any /// @return Option value -Object nvim_win_get_option(Window window, String name, Error *err) +Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err) FUNC_API_SINCE(1) { win_T *win = find_window_by_handle(window, err); @@ -346,7 +347,7 @@ void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object /// @param name The option name /// @param[out] err Details of an error that may have occurred /// @return the option value -Object get_option_from(void *from, int type, String name, Error *err) +static Object get_option_from(void *from, int type, String name, Error *err) { Object rv = OBJECT_INIT; @@ -358,8 +359,7 @@ Object get_option_from(void *from, int type, String name, Error *err) // Return values int64_t numval; char *stringval = NULL; - int flags = get_option_value_strict(name.data, &numval, &stringval, - type, from); + int flags = get_option_value_strict(name.data, &numval, &stringval, type, from); if (!flags) { api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'", @@ -487,7 +487,7 @@ static getoption_T access_option_value(char *key, long *numval, char **stringval bool get, Error *err) { if (get) { - return get_option_value(key, numval, stringval, opt_flags); + return get_option_value(key, numval, stringval, NULL, opt_flags); } else { char *errmsg; if ((errmsg = set_option_value(key, *numval, *stringval, opt_flags))) { diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 8724ef4432..58ff552ab7 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -2,17 +2,24 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <stdbool.h> #include <stddef.h> +#include <stdint.h> #include <stdlib.h> +#include "klib/kvec.h" #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/assert.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" -#include "nvim/lua/converter.h" +#include "nvim/garray.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" +#include "nvim/types.h" +#include "nvim/vim.h" /// Helper structure for vim_to_object typedef struct { @@ -65,8 +72,8 @@ typedef struct { #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ ufunc_T *fp = find_func(fun); \ - if (fp != NULL && fp->uf_cb == nlua_CFunction_func_call) { \ - LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); \ + if (fp != NULL && (fp->uf_flags & FC_LUAREF)) { \ + LuaRef ref = api_new_luaref(fp->uf_luaref); \ kvi_push(edata->stack, LUAREF_OBJ(ref)); \ } else { \ TYPVAL_ENCODE_CONV_NIL(tv); \ @@ -351,10 +358,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) } case kObjectTypeLuaRef: { - LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); - state->lua_callable.func_ref = api_new_luaref(obj.data.luaref); - char *name = - (char *)register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state); + char *name = register_luafunc(api_new_luaref(obj.data.luaref)); tv->v_type = VAR_FUNC; tv->vval.v_string = xstrdup(name); break; diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 9c7e59e4b3..8acbf0d9de 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -5,14 +5,14 @@ #include <stdint.h> #include <string.h> +#include "klib/kvec.h" #include "nvim/func_attr.h" -#include "nvim/lib/kvec.h" #include "nvim/types.h" #define ARRAY_DICT_INIT KV_INITIAL_VALUE #define STRING_INIT { .data = NULL, .size = 0 } #define OBJECT_INIT { .type = kObjectTypeNil } -#define ERROR_INIT { .type = kErrorTypeNone, .msg = NULL } +#define ERROR_INIT ((Error) { .type = kErrorTypeNone, .msg = NULL }) #define REMOTE_TYPE(type) typedef handle_T type #define ERROR_SET(e) ((e)->type != kErrorTypeNone) @@ -48,7 +48,7 @@ typedef enum { /// Internal call from lua code #define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1) -static inline bool is_internal_call(const uint64_t channel_id) +static inline bool is_internal_call(uint64_t channel_id) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST; /// Check whether call is internal diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index d6a6fc1219..f427bba00e 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -1,38 +1,11 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <assert.h> -#include <inttypes.h> -#include <msgpack.h> -#include <stdbool.h> +#include <stddef.h> -#include "nvim/api/deprecated.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" -#include "nvim/log.h" -#include "nvim/map.h" -#include "nvim/msgpack_rpc/helpers.h" -#include "nvim/vim.h" - -// =========================================================================== -// NEW API FILES MUST GO HERE. -// -// When creating a new API file, you must include it here, -// so that the dispatcher can find the C functions that you are creating! -// =========================================================================== -#include "nvim/api/autocmd.h" -#include "nvim/api/buffer.h" -#include "nvim/api/command.h" -#include "nvim/api/extmark.h" -#include "nvim/api/options.h" -#include "nvim/api/tabpage.h" -#include "nvim/api/ui.h" -#include "nvim/api/vim.h" -#include "nvim/api/vimscript.h" -#include "nvim/api/win_config.h" -#include "nvim/api/window.h" -#include "nvim/ui_client.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/dispatch_wrappers.generated.h" diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h index 4b7c394944..4ae61b2bfb 100644 --- a/src/nvim/api/private/dispatch.h +++ b/src/nvim/api/private/dispatch.h @@ -1,22 +1,29 @@ #ifndef NVIM_API_PRIVATE_DISPATCH_H #define NVIM_API_PRIVATE_DISPATCH_H +#include <stdbool.h> +#include <stdint.h> + #include "nvim/api/private/defs.h" +#include "nvim/memory.h" +#include "nvim/types.h" -typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, - Array args, - Error *error); +typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, Array args, Arena *arena, Error *error); /// The rpc_method_handlers table, used in msgpack_rpc_dispatch(), stores /// functions of this type. -typedef struct { +struct MsgpackRpcRequestHandler { const char *name; ApiDispatchWrapper fn; bool fast; // Function is safe to be executed immediately while running the // uv loop (the loop is run very frequently due to breakcheck). // If "fast" is false, the function is deferred, i e the call will // be put in the event queue, for safe handling later. -} MsgpackRpcRequestHandler; + bool arena_return; // return value is allocated in the arena (or statically) + // and should not be freed as such. +}; + +extern const MsgpackRpcRequestHandler method_handlers[]; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/dispatch.h.generated.h" diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index c466fc53e1..519f2cc5bf 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -3,37 +3,36 @@ #include <assert.h> #include <inttypes.h> +#include <limits.h> +#include <msgpack/unpack.h> +#include <stdarg.h> #include <stdbool.h> #include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include "klib/kvec.h" #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/ascii.h" -#include "nvim/assert.h" -#include "nvim/buffer.h" -#include "nvim/charset.h" -#include "nvim/eval.h" +#include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" -#include "nvim/ex_cmds_defs.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_eval.h" -#include "nvim/extmark.h" +#include "nvim/garray.h" #include "nvim/highlight_group.h" -#include "nvim/lib/kvec.h" #include "nvim/lua/executor.h" #include "nvim/map.h" -#include "nvim/map_defs.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/msgpack_rpc/helpers.h" +#include "nvim/pos.h" #include "nvim/ui.h" #include "nvim/version.h" -#include "nvim/vim.h" -#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/funcs_metadata.generated.h" @@ -58,6 +57,7 @@ void try_enter(TryState *const tstate) .private_msg_list = NULL, .trylevel = trylevel, .got_int = got_int, + .did_throw = did_throw, .need_rethrow = need_rethrow, .did_emsg = did_emsg, }; @@ -65,6 +65,7 @@ void try_enter(TryState *const tstate) current_exception = NULL; trylevel = 1; got_int = false; + did_throw = false; need_rethrow = false; did_emsg = false; } @@ -85,6 +86,7 @@ bool try_leave(const TryState *const tstate, Error *const err) assert(trylevel == 0); assert(!need_rethrow); assert(!got_int); + assert(!did_throw); assert(!did_emsg); assert(msg_list == &tstate->private_msg_list); assert(*msg_list == NULL); @@ -93,6 +95,7 @@ bool try_leave(const TryState *const tstate, Error *const err) current_exception = tstate->current_exception; trylevel = tstate->trylevel; got_int = tstate->got_int; + did_throw = tstate->did_throw; need_rethrow = tstate->need_rethrow; did_emsg = tstate->did_emsg; return ret; @@ -127,7 +130,7 @@ bool try_end(Error *err) force_abort = false; if (got_int) { - if (current_exception) { + if (did_throw) { // If we got an interrupt, discard the current exception discard_current_exception(); } @@ -146,8 +149,19 @@ bool try_end(Error *err) if (should_free) { xfree(msg); } - } else if (current_exception) { - api_set_error(err, kErrorTypeException, "%s", current_exception->value); + } else if (did_throw) { + if (*current_exception->throw_name != NUL) { + if (current_exception->throw_lnum != 0) { + api_set_error(err, kErrorTypeException, "%s, line %" PRIdLINENR ": %s", + current_exception->throw_name, current_exception->throw_lnum, + current_exception->value); + } else { + api_set_error(err, kErrorTypeException, "%s: %s", + current_exception->throw_name, current_exception->value); + } + } else { + api_set_error(err, kErrorTypeException, "%s", current_exception->value); + } discard_current_exception(); } @@ -214,6 +228,8 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva return rv; } + bool watched = tv_dict_is_watched(dict); + if (del) { // Delete the key if (di == NULL) { @@ -221,6 +237,10 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data); } else { + // Notify watchers + if (watched) { + tv_dict_watcher_notify(dict, key.data, NULL, &di->di_tv); + } // Return the old value if (retval) { rv = vim_to_object(&di->di_tv); @@ -237,11 +257,16 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva return rv; } + typval_T oldtv = TV_INITIAL_VALUE; + if (di == NULL) { // Need to create an entry di = tv_dict_item_alloc_len(key.data, key.size); tv_dict_add(dict, di); } else { + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } // Return the old value if (retval) { rv = vim_to_object(&di->di_tv); @@ -251,6 +276,13 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva // Update the value tv_copy(&tv, &di->di_tv); + + // Notify watchers + if (watched) { + tv_dict_watcher_notify(dict, key.data, &tv, &oldtv); + tv_clear(&oldtv); + } + // Clear the temporary variable tv_clear(&tv); } @@ -369,7 +401,13 @@ String cbuf_to_string(const char *buf, size_t size) String cstrn_to_string(const char *str, size_t maxsize) FUNC_ATTR_NONNULL_ALL { - return cbuf_to_string(str, STRNLEN(str, maxsize)); + return cbuf_to_string(str, strnlen(str, maxsize)); +} + +String cstrn_as_string(char *str, size_t maxsize) + FUNC_ATTR_NONNULL_ALL +{ + return cbuf_as_string(str, strnlen(str, maxsize)); } /// Creates a String using the given C string. Unlike @@ -440,53 +478,15 @@ Array string_to_array(const String input, bool crlf) return ret; } -/// Collects `n` buffer lines into array `l`, optionally replacing newlines -/// with NUL. -/// -/// @param buf Buffer to get lines from -/// @param n Number of lines to collect -/// @param replace_nl Replace newlines ("\n") with NUL -/// @param start Line number to start from -/// @param[out] l Lines are copied here -/// @param err[out] Error, if any -/// @return true unless `err` was set -bool buf_collect_lines(buf_T *buf, size_t n, int64_t start, bool replace_nl, Array *l, Error *err) -{ - for (size_t i = 0; i < n; i++) { - int64_t lnum = start + (int64_t)i; - - if (lnum >= MAXLNUM) { - if (err != NULL) { - api_set_error(err, kErrorTypeValidation, "Line index is too high"); - } - return false; - } - - const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); - Object str = STRING_OBJ(cstr_to_string(bufstr)); - - if (replace_nl) { - // Vim represents NULs as NLs, but this may confuse clients. - strchrsub(str.data.string.data, '\n', '\0'); - } - - l->items[i] = str; - } - - return true; -} - /// Returns a substring of a buffer line /// /// @param buf Buffer handle /// @param lnum Line number (1-based) /// @param start_col Starting byte offset into line (0-based) /// @param end_col Ending byte offset into line (0-based, exclusive) -/// @param replace_nl Replace newlines ('\n') with null ('\0') /// @param err Error object /// @return The text between start_col and end_col on line lnum of buffer buf -String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, bool replace_nl, - Error *err) +String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col, Error *err) { String rv = STRING_INIT; @@ -495,7 +495,7 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col return rv; } - const char *bufstr = (char *)ml_get_buf(buf, (linenr_T)lnum, false); + char *bufstr = ml_get_buf(buf, (linenr_T)lnum, false); size_t line_length = strlen(bufstr); start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col; @@ -515,12 +515,7 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col return rv; } - rv = cstrn_to_string(&bufstr[start_col], (size_t)(end_col - start_col)); - if (replace_nl) { - strchrsub(rv.data, '\n', '\0'); - } - - return rv; + return cstrn_as_string(&bufstr[start_col], (size_t)(end_col - start_col)); } void api_free_string(String value) @@ -618,6 +613,7 @@ void api_clear_error(Error *value) value->type = kErrorTypeNone; } +/// @returns a shared value. caller must not modify it! Dictionary api_metadata(void) { static Dictionary metadata = ARRAY_DICT_INIT; @@ -630,7 +626,7 @@ Dictionary api_metadata(void) init_type_metadata(&metadata); } - return copy_object(DICTIONARY_OBJ(metadata)).data.dictionary; + return metadata; } static void init_function_metadata(Dictionary *metadata) @@ -715,36 +711,40 @@ static void init_type_metadata(Dictionary *metadata) PUT(*metadata, "types", DICTIONARY_OBJ(types)); } -String copy_string(String str) +// all the copy_[object] functions allow arena=NULL, +// then global allocations are used, and the resulting object +// should be freed with an api_free_[object] function + +String copy_string(String str, Arena *arena) { if (str.data != NULL) { - return (String){ .data = xmemdupz(str.data, str.size), .size = str.size }; + return (String){ .data = arena_memdupz(arena, str.data, str.size), .size = str.size }; } else { return (String)STRING_INIT; } } -Array copy_array(Array array) +Array copy_array(Array array, Arena *arena) { - Array rv = ARRAY_DICT_INIT; + Array rv = arena_array(arena, array.size); for (size_t i = 0; i < array.size; i++) { - ADD(rv, copy_object(array.items[i])); + ADD(rv, copy_object(array.items[i], arena)); } return rv; } -Dictionary copy_dictionary(Dictionary dict) +Dictionary copy_dictionary(Dictionary dict, Arena *arena) { - Dictionary rv = ARRAY_DICT_INIT; + Dictionary rv = arena_dict(arena, dict.size); for (size_t i = 0; i < dict.size; i++) { KeyValuePair item = dict.items[i]; - PUT(rv, item.key.data, copy_object(item.value)); + PUT_C(rv, copy_string(item.key, arena).data, copy_object(item.value, arena)); } return rv; } /// Creates a deep clone of an object -Object copy_object(Object obj) +Object copy_object(Object obj, Arena *arena) { switch (obj.type) { case kObjectTypeBuffer: @@ -757,13 +757,13 @@ Object copy_object(Object obj) return obj; case kObjectTypeString: - return STRING_OBJ(copy_string(obj.data.string)); + return STRING_OBJ(copy_string(obj.data.string, arena)); case kObjectTypeArray: - return ARRAY_OBJ(copy_array(obj.data.array)); + return ARRAY_OBJ(copy_array(obj.data.array, arena)); case kObjectTypeDictionary: - return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary)); + return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary, arena)); case kObjectTypeLuaRef: return LUAREF_OBJ(api_new_luaref(obj.data.luaref)); @@ -844,7 +844,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err) goto free_exit; } - String str = copy_string(chunk.items[0].data.string); + String str = copy_string(chunk.items[0].data.string, NULL); int attr = 0; if (chunk.size == 2) { diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 4608554448..ec97ba9ec6 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -1,11 +1,17 @@ #ifndef NVIM_API_PRIVATE_HELPERS_H #define NVIM_API_PRIVATE_HELPERS_H +#include <stdbool.h> +#include <stddef.h> + +#include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/decoration.h" #include "nvim/ex_eval_defs.h" #include "nvim/getchar.h" -#include "nvim/lib/kvec.h" +#include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/map.h" #include "nvim/memory.h" #include "nvim/vim.h" @@ -134,6 +140,7 @@ typedef struct { const msglist_T *const *msg_list; int trylevel; int got_int; + bool did_throw; int need_rethrow; int did_emsg; } TryState; diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index b81fc3b7d7..21eb326c3b 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -2,13 +2,14 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <stdbool.h> -#include <stdint.h> #include <stdlib.h> #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/tabpage.h" #include "nvim/api/vim.h" +#include "nvim/buffer_defs.h" +#include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/window.h" @@ -110,15 +111,14 @@ Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) if (tab == curtab) { return nvim_get_current_win(); - } else { - FOR_ALL_WINDOWS_IN_TAB(wp, tab) { - if (wp == tab->tp_curwin) { - return wp->handle; - } + } + FOR_ALL_WINDOWS_IN_TAB(wp, tab) { + if (wp == tab->tp_curwin) { + return wp->handle; } - // There should always be a current window for a tabpage - abort(); } + // There should always be a current window for a tabpage + abort(); } /// Gets the tabpage number diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 6f7bfa244a..e67607a7e4 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -2,67 +2,41 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <inttypes.h> +#include <msgpack/pack.h> #include <stdbool.h> -#include <stddef.h> #include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/ui.h" +#include "nvim/autocmd.h" #include "nvim/channel.h" -#include "nvim/cursor_shape.h" +#include "nvim/event/loop.h" +#include "nvim/event/wstream.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" +#include "nvim/main.h" #include "nvim/map.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/option.h" -#include "nvim/popupmenu.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" -#include "nvim/window.h" - -typedef struct { - uint64_t channel_id; - -#define UI_BUF_SIZE 4096 ///< total buffer size for pending msgpack data. - /// guaranteed size available for each new event (so packing of simple events - /// and the header of grid_line will never fail) -#define EVENT_BUF_SIZE 256 - char buf[UI_BUF_SIZE]; ///< buffer of packed but not yet sent msgpack data - char *buf_wptr; ///< write head of buffer - const char *cur_event; ///< name of current event (might get multiple arglists) - Array call_buf; ///< buffer for constructing a single arg list (max 16 elements!) - - // state for write_cb, while packing a single arglist to msgpack. This - // might fail due to buffer overflow. - size_t pack_totlen; - bool buf_overflow; - char *temp_buf; - - // We start packing the two outermost msgpack arrays before knowing the total - // number of elements. Thus track the location where array size will need - // to be written in the msgpack buffer, once the specific array is finished. - char *nevents_pos; - char *ncalls_pos; - uint32_t nevents; ///< number of distinct events (top-level args to "redraw" - uint32_t ncalls; ///< number of calls made to the current event (plus one for the name!) - bool flushed_events; ///< events where sent to client without "flush" event - - int hl_id; // Current highlight for legacy put event. - Integer cursor_row, cursor_col; // Intended visible cursor position. - - // Position of legacy cursor, used both for drawing and visible user cursor. - Integer client_row, client_col; - bool wildmenu_active; -} UIData; #define BUF_POS(data) ((size_t)((data)->buf_wptr - (data)->buf)) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/ui.c.generated.h" -# include "ui_events_remote.generated.h" +# include "ui_events_remote.generated.h" // IWYU pragma: export #endif static PMap(uint64_t) connected_uis = MAP_INIT; @@ -122,7 +96,7 @@ static char *mpack_array_dyn16(char **buf) static void mpack_str(char **buf, const char *str) { assert(sizeof(schar_T) - 1 < 0x20); - size_t len = STRLEN(str); + size_t len = strlen(str); mpack_w(buf, 0xa0 | len); memcpy(*buf, str, len); *buf += len; @@ -137,8 +111,6 @@ void remote_ui_disconnect(uint64_t channel_id) UIData *data = ui->data; kv_destroy(data->call_buf); pmap_del(uint64_t)(&connected_uis, channel_id); - xfree(data); - ui->data = NULL; // Flag UI as "stopped". ui_detach_impl(ui, channel_id); xfree(ui); } @@ -198,31 +170,6 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona ui->pum_col = -1.0; ui->rgb = true; ui->override = false; - ui->grid_resize = remote_ui_grid_resize; - ui->grid_clear = remote_ui_grid_clear; - ui->grid_cursor_goto = remote_ui_grid_cursor_goto; - ui->mode_info_set = remote_ui_mode_info_set; - ui->update_menu = remote_ui_update_menu; - ui->busy_start = remote_ui_busy_start; - ui->busy_stop = remote_ui_busy_stop; - ui->mouse_on = remote_ui_mouse_on; - ui->mouse_off = remote_ui_mouse_off; - ui->mode_change = remote_ui_mode_change; - ui->grid_scroll = remote_ui_grid_scroll; - ui->hl_attr_define = remote_ui_hl_attr_define; - ui->hl_group_set = remote_ui_hl_group_set; - ui->raw_line = remote_ui_raw_line; - ui->bell = remote_ui_bell; - ui->visual_bell = remote_ui_visual_bell; - ui->default_colors_set = remote_ui_default_colors_set; - ui->flush = remote_ui_flush; - ui->suspend = remote_ui_suspend; - ui->set_title = remote_ui_set_title; - ui->set_icon = remote_ui_set_icon; - ui->option_set = remote_ui_option_set; - ui->msg_set_pos = remote_ui_msg_set_pos; - ui->event = remote_ui_event; - ui->inspect = remote_ui_inspect; CLEAR_FIELD(ui->ui_ext); @@ -245,7 +192,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona ui->ui_ext[kUICmdline] = true; } - UIData *data = xmalloc(sizeof(UIData)); + UIData *data = ui->data; data->channel_id = channel_id; data->cur_event = NULL; data->hl_id = 0; @@ -260,7 +207,6 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona data->wildmenu_active = false; data->call_buf = (Array)ARRAY_DICT_INIT; kv_ensure_space(data->call_buf, 16); - ui->data = data; pmap_put(uint64_t)(&connected_uis, channel_id, ui); ui_attach_impl(ui, channel_id); @@ -276,6 +222,19 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height, Boolean enabl api_free_dictionary(opts); } +/// Tells the nvim server if focus was gained or lost by the GUI +void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error) + FUNC_API_SINCE(11) FUNC_API_REMOTE_ONLY +{ + if (!pmap_has(uint64_t)(&connected_uis, channel_id)) { + api_set_error(error, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); + return; + } + + do_autocmd_focusgained((bool)gained); +} + /// Deactivates UI events on the channel. /// /// Removes the client from the list of UIs. |nvim_list_uis()| @@ -293,6 +252,10 @@ void nvim_ui_detach(uint64_t channel_id, Error *err) remote_ui_disconnect(channel_id); } +// TODO(bfredl): use me to detach a specifc ui from the server +void remote_ui_stop(UI *ui) +{} + void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { @@ -395,6 +358,24 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e return; } + if (strequal(name.data, "stdin_tty")) { + if (value.type != kObjectTypeBoolean) { + api_set_error(error, kErrorTypeValidation, "stdin_tty must be a Boolean"); + return; + } + stdin_isatty = value.data.boolean; + return; + } + + if (strequal(name.data, "stdout_tty")) { + if (value.type != kObjectTypeBoolean) { + api_set_error(error, kErrorTypeValidation, "stdout_tty must be a Boolean"); + return; + } + stdout_isatty = value.data.boolean; + return; + } + // LEGACY: Deprecated option, use `ext_cmdline` instead. bool is_popupmenu = strequal(name.data, "popupmenu_external"); @@ -452,7 +433,7 @@ void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, I } } -/// Tells Nvim the number of elements displaying in the popumenu, to decide +/// Tells Nvim the number of elements displaying in the popupmenu, to decide /// <PageUp> and <PageDown> movement. /// /// @param channel_id @@ -482,7 +463,7 @@ void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err) ui->pum_nlines = (int)height; } -/// Tells Nvim the geometry of the popumenu, to align floating windows with an +/// Tells Nvim the geometry of the popupmenu, to align floating windows with an /// external popup menu. /// /// Note that this method is not to be confused with |nvim_ui_pum_set_height()|, @@ -646,7 +627,7 @@ static void push_call(UI *ui, const char *name, Array args) data->ncalls++; } -static void remote_ui_grid_clear(UI *ui, Integer grid) +void remote_ui_grid_clear(UI *ui, Integer grid) { UIData *data = ui->data; Array args = data->call_buf; @@ -657,7 +638,7 @@ static void remote_ui_grid_clear(UI *ui, Integer grid) push_call(ui, name, args); } -static void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height) +void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height) { UIData *data = ui->data; Array args = data->call_buf; @@ -670,8 +651,8 @@ static void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer h push_call(ui, name, args); } -static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left, - Integer right, Integer rows, Integer cols) +void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left, + Integer right, Integer rows, Integer cols) { UIData *data = ui->data; if (ui->ui_ext[kUILinegrid]) { @@ -707,8 +688,8 @@ static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot } } -static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, - Integer cterm_fg, Integer cterm_bg) +void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) { if (!ui->ui_ext[kUITermColors]) { HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp); @@ -738,8 +719,8 @@ static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, } } -static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, - Array info) +void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, + Array info) { if (!ui->ui_ext[kUILinegrid]) { return; @@ -748,10 +729,12 @@ static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAt UIData *data = ui->data; Array args = data->call_buf; ADD_C(args, INTEGER_OBJ(id)); - MAXSIZE_TEMP_DICT(rgb, 16); - MAXSIZE_TEMP_DICT(cterm, 16); - ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(&rgb, rgb_attrs, true))); - ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(&cterm, cterm_attrs, false))); + MAXSIZE_TEMP_DICT(rgb, HLATTRS_DICT_SIZE); + MAXSIZE_TEMP_DICT(cterm, HLATTRS_DICT_SIZE); + hlattrs2dict(&rgb, rgb_attrs, true); + hlattrs2dict(&cterm, rgb_attrs, false); + ADD_C(args, DICTIONARY_OBJ(rgb)); + ADD_C(args, DICTIONARY_OBJ(cterm)); if (ui->ui_ext[kUIHlState]) { ADD_C(args, ARRAY_OBJ(info)); @@ -762,7 +745,7 @@ static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAt push_call(ui, "hl_attr_define", args); } -static void remote_ui_highlight_set(UI *ui, int id) +void remote_ui_highlight_set(UI *ui, int id) { UIData *data = ui->data; Array args = data->call_buf; @@ -771,13 +754,14 @@ static void remote_ui_highlight_set(UI *ui, int id) return; } data->hl_id = id; - MAXSIZE_TEMP_DICT(dict, 16); - ADD_C(args, DICTIONARY_OBJ(hlattrs2dict(&dict, syn_attr2entry(id), ui->rgb))); + MAXSIZE_TEMP_DICT(dict, HLATTRS_DICT_SIZE); + hlattrs2dict(&dict, syn_attr2entry(id), ui->rgb); + ADD_C(args, DICTIONARY_OBJ(dict)); push_call(ui, "highlight_set", args); } /// "true" cursor used only for input focus -static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col) +void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col) { if (ui->ui_ext[kUILinegrid]) { UIData *data = ui->data; @@ -795,7 +779,7 @@ static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Intege } /// emulated cursor used both for drawing and for input focus -static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) +void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) { UIData *data = ui->data; if (data->client_row == row && data->client_col == col) { @@ -809,7 +793,7 @@ static void remote_ui_cursor_goto(UI *ui, Integer row, Integer col) push_call(ui, "cursor_goto", args); } -static void remote_ui_put(UI *ui, const char *cell) +void remote_ui_put(UI *ui, const char *cell) { UIData *data = ui->data; data->client_col++; @@ -818,9 +802,9 @@ static void remote_ui_put(UI *ui, const char *cell) push_call(ui, "put", args); } -static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, - Integer clearcol, Integer clearattr, LineFlags flags, - const schar_T *chunk, const sattr_T *attrs) +void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, + Integer clearcol, Integer clearattr, LineFlags flags, const schar_T *chunk, + const sattr_T *attrs) { UIData *data = ui->data; if (ui->ui_ext[kUILinegrid]) { @@ -841,7 +825,7 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc for (size_t i = 0; i < ncells; i++) { repeat++; if (i == ncells - 1 || attrs[i] != attrs[i + 1] - || STRCMP(chunk[i], chunk[i + 1])) { + || strcmp(chunk[i], chunk[i + 1]) != 0) { if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5)) { // close to overflowing the redraw buffer. finish this event, // flush, and start a new "grid_line" event at the current position. @@ -912,7 +896,7 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startc /// /// This might happen multiple times before the actual ui_flush, if the /// total redraw size is large! -static void remote_ui_flush_buf(UI *ui) +void remote_ui_flush_buf(UI *ui) { UIData *data = ui->data; if (!data->nevents_pos) { @@ -939,7 +923,7 @@ static void remote_ui_flush_buf(UI *ui) /// /// Clients can know this happened by a final "flush" event at the end of the /// "redraw" batch. -static void remote_ui_flush(UI *ui) +void remote_ui_flush(UI *ui) { UIData *data = ui->data; if (data->nevents > 0 || data->flushed_events) { @@ -952,65 +936,63 @@ static void remote_ui_flush(UI *ui) } } -static Array translate_contents(UI *ui, Array contents) +static Array translate_contents(UI *ui, Array contents, Arena *arena) { - Array new_contents = ARRAY_DICT_INIT; + Array new_contents = arena_array(arena, contents.size); for (size_t i = 0; i < contents.size; i++) { Array item = contents.items[i].data.array; - Array new_item = ARRAY_DICT_INIT; + Array new_item = arena_array(arena, 2); int attr = (int)item.items[0].data.integer; if (attr) { - Dictionary rgb_attrs = hlattrs2dict(NULL, syn_attr2entry(attr), ui->rgb); + Dictionary rgb_attrs = arena_dict(arena, HLATTRS_DICT_SIZE); + hlattrs2dict(&rgb_attrs, syn_attr2entry(attr), ui->rgb); ADD(new_item, DICTIONARY_OBJ(rgb_attrs)); } else { ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); } - ADD(new_item, copy_object(item.items[1])); + ADD(new_item, item.items[1]); ADD(new_contents, ARRAY_OBJ(new_item)); } return new_contents; } -static Array translate_firstarg(UI *ui, Array args) +static Array translate_firstarg(UI *ui, Array args, Arena *arena) { - Array new_args = ARRAY_DICT_INIT; + Array new_args = arena_array(arena, args.size); Array contents = args.items[0].data.array; - ADD(new_args, ARRAY_OBJ(translate_contents(ui, contents))); + ADD_C(new_args, ARRAY_OBJ(translate_contents(ui, contents, arena))); for (size_t i = 1; i < args.size; i++) { - ADD(new_args, copy_object(args.items[i])); + ADD(new_args, args.items[i]); } return new_args; } -static void remote_ui_event(UI *ui, char *name, Array args) +void remote_ui_event(UI *ui, char *name, Array args) { + Arena arena = ARENA_EMPTY; UIData *data = ui->data; if (!ui->ui_ext[kUILinegrid]) { // the representation of highlights in cmdline changed, translate back // never consumes args if (strequal(name, "cmdline_show")) { - Array new_args = translate_firstarg(ui, args); + Array new_args = translate_firstarg(ui, args, &arena); push_call(ui, name, new_args); - api_free_array(new_args); - return; + goto free_ret; } else if (strequal(name, "cmdline_block_show")) { Array new_args = data->call_buf; Array block = args.items[0].data.array; - Array new_block = ARRAY_DICT_INIT; + Array new_block = arena_array(&arena, block.size); for (size_t i = 0; i < block.size; i++) { - ADD(new_block, - ARRAY_OBJ(translate_contents(ui, block.items[i].data.array))); + ADD_C(new_block, ARRAY_OBJ(translate_contents(ui, block.items[i].data.array, &arena))); } ADD_C(new_args, ARRAY_OBJ(new_block)); push_call(ui, name, new_args); - api_free_array(new_block); - return; + goto free_ret; } else if (strequal(name, "cmdline_block_append")) { - Array new_args = translate_firstarg(ui, args); + Array new_args = translate_firstarg(ui, args, &arena); push_call(ui, name, new_args); - api_free_array(new_args); - return; + goto free_ret; } } @@ -1022,19 +1004,18 @@ static void remote_ui_event(UI *ui, char *name, Array args) if (data->wildmenu_active) { Array new_args = data->call_buf; Array items = args.items[0].data.array; - Array new_items = ARRAY_DICT_INIT; + Array new_items = arena_array(&arena, items.size); for (size_t i = 0; i < items.size; i++) { - ADD(new_items, copy_object(items.items[i].data.array.items[0])); + ADD_C(new_items, items.items[i].data.array.items[0]); } ADD_C(new_args, ARRAY_OBJ(new_items)); push_call(ui, "wildmenu_show", new_args); - api_free_array(new_items); if (args.items[1].data.integer != -1) { Array new_args2 = data->call_buf; ADD_C(new_args2, args.items[1]); push_call(ui, "wildmenu_select", new_args2); } - return; + goto free_ret; } } else if (strequal(name, "popupmenu_select")) { if (data->wildmenu_active) { @@ -1048,9 +1029,13 @@ static void remote_ui_event(UI *ui, char *name, Array args) } push_call(ui, name, args); + return; + +free_ret: + arena_mem_free(arena_finish(&arena)); } -static void remote_ui_inspect(UI *ui, Dictionary *info) +void remote_ui_inspect(UI *ui, Dictionary *info) { UIData *data = ui->data; PUT(*info, "chan", INTEGER_OBJ((Integer)data->channel_id)); diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h index bc70406acb..b3fe0fa2bb 100644 --- a/src/nvim/api/ui.h +++ b/src/nvim/api/ui.h @@ -5,8 +5,10 @@ #include "nvim/api/private/defs.h" #include "nvim/map.h" +#include "nvim/ui.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/ui.h.generated.h" +# include "ui_events_remote.h.generated.h" #endif #endif // NVIM_API_UI_H diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 8b7e01e1c3..a08e8dbfeb 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -31,15 +31,15 @@ void visual_bell(void) void flush(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; void suspend(void) - FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3); void set_title(String title) FUNC_API_SINCE(3); void set_icon(String icon) FUNC_API_SINCE(3); void screenshot(String path) - FUNC_API_SINCE(7) FUNC_API_REMOTE_IMPL; + FUNC_API_SINCE(7); void option_set(String name, Object value) - FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(4); // Stop event is not exported as such, represented by EOF in the msgpack stream. void stop(void) FUNC_API_NOEXPORT; @@ -69,14 +69,13 @@ void scroll(Integer count) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; // Second revision of the grid protocol, used with ext_linegrid ui option -void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, - Integer cterm_fg, Integer cterm_bg) +void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, Integer cterm_fg, + Integer cterm_bg) FUNC_API_SINCE(4) FUNC_API_REMOTE_IMPL; -void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, - Array info) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; +void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, Array info) + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; void hl_group_set(String name, Integer id) - FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(6) FUNC_API_CLIENT_IGNORE; void grid_resize(Integer grid, Integer width, Integer height) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IMPL; void grid_clear(Integer grid) @@ -85,8 +84,8 @@ void grid_cursor_goto(Integer grid, Integer row, Integer col) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_line(Integer grid, Integer row, Integer col_start, Array data) FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL; -void grid_scroll(Integer grid, Integer top, Integer bot, - Integer left, Integer right, Integer rows, Integer cols) +void grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, Integer rows, + Integer cols) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_destroy(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; @@ -94,20 +93,15 @@ void grid_destroy(Integer grid) // For performance and simplicity, we use the dense screen representation // in internal code, such as compositor and TUI. The remote_ui module will // translate this in to the public grid_line format. -void raw_line(Integer grid, Integer row, Integer startcol, - Integer endcol, Integer clearcol, Integer clearattr, - LineFlags flags, const schar_T *chunk, const sattr_T *attrs) +void raw_line(Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, + Integer clearattr, LineFlags flags, const schar_T *chunk, const sattr_T *attrs) FUNC_API_NOEXPORT FUNC_API_COMPOSITOR_IMPL; -void event(char *name, Array args) - FUNC_API_NOEXPORT; - -void win_pos(Integer grid, Window win, Integer startrow, - Integer startcol, Integer width, Integer height) +void win_pos(Integer grid, Window win, Integer startrow, Integer startcol, Integer width, + Integer height) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; -void win_float_pos(Integer grid, Window win, String anchor, Integer anchor_grid, - Float anchor_row, Float anchor_col, Boolean focusable, - Integer zindex) +void win_float_pos(Integer grid, Window win, String anchor, Integer anchor_grid, Float anchor_row, + Float anchor_col, Boolean focusable, Integer zindex) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_external_pos(Integer grid, Window win) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; @@ -115,32 +109,29 @@ void win_hide(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; void win_close(Integer grid) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; + void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char) - FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL FUNC_API_COMPOSITOR_IMPL; + FUNC_API_SINCE(6) FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IGNORE; -void win_viewport(Integer grid, Window win, Integer topline, - Integer botline, Integer curline, Integer curcol, - Integer line_count) - FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY; +void win_viewport(Integer grid, Window win, Integer topline, Integer botline, Integer curline, + Integer curcol, Integer line_count) + FUNC_API_SINCE(7) FUNC_API_CLIENT_IGNORE; -void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, - Integer row, Integer col) +void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, Integer row, Integer col) FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY; -void popupmenu_show(Array items, Integer selected, - Integer row, Integer col, Integer grid) +void popupmenu_show(Array items, Integer selected, Integer row, Integer col, Integer grid) 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, - Buffer current_buffer, Array buffers) +void tabline_update(Tabpage current, Array tabs, Buffer current_buffer, Array buffers) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; -void cmdline_show(Array content, Integer pos, String firstc, String prompt, - Integer indent, Integer level) +void cmdline_show(Array content, Integer pos, String firstc, String prompt, Integer indent, + Integer level) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void cmdline_pos(Integer pos, Integer level) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; @@ -156,11 +147,11 @@ void cmdline_block_hide(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void wildmenu_show(Array items) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void wildmenu_select(Integer selected) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void wildmenu_hide(void) - FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; void msg_show(String kind, Array content, Boolean replace_last) FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index ce91c1b4af..a53b30dd8a 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -5,9 +5,13 @@ #include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/buffer.h" #include "nvim/api/deprecated.h" #include "nvim/api/private/converter.h" @@ -15,58 +19,55 @@ #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" -#include "nvim/api/window.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" -#include "nvim/buffer_defs.h" -#include "nvim/charset.h" +#include "nvim/channel.h" #include "nvim/context.h" -#include "nvim/decoration.h" -#include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" -#include "nvim/eval/userfunc.h" -#include "nvim/ex_cmds_defs.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" -#include "nvim/file_search.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" -#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" -#include "nvim/insexpand.h" +#include "nvim/keycodes.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" -#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/unpacker.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/optionstr.h" #include "nvim/os/input.h" +#include "nvim/os/os_defs.h" #include "nvim/os/process.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/runtime.h" #include "nvim/state.h" #include "nvim/statusline.h" +#include "nvim/strings.h" +#include "nvim/terminal.h" #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" -#include "nvim/viml/parser/expressions.h" -#include "nvim/viml/parser/parser.h" #include "nvim/window.h" -#define LINE_BUFFER_SIZE 4096 +#define LINE_BUFFER_MIN_SIZE 4096 #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/vim.c.generated.h" @@ -79,19 +80,17 @@ /// @param[out] err Error details, if any /// @return Highlight definition map /// @see nvim_get_hl_by_id -Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err) +Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *err) FUNC_API_SINCE(3) { Dictionary result = ARRAY_DICT_INIT; int id = syn_name2id(name.data); if (id == 0) { - api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", - name.data); + api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data); return result; } - result = nvim_get_hl_by_id(id, rgb, err); - return result; + return nvim_get_hl_by_id(id, rgb, arena, err); } /// Gets a highlight definition by id. |hlID()| @@ -100,17 +99,16 @@ Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Error *err) /// @param[out] err Error details, if any /// @return Highlight definition map /// @see nvim_get_hl_by_name -Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err) +Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *err) FUNC_API_SINCE(3) { Dictionary dic = ARRAY_DICT_INIT; if (syn_get_final_id((int)hl_id) == 0) { - api_set_error(err, kErrorTypeException, - "Invalid highlight id: %" PRId64, hl_id); + api_set_error(err, kErrorTypeException, "Invalid highlight id: %" PRId64, hl_id); return dic; } int attrcode = syn_id2attr((int)hl_id); - return hl_get_attr_by_id(attrcode, rgb, err); + return hl_get_attr_by_id(attrcode, rgb, arena, err); } /// Gets a highlight group by name @@ -122,10 +120,10 @@ Integer nvim_get_hl_id_by_name(String name) return syn_check_group(name.data, name.size); } -Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) +Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err) { if (ns_id == 0) { - return get_global_hl_defs(); + return get_global_hl_defs(arena); } abort(); } @@ -163,8 +161,8 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// - nocombine: boolean /// - link: name of another highlight group to link to, see |:hi-link|. /// - default: Don't override existing definition |:hi-default| -/// - ctermfg: Sets foreground of cterm color |highlight-ctermfg| -/// - ctermbg: Sets background of cterm color |highlight-ctermbg| +/// - ctermfg: Sets foreground of cterm color |ctermfg| +/// - ctermbg: Sets background of cterm color |ctermbg| /// - cterm: cterm attribute map, like |highlight-args|. If not set, /// cterm attributes will match those from the attribute map /// documented above. @@ -175,6 +173,10 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err) FUNC_API_SINCE(7) { int hl_id = syn_check_group(name.data, name.size); + if (hl_id == 0) { + api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data); + return; + } int link_id = -1; HlAttrs attrs = dict2hlattrs(val, true, &link_id, err); @@ -184,7 +186,7 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err) } /// Set active namespace for highlights. This can be set for a single window, -/// see |nvim_win_set_hl_ns|. +/// see |nvim_win_set_hl_ns()|. /// /// @param ns_id the namespace to use /// @param[out] err Error details, if any @@ -198,13 +200,13 @@ void nvim_set_hl_ns(Integer ns_id, Error *err) ns_hl_global = (NS)ns_id; hl_check_ns(); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } /// Set active namespace for highlights while redrawing. /// /// This function meant to be called while redrawing, primarily from -/// |nvim_set_decoration_provider| on_win and on_line callbacks, which +/// |nvim_set_decoration_provider()| on_win and on_line callbacks, which /// are allowed to change the namespace during a redraw cycle. /// /// @param ns_id the namespace to activate @@ -227,14 +229,14 @@ void nvim_set_hl_ns_fast(Integer ns_id, Error *err) /// nvim_feedkeys(). /// /// Example: -/// <pre> +/// <pre>vim /// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) /// :call nvim_feedkeys(key, 'n', v:false) /// </pre> /// /// @param keys to be typed /// @param mode behavior flags, see |feedkeys()| -/// @param escape_ks If true, escape K_SPECIAL bytes in `keys` +/// @param escape_ks If true, escape K_SPECIAL bytes in `keys`. /// This should be false if you already used /// |nvim_replace_termcodes()|, and true otherwise. /// @see feedkeys() @@ -335,9 +337,9 @@ Integer nvim_input(String keys) /// mouse input in a GUI. The deprecated pseudokey form /// ("<LeftMouse><col,row>") of |nvim_input()| has the same limitation. /// -/// @param button Mouse button: one of "left", "right", "middle", "wheel". +/// @param button Mouse button: one of "left", "right", "middle", "wheel", "move". /// @param action For ordinary buttons, one of "press", "drag", "release". -/// For the wheel, one of "up", "down", "left", "right". +/// For the wheel, one of "up", "down", "left", "right". Ignored for "move". /// @param modifier String of modifiers each represented by a single char. /// The same specifiers are used as for a key press, except /// that the "-" separator is optional, so "C-A-", "c-a" @@ -364,6 +366,8 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri code = KE_RIGHTMOUSE; } else if (strequal(button.data, "wheel")) { code = KE_MOUSEDOWN; + } else if (strequal(button.data, "move")) { + code = KE_MOUSEMOVE; } else { goto error; } @@ -380,7 +384,7 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri } else { goto error; } - } else { + } else if (code != KE_MOUSEMOVE) { if (strequal(action.data, "press")) { // pass } else if (strequal(action.data, "drag")) { @@ -520,7 +524,7 @@ Array nvim__runtime_inspect(void) /// Find files in runtime directories /// -/// 'name' can contain wildcards. For example +/// "name" can contain wildcards. For example /// nvim_get_runtime_file("colors/*.vim", true) will return all color /// scheme files. Always use forward slashes (/) in the search pattern for /// subdirectories regardless of platform. @@ -722,8 +726,11 @@ void nvim_set_vvar(String name, Object value, Error *err) /// text chunk with specified highlight. `hl_group` element /// can be omitted for no highlight. /// @param history if true, add to |message-history|. -/// @param opts Optional parameters. Reserved for future use. -void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err) +/// @param opts Optional parameters. +/// - verbose: Message was printed as a result of 'verbose' option +/// if Nvim was invoked with -V3log_file, the message will be +/// redirected to the log_file and suppressed from direct output. +void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err) FUNC_API_SINCE(7) { HlMessage hl_msg = parse_hl_msg(chunks, err); @@ -731,13 +738,19 @@ void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err) goto error; } - if (opts.size > 0) { - api_set_error(err, kErrorTypeValidation, "opts dict isn't empty"); - goto error; + bool verbose = api_object_to_bool(opts->verbose, "verbose", false, err); + + if (verbose) { + verbose_enter(); } msg_multiattr(hl_msg, history ? "echomsg" : "echo", history); + if (verbose) { + verbose_leave(); + verbose_stop(); // flush now + } + if (history) { // history takes ownership return; @@ -961,7 +974,7 @@ fail: /// mode. Note: keypresses are sent raw as they would be to the pty /// master end. For instance, a carriage return is sent /// as a "\r", not as a "\n". |textlock| applies. It is possible -/// to call |nvim_chan_send| directly in the callback however. +/// to call |nvim_chan_send()| directly in the callback however. /// ["input", term, bufnr, data] /// @param[out] err Error details, if any /// @return Channel id, or 0 on error @@ -1009,7 +1022,7 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err) return (Integer)chan->id; } -static void term_write(char *buf, size_t size, void *data) +static void term_write(char *buf, size_t size, void *data) // NOLINT(readability-non-const-parameter) { Channel *chan = data; LuaRef cb = chan->stream.internal.cb; @@ -1291,7 +1304,7 @@ void nvim_unsubscribe(uint64_t channel_id, String event) /// "#rrggbb" hexadecimal string. /// /// Example: -/// <pre> +/// <pre>vim /// :echo nvim_get_color_by_name("Pink") /// :echo nvim_get_color_by_name("#cbcbcb") /// </pre> @@ -1419,10 +1432,10 @@ Dictionary nvim_get_mode(void) /// @param mode Mode short-name ("n", "i", "v", ...) /// @returns Array of |maparg()|-like dictionaries describing mappings. /// The "buffer" key is always zero. -ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) +ArrayOf(Dictionary) nvim_get_keymap(String mode) FUNC_API_SINCE(3) { - return keymap_array(mode, NULL, channel_id == LUA_INTERNAL_CALL); + return keymap_array(mode, NULL); } /// Sets a global |mapping| for the given mode. @@ -1433,12 +1446,12 @@ ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) /// Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual. /// /// Example: -/// <pre> +/// <pre>vim /// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) /// </pre> /// /// is equivalent to: -/// <pre> +/// <pre>vim /// nmap <nowait> <Space><NL> <Nop> /// </pre> /// @@ -1449,7 +1462,7 @@ ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) /// @param rhs Right-hand-side |{rhs}| of the mapping. /// @param opts Optional parameters map: keys are |:map-arguments|, values are booleans (default /// false). Accepts all |:map-arguments| as keys excluding |<buffer>| but including -/// |noremap| and "desc". Unknown key is an error. +/// |:noremap| and "desc". Unknown key is an error. /// "desc" can be used to give a description to the mapping. /// When called from Lua, also accepts a "callback" key that takes a Lua function to /// call when the mapping is executed. @@ -1479,14 +1492,14 @@ void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err) /// 1 is the |api-metadata| map (Dictionary). /// /// @returns 2-tuple [{channel-id}, {api-metadata}] -Array nvim_get_api_info(uint64_t channel_id) +Array nvim_get_api_info(uint64_t channel_id, Arena *arena) FUNC_API_SINCE(1) FUNC_API_FAST FUNC_API_REMOTE_ONLY { - Array rv = ARRAY_DICT_INIT; + Array rv = arena_array(arena, 2); assert(channel_id <= INT64_MAX); - ADD(rv, INTEGER_OBJ((int64_t)channel_id)); - ADD(rv, DICTIONARY_OBJ(api_metadata())); + ADD_C(rv, INTEGER_OBJ((int64_t)channel_id)); + ADD_C(rv, DICTIONARY_OBJ(api_metadata())); return rv; } @@ -1545,9 +1558,9 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY { Dictionary info = ARRAY_DICT_INIT; - PUT(info, "name", copy_object(STRING_OBJ(name))); + PUT(info, "name", copy_object(STRING_OBJ(name), NULL)); - version = copy_dictionary(version); + version = copy_dictionary(version, NULL); bool has_major = false; for (size_t i = 0; i < version.size; i++) { if (strequal(version.items[i].key.data, "major")) { @@ -1560,9 +1573,9 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, } PUT(info, "version", DICTIONARY_OBJ(version)); - PUT(info, "type", copy_object(STRING_OBJ(type))); - PUT(info, "methods", DICTIONARY_OBJ(copy_dictionary(methods))); - PUT(info, "attributes", DICTIONARY_OBJ(copy_dictionary(attributes))); + PUT(info, "type", copy_object(STRING_OBJ(type), NULL)); + PUT(info, "methods", DICTIONARY_OBJ(copy_dictionary(methods, NULL))); + PUT(info, "attributes", DICTIONARY_OBJ(copy_dictionary(attributes, NULL))); rpc_set_client_info(channel_id, info); } @@ -1629,11 +1642,11 @@ Array nvim_list_chans(void) /// an error, it is a three-element array with the zero-based index of the call /// which resulted in an error, the error type and the error message. If an /// error occurred, the values from all preceding calls will still be returned. -Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) +Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { - Array rv = ARRAY_DICT_INIT; - Array results = ARRAY_DICT_INIT; + Array rv = arena_array(arena, 2); + Array results = arena_array(arena, calls.size); Error nested_error = ERROR_INIT; size_t i; // also used for freeing the variables @@ -1642,21 +1655,21 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) api_set_error(err, kErrorTypeValidation, "Items in calls array must be arrays"); - goto validation_error; + goto theend; } Array call = calls.items[i].data.array; if (call.size != 2) { api_set_error(err, kErrorTypeValidation, "Items in calls array must be arrays of size 2"); - goto validation_error; + goto theend; } if (call.items[0].type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, "Name must be String"); - goto validation_error; + goto theend; } String name = call.items[0].data.string; @@ -1664,7 +1677,7 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) api_set_error(err, kErrorTypeValidation, "Args must be Array"); - goto validation_error; + goto theend; } Array args = call.items[1].data.array; @@ -1676,29 +1689,32 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) if (ERROR_SET(&nested_error)) { break; } - Object result = handler.fn(channel_id, args, &nested_error); + + Object result = handler.fn(channel_id, args, arena, &nested_error); if (ERROR_SET(&nested_error)) { // error handled after loop break; } - - ADD(results, result); + // TODO(bfredl): wasteful copy. It could be avoided to encoding to msgpack + // directly here. But `result` might become invalid when next api function + // is called in the loop. + ADD_C(results, copy_object(result, arena)); + if (!handler.arena_return) { + api_free_object(result); + } } - ADD(rv, ARRAY_OBJ(results)); + ADD_C(rv, ARRAY_OBJ(results)); if (ERROR_SET(&nested_error)) { - Array errval = ARRAY_DICT_INIT; - ADD(errval, INTEGER_OBJ((Integer)i)); - ADD(errval, INTEGER_OBJ(nested_error.type)); - ADD(errval, STRING_OBJ(cstr_to_string(nested_error.msg))); - ADD(rv, ARRAY_OBJ(errval)); + Array errval = arena_array(arena, 3); + ADD_C(errval, INTEGER_OBJ((Integer)i)); + ADD_C(errval, INTEGER_OBJ(nested_error.type)); + ADD_C(errval, STRING_OBJ(copy_string(cstr_as_string(nested_error.msg), arena))); + ADD_C(rv, ARRAY_OBJ(errval)); } else { - ADD(rv, NIL); + ADD_C(rv, NIL); } - goto theend; -validation_error: - api_free_array(results); theend: api_clear_error(&nested_error); return rv; @@ -1712,17 +1728,21 @@ theend: /// @param to_err true: message is an error (uses `emsg` instead of `msg`) static void write_msg(String message, bool to_err) { - static size_t out_pos = 0, err_pos = 0; - static char out_line_buf[LINE_BUFFER_SIZE], err_line_buf[LINE_BUFFER_SIZE]; + static StringBuilder out_line_buf = KV_INITIAL_VALUE; + static StringBuilder err_line_buf = KV_INITIAL_VALUE; -#define PUSH_CHAR(i, pos, line_buf, msg) \ - if (message.data[i] == NL || (pos) == LINE_BUFFER_SIZE - 1) { \ - (line_buf)[pos] = NUL; \ - msg(line_buf); \ - (pos) = 0; \ +#define PUSH_CHAR(i, line_buf, msg) \ + if (kv_max(line_buf) == 0) { \ + kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \ + } \ + if (message.data[i] == NL) { \ + kv_push(line_buf, NUL); \ + msg(line_buf.items); \ + kv_drop(line_buf, kv_size(line_buf)); \ + kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \ continue; \ } \ - (line_buf)[(pos)++] = message.data[i]; + kv_push(line_buf, message.data[i]); no_wait_return++; for (uint32_t i = 0; i < message.size; i++) { @@ -1730,9 +1750,9 @@ static void write_msg(String message, bool to_err) break; } if (to_err) { - PUSH_CHAR(i, err_pos, err_line_buf, emsg); + PUSH_CHAR(i, err_line_buf, emsg); } else { - PUSH_CHAR(i, out_pos, out_line_buf, msg); + PUSH_CHAR(i, out_line_buf, msg); } } no_wait_return--; @@ -1751,7 +1771,7 @@ static void write_msg(String message, bool to_err) /// @return its argument. Object nvim__id(Object obj) { - return copy_object(obj); + return copy_object(obj, NULL); } /// Returns array given as argument. @@ -1764,7 +1784,7 @@ Object nvim__id(Object obj) /// @return its argument. Array nvim__id_array(Array arr) { - return copy_object(ARRAY_OBJ(arr)).data.array; + return copy_array(arr, NULL); } /// Returns dictionary given as argument. @@ -1777,7 +1797,7 @@ Array nvim__id_array(Array arr) /// @return its argument. Dictionary nvim__id_dictionary(Dictionary dct) { - return copy_object(DICTIONARY_OBJ(dct)).data.dictionary; + return copy_dictionary(dct, NULL); } /// Returns floating-point value given as argument. @@ -1803,6 +1823,7 @@ Dictionary nvim__stats(void) PUT(rv, "log_skip", INTEGER_OBJ(g_stats.log_skip)); PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count())); PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); + PUT(rv, "arena_alloc_count", INTEGER_OBJ((Integer)arena_alloc_count)); return rv; } @@ -1813,7 +1834,7 @@ Dictionary nvim__stats(void) /// - "width" Requested width of the UI /// - "rgb" true if the UI uses RGB colors (false implies |cterm-colors|) /// - "ext_..." Requested UI extensions, see |ui-option| -/// - "chan" Channel id of remote UI (not present for TUI) +/// - "chan" |channel-id| of remote UI Array nvim_list_uis(void) FUNC_API_SINCE(4) { @@ -1875,7 +1896,7 @@ Object nvim_get_proc(Integer pid, Error *err) api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); return NIL; } -#ifdef WIN32 +#ifdef MSWIN rvobj.data.dictionary = os_proc_info((int)pid); if (rvobj.data.dictionary.size == 0) { // Process not found. return NIL; @@ -1897,19 +1918,20 @@ Object nvim_get_proc(Integer pid, Error *err) return rvobj; } -/// Selects an item in the completion popupmenu. +/// Selects an item in the completion popup menu. /// -/// If |ins-completion| is not active this API call is silently ignored. -/// Useful for an external UI using |ui-popupmenu| to control the popupmenu -/// with the mouse. Can also be used in a mapping; use <cmd> |:map-cmd| to -/// ensure the mapping doesn't end completion mode. +/// If neither |ins-completion| nor |cmdline-completion| popup menu is active +/// this API call is silently ignored. +/// Useful for an external UI using |ui-popupmenu| to control the popup menu with the mouse. +/// Can also be used in a mapping; use <Cmd> |:map-cmd| or a Lua mapping to ensure the mapping +/// doesn't end completion mode. /// -/// @param item Index (zero-based) of the item to select. Value of -1 selects -/// nothing and restores the original text. -/// @param insert Whether the selection should be inserted in the buffer. -/// @param finish Finish the completion and dismiss the popupmenu. Implies -/// `insert`. -/// @param opts Optional parameters. Reserved for future use. +/// @param item Index (zero-based) of the item to select. Value of -1 selects nothing +/// and restores the original text. +/// @param insert For |ins-completion|, whether the selection should be inserted in the buffer. +/// Ignored for |cmdline-completion|. +/// @param finish Finish the completion and dismiss the popup menu. Implies {insert}. +/// @param opts Optional parameters. Reserved for future use. /// @param[out] err Error details, if any void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Dictionary opts, Error *err) @@ -1928,7 +1950,7 @@ void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Di } /// NB: if your UI doesn't use hlstate, this will not return hlstate first time -Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) +Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, Error *err) { Array ret = ARRAY_DICT_INIT; @@ -1952,13 +1974,14 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) || col < 0 || col >= g->cols) { return ret; } + ret = arena_array(arena, 3); size_t off = g->line_offset[(size_t)row] + (size_t)col; - ADD(ret, STRING_OBJ(cstr_to_string((char *)g->chars[off]))); + ADD_C(ret, STRING_OBJ(cstr_as_string((char *)g->chars[off]))); int attr = g->attrs[off]; - ADD(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, err))); + ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err))); // will not work first time if (!highlight_use_hlstate()) { - ADD(ret, ARRAY_OBJ(hl_inspect(attr))); + ADD_C(ret, ARRAY_OBJ(hl_inspect(attr))); } return ret; } @@ -2040,7 +2063,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) // Marks are from an open buffer it fnum is non zero if (mark->fmark.fnum != 0) { bufnr = mark->fmark.fnum; - filename = (char *)buflist_nr2name(bufnr, true, true); + filename = buflist_nr2name(bufnr, true, true); allocated = true; // Marks comes from shada } else { @@ -2088,7 +2111,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) /// 'fillchars'). Treated as single-width even if it isn't. /// - highlights: (boolean) Return highlight information. /// - use_winbar: (boolean) Evaluate winbar instead of statusline. -/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When |TRUE|, {winid} +/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When true, {winid} /// is ignored. Mutually exclusive with {use_winbar}. /// /// @param[out] err Error details, if any. @@ -2096,7 +2119,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) /// - str: (string) Characters that will be displayed on the statusline. /// - width: (number) Display width of the statusline. /// - highlights: Array containing highlight information of the statusline. Only included when -/// the "highlights" key in {opts} is |TRUE|. Each element of the array is a +/// the "highlights" key in {opts} is true. Each element of the array is a /// |Dictionary| with these keys: /// - start: (number) Byte index (0-based) of first character that uses the highlight. /// - group: (string) Name of highlight group. @@ -2112,7 +2135,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * bool use_tabline = false; bool highlights = false; - if (str.size < 2 || memcmp(str.data, "%!", 2)) { + if (str.size < 2 || memcmp(str.data, "%!", 2) != 0) { const char *const errmsg = check_stl_option(str.data); if (errmsg) { api_set_error(err, kErrorTypeValidation, "%s", errmsg); @@ -2210,10 +2233,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * buf, sizeof(buf), str.data, - false, + NULL, + 0, fillchar, maxwidth, hltab_ptr, + NULL, NULL); PUT(result, "width", INTEGER_OBJ(width)); @@ -2228,7 +2253,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * // If first character doesn't have a defined highlight, // add the default highlight at the beginning of the highlight list - if (hltab->start == NULL || ((char *)hltab->start - buf) != 0) { + if (hltab->start == NULL || (hltab->start - buf) != 0) { Dictionary hl_info = ARRAY_DICT_INIT; grpname = get_default_stl_hl(wp, use_winbar); @@ -2246,7 +2271,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * if (sp->userhl == 0) { grpname = get_default_stl_hl(wp, use_winbar); } else if (sp->userhl < 0) { - grpname = (char *)syn_id2name(-sp->userhl); + grpname = syn_id2name(-sp->userhl); } else { snprintf(user_group, sizeof(user_group), "User%d", sp->userhl); grpname = user_group; diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index a28bfd2ab9..af1b23b712 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -2,26 +2,31 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <limits.h> -#include <stdlib.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include "klib/kvec.h" #include "nvim/api/private/converter.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vimscript.h" #include "nvim/ascii.h" -#include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_docmd.h" -#include "nvim/ops.h" +#include "nvim/garray.h" +#include "nvim/globals.h" +#include "nvim/memory.h" +#include "nvim/pos.h" #include "nvim/runtime.h" -#include "nvim/strings.h" #include "nvim/vim.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" -#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/vimscript.c.generated.h" @@ -137,7 +142,7 @@ Object nvim_eval(String expr, Error *err) if (!recursive) { force_abort = false; suppress_errthrow = false; - current_exception = NULL; + did_throw = false; // `did_emsg` is set by emsg(), which cancels execution. did_emsg = false; } @@ -196,7 +201,7 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err) if (!recursive) { force_abort = false; suppress_errthrow = false; - current_exception = NULL; + did_throw = false; // `did_emsg` is set by emsg(), which cancels execution. did_emsg = false; } @@ -204,10 +209,10 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err) try_start(); typval_T rettv; funcexe_T funcexe = FUNCEXE_INIT; - funcexe.firstline = curwin->w_cursor.lnum; - funcexe.lastline = curwin->w_cursor.lnum; - funcexe.evaluate = true; - funcexe.selfdict = self; + funcexe.fe_firstline = curwin->w_cursor.lnum; + funcexe.fe_lastline = curwin->w_cursor.lnum; + funcexe.fe_evaluate = true; + funcexe.fe_selfdict = self; // call_func() retval is deceptive, ignore it. Instead we set `msg_list` // (see above) to capture abort-causing non-exception errors. (void)call_func(fn.data, (int)fn.size, &rettv, (int)args.size, @@ -304,7 +309,7 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err) } fn = (String) { .data = di->di_tv.vval.v_string, - .size = STRLEN(di->di_tv.vval.v_string), + .size = strlen(di->di_tv.vval.v_string), }; } @@ -532,7 +537,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E kv_drop(ast_conv_stack, 1); } else { if (cur_item.ret_node_p->type == kObjectTypeNil) { - size_t items_size = (size_t)(3 // "type", "start" and "len" + size_t items_size = (size_t)(3 // "type", "start" and "len" // NOLINT(bugprone-misplaced-widening-cast) + (node->children != NULL) // "children" + (node->type == kExprNodeOption || node->type == kExprNodePlainIdentifier) // "scope" diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 6c37df6af8..0ffeac1bff 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -1,19 +1,27 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <assert.h> #include <stdbool.h> -#include <stddef.h> -#include <stdint.h> +#include <string.h> +#include "klib/kvec.h" +#include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/api/win_config.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" +#include "nvim/decoration.h" #include "nvim/drawscreen.h" +#include "nvim/extmark_defs.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/highlight_group.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" #include "nvim/option.h" -#include "nvim/strings.h" +#include "nvim/pos.h" #include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/window.h" @@ -47,13 +55,13 @@ /// this should not be used to specify arbitrary WM screen positions. /// /// Example (Lua): window-relative float -/// <pre> +/// <pre>lua /// vim.api.nvim_open_win(0, false, /// {relative='win', row=3, col=3, width=12, height=3}) /// </pre> /// /// Example (Lua): buffer-relative float (travels as buffer is scrolled) -/// <pre> +/// <pre>lua /// vim.api.nvim_open_win(0, false, /// {relative='win', width=12, height=3, bufpos={100,10}}) /// </pre> @@ -66,6 +74,7 @@ /// - "editor" The global editor grid /// - "win" Window given by the `win` field, or current window. /// - "cursor" Cursor position in current window. +/// - "mouse" Mouse position /// - win: |window-ID| for relative="win". /// - anchor: Decides which corner of the float to place at (row,col): /// - "NW" northwest (default) @@ -106,10 +115,11 @@ /// float where the text should not be edited. Disables /// 'number', 'relativenumber', 'cursorline', 'cursorcolumn', /// 'foldcolumn', 'spell' and 'list' options. 'signcolumn' -/// is changed to `auto` and 'colorcolumn' is cleared. The -/// end-of-buffer region is hidden by setting `eob` flag of +/// is changed to `auto` and 'colorcolumn' is cleared. +/// 'statuscolumn' is changed to empty. The end-of-buffer +/// region is hidden by setting `eob` flag of /// 'fillchars' to a space char, and clearing the -/// |EndOfBuffer| region in 'winhighlight'. +/// |hl-EndOfBuffer| region in 'winhighlight'. /// - border: Style of (optional) window border. This can either be a string /// or an array. The string values are /// - "none": No border (default). @@ -133,7 +143,12 @@ /// will only make vertical borders but not horizontal ones. /// By default, `FloatBorder` highlight is used, which links to `WinSeparator` /// when not defined. It could also be specified by character: -/// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]. +/// [ ["+", "MyCorner"], ["x", "MyBorder"] ]. +/// - title: Title (optional) in window border, String or list. +/// List is [text, highlight] tuples. if is string the default +/// highlight group is `FloatTitle`. +/// - title_pos: Title position must set with title option. +/// value can be of `left` `center` `right` default is left. /// - noautocmd: If true then no buffer-related autocommand events such as /// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from /// calling this function. @@ -202,7 +217,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) if (!win_new_float(win, false, fconfig, err)) { return; } - redraw_later(win, NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } else { win_config_float(win, fconfig); win->w_pos_changed = true; @@ -263,7 +278,7 @@ Dictionary nvim_win_get_config(Window window, Error *err) String s = cstrn_to_string((const char *)config->border_chars[i], sizeof(schar_T)); int hi_id = config->border_hl_ids[i]; - char *hi_name = (char *)syn_id2name(hi_id); + char *hi_name = syn_id2name(hi_id); if (hi_name[0]) { ADD(tuple, STRING_OBJ(s)); ADD(tuple, STRING_OBJ(cstr_to_string((const char *)hi_name))); @@ -273,6 +288,29 @@ Dictionary nvim_win_get_config(Window window, Error *err) } } PUT(rv, "border", ARRAY_OBJ(border)); + if (config->title) { + Array titles = ARRAY_DICT_INIT; + VirtText title_datas = config->title_chunks; + for (size_t i = 0; i < title_datas.size; i++) { + Array tuple = ARRAY_DICT_INIT; + ADD(tuple, CSTR_TO_OBJ((const char *)title_datas.items[i].text)); + if (title_datas.items[i].hl_id > 0) { + ADD(tuple, + STRING_OBJ(cstr_to_string((const char *)syn_id2name(title_datas.items[i].hl_id)))); + } + ADD(titles, ARRAY_OBJ(tuple)); + } + PUT(rv, "title", ARRAY_OBJ(titles)); + char *title_pos; + if (config->title_pos == kAlignLeft) { + title_pos = "left"; + } else if (config->title_pos == kAlignCenter) { + title_pos = "center"; + } else { + title_pos = "right"; + } + PUT(rv, "title_pos", CSTR_TO_OBJ(title_pos)); + } } } @@ -312,6 +350,8 @@ static bool parse_float_relative(String relative, FloatRelative *out) *out = kFloatRelativeWindow; } else if (striequal(str, "cursor")) { *out = kFloatRelativeCursor; + } else if (striequal(str, "mouse")) { + *out = kFloatRelativeMouse; } else { return false; } @@ -330,7 +370,74 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out) return true; } -static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) +static void parse_border_title(Object title, Object title_pos, FloatConfig *fconfig, Error *err) +{ + if (!parse_title_pos(title_pos, fconfig, err)) { + return; + } + + if (title.type == kObjectTypeString) { + if (title.data.string.size == 0) { + fconfig->title = false; + return; + } + int hl_id = syn_check_group(S_LEN("FloatTitle")); + kv_push(fconfig->title_chunks, ((VirtTextChunk){ .text = xstrdup(title.data.string.data), + .hl_id = hl_id })); + fconfig->title_width = (int)mb_string2cells(title.data.string.data); + fconfig->title = true; + return; + } + + if (title.type != kObjectTypeArray) { + api_set_error(err, kErrorTypeValidation, "title must be string or array"); + return; + } + + if (title.data.array.size == 0) { + api_set_error(err, kErrorTypeValidation, "title cannot be an empty array"); + return; + } + + fconfig->title_width = 0; + fconfig->title_chunks = parse_virt_text(title.data.array, err, &fconfig->title_width); + + fconfig->title = true; +} + +static bool parse_title_pos(Object title_pos, FloatConfig *fconfig, Error *err) +{ + if (!HAS_KEY(title_pos)) { + fconfig->title_pos = kAlignLeft; + return true; + } + + if (title_pos.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "title_pos must be string"); + return false; + } + + if (title_pos.data.string.size == 0) { + fconfig->title_pos = kAlignLeft; + return true; + } + + char *pos = title_pos.data.string.data; + + if (strequal(pos, "left")) { + fconfig->title_pos = kAlignLeft; + } else if (strequal(pos, "center")) { + fconfig->title_pos = kAlignCenter; + } else if (strequal(pos, "right")) { + fconfig->title_pos = kAlignRight; + } else { + api_set_error(err, kErrorTypeValidation, "invalid title_pos value"); + return false; + } + return true; +} + +static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) { struct { const char *name; @@ -414,6 +521,8 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err) String str = style.data.string; if (str.size == 0 || strequal(str.data, "none")) { fconfig->border = false; + // title does not work with border equal none + fconfig->title = false; return; } for (size_t i = 0; defaults[i].name; i++) { @@ -603,6 +712,29 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, return false; } + if (HAS_KEY(config->title_pos)) { + if (!HAS_KEY(config->title)) { + api_set_error(err, kErrorTypeException, "title_pos requires title to be set"); + return false; + } + } + + if (HAS_KEY(config->title)) { + // title only work with border + if (!HAS_KEY(config->border) && !fconfig->border) { + api_set_error(err, kErrorTypeException, "title requires border to be set"); + return false; + } + + if (fconfig->title) { + clear_virttext(&fconfig->title_chunks); + } + parse_border_title(config->title, config->title_pos, fconfig, err); + if (ERROR_SET(err)) { + return false; + } + } + if (HAS_KEY(config->border)) { parse_border_style(config->border, fconfig, err); if (ERROR_SET(err)) { diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 580dfd8639..e2c234ab29 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -10,16 +10,18 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/window.h" #include "nvim/ascii.h" -#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" +#include "nvim/eval/window.h" #include "nvim/ex_docmd.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/memline_defs.h" #include "nvim/move.h" -#include "nvim/option.h" -#include "nvim/syntax.h" -#include "nvim/vim.h" +#include "nvim/pos.h" +#include "nvim/types.h" #include "nvim/window.h" /// Gets the current buffer in a window @@ -51,7 +53,9 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) win_set_buf(window, buffer, false, err); } -/// Gets the (1,0)-indexed cursor position in the window. |api-indexing| +/// Gets the (1,0)-indexed, buffer-relative cursor position for a given window +/// (different windows showing the same buffer have independent cursor +/// positions). |api-indexing| /// /// @param window Window handle, or 0 for current window /// @param[out] err Error details, if any @@ -115,10 +119,15 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) // Make sure we stick in this column. win->w_set_curswant = true; - // make sure cursor is in visible range even if win != curwin - update_topline_win(win); + // make sure cursor is in visible range and + // cursorcolumn and cursorline are updated even if win != curwin + switchwin_T switchwin; + switch_win(&switchwin, win, NULL, true); + update_topline(curwin); + validate_cursor(); + restore_win(&switchwin, true); - redraw_later(win, VALID); + redraw_later(win, UPD_VALID); win->w_redr_status = true; } @@ -160,9 +169,11 @@ void nvim_win_set_height(Window window, Integer height, Error *err) win_T *savewin = curwin; curwin = win; + curbuf = curwin->w_buffer; try_start(); win_setheight((int)height); curwin = savewin; + curbuf = curwin->w_buffer; try_end(err); } @@ -205,9 +216,11 @@ void nvim_win_set_width(Window window, Integer width, Error *err) win_T *savewin = curwin; curwin = win; + curbuf = curwin->w_buffer; try_start(); win_setwidth((int)width); curwin = savewin; + curbuf = curwin->w_buffer; try_end(err); } @@ -340,7 +353,7 @@ Boolean nvim_win_is_valid(Window window) /// /// Like |:hide| the buffer becomes hidden unless another window is editing it, /// or 'bufhidden' is `unload`, `delete` or `wipe` as opposed to |:close| or -/// |nvim_win_close|, which will close the buffer. +/// |nvim_win_close()|, which will close the buffer. /// /// @param window Window handle, or 0 for current window /// @param[out] err Error details, if any @@ -356,11 +369,16 @@ void nvim_win_hide(Window window, Error *err) tabpage_T *tabpage = win_find_tabpage(win); TryState tstate; try_enter(&tstate); - if (tabpage == curtab) { + + // Never close the autocommand window. + if (is_aucmd_win(win)) { + emsg(_(e_autocmd_close)); + } else if (tabpage == curtab) { win_close(win, false, false); } else { win_close_othertab(win, false, tabpage); } + vim_ignored = try_leave(&tstate, err); } @@ -430,7 +448,7 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err) /// Set highlight namespace for a window. This will use highlights defined in /// this namespace, but fall back to global highlights (ns=0) when missing. /// -/// This takes predecence over the 'winhighlight' option. +/// This takes precedence over the 'winhighlight' option. /// /// @param ns_id the namespace to use /// @param[out] err Error details, if any @@ -449,5 +467,5 @@ void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err) win->w_ns_hl = (NS)ns_id; win->w_hl_needs_update = true; - redraw_later(win, NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index 06536e6e2b..41024cafda 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -21,76 +21,83 @@ /// Stand-Alone - unicode form-B isolated char denoted with a_s_* (NOT USED) #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> #include "nvim/arabic.h" #include "nvim/ascii.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/option_defs.h" #include "nvim/vim.h" // Unicode values for Arabic characters. -#define a_HAMZA 0x0621 -#define a_ALEF_MADDA 0x0622 -#define a_ALEF_HAMZA_ABOVE 0x0623 -#define a_WAW_HAMZA 0x0624 -#define a_ALEF_HAMZA_BELOW 0x0625 -#define a_YEH_HAMZA 0x0626 -#define a_ALEF 0x0627 -#define a_BEH 0x0628 -#define a_TEH_MARBUTA 0x0629 -#define a_TEH 0x062a -#define a_THEH 0x062b -#define a_JEEM 0x062c -#define a_HAH 0x062d -#define a_KHAH 0x062e -#define a_DAL 0x062f -#define a_THAL 0x0630 -#define a_REH 0x0631 -#define a_ZAIN 0x0632 -#define a_SEEN 0x0633 -#define a_SHEEN 0x0634 -#define a_SAD 0x0635 -#define a_DAD 0x0636 -#define a_TAH 0x0637 -#define a_ZAH 0x0638 -#define a_AIN 0x0639 -#define a_GHAIN 0x063a -#define a_TATWEEL 0x0640 -#define a_FEH 0x0641 -#define a_QAF 0x0642 -#define a_KAF 0x0643 -#define a_LAM 0x0644 -#define a_MEEM 0x0645 -#define a_NOON 0x0646 -#define a_HEH 0x0647 -#define a_WAW 0x0648 -#define a_ALEF_MAKSURA 0x0649 -#define a_YEH 0x064a -#define a_FATHATAN 0x064b -#define a_DAMMATAN 0x064c -#define a_KASRATAN 0x064d -#define a_FATHA 0x064e -#define a_DAMMA 0x064f -#define a_KASRA 0x0650 -#define a_SHADDA 0x0651 -#define a_SUKUN 0x0652 -#define a_MADDA_ABOVE 0x0653 -#define a_HAMZA_ABOVE 0x0654 -#define a_HAMZA_BELOW 0x0655 - -#define a_PEH 0x067e -#define a_TCHEH 0x0686 -#define a_JEH 0x0698 -#define a_FKAF 0x06a9 -#define a_GAF 0x06af -#define a_FYEH 0x06cc - -#define a_s_LAM_ALEF_MADDA_ABOVE 0xfef5 -#define a_f_LAM_ALEF_MADDA_ABOVE 0xfef6 -#define a_s_LAM_ALEF_HAMZA_ABOVE 0xfef7 -#define a_f_LAM_ALEF_HAMZA_ABOVE 0xfef8 -#define a_s_LAM_ALEF_HAMZA_BELOW 0xfef9 -#define a_f_LAM_ALEF_HAMZA_BELOW 0xfefa -#define a_s_LAM_ALEF 0xfefb -#define a_f_LAM_ALEF 0xfefc +enum { + a_HAMZA = 0x0621, + a_ALEF_MADDA = 0x0622, + a_ALEF_HAMZA_ABOVE = 0x0623, + a_WAW_HAMZA = 0x0624, + a_ALEF_HAMZA_BELOW = 0x0625, + a_YEH_HAMZA = 0x0626, + a_ALEF = 0x0627, + a_BEH = 0x0628, + a_TEH_MARBUTA = 0x0629, + a_TEH = 0x062a, + a_THEH = 0x062b, + a_JEEM = 0x062c, + a_HAH = 0x062d, + a_KHAH = 0x062e, + a_DAL = 0x062f, + a_THAL = 0x0630, + a_REH = 0x0631, + a_ZAIN = 0x0632, + a_SEEN = 0x0633, + a_SHEEN = 0x0634, + a_SAD = 0x0635, + a_DAD = 0x0636, + a_TAH = 0x0637, + a_ZAH = 0x0638, + a_AIN = 0x0639, + a_GHAIN = 0x063a, + a_TATWEEL = 0x0640, + a_FEH = 0x0641, + a_QAF = 0x0642, + a_KAF = 0x0643, + a_LAM = 0x0644, + a_MEEM = 0x0645, + a_NOON = 0x0646, + a_HEH = 0x0647, + a_WAW = 0x0648, + a_ALEF_MAKSURA = 0x0649, + a_YEH = 0x064a, + a_FATHATAN = 0x064b, + a_DAMMATAN = 0x064c, + a_KASRATAN = 0x064d, + a_FATHA = 0x064e, + a_DAMMA = 0x064f, + a_KASRA = 0x0650, + a_SHADDA = 0x0651, + a_SUKUN = 0x0652, + a_MADDA_ABOVE = 0x0653, + a_HAMZA_ABOVE = 0x0654, + a_HAMZA_BELOW = 0x0655, + + a_PEH = 0x067e, + a_TCHEH = 0x0686, + a_JEH = 0x0698, + a_FKAF = 0x06a9, + a_GAF = 0x06af, + a_FYEH = 0x06cc, + + a_s_LAM_ALEF_MADDA_ABOVE = 0xfef5, + a_f_LAM_ALEF_MADDA_ABOVE = 0xfef6, + a_s_LAM_ALEF_HAMZA_ABOVE = 0xfef7, + a_f_LAM_ALEF_HAMZA_ABOVE = 0xfef8, + a_s_LAM_ALEF_HAMZA_BELOW = 0xfef9, + a_f_LAM_ALEF_HAMZA_BELOW = 0xfefa, + a_s_LAM_ALEF = 0xfefb, + a_f_LAM_ALEF = 0xfefc, +}; static struct achar { unsigned c; @@ -320,22 +327,22 @@ int arabic_shape(int c, int *ccp, int *c1p, int prev_c, int prev_c1, int next_c) int backward_combine = !prev_laa && can_join(prev_c, c); int forward_combine = can_join(c, next_c); - if (backward_combine && forward_combine) { - curr_c = (int)curr_a->medial; - } - if (backward_combine && !forward_combine) { - curr_c = (int)curr_a->final; - } - if (!backward_combine && forward_combine) { - curr_c = (int)curr_a->initial; - } - if (!backward_combine && !forward_combine) { - curr_c = (int)curr_a->isolated; + if (backward_combine) { + if (forward_combine) { + curr_c = (int)curr_a->medial; + } else { + curr_c = (int)curr_a->final; + } + } else { + if (forward_combine) { + curr_c = (int)curr_a->initial; + } else { + curr_c = (int)curr_a->isolated; + } } } - // Sanity check -- curr_c should, in the future, never be 0. - // We should, in the future, insert a fatal error here. + // Character missing from the table means using original character. if (curr_c == NUL) { curr_c = c; } diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c index 0be83f0c05..c6a4be7e13 100644 --- a/src/nvim/arglist.c +++ b/src/nvim/arglist.c @@ -5,41 +5,92 @@ #include <assert.h> #include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include "auto/config.h" #include "nvim/arglist.h" +#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" -#include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/window.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/macros.h" #include "nvim/mark.h" +#include "nvim/memline_defs.h" #include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/regexp.h" -#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/undo.h" #include "nvim/version.h" #include "nvim/vim.h" #include "nvim/window.h" +/// State used by the :all command to open all the files in the argument list in +/// separate windows. +typedef struct { + alist_T *alist; ///< argument list to be used + int had_tab; + bool keep_tabs; + bool forceit; + + bool use_firstwin; ///< use first window for arglist + uint8_t *opened; ///< Array of weight for which args are open: + ///< 0: not opened + ///< 1: opened in other tab + ///< 2: opened in curtab + ///< 3: opened in curtab and curwin + int opened_len; ///< length of opened[] + win_T *new_curwin; + tabpage_T *new_curtab; +} arg_all_state_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "arglist.c.generated.h" #endif +static char e_cannot_change_arglist_recursively[] + = N_("E1156: Cannot change the argument list recursively"); + enum { AL_SET = 1, AL_ADD = 2, AL_DEL = 3, }; +/// This flag is set whenever the argument list is being changed and calling a +/// function that might trigger an autocommand. +static bool arglist_locked = false; + +static int check_arglist_locked(void) +{ + if (arglist_locked) { + emsg(_(e_cannot_change_arglist_recursively)); + return FAIL; + } + return OK; +} + /// Clear an argument list: free all file names and reset it to zero entries. void alist_clear(alist_T *al) { + if (check_arglist_locked() == FAIL) { + return; + } #define FREE_AENTRY_FNAME(arg) xfree((arg)->ae_fname) GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME); } @@ -79,13 +130,14 @@ void alist_expand(int *fnum_list, int fnum_len) { char *save_p_su = p_su; + char **old_arg_files = xmalloc(sizeof(*old_arg_files) * GARGCOUNT); + // Don't use 'suffixes' here. This should work like the shell did the // expansion. Also, the vimrc file isn't read yet, thus the user // can't set the options. p_su = empty_option; - char **old_arg_files = xmalloc(sizeof(*old_arg_files) * GARGCOUNT); for (int i = 0; i < GARGCOUNT; i++) { - old_arg_files[i] = vim_strsave(GARGLIST[i].ae_fname); + old_arg_files[i] = xstrdup(GARGLIST[i].ae_fname); } int old_arg_count = GARGCOUNT; char **new_arg_files; @@ -106,13 +158,9 @@ void alist_expand(int *fnum_list, int fnum_len) /// Takes over the allocated files[] and the allocated fnames in it. void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_list, int fnum_len) { - static int recursive = 0; - - if (recursive) { - emsg(_(e_au_recursive)); + if (check_arglist_locked() == FAIL) { return; } - recursive++; alist_clear(al); ga_grow(&al->al_ga, count); @@ -130,7 +178,9 @@ void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_l // May set buffer name of a buffer previously used for the // argument list, so that it's re-used by alist_add. if (fnum_list != NULL && i < fnum_len) { + arglist_locked = true; buf_set_name(fnum_list[i], files[i]); + arglist_locked = false; } alist_add(al, files[i], use_curbuf ? 2 : 1); @@ -142,7 +192,6 @@ void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_l if (al == &global_alist) { arg_had_last = false; } - recursive--; } /// Add file "fname" to argument list "al". @@ -154,15 +203,22 @@ void alist_add(alist_T *al, char *fname, int set_fnum) if (fname == NULL) { // don't add NULL file names return; } + if (check_arglist_locked() == FAIL) { + return; + } + arglist_locked = true; + #ifdef BACKSLASH_IN_FILENAME slash_adjust(fname); #endif - AARGLIST(al)[al->al_ga.ga_len].ae_fname = (char_u *)fname; + AARGLIST(al)[al->al_ga.ga_len].ae_fname = fname; if (set_fnum > 0) { AARGLIST(al)[al->al_ga.ga_len].ae_fnum = buflist_add(fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0)); } al->al_ga.ga_len++; + + arglist_locked = false; } #if defined(BACKSLASH_IN_FILENAME) @@ -202,7 +258,7 @@ static char *do_one_arg(char *str) for (p = str; *str; str++) { // When the backslash is used for escaping the special meaning of a // character we need to keep it until wildcard expansion. - if (rem_backslash((char_u *)str)) { + if (rem_backslash(str)) { *p++ = *str++; *p++ = *str; } else { @@ -226,7 +282,7 @@ static char *do_one_arg(char *str) /// growarray "gap". static void get_arglist(garray_T *gap, char *str, int escaped) { - ga_init(gap, (int)sizeof(char_u *), 20); + ga_init(gap, (int)sizeof(char *), 20); while (*str != NUL) { GA_APPEND(char *, gap, str); @@ -244,12 +300,12 @@ static void get_arglist(garray_T *gap, char *str, int escaped) /// "fnames[fcountp]". When "wig" is true, removes files matching 'wildignore'. /// /// @return FAIL or OK. -int get_arglist_exp(char_u *str, int *fcountp, char ***fnamesp, bool wig) +int get_arglist_exp(char *str, int *fcountp, char ***fnamesp, bool wig) { garray_T ga; int i; - get_arglist(&ga, (char *)str, true); + get_arglist(&ga, str, true); if (wig) { i = expand_wildcards(ga.ga_len, ga.ga_data, @@ -284,7 +340,7 @@ static void alist_add_list(int count, char **files, int after, bool will_edit) { int old_argcount = ARGCOUNT; ga_grow(&ALIST(curwin)->al_ga, count); - { + if (check_arglist_locked() != FAIL) { if (after < 0) { after = 0; } @@ -295,11 +351,13 @@ static void alist_add_list(int count, char **files, int after, bool will_edit) memmove(&(ARGLIST[after + count]), &(ARGLIST[after]), (size_t)(ARGCOUNT - after) * sizeof(aentry_T)); } + arglist_locked = true; for (int i = 0; i < count; i++) { const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0); - ARGLIST[after + i].ae_fname = (char_u *)files[i]; + ARGLIST[after + i].ae_fname = files[i]; ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags); } + arglist_locked = false; ALIST(curwin)->al_ga.ga_len += count; if (old_argcount > 0 && curwin->w_arg_idx >= after) { curwin->w_arg_idx += count; @@ -308,6 +366,50 @@ static void alist_add_list(int count, char **files, int after, bool will_edit) } } +/// Delete the file names in "alist_ga" from the argument list. +static void arglist_del_files(garray_T *alist_ga) +{ + regmatch_T regmatch; + + // Delete the items: use each item as a regexp and find a match in the + // argument list. + regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set + for (int i = 0; i < alist_ga->ga_len && !got_int; i++) { + char *p = ((char **)alist_ga->ga_data)[i]; + p = file_pat_to_reg_pat(p, NULL, NULL, false); + if (p == NULL) { + break; + } + regmatch.regprog = vim_regcomp(p, magic_isset() ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) { + xfree(p); + break; + } + + bool didone = false; + for (int match = 0; match < ARGCOUNT; match++) { + if (vim_regexec(®match, alist_name(&ARGLIST[match]), (colnr_T)0)) { + didone = true; + xfree(ARGLIST[match].ae_fname); + memmove(ARGLIST + match, ARGLIST + match + 1, + (size_t)(ARGCOUNT - match - 1) * sizeof(aentry_T)); + ALIST(curwin)->al_ga.ga_len--; + if (curwin->w_arg_idx > match) { + curwin->w_arg_idx--; + } + match--; + } + } + + vim_regfree(regmatch.regprog); + xfree(p); + if (!didone) { + semsg(_(e_nomatch2), ((char **)alist_ga->ga_data)[i]); + } + } + ga_clear(alist_ga); +} + /// @param str /// @param what /// AL_SET: Redefine the argument list to 'str'. @@ -324,10 +426,12 @@ static int do_arglist(char *str, int what, int after, bool will_edit) garray_T new_ga; int exp_count; char **exp_files; - char *p; - int match; int arg_escaped = true; + if (check_arglist_locked() == FAIL) { + return FAIL; + } + // Set default argument for ":argadd" command. if (what == AL_ADD && *str == NUL) { if (curbuf->b_ffname == NULL) { @@ -341,46 +445,7 @@ static int do_arglist(char *str, int what, int after, bool will_edit) get_arglist(&new_ga, str, arg_escaped); if (what == AL_DEL) { - regmatch_T regmatch; - bool didone; - - // Delete the items: use each item as a regexp and find a match in the - // argument list. - regmatch.rm_ic = p_fic; // ignore case when 'fileignorecase' is set - for (int i = 0; i < new_ga.ga_len && !got_int; i++) { - p = ((char **)new_ga.ga_data)[i]; - p = file_pat_to_reg_pat(p, NULL, NULL, false); - if (p == NULL) { - break; - } - regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); - if (regmatch.regprog == NULL) { - xfree(p); - break; - } - - didone = false; - for (match = 0; match < ARGCOUNT; match++) { - if (vim_regexec(®match, alist_name(&ARGLIST[match]), (colnr_T)0)) { - didone = true; - xfree(ARGLIST[match].ae_fname); - memmove(ARGLIST + match, ARGLIST + match + 1, - (size_t)(ARGCOUNT - match - 1) * sizeof(aentry_T)); - ALIST(curwin)->al_ga.ga_len--; - if (curwin->w_arg_idx > match) { - curwin->w_arg_idx--; - } - match--; - } - } - - vim_regfree(regmatch.regprog); - xfree(p); - if (!didone) { - semsg(_(e_nomatch2), ((char_u **)new_ga.ga_data)[i]); - } - } - ga_clear(&new_ga); + arglist_del_files(&new_ga); } else { int i = expand_wildcards(new_ga.ga_len, new_ga.ga_data, &exp_count, &exp_files, @@ -457,6 +522,9 @@ void check_arg_idx(win_T *win) void ex_args(exarg_T *eap) { if (eap->cmdidx != CMD_args) { + if (check_arglist_locked() == FAIL) { + return; + } alist_unlink(ALIST(curwin)); if (eap->cmdidx == CMD_argglobal) { ALIST(curwin) = &global_alist; @@ -465,34 +533,47 @@ void ex_args(exarg_T *eap) } } + // ":args file ..": define new argument list, handle like ":next" + // Also for ":argslocal file .." and ":argsglobal file ..". if (*eap->arg != NUL) { - // ":args file ..": define new argument list, handle like ":next" - // Also for ":argslocal file .." and ":argsglobal file ..". + if (check_arglist_locked() == FAIL) { + return; + } ex_next(eap); - } else if (eap->cmdidx == CMD_args) { - // ":args": list arguments. - if (ARGCOUNT > 0) { - char **items = xmalloc(sizeof(char_u *) * (size_t)ARGCOUNT); - // Overwrite the command, for a short list there is no scrolling - // required and no wait_return(). - gotocmdline(true); - for (int i = 0; i < ARGCOUNT; i++) { - items[i] = alist_name(&ARGLIST[i]); - } - list_in_columns(items, ARGCOUNT, curwin->w_arg_idx); - xfree(items); + return; + } + + // ":args": list arguments. + if (eap->cmdidx == CMD_args) { + if (ARGCOUNT <= 0) { + return; // empty argument list } - } else if (eap->cmdidx == CMD_arglocal) { + + char **items = xmalloc(sizeof(char *) * (size_t)ARGCOUNT); + + // Overwrite the command, for a short list there is no scrolling + // required and no wait_return(). + gotocmdline(true); + + for (int i = 0; i < ARGCOUNT; i++) { + items[i] = alist_name(&ARGLIST[i]); + } + list_in_columns(items, ARGCOUNT, curwin->w_arg_idx); + xfree(items); + + return; + } + + // ":argslocal": make a local copy of the global argument list. + if (eap->cmdidx == CMD_arglocal) { garray_T *gap = &curwin->w_alist->al_ga; - // ":argslocal": make a local copy of the global argument list. ga_grow(gap, GARGCOUNT); + for (int i = 0; i < GARGCOUNT; i++) { if (GARGLIST[i].ae_fname != NULL) { - AARGLIST(curwin->w_alist)[gap->ga_len].ae_fname = - vim_strsave(GARGLIST[i].ae_fname); - AARGLIST(curwin->w_alist)[gap->ga_len].ae_fnum = - GARGLIST[i].ae_fnum; + AARGLIST(curwin->w_alist)[gap->ga_len].ae_fname = xstrdup(GARGLIST[i].ae_fname); + AARGLIST(curwin->w_alist)[gap->ga_len].ae_fnum = GARGLIST[i].ae_fnum; gap->ga_len++; } } @@ -550,50 +631,52 @@ void do_argfile(exarg_T *eap, int argn) } else { emsg(_("E165: Cannot go beyond last file")); } - } else { - setpcmark(); - // split window or create new tab page first - if (*eap->cmd == 's' || cmdmod.cmod_tab != 0) { - if (win_split(0, 0) == FAIL) { - return; - } - RESET_BINDING(curwin); - } else { - // if 'hidden' set, only check for changed file when re-editing - // the same buffer - other = true; - if (buf_hide(curbuf)) { - p = fix_fname(alist_name(&ARGLIST[argn])); - other = otherfile(p); - xfree(p); - } - if ((!buf_hide(curbuf) || !other) - && check_changed(curbuf, CCGD_AW - | (other ? 0 : CCGD_MULTWIN) - | (eap->forceit ? CCGD_FORCEIT : 0) - | CCGD_EXCMD)) { - return; - } - } + return; + } - curwin->w_arg_idx = argn; - if (argn == ARGCOUNT - 1 && curwin->w_alist == &global_alist) { - arg_had_last = true; - } + setpcmark(); - // Edit the file; always use the last known line number. - // When it fails (e.g. Abort for already edited file) restore the - // argument index. - if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL, - eap, ECMD_LAST, - (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0) - + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) { - curwin->w_arg_idx = old_arg_idx; - } else if (eap->cmdidx != CMD_argdo) { - // like Vi: set the mark where the cursor is in the file. - setmark('\''); + // split window or create new tab page first + if (*eap->cmd == 's' || cmdmod.cmod_tab != 0) { + if (win_split(0, 0) == FAIL) { + return; } + RESET_BINDING(curwin); + } else { + // if 'hidden' set, only check for changed file when re-editing + // the same buffer + other = true; + if (buf_hide(curbuf)) { + p = fix_fname(alist_name(&ARGLIST[argn])); + other = otherfile(p); + xfree(p); + } + if ((!buf_hide(curbuf) || !other) + && check_changed(curbuf, CCGD_AW + | (other ? 0 : CCGD_MULTWIN) + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) { + return; + } + } + + curwin->w_arg_idx = argn; + if (argn == ARGCOUNT - 1 && curwin->w_alist == &global_alist) { + arg_had_last = true; + } + + // Edit the file; always use the last known line number. + // When it fails (e.g. Abort for already edited file) restore the + // argument index. + if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL, + eap, ECMD_LAST, + (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0) + + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) { + curwin->w_arg_idx = old_arg_idx; + } else if (eap->cmdidx != CMD_argdo) { + // like Vi: set the mark where the cursor is in the file. + setmark('\''); } } @@ -625,8 +708,17 @@ void ex_next(exarg_T *eap) void ex_argdedupe(exarg_T *eap FUNC_ATTR_UNUSED) { for (int i = 0; i < ARGCOUNT; i++) { + // Expand each argument to a full path to catch different paths leading + // to the same file. + char *firstFullname = FullName_save(ARGLIST[i].ae_fname, false); + for (int j = i + 1; j < ARGCOUNT; j++) { - if (FNAMECMP(ARGLIST[i].ae_fname, ARGLIST[j].ae_fname) == 0) { + char *secondFullname = FullName_save(ARGLIST[j].ae_fname, false); + bool areNamesDuplicate = path_fnamecmp(firstFullname, secondFullname) == 0; + xfree(secondFullname); + + if (areNamesDuplicate) { + // remove one duplicate argument xfree(ARGLIST[j].ae_fname); memmove(ARGLIST + j, ARGLIST + j + 1, (size_t)(ARGCOUNT - j - 1) * sizeof(aentry_T)); @@ -641,6 +733,8 @@ void ex_argdedupe(exarg_T *eap FUNC_ATTR_UNUSED) j--; } } + + xfree(firstFullname); } } @@ -679,6 +773,10 @@ void ex_argadd(exarg_T *eap) /// ":argdelete" void ex_argdelete(exarg_T *eap) { + if (check_arglist_locked() == FAIL) { + return; + } + if (eap->addr_count > 0 || *eap->arg == NUL) { // ":argdel" works like ":.argdel" if (eap->addr_count == 0) { @@ -742,81 +840,37 @@ char *alist_name(aentry_T *aep) // Use the name from the associated buffer if it exists. bp = buflist_findnr(aep->ae_fnum); if (bp == NULL || bp->b_fname == NULL) { - return (char *)aep->ae_fname; + return aep->ae_fname; } return bp->b_fname; } -/// do_arg_all(): Open up to 'count' windows, one for each argument. -/// -/// @param forceit hide buffers in current windows -/// @param keep_tabs keep current tabs, for ":tab drop file" -static void do_arg_all(int count, int forceit, int keep_tabs) +/// Close all the windows containing files which are not in the argument list. +/// Used by the ":all" command. +static void arg_all_close_unused_windows(arg_all_state_T *aall) { - uint8_t *opened; // Array of weight for which args are open: - // 0: not opened - // 1: opened in other tab - // 2: opened in curtab - // 3: opened in curtab and curwin - - int opened_len; // length of opened[] - int use_firstwin = false; // use first window for arglist - bool tab_drop_empty_window = false; - int split_ret = OK; - bool p_ea_save; - alist_T *alist; // argument list to be used - buf_T *buf; - tabpage_T *tpnext; - int had_tab = cmdmod.cmod_tab; - win_T *old_curwin, *last_curwin; - tabpage_T *old_curtab, *last_curtab; - win_T *new_curwin = NULL; - tabpage_T *new_curtab = NULL; - - assert(firstwin != NULL); // satisfy coverity - - if (ARGCOUNT <= 0) { - // Don't give an error message. We don't want it when the ":all" command is in the .vimrc. - return; - } - setpcmark(); - - opened_len = ARGCOUNT; - opened = xcalloc((size_t)opened_len, 1); - - // Autocommands may do anything to the argument list. Make sure it's not - // freed while we are working here by "locking" it. We still have to - // watch out for its size to be changed. - alist = curwin->w_alist; - alist->al_refcount++; + win_T *old_curwin = curwin; + tabpage_T *old_curtab = curtab; - old_curwin = curwin; - old_curtab = curtab; - - // Try closing all windows that are not in the argument list. - // Also close windows that are not full width; - // When 'hidden' or "forceit" set the buffer becomes hidden. - // Windows that have a changed buffer and can't be hidden won't be closed. - // When the ":tab" modifier was used do this for all tab pages. - if (had_tab > 0) { + if (aall->had_tab > 0) { goto_tabpage_tp(first_tabpage, true, true); } for (;;) { win_T *wpnext = NULL; - tpnext = curtab->tp_next; + tabpage_T *tpnext = curtab->tp_next; for (win_T *wp = firstwin; wp != NULL; wp = wpnext) { int i; wpnext = wp->w_next; - buf = wp->w_buffer; + buf_T *buf = wp->w_buffer; if (buf->b_ffname == NULL - || (!keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) { - i = opened_len; + || (!aall->keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) { + i = aall->opened_len; } else { // check if the buffer in this window is in the arglist - for (i = 0; i < opened_len; i++) { - if (i < alist->al_ga.ga_len - && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum - || path_full_compare(alist_name(&AARGLIST(alist)[i]), + for (i = 0; i < aall->opened_len; i++) { + if (i < aall->alist->al_ga.ga_len + && (AARGLIST(aall->alist)[i].ae_fnum == buf->b_fnum + || path_full_compare(alist_name(&AARGLIST(aall->alist)[i]), buf->b_ffname, true, true) & kEqualFiles)) { int weight = 1; @@ -828,23 +882,24 @@ static void do_arg_all(int count, int forceit, int keep_tabs) } } - if (weight > (int)opened[i]) { - opened[i] = (uint8_t)weight; + if (weight > (int)aall->opened[i]) { + aall->opened[i] = (uint8_t)weight; if (i == 0) { - if (new_curwin != NULL) { - new_curwin->w_arg_idx = opened_len; + if (aall->new_curwin != NULL) { + aall->new_curwin->w_arg_idx = aall->opened_len; } - new_curwin = wp; - new_curtab = curtab; + aall->new_curwin = wp; + aall->new_curtab = curtab; } - } else if (keep_tabs) { - i = opened_len; + } else if (aall->keep_tabs) { + i = aall->opened_len; } - if (wp->w_alist != alist) { - // Use the current argument list for all windows containing a file from it. + if (wp->w_alist != aall->alist) { + // Use the current argument list for all windows + // containing a file from it. alist_unlink(wp->w_alist); - wp->w_alist = alist; + wp->w_alist = aall->alist; wp->w_alist->al_refcount++; } break; @@ -853,8 +908,8 @@ static void do_arg_all(int count, int forceit, int keep_tabs) } wp->w_arg_idx = i; - if (i == opened_len && !keep_tabs) { // close this window - if (buf_hide(buf) || forceit || buf->b_nwindows > 1 + if (i == aall->opened_len && !aall->keep_tabs) { // close this window + if (buf_hide(buf) || aall->forceit || buf->b_nwindows > 1 || !bufIsChanged(buf)) { // If the buffer was changed, and we would like to hide it, try autowriting. if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { @@ -869,8 +924,8 @@ static void do_arg_all(int count, int forceit, int keep_tabs) } // don't close last window if (ONE_WINDOW - && (first_tabpage->tp_next == NULL || !had_tab)) { - use_firstwin = true; + && (first_tabpage->tp_next == NULL || !aall->had_tab)) { + aall->use_firstwin = true; } else { win_close(wp, !buf_hide(buf) && !bufIsChanged(buf), false); // check if autocommands removed the next window @@ -884,7 +939,7 @@ static void do_arg_all(int count, int forceit, int keep_tabs) } // Without the ":tab" modifier only do the current tab page. - if (had_tab == 0 || tpnext == NULL) { + if (aall->had_tab == 0 || tpnext == NULL) { break; } @@ -894,39 +949,35 @@ static void do_arg_all(int count, int forceit, int keep_tabs) } goto_tabpage_tp(tpnext, true, true); } +} - // Open a window for files in the argument list that don't have one. - // ARGCOUNT may change while doing this, because of autocommands. - if (count > opened_len || count <= 0) { - count = opened_len; - } +/// Open up to "count" windows for the files in the argument list "aall->alist". +static void arg_all_open_windows(arg_all_state_T *aall, int count) +{ + bool tab_drop_empty_window = false; - // Don't execute Win/Buf Enter/Leave autocommands here. - autocmd_no_enter++; - autocmd_no_leave++; - last_curwin = curwin; - last_curtab = curtab; - win_enter(lastwin, false); // ":tab drop file" should re-use an empty window to avoid "--remote-tab" // leaving an empty tab page when executed locally. - if (keep_tabs && buf_is_empty(curbuf) && curbuf->b_nwindows == 1 + if (aall->keep_tabs && buf_is_empty(curbuf) && curbuf->b_nwindows == 1 && curbuf->b_ffname == NULL && !curbuf->b_changed) { - use_firstwin = true; + aall->use_firstwin = true; tab_drop_empty_window = true; } + int split_ret = OK; + for (int i = 0; i < count && !got_int; i++) { - if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) { + if (aall->alist == &global_alist && i == global_alist.al_ga.ga_len - 1) { arg_had_last = true; } - if (opened[i] > 0) { + if (aall->opened[i] > 0) { // Move the already present window to below the current window if (curwin->w_arg_idx != i) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_arg_idx == i) { - if (keep_tabs) { - new_curwin = wp; - new_curtab = curtab; + if (aall->keep_tabs) { + aall->new_curwin = wp; + aall->new_curtab = curtab; } else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) { emsg(_("E249: window layout changed unexpectedly")); i = count; @@ -943,8 +994,8 @@ static void do_arg_all(int count, int forceit, int keep_tabs) if (tab_drop_empty_window && i == count - 1) { autocmd_no_enter--; } - if (!use_firstwin) { // split current window - p_ea_save = p_ea; + if (!aall->use_firstwin) { // split current window + bool p_ea_save = p_ea; p_ea = true; // use space from all windows split_ret = win_split(0, WSP_ROOM | WSP_BELOW); p_ea = p_ea_save; @@ -958,36 +1009,102 @@ static void do_arg_all(int count, int forceit, int keep_tabs) // edit file "i" curwin->w_arg_idx = i; if (i == 0) { - new_curwin = curwin; - new_curtab = curtab; + aall->new_curwin = curwin; + aall->new_curtab = curtab; } - (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, ECMD_ONE, + (void)do_ecmd(0, alist_name(&AARGLIST(aall->alist)[i]), NULL, NULL, ECMD_ONE, ((buf_hide(curwin->w_buffer) - || bufIsChanged(curwin->w_buffer)) - ? ECMD_HIDE : 0) + ECMD_OLDBUF, + || bufIsChanged(curwin->w_buffer)) ? ECMD_HIDE : 0) + ECMD_OLDBUF, curwin); if (tab_drop_empty_window && i == count - 1) { autocmd_no_enter++; } - if (use_firstwin) { + if (aall->use_firstwin) { autocmd_no_leave++; } - use_firstwin = false; + aall->use_firstwin = false; } os_breakcheck(); // When ":tab" was used open a new tab for a new window repeatedly. - if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) { + if (aall->had_tab > 0 && tabpage_index(NULL) <= p_tpm) { cmdmod.cmod_tab = 9999; } } +} + +/// do_arg_all(): Open up to 'count' windows, one for each argument. +/// +/// @param forceit hide buffers in current windows +/// @param keep_tabs keep current tabs, for ":tab drop file" +static void do_arg_all(int count, int forceit, int keep_tabs) +{ + win_T *last_curwin; + tabpage_T *last_curtab; + bool prev_arglist_locked = arglist_locked; + + assert(firstwin != NULL); // satisfy coverity + + if (cmdwin_type != 0) { + emsg(_(e_cmdwin)); + return; + } + if (ARGCOUNT <= 0) { + // Don't give an error message. We don't want it when the ":all" + // command is in the .vimrc. + return; + } + setpcmark(); + + arg_all_state_T aall = { + .use_firstwin = false, + .had_tab = cmdmod.cmod_tab, + .new_curwin = NULL, + .new_curtab = NULL, + .forceit = forceit, + .keep_tabs = keep_tabs, + .opened_len = ARGCOUNT, + .opened = xcalloc((size_t)ARGCOUNT, 1), + }; + + // Autocommands may do anything to the argument list. Make sure it's not + // freed while we are working here by "locking" it. We still have to + // watch out for its size to be changed. + aall.alist = curwin->w_alist; + aall.alist->al_refcount++; + arglist_locked = true; + + // Try closing all windows that are not in the argument list. + // Also close windows that are not full width; + // When 'hidden' or "forceit" set the buffer becomes hidden. + // Windows that have a changed buffer and can't be hidden won't be closed. + // When the ":tab" modifier was used do this for all tab pages. + arg_all_close_unused_windows(&aall); + + // Open a window for files in the argument list that don't have one. + // ARGCOUNT may change while doing this, because of autocommands. + if (count > aall.opened_len || count <= 0) { + count = aall.opened_len; + } + + // Don't execute Win/Buf Enter/Leave autocommands here. + autocmd_no_enter++; + autocmd_no_leave++; + last_curwin = curwin; + last_curtab = curtab; + win_enter(lastwin, false); + + // Open up to "count" windows. + arg_all_open_windows(&aall, count); // Remove the "lock" on the argument list. - alist_unlink(alist); + alist_unlink(aall.alist); + arglist_locked = prev_arglist_locked; autocmd_no_enter--; + // restore last referenced tabpage's curwin - if (last_curtab != new_curtab) { + if (last_curtab != aall.new_curtab) { if (valid_tabpage(last_curtab)) { goto_tabpage_tp(last_curtab, true, true); } @@ -996,15 +1113,15 @@ static void do_arg_all(int count, int forceit, int keep_tabs) } } // to window with first arg - if (valid_tabpage(new_curtab)) { - goto_tabpage_tp(new_curtab, true, true); + if (valid_tabpage(aall.new_curtab)) { + goto_tabpage_tp(aall.new_curtab, true, true); } - if (win_valid(new_curwin)) { - win_enter(new_curwin, false); + if (win_valid(aall.new_curwin)) { + win_enter(aall.new_curwin, false); } autocmd_no_leave--; - xfree(opened); + xfree(aall.opened); } /// ":all" and ":sall". @@ -1074,7 +1191,7 @@ char *arg_all(void) } /// "argc([window id])" function -void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_argc(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type == VAR_UNKNOWN) { // use the current window @@ -1095,13 +1212,13 @@ void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "argidx()" function -void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_argidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = curwin->w_arg_idx; } /// "arglistid()" function -void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_arglistid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; win_T *wp = find_tabwin(&argvars[0], &argvars[1]); @@ -1123,36 +1240,38 @@ static void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rett } /// "argv(nr)" function -void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_argv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { aentry_T *arglist = NULL; int argcount = -1; - if (argvars[0].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type == VAR_UNKNOWN) { - arglist = ARGLIST; - argcount = ARGCOUNT; - } else if (argvars[1].v_type == VAR_NUMBER - && tv_get_number(&argvars[1]) == -1) { - arglist = GARGLIST; - argcount = GARGCOUNT; - } else { - win_T *wp = find_win_by_nr_or_id(&argvars[1]); - if (wp != NULL) { - // Use the argument list of the specified window - arglist = WARGLIST(wp); - argcount = WARGCOUNT(wp); - } - } - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - int idx = (int)tv_get_number_chk(&argvars[0], NULL); - if (arglist != NULL && idx >= 0 && idx < argcount) { - rettv->vval.v_string = xstrdup((const char *)alist_name(&arglist[idx])); - } else if (idx == -1) { - get_arglist_as_rettv(arglist, argcount, rettv); - } - } else { + if (argvars[0].v_type == VAR_UNKNOWN) { get_arglist_as_rettv(ARGLIST, ARGCOUNT, rettv); + return; + } + + if (argvars[1].v_type == VAR_UNKNOWN) { + arglist = ARGLIST; + argcount = ARGCOUNT; + } else if (argvars[1].v_type == VAR_NUMBER + && tv_get_number(&argvars[1]) == -1) { + arglist = GARGLIST; + argcount = GARGCOUNT; + } else { + win_T *wp = find_win_by_nr_or_id(&argvars[1]); + if (wp != NULL) { + // Use the argument list of the specified window + arglist = WARGLIST(wp); + argcount = WARGCOUNT(wp); + } + } + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + int idx = (int)tv_get_number_chk(&argvars[0], NULL); + if (arglist != NULL && idx >= 0 && idx < argcount) { + rettv->vval.v_string = xstrdup((const char *)alist_name(&arglist[idx])); + } else if (idx == -1) { + get_arglist_as_rettv(arglist, argcount, rettv); } } diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index b1241166bf..96d6fe214a 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -84,31 +84,31 @@ # define PATHSEPSTR "/" #endif -static inline bool ascii_iswhite(int) +static inline bool ascii_iswhite(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_iswhite_or_nul(int) +static inline bool ascii_iswhite_or_nul(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isdigit(int) +static inline bool ascii_isdigit(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isxdigit(int) +static inline bool ascii_isxdigit(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isident(int) +static inline bool ascii_isident(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isbdigit(int) +static inline bool ascii_isbdigit(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ascii_isspace(int) +static inline bool ascii_isspace(int c) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; diff --git a/src/nvim/assert.h b/src/nvim/assert.h index ad92d9a2af..1941f4c33c 100644 --- a/src/nvim/assert.h +++ b/src/nvim/assert.h @@ -61,7 +61,7 @@ # define STATIC_ASSERT_STATEMENT(cond, msg) _Static_assert(cond, msg) // if we're dealing with gcc >= 4.6 in C99 mode, we can still use // _Static_assert but we need to suppress warnings, this is pretty ugly. -#elif (!defined(__clang__) && !defined(__INTEL_COMPILER)) && \ +#elif (!defined(__clang__) && !defined(__INTEL_COMPILER)) && /* NOLINT(whitespace/parens)*/ \ (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) # define STATIC_ASSERT_STATEMENT(cond, msg) _Static_assert(cond, msg) diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 93a870fe04..a75ee3bbd5 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -108,6 +108,7 @@ return { 'TextChanged', -- text was modified 'TextChangedI', -- text was modified in Insert mode(no popup) 'TextChangedP', -- text was modified in Insert mode(popup) + 'TextChangedT', -- text was modified in Terminal mode 'TextYankPost', -- after a yank or delete was done (y, d, c) 'UIEnter', -- after UI attaches 'UILeave', -- after UI detaches @@ -122,13 +123,26 @@ return { 'WinEnter', -- after entering a window 'WinLeave', -- before leaving a window 'WinNew', -- when entering a new window - 'WinScrolled', -- after scrolling a window + 'WinResized', -- after a window was resized + 'WinScrolled', -- after a window was scrolled or resized }, aliases = { - BufCreate = 'BufAdd', - BufRead = 'BufReadPost', - BufWrite = 'BufWritePre', - FileEncoding = 'EncodingChanged', + { + 'BufCreate', + 'BufAdd' + }, + { + 'BufRead', + 'BufReadPost' + }, + { + 'BufWrite', + 'BufWritePre' + }, + { + 'FileEncoding', + 'EncodingChanged' + }, }, -- List of nvim-specific events or aliases for the purpose of generating -- syntax file diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 579c6c029f..01ebdfdafe 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -2,9 +2,13 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com // autocmd.c: Autocommand related functions -#include <signal.h> -#include "lauxlib.h" +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" @@ -12,26 +16,43 @@ #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" +#include "nvim/event/defs.h" +#include "nvim/event/loop.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" +#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/grid.h" +#include "nvim/hashtab.h" +#include "nvim/highlight_defs.h" #include "nvim/insexpand.h" #include "nvim/lua/executor.h" +#include "nvim/main.h" #include "nvim/map.h" +#include "nvim/memline_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/optionstr.h" #include "nvim/os/input.h" +#include "nvim/os/os.h" +#include "nvim/os/time.h" +#include "nvim/path.h" #include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/runtime.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" +#include "nvim/strings.h" +#include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -266,7 +287,7 @@ static void au_show_for_event(int group, event_T event, char *pat) // normalize pat into standard "<buffer>#N" form aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, aupat_get_buflocal_nr(pat, patlen)); pat = (char *)buflocal_pat; - patlen = (int)STRLEN(buflocal_pat); + patlen = (int)strlen(buflocal_pat); } assert(*pat != NUL); @@ -283,7 +304,7 @@ static void au_show_for_event(int group, event_T event, char *pat) // all buffer-local patterns. if ((group == AUGROUP_ALL || ap->group == group) && ap->patlen == patlen - && STRNCMP(pat, ap->pat, patlen) == 0) { + && strncmp(pat, ap->pat, (size_t)patlen) == 0) { // Show autocmd's for this autopat, or buflocals <buffer=X> aupat_show(ap, event, previous_group); previous_group = ap->group; @@ -478,34 +499,37 @@ void augroup_del(char *name, bool stupid_legacy_mode) int i = augroup_find(name); if (i == AUGROUP_ERROR) { // the group doesn't exist semsg(_("E367: No such group: \"%s\""), name); - } else if (i == current_augroup) { + return; + } + if (i == current_augroup) { emsg(_("E936: Cannot delete the current group")); - } else { - if (stupid_legacy_mode) { - FOR_ALL_AUEVENTS(event) { - FOR_ALL_AUPATS_IN_EVENT(event, ap) { - if (ap->group == i && ap->pat != NULL) { - give_warning(_("W19: Deleting augroup that is still in use"), true); - map_put(String, int)(&map_augroup_name_to_id, cstr_as_string(name), AUGROUP_DELETED); - augroup_map_del(ap->group, NULL); - return; - } + return; + } + + if (stupid_legacy_mode) { + FOR_ALL_AUEVENTS(event) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (ap->group == i && ap->pat != NULL) { + give_warning(_("W19: Deleting augroup that is still in use"), true); + map_put(String, int)(&map_augroup_name_to_id, cstr_as_string(name), AUGROUP_DELETED); + augroup_map_del(ap->group, NULL); + return; } } - } else { - FOR_ALL_AUEVENTS(event) { - FOR_ALL_AUPATS_IN_EVENT(event, ap) { - if (ap->group == i) { - aupat_del(ap); - } + } + } else { + FOR_ALL_AUEVENTS(event) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (ap->group == i) { + aupat_del(ap); } } } - - // Remove the group because it's not currently in use. - augroup_map_del(i, name); - au_cleanup(); } + + // Remove the group because it's not currently in use. + augroup_map_del(i, name); + au_cleanup(); } /// Find the ID of an autocmd group name. @@ -635,9 +659,22 @@ void free_all_autocmds(void) api_free_string(name); }) map_destroy(int, String)(&map_augroup_id_to_name); + + // aucmd_win[] is freed in win_free_all() } #endif +/// Return true if "win" is an active entry in aucmd_win[]. +bool is_aucmd_win(win_T *win) +{ + for (int i = 0; i < AUCMD_WIN_COUNT; i++) { + if (aucmd_win[i].auc_win_used && aucmd_win[i].auc_win == win) { + return true; + } + } + return false; +} + // Return the event number for event name "start". // Return NUM_EVENTS if the event name was not found. // Return a pointer to the next event name in "end". @@ -686,7 +723,7 @@ const char *event_nr2name(event_T event) static bool event_ignored(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - char *p = (char *)p_ei; + char *p = p_ei; while (*p != NUL) { if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) { @@ -703,7 +740,7 @@ static bool event_ignored(event_T event) // Return OK when the contents of p_ei is valid, FAIL otherwise. int check_ei(void) { - char *p = (char *)p_ei; + char *p = p_ei; while (*p) { if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) { @@ -724,8 +761,8 @@ int check_ei(void) // Returns the old value of 'eventignore' in allocated memory. char *au_event_disable(char *what) { - char *save_ei = (char *)vim_strsave(p_ei); - char *new_ei = (char *)vim_strnsave(p_ei, STRLEN(p_ei) + STRLEN(what)); + char *save_ei = xstrdup(p_ei); + char *new_ei = xstrnsave(p_ei, strlen(p_ei) + strlen(what)); if (*what == ',' && *p_ei == NUL) { STRCPY(new_ei, what + 1); } else { @@ -733,7 +770,6 @@ char *au_event_disable(char *what) } set_string_option_direct("ei", -1, new_ei, OPT_FREE, SID_NONE); xfree(new_ei); - return save_ei; } @@ -777,7 +813,7 @@ void au_event_restore(char *old_ei) // :autocmd * *.c show all autocommands for *.c files. // // Mostly a {group} argument can optionally appear before <event>. -void do_autocmd(char *arg_in, int forceit) +void do_autocmd(exarg_T *eap, char *arg_in, int forceit) { char *arg = arg_in; char *envpat = NULL; @@ -788,6 +824,7 @@ void do_autocmd(char *arg_in, int forceit) int group; if (*arg == '|') { + eap->nextcmd = arg + 1; arg = ""; group = AUGROUP_ALL; // no argument, use all groups } else { @@ -804,6 +841,7 @@ void do_autocmd(char *arg_in, int forceit) pat = skipwhite(pat); if (*pat == '|') { + eap->nextcmd = pat + 1; pat = ""; cmd = ""; } else { @@ -837,13 +875,15 @@ void do_autocmd(char *arg_in, int forceit) bool invalid_flags = false; for (size_t i = 0; i < 2; i++) { - if (*cmd != NUL) { - invalid_flags |= arg_autocmd_flag_get(&once, &cmd, "++once", 6); - invalid_flags |= arg_autocmd_flag_get(&nested, &cmd, "++nested", 8); - - // Check the deprecated "nested" flag. - invalid_flags |= arg_autocmd_flag_get(&nested, &cmd, "nested", 6); + if (*cmd == NUL) { + continue; } + + invalid_flags |= arg_autocmd_flag_get(&once, &cmd, "++once", 6); + invalid_flags |= arg_autocmd_flag_get(&nested, &cmd, "++nested", 8); + + // Check the deprecated "nested" flag. + invalid_flags |= arg_autocmd_flag_get(&nested, &cmd, "nested", 6); } if (invalid_flags) { @@ -877,7 +917,7 @@ void do_autocmd(char *arg_in, int forceit) } } else { if (*arg == '*' || *arg == NUL || *arg == '|') { - if (!forceit && *cmd != NUL) { + if (*cmd != NUL) { emsg(_(e_cannot_define_autocommands_for_all_events)); } else { do_all_autocmd_events(pat, once, nested, cmd, forceit, group); @@ -957,7 +997,7 @@ int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd, aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, buflocal_nr); pat = buflocal_pat; - patlen = (int)STRLEN(buflocal_pat); + patlen = (int)strlen(buflocal_pat); } if (delete) { @@ -975,7 +1015,7 @@ int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd, // all buffer-local patterns. if (ap->group == findgroup && ap->patlen == patlen - && STRNCMP(pat, ap->pat, patlen) == 0) { + && strncmp(pat, ap->pat, (size_t)patlen) == 0) { // Remove existing autocommands. // If adding any new autocmd's for this AutoPat, don't // delete the pattern from the autopat list, append to @@ -1018,7 +1058,7 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group int findgroup; char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>" - if (patlen > (int)STRLEN(pat)) { + if (patlen > (int)strlen(pat)) { return FAIL; } @@ -1039,7 +1079,7 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, buflocal_nr); pat = buflocal_pat; - patlen = (int)STRLEN(buflocal_pat); + patlen = (int)strlen(buflocal_pat); } // always goes at or after the last one, so start at the end. @@ -1059,7 +1099,7 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group // all buffer-local patterns. if (ap->group == findgroup && ap->patlen == patlen - && STRNCMP(pat, ap->pat, patlen) == 0) { + && strncmp(pat, ap->pat, (size_t)patlen) == 0) { if (ap->next == NULL) { // Add autocmd to this autopat, if it's the last one. break; @@ -1114,13 +1154,19 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group curwin->w_last_cursormoved = curwin->w_cursor; } - // Initialize the fields checked by the WinScrolled trigger to - // stop it from firing right after the first autocmd is defined. - if (event == EVENT_WINSCROLLED && !has_event(EVENT_WINSCROLLED)) { - curwin->w_last_topline = curwin->w_topline; - curwin->w_last_leftcol = curwin->w_leftcol; - curwin->w_last_width = curwin->w_width; - curwin->w_last_height = curwin->w_height; + // Initialize the fields checked by the WinScrolled and + // WinResized trigger to prevent them from firing right after + // the first autocmd is defined. + if ((event == EVENT_WINSCROLLED || event == EVENT_WINRESIZED) + && !(has_event(EVENT_WINSCROLLED) || has_event(EVENT_WINRESIZED))) { + tabpage_T *save_curtab = curtab; + FOR_ALL_TABS(tp) { + unuse_tabpage(curtab); + use_tabpage(tp); + snapshot_windows_scroll_size(); + } + unuse_tabpage(curtab); + use_tabpage(save_curtab); } ap->cmds = NULL; @@ -1192,7 +1238,7 @@ size_t aucmd_pattern_length(char *pat) return (size_t)(endpat - pat); } - return STRLEN(pat); + return strlen(pat); } char *aucmd_next_pattern(char *pat, size_t patlen) @@ -1274,6 +1320,7 @@ void ex_doautoall(exarg_T *eap) if (buf->b_ml.ml_mfp == NULL || buf == curbuf) { continue; } + // Find a window for this buffer and save some values. aucmd_prepbuf(&aco, buf); set_bufref(&bufref, buf); @@ -1285,7 +1332,7 @@ void ex_doautoall(exarg_T *eap) // Execute the modeline settings, but don't set window-local // options if we are using the current window for another // buffer. - do_modelines(curwin == aucmd_win ? OPT_NOWIN : 0); + do_modelines(is_aucmd_win(curwin) ? OPT_NOWIN : 0); } // restore the current window @@ -1315,7 +1362,7 @@ void ex_doautoall(exarg_T *eap) bool check_nomodeline(char **argp) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - if (STRNCMP(*argp, "<nomodeline>", 12) == 0) { + if (strncmp(*argp, "<nomodeline>", 12) == 0) { *argp = skipwhite(*argp + 12); return false; } @@ -1324,7 +1371,7 @@ bool check_nomodeline(char **argp) /// Prepare for executing autocommands for (hidden) buffer `buf`. /// If the current buffer is not in any visible window, put it in a temporary -/// floating window `aucmd_win`. +/// floating window using an entry in `aucmd_win[]`. /// Set `curbuf` and `curwin` to match `buf`. /// /// @param aco structure to save values in @@ -1347,15 +1394,29 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) } } - // Allocate the `aucmd_win` dummy floating window. - if (win == NULL && aucmd_win == NULL) { - win_alloc_aucmd_win(); - need_append = false; - } - if (win == NULL && aucmd_win_used) { - // Strange recursive autocommand, fall back to using the current - // window. Expect a few side effects... - win = curwin; + // Allocate a window when needed. + win_T *auc_win = NULL; + int auc_idx = AUCMD_WIN_COUNT; + if (win == NULL) { + for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; auc_idx++) { + if (!aucmd_win[auc_idx].auc_win_used) { + break; + } + } + + if (auc_idx == AUCMD_WIN_COUNT) { + kv_push(aucmd_win_vec, ((aucmdwin_T){ + .auc_win = NULL, + .auc_win_used = false, + })); + } + + if (aucmd_win[auc_idx].auc_win == NULL) { + win_alloc_aucmd_win(auc_idx); + need_append = false; + } + auc_win = aucmd_win[auc_idx].auc_win; + aucmd_win[auc_idx].auc_win_used = true; } aco->save_curwin_handle = curwin->handle; @@ -1365,42 +1426,41 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) // There is a window for "buf" in the current tab page, make it the // curwin. This is preferred, it has the least side effects (esp. if // "buf" is curbuf). - aco->use_aucmd_win = false; + aco->use_aucmd_win_idx = -1; curwin = win; } else { - // There is no window for "buf", use "aucmd_win". To minimize the side + // There is no window for "buf", use "auc_win". To minimize the side // effects, insert it in the current tab page. // Anything related to a window (e.g., setting folds) may have // unexpected results. - aco->use_aucmd_win = true; - aucmd_win_used = true; - aucmd_win->w_buffer = buf; - aucmd_win->w_s = &buf->b_s; + aco->use_aucmd_win_idx = auc_idx; + auc_win->w_buffer = buf; + auc_win->w_s = &buf->b_s; buf->b_nwindows++; - win_init_empty(aucmd_win); // set cursor and topline to safe values + win_init_empty(auc_win); // set cursor and topline to safe values // Make sure w_localdir and globaldir are NULL to avoid a chdir() in // win_enter_ext(). - XFREE_CLEAR(aucmd_win->w_localdir); + XFREE_CLEAR(auc_win->w_localdir); aco->globaldir = globaldir; globaldir = NULL; block_autocmds(); // We don't want BufEnter/WinEnter autocommands. if (need_append) { - win_append(lastwin, aucmd_win); - pmap_put(handle_T)(&window_handles, aucmd_win->handle, aucmd_win); - win_config_float(aucmd_win, aucmd_win->w_float_config); + win_append(lastwin, auc_win); + pmap_put(handle_T)(&window_handles, auc_win->handle, auc_win); + win_config_float(auc_win, auc_win->w_float_config); } // Prevent chdir() call in win_enter_ext(), through do_autochdir() int save_acd = p_acd; p_acd = false; // no redrawing and don't set the window title RedrawingDisabled++; - win_enter(aucmd_win, false); + win_enter(auc_win, false); RedrawingDisabled--; p_acd = save_acd; unblock_autocmds(); - curwin = aucmd_win; + curwin = auc_win; } curbuf = buf; aco->new_curwin_handle = curwin->handle; @@ -1417,18 +1477,20 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) /// @param aco structure holding saved values void aucmd_restbuf(aco_save_T *aco) { - if (aco->use_aucmd_win) { + if (aco->use_aucmd_win_idx >= 0) { + win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win; + curbuf->b_nwindows--; - // Find "aucmd_win", it can't be closed, but it may be in another tab page. + // Find "awp", it can't be closed, but it may be in another tab page. // Do not trigger autocommands here. block_autocmds(); - if (curwin != aucmd_win) { + if (curwin != awp) { FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp == aucmd_win) { + if (wp == awp) { if (tp != curtab) { goto_tabpage_tp(tp, true, true); } - win_goto(aucmd_win); + win_goto(awp); goto win_found; } } @@ -1443,8 +1505,9 @@ win_found: grid_free(&curwin->w_grid_alloc); } - aucmd_win_used = false; - last_status(false); // may need to remove last status line + // The window is marked as not used, but it is not freed, it can be + // used again. + aucmd_win[aco->use_aucmd_win_idx].auc_win_used = false; if (!valid_tabpage_win(curtab)) { // no valid window in current tabpage @@ -1465,8 +1528,8 @@ win_found: entering_window(curwin); prevwin = win_find_by_handle(aco->save_prevwin_handle); - vars_clear(&aucmd_win->w_vars->dv_hashtab); // free all w: variables - hash_init(&aucmd_win->w_vars->dv_hashtab); // re-use the hashtab + vars_clear(&awp->w_vars->dv_hashtab); // free all w: variables + hash_init(&awp->w_vars->dv_hashtab); // re-use the hashtab xfree(globaldir); globaldir = aco->globaldir; @@ -1720,9 +1783,9 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force fname = NULL; } else { if (event == EVENT_SYNTAX) { - fname = (char *)buf->b_p_syn; + fname = buf->b_p_syn; } else if (event == EVENT_FILETYPE) { - fname = (char *)buf->b_p_ft; + fname = buf->b_p_ft; } else { if (buf->b_sfname != NULL) { sfname = xstrdup(buf->b_sfname); @@ -1738,17 +1801,18 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force sfname = xstrdup(fname); // Don't try expanding the following events. if (event == EVENT_CMDLINECHANGED || event == EVENT_CMDLINEENTER - || event == EVENT_CMDLINELEAVE || event == EVENT_CMDWINENTER - || event == EVENT_CMDWINLEAVE || event == EVENT_CMDUNDEFINED + || event == EVENT_CMDLINELEAVE || event == EVENT_CMDUNDEFINED + || event == EVENT_CMDWINENTER || event == EVENT_CMDWINLEAVE || event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE || event == EVENT_DIRCHANGED || event == EVENT_DIRCHANGEDPRE || event == EVENT_FILETYPE || event == EVENT_FUNCUNDEFINED - || event == EVENT_MODECHANGED || event == EVENT_OPTIONSET - || event == EVENT_QUICKFIXCMDPOST || event == EVENT_QUICKFIXCMDPRE - || event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING - || event == EVENT_SYNTAX || event == EVENT_SIGNAL - || event == EVENT_TABCLOSED || event == EVENT_USER - || event == EVENT_WINCLOSED || event == EVENT_WINSCROLLED) { + || event == EVENT_MENUPOPUP || event == EVENT_MODECHANGED + || event == EVENT_OPTIONSET || event == EVENT_QUICKFIXCMDPOST + || event == EVENT_QUICKFIXCMDPRE || event == EVENT_REMOTEREPLY + || event == EVENT_SIGNAL || event == EVENT_SPELLFILEMISSING + || event == EVENT_SYNTAX || event == EVENT_TABCLOSED + || event == EVENT_USER || event == EVENT_WINCLOSED + || event == EVENT_WINRESIZED || event == EVENT_WINSCROLLED) { fname = xstrdup(fname); } else { fname = FullName_save(fname, false); @@ -1811,16 +1875,15 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force char *tail = path_tail(fname); // Find first autocommand that matches - AutoPatCmd patcmd; - patcmd.curpat = first_autopat[(int)event]; - patcmd.nextcmd = NULL; - patcmd.group = group; - patcmd.fname = fname; - patcmd.sfname = sfname; - patcmd.tail = tail; - patcmd.event = event; - patcmd.arg_bufnr = autocmd_bufnr; - patcmd.next = NULL; + AutoPatCmd patcmd = { + .curpat = first_autopat[(int)event], + .group = group, + .fname = fname, + .sfname = sfname, + .tail = tail, + .event = event, + .arg_bufnr = autocmd_bufnr, + }; auto_next_pat(&patcmd, false); // found one, start executing the autocommands @@ -1984,9 +2047,12 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last) AutoPat *ap; AutoCmd *cp; char *s; - char **const sourcing_namep = &SOURCING_NAME; - XFREE_CLEAR(*sourcing_namep); + estack_T *const entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1; + + // Clear the exestack entry for this ETYPE_AUCMD entry. + XFREE_CLEAR(entry->es_name); + entry->es_info.aucmd = NULL; for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) { apc->curpat = NULL; @@ -2009,16 +2075,20 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last) s = _("%s Autocommands for \"%s\""); const size_t sourcing_name_len - = (STRLEN(s) + strlen(name) + (size_t)ap->patlen + 1); + = (strlen(s) + strlen(name) + (size_t)ap->patlen + 1); - *sourcing_namep = xmalloc(sourcing_name_len); - snprintf(*sourcing_namep, sourcing_name_len, s, name, ap->pat); + char *const namep = xmalloc(sourcing_name_len); + snprintf(namep, sourcing_name_len, s, name, ap->pat); if (p_verbose >= 8) { verbose_enter(); - smsg(_("Executing %s"), *sourcing_namep); + smsg(_("Executing %s"), namep); verbose_leave(); } + // Update the exestack entry for this autocmd. + entry->es_name = namep; + entry->es_info.aucmd = apc; + apc->curpat = ap; apc->nextcmd = ap->cmds; // mark last command @@ -2051,7 +2121,7 @@ static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc) PUT(data, "buf", INTEGER_OBJ(autocmd_bufnr)); if (apc->data) { - PUT(data, "data", copy_object(*apc->data)); + PUT(data, "data", copy_object(*apc->data, NULL)); } int group = apc->curpat->group; @@ -2150,6 +2220,7 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat) // lua code, so that it works properly autocmd_nested = ac->nested; current_sctx = ac->script_ctx; + acp->script_ctx = current_sctx; if (ac->exec.type == CALLABLE_CB) { if (call_autocmd_callback(ac, acp)) { @@ -2206,7 +2277,7 @@ bool has_autocmd(event_T event, char *sfname, buf_T *buf) #ifdef BACKSLASH_IN_FILENAME // Replace all backslashes with forward slashes. This makes the // autocommand patterns portable between Unix and Windows. - sfname = vim_strsave(sfname); + sfname = xstrdup(sfname); forward_slash(sfname); forward_slash(fname); #endif @@ -2388,7 +2459,7 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT } // if pattern is "<buffer>", special handling is needed which uses curbuf - // for pattern "<buffer=N>, FNAMECMP() will work fine + // for pattern "<buffer=N>, path_fnamecmp() will work fine if (pattern != NULL && STRICMP(pattern, "<buffer>") == 0) { buflocal_buf = curbuf; } @@ -2396,12 +2467,12 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT // Check if there is an autocommand with the given pattern. for (; ap != NULL; ap = ap->next) { // only use a pattern when it has not been removed and has commands. - // For buffer-local autocommands, FNAMECMP() works fine. + // For buffer-local autocommands, path_fnamecmp() works fine. if (ap->pat != NULL && ap->cmds != NULL && (group == AUGROUP_ALL || ap->group == group) && (pattern == NULL || (buflocal_buf == NULL - ? FNAMECMP(ap->pat, pattern) == 0 + ? path_fnamecmp(ap->pat, pattern) == 0 : ap->buflocal_nr == buflocal_buf->b_fnum))) { retval = true; break; @@ -2418,7 +2489,7 @@ bool aupat_is_buflocal(char *pat, int patlen) FUNC_ATTR_PURE { return patlen >= 8 - && STRNCMP(pat, "<buffer", 7) == 0 + && strncmp(pat, "<buffer", 7) == 0 && (pat)[patlen - 1] == '>'; } @@ -2605,26 +2676,27 @@ static int arg_augroup_get(char **argp) { char *p; char *arg = *argp; - int group = AUGROUP_ALL; for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) {} - if (p > arg) { - char *group_name = xstrnsave(arg, (size_t)(p - arg)); - group = augroup_find(group_name); - if (group == AUGROUP_ERROR) { - group = AUGROUP_ALL; // no match, use all groups - } else { - *argp = skipwhite(p); // match, skip over group name - } - xfree(group_name); + if (p <= arg) { + return AUGROUP_ALL; + } + + char *group_name = xstrnsave(arg, (size_t)(p - arg)); + int group = augroup_find(group_name); + if (group == AUGROUP_ERROR) { + group = AUGROUP_ALL; // no match, use all groups + } else { + *argp = skipwhite(p); // match, skip over group name } + xfree(group_name); return group; } /// Handles grabbing arguments from `:autocmd` such as ++once and ++nested static bool arg_autocmd_flag_get(bool *flag, char **cmd_ptr, char *pattern, int len) { - if (STRNCMP(*cmd_ptr, pattern, len) == 0 && ascii_iswhite((*cmd_ptr)[len])) { + if (strncmp(*cmd_ptr, pattern, (size_t)len) == 0 && ascii_iswhite((*cmd_ptr)[len])) { if (*flag) { semsg(_(e_duparg2), pattern); return true; @@ -2661,22 +2733,7 @@ void do_autocmd_uienter(uint64_t chanid, bool attached) // FocusGained -static void focusgained_event(void **argv) -{ - bool *gainedp = argv[0]; - do_autocmd_focusgained(*gainedp); - xfree(gainedp); -} - -void autocmd_schedule_focusgained(bool gained) -{ - bool *gainedp = xmalloc(sizeof(*gainedp)); - *gainedp = gained; - loop_schedule_deferred(&main_loop, - event_create(focusgained_event, 1, gainedp)); -} - -static void do_autocmd_focusgained(bool gained) +void do_autocmd_focusgained(bool gained) { static bool recursive = false; static Timestamp last_time = (time_t)0; @@ -2708,7 +2765,7 @@ static void do_autocmd_focusgained(bool gained) redrawcmdline(); } else if ((State & MODE_NORMAL) || (State & MODE_INSERT)) { if (must_redraw != 0) { - update_screen(0); + update_screen(); } setcursor(); diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index d559d8c3d2..791b589167 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -1,8 +1,20 @@ #ifndef NVIM_AUTOCMD_H #define NVIM_AUTOCMD_H +#include <stdbool.h> +#include <stdint.h> + +#include "nvim/api/private/defs.h" #include "nvim/buffer_defs.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/macros.h" +#include "nvim/regexp_defs.h" +#include "nvim/types.h" + +struct AutoCmd_S; +struct AutoPatCmd_S; +struct AutoPat_S; // event_T definition #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -13,7 +25,7 @@ // not the current buffer. typedef struct { buf_T *save_curbuf; ///< saved curbuf - bool use_aucmd_win; ///< using aucmd_win + int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0 handle_T save_curwin_handle; ///< ID of saved curwin handle_T new_curwin_handle; ///< ID of new curwin handle_T save_prevwin_handle; ///< ID of saved prevwin @@ -29,7 +41,7 @@ struct AutoCmd_S { bool nested; // If autocommands nest here bool last; // last command in list int64_t id; // ID used for uniquely tracking an autocmd. - sctx_T script_ctx; // script context where defined + sctx_T script_ctx; // script context where it is defined char *desc; // Description for the autocmd. AutoCmd *next; // Next AutoCmd in list }; @@ -59,6 +71,7 @@ struct AutoPatCmd_S { char *sfname; // sfname to match with char *tail; // tail of fname event_T event; // current event + sctx_T script_ctx; // script context where it is defined int arg_bufnr; // initially equal to <abuf>, set to zero when buf is deleted Object *data; // arbitrary data AutoPatCmd *next; // chain of active apc-s for auto-invalidation diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index ab5b32bf3e..5dcb10751f 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -20,10 +20,16 @@ // #include <assert.h> +#include <ctype.h> #include <inttypes.h> #include <stdbool.h> +#include <stdlib.h> #include <string.h> +#include <sys/stat.h> +#include <time.h> +#include "auto/config.h" +#include "klib/kvec.h" #include "nvim/api/private/helpers.h" #include "nvim/arglist.h" #include "nvim/ascii.h" @@ -44,6 +50,7 @@ #include "nvim/eval/vars.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" @@ -53,21 +60,25 @@ #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/hashtab.h" #include "nvim/help.h" -#include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/main.h" +#include "nvim/map.h" #include "nvim/mapping.h" #include "nvim/mark.h" -#include "nvim/mark_defs.h" #include "nvim/mbyte.h" +#include "nvim/memline_defs.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" @@ -76,11 +87,15 @@ #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/runtime.h" +#include "nvim/screen.h" +#include "nvim/search.h" #include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" +#include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/usercmd.h" @@ -93,7 +108,8 @@ #endif static char *e_auabort = N_("E855: Autocommands caused command to abort"); -static char *e_buflocked = N_("E937: Attempt to delete a buffer that is in use"); +static char e_attempt_to_delete_buffer_that_is_in_use_str[] + = N_("E937: Attempt to delete a buffer that is in use: %s"); // Number of times free_buffer() was called. static int buf_free_count = 0; @@ -160,16 +176,33 @@ static int read_buffer(int read_stdin, exarg_T *eap, int flags) return retval; } +/// Ensure buffer "buf" is loaded. Does not trigger the swap-exists action. +void buffer_ensure_loaded(buf_T *buf) +{ + if (buf->b_ml.ml_mfp != NULL) { + return; + } + + aco_save_T aco; + + // Make sure the buffer is in a window. + aucmd_prepbuf(&aco, buf); + swap_exists_action = SEA_NONE; + open_buffer(false, NULL, 0); + aucmd_restbuf(&aco); +} + /// Open current buffer, that is: open the memfile and read the file into /// memory. /// /// @param read_stdin read file from stdin /// @param eap for forced 'ff' and 'fenc' or NULL -/// @param flags extra flags for readfile() +/// @param flags_arg extra flags for readfile() /// /// @return FAIL for failure, OK otherwise. -int open_buffer(int read_stdin, exarg_T *eap, int flags) +int open_buffer(int read_stdin, exarg_T *eap, int flags_arg) { + int flags = flags_arg; int retval = OK; bufref_T old_curbuf; long old_tw = curbuf->b_p_tw; @@ -224,6 +257,13 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags) // mark cursor position as being invalid curwin->w_valid = 0; + // A buffer without an actual file should not use the buffer name to read a + // file. + if (bt_nofileread(curbuf)) { + flags |= READ_NOFILE; + } + + // Read the file if there is one. if (curbuf->b_ffname != NULL) { #ifdef UNIX int save_bin = curbuf->b_p_bin; @@ -320,8 +360,9 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags) } apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, false, curbuf, &retval); + // if (retval != OK) { if (retval == FAIL) { - return FAIL; + return retval; } // The autocommands may have changed the current buffer. Apply the @@ -329,7 +370,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags) if (bufref_valid(&old_curbuf) && old_curbuf.br_buf->b_ml.ml_mfp != NULL) { aco_save_T aco; - // Go to the buffer that was opened. + // Go to the buffer that was opened, make sure it is in a window. aucmd_prepbuf(&aco, old_curbuf.br_buf); do_modelines(0); curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED); @@ -393,6 +434,29 @@ bool buf_valid(buf_T *buf) return false; } +/// Return true when buffer "buf" can be unloaded. +/// Give an error message and return false when the buffer is locked or the +/// screen is being redrawn and the buffer is in a window. +static bool can_unload_buffer(buf_T *buf) +{ + bool can_unload = !buf->b_locked; + + if (can_unload && updating_screen) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf) { + can_unload = false; + break; + } + } + } + if (!can_unload) { + char *fname = buf->b_fname != NULL ? buf->b_fname : buf->b_ffname; + semsg(_(e_attempt_to_delete_buffer_that_is_in_use_str), + fname != NULL ? fname : "[No Name]"); + } + return can_unload; +} + /// Close the link to a buffer. /// /// @param win If not NULL, set b_last_cursor. @@ -450,8 +514,7 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i // Disallow deleting the buffer when it is locked (already being closed or // halfway a command that relies on it). Unloading is allowed. - if (buf->b_locked > 0 && (del_buf || wipe_buf)) { - emsg(_(e_buflocked)); + if ((del_buf || wipe_buf) && !can_unload_buffer(buf)) { return false; } @@ -658,6 +721,8 @@ void buf_clear_file(buf_T *buf) { buf->b_ml.ml_line_count = 1; unchanged(buf, true, true); + buf->b_p_eof = false; + buf->b_start_eof = false; buf->b_p_eol = true; buf->b_start_eol = true; buf->b_p_bomb = false; @@ -804,6 +869,18 @@ static void free_buffer(buf_T *buf) } } +/// Free the b_wininfo list for buffer "buf". +static void clear_wininfo(buf_T *buf) +{ + wininfo_T *wip; + + while (buf->b_wininfo != NULL) { + wip = buf->b_wininfo; + buf->b_wininfo = wip->wi_next; + free_wininfo(wip, buf); + } +} + /// Free stuff in the buffer for ":bdel" and when wiping out the buffer. /// /// @param buf Buffer pointer @@ -838,18 +915,6 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) buf_updates_unload(buf, false); } -/// Free the b_wininfo list for buffer "buf". -static void clear_wininfo(buf_T *buf) -{ - wininfo_T *wip; - - while (buf->b_wininfo != NULL) { - wip = buf->b_wininfo; - buf->b_wininfo = wip->wi_next; - free_wininfo(wip, buf); - } -} - /// Go to another buffer. Handles the result of the ATTENTION dialog. void goto_buffer(exarg_T *eap, int start, int dir, int count) { @@ -1026,16 +1091,16 @@ char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_b } else { STRCPY(IObuff, _("E517: No buffers were wiped out")); } - errormsg = (char *)IObuff; + errormsg = IObuff; } else if (deleted >= p_report) { if (command == DOBUF_UNLOAD) { - smsg(NGETTEXT("%d buffer unloaded", "%d buffers unloaded", (unsigned long)deleted), + smsg(NGETTEXT("%d buffer unloaded", "%d buffers unloaded", deleted), deleted); } else if (command == DOBUF_DEL) { - smsg(NGETTEXT("%d buffer deleted", "%d buffers deleted", (unsigned long)deleted), + smsg(NGETTEXT("%d buffer deleted", "%d buffers deleted", deleted), deleted); } else { - smsg(NGETTEXT("%d buffer wiped out", "%d buffers wiped out", (unsigned long)deleted), + smsg(NGETTEXT("%d buffer wiped out", "%d buffers wiped out", deleted), deleted); } } @@ -1193,13 +1258,17 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } return FAIL; } + if ((action == DOBUF_GOTO || action == DOBUF_SPLIT) && (buf->b_flags & BF_DUMMY)) { + // disallow navigating to the dummy buffer + semsg(_(e_nobufnr), count); + return FAIL; + } // delete buffer "buf" from memory and/or the list if (unload) { int forward; bufref_T bufref; - if (buf->b_locked) { - emsg(_(e_buflocked)); + if (!can_unload_buffer(buf)) { return FAIL; } set_bufref(&bufref, buf); @@ -1265,7 +1334,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) // Repeat this so long as we end up in a window with this buffer. while (buf == curbuf && !(curwin->w_closing || curwin->w_buffer->b_locked > 0) - && (lastwin == aucmd_win || !last_window(curwin))) { + && (is_aucmd_win(lastwin) || !last_window(curwin))) { if (win_close(curwin, false, false) == FAIL) { break; } @@ -1623,7 +1692,7 @@ void enter_buffer(buf_T *buf) } curbuf->b_last_used = time(NULL); - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } /// Change to the directory of the current buffer. @@ -1805,7 +1874,7 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags) pmap_put(handle_T)(&buffer_handles, buf->b_fnum, buf); if (top_file_num < 0) { // wrap around (may cause duplicates) emsg(_("W14: Warning: List of file names overflow")); - if (emsg_silent == 0) { + if (emsg_silent == 0 && !in_assert_fails) { ui_flush(); os_delay(3001L, true); // make sure it is noticed } @@ -1929,12 +1998,16 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_ft); clear_string_option(&buf->b_p_cink); clear_string_option(&buf->b_p_cino); - clear_string_option(&buf->b_p_cinw); + clear_string_option(&buf->b_p_lop); clear_string_option(&buf->b_p_cinsd); + clear_string_option(&buf->b_p_cinw); clear_string_option(&buf->b_p_cpt); clear_string_option(&buf->b_p_cfu); + callback_free(&buf->b_cfu_cb); clear_string_option(&buf->b_p_ofu); + callback_free(&buf->b_ofu_cb); clear_string_option(&buf->b_p_tsrfu); + callback_free(&buf->b_tsrfu_cb); clear_string_option(&buf->b_p_gp); clear_string_option(&buf->b_p_mp); clear_string_option(&buf->b_p_efm); @@ -1943,6 +2016,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_tags); clear_string_option(&buf->b_p_tc); clear_string_option(&buf->b_p_tfu); + callback_free(&buf->b_tfu_cb); clear_string_option(&buf->b_p_dict); clear_string_option(&buf->b_p_tsr); clear_string_option(&buf->b_p_qe); @@ -2162,7 +2236,7 @@ int buflist_findpat(const char *pattern, const char *pattern_end, bool unlisted, if (pat == NULL) { return -1; } - patend = pat + STRLEN(pat) - 1; + patend = pat + strlen(pat) - 1; toggledollar = (patend > pat && *patend == '$'); // First try finding a listed buffer. If not found and "unlisted" @@ -2180,13 +2254,14 @@ int buflist_findpat(const char *pattern, const char *pattern_end, bool unlisted, } regmatch_T regmatch; - regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); - if (regmatch.regprog == NULL) { - xfree(pat); - return -1; - } + regmatch.regprog = vim_regcomp(p, magic_isset() ? RE_MAGIC : 0); FOR_ALL_BUFFERS_BACKWARDS(buf) { + if (regmatch.regprog == NULL) { + // invalid pattern, possibly after switching engine + xfree(pat); + return -1; + } if (buf->b_p_bl == find_listed && (!diffmode || diff_mode_buf(buf)) && buflist_match(®match, buf, false) != NULL) { @@ -2264,7 +2339,6 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options) int round; char *p; int attempt; - char *patc; bufmatch_T *matches = NULL; *num_file = 0; // return values in case of FAIL @@ -2274,31 +2348,34 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options) return FAIL; } - // Make a copy of "pat" and change "^" to "\(^\|[\/]\)". - if (*pat == '^') { - patc = xmalloc(STRLEN(pat) + 11); - STRCPY(patc, "\\(^\\|[\\/]\\)"); - STRCPY(patc + 11, pat + 1); - } else { - patc = pat; + const bool fuzzy = cmdline_fuzzy_complete(pat); + + char *patc = NULL; + // Make a copy of "pat" and change "^" to "\(^\|[\/]\)" (if doing regular + // expression matching) + if (!fuzzy) { + if (*pat == '^') { + patc = xmalloc(strlen(pat) + 11); + STRCPY(patc, "\\(^\\|[\\/]\\)"); + STRCPY(patc + 11, pat + 1); + } else { + patc = pat; + } } + fuzmatch_str_T *fuzmatch = NULL; // attempt == 0: try match with '\<', match at start of word // attempt == 1: try match without '\<', match anywhere - for (attempt = 0; attempt <= 1; attempt++) { - if (attempt > 0 && patc == pat) { - break; // there was no anchor, no need to try again - } - + for (attempt = 0; attempt <= (fuzzy ? 0 : 1); attempt++) { regmatch_T regmatch; - regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC); - if (regmatch.regprog == NULL) { - if (patc != pat) { - xfree(patc); + if (!fuzzy) { + if (attempt > 0 && patc == pat) { + break; // there was no anchor, no need to try again } - return FAIL; + regmatch.regprog = vim_regcomp(patc + attempt * 11, RE_MAGIC); } + int score = 0; // round == 1: Count the matches. // round == 2: Build the array to keep the matches. for (round = 1; round <= 2; round++) { @@ -2314,64 +2391,108 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options) continue; } } - p = buflist_match(®match, buf, p_wic); - if (p != NULL) { - if (round == 1) { - count++; - } else { - if (options & WILD_HOME_REPLACE) { - p = home_replace_save(buf, p); - } else { - p = xstrdup(p); + + if (!fuzzy) { + if (regmatch.regprog == NULL) { + // invalid pattern, possibly after recompiling + if (patc != pat) { + xfree(patc); } - if (matches != NULL) { - matches[count].buf = buf; - matches[count].match = p; - count++; - } else { - (*file)[count++] = p; + return FAIL; + } + p = buflist_match(®match, buf, p_wic); + } else { + p = NULL; + // first try matching with the short file name + if ((score = fuzzy_match_str(buf->b_sfname, pat)) != 0) { + p = buf->b_sfname; + } + if (p == NULL) { + // next try matching with the full path file name + if ((score = fuzzy_match_str(buf->b_ffname, pat)) != 0) { + p = buf->b_ffname; } } } + + if (p == NULL) { + continue; + } + + if (round == 1) { + count++; + continue; + } + + if (options & WILD_HOME_REPLACE) { + p = home_replace_save(buf, p); + } else { + p = xstrdup(p); + } + + if (!fuzzy) { + if (matches != NULL) { + matches[count].buf = buf; + matches[count].match = p; + count++; + } else { + (*file)[count++] = p; + } + } else { + fuzmatch[count].idx = count; + fuzmatch[count].str = p; + fuzmatch[count].score = score; + count++; + } } if (count == 0) { // no match found, break here break; } if (round == 1) { - *file = xmalloc((size_t)count * sizeof(**file)); - - if (options & WILD_BUFLASTUSED) { - matches = xmalloc((size_t)count * sizeof(*matches)); + if (!fuzzy) { + *file = xmalloc((size_t)count * sizeof(**file)); + if (options & WILD_BUFLASTUSED) { + matches = xmalloc((size_t)count * sizeof(*matches)); + } + } else { + fuzmatch = xmalloc((size_t)count * sizeof(fuzmatch_str_T)); } } } - vim_regfree(regmatch.regprog); - if (count) { // match(es) found, break here - break; + + if (!fuzzy) { + vim_regfree(regmatch.regprog); + if (count) { // match(es) found, break here + break; + } } } - if (patc != pat) { + if (!fuzzy && patc != pat) { xfree(patc); } - if (matches != NULL) { - if (count > 1) { - qsort(matches, (size_t)count, sizeof(bufmatch_T), buf_time_compare); - } - - // if the current buffer is first in the list, place it at the end - if (matches[0].buf == curbuf) { - for (int i = 1; i < count; i++) { - (*file)[i - 1] = matches[i].match; + if (!fuzzy) { + if (matches != NULL) { + if (count > 1) { + qsort(matches, (size_t)count, sizeof(bufmatch_T), buf_time_compare); } - (*file)[count - 1] = matches[0].match; - } else { - for (int i = 0; i < count; i++) { - (*file)[i] = matches[i].match; + + // if the current buffer is first in the list, place it at the end + if (matches[0].buf == curbuf) { + for (int i = 1; i < count; i++) { + (*file)[i - 1] = matches[i].match; + } + (*file)[count - 1] = matches[0].match; + } else { + for (int i = 0; i < count; i++) { + (*file)[i] = matches[i].match; + } } + xfree(matches); } - xfree(matches); + } else { + fuzzymatches_to_strmatches(fuzmatch, file, count, false); } *num_file = count; @@ -2379,6 +2500,7 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options) } /// Check for a match on the file name for buffer "buf" with regprog "prog". +/// Note that rmp->regprog may become NULL when switching regexp engine. /// /// @param ignore_case When true, ignore case. Use 'fic' otherwise. static char *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case) @@ -2391,7 +2513,8 @@ static char *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case) return match; } -/// Try matching the regexp in "prog" with file name "name". +/// Try matching the regexp in "rmp->regprog" with file name "name". +/// Note that rmp->regprog may become NULL when switching regexp engine. /// /// @param ignore_case When true, ignore case. Use 'fileignorecase' otherwise. /// @@ -2401,19 +2524,22 @@ static char *fname_match(regmatch_T *rmp, char *name, bool ignore_case) char *match = NULL; char *p; - if (name != NULL) { - // Ignore case when 'fileignorecase' or the argument is set. - rmp->rm_ic = p_fic || ignore_case; - if (vim_regexec(rmp, name, (colnr_T)0)) { + // extra check for valid arguments + if (name == NULL || rmp->regprog == NULL) { + return NULL; + } + + // Ignore case when 'fileignorecase' or the argument is set. + rmp->rm_ic = p_fic || ignore_case; + if (vim_regexec(rmp, name, (colnr_T)0)) { + match = name; + } else if (rmp->regprog != NULL) { + // Replace $(HOME) with '~' and try matching again. + p = home_replace_save(NULL, name); + if (vim_regexec(rmp, p, (colnr_T)0)) { match = name; - } else if (rmp->regprog != NULL) { - // Replace $(HOME) with '~' and try matching again. - p = home_replace_save(NULL, name); - if (vim_regexec(rmp, p, (colnr_T)0)) { - match = name; - } - xfree(p); } + xfree(p); } return match; @@ -2520,17 +2646,18 @@ void buflist_setfpos(buf_T *const buf, win_T *const win, linenr_T lnum, colnr_T static bool wininfo_other_tab_diff(wininfo_T *wip) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - if (wip->wi_opt.wo_diff) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - // return false when it's a window in the current tab page, thus - // the buffer was in diff mode here - if (wip->wi_win == wp) { - return false; - } + if (!wip->wi_opt.wo_diff) { + return false; + } + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + // return false when it's a window in the current tab page, thus + // the buffer was in diff mode here + if (wip->wi_win == wp) { + return false; } - return true; } - return false; + return true; } /// Find info for the current window in buffer "buf". @@ -2553,25 +2680,27 @@ static wininfo_T *find_wininfo(buf_T *buf, bool need_options, bool skip_diff_buf } } + if (wip != NULL) { + return wip; + } + // If no wininfo for curwin, use the first in the list (that doesn't have // 'diff' set and is in another tab page). // If "need_options" is true skip entries that don't have options set, // unless the window is editing "buf", so we can copy from the window // itself. - if (wip == NULL) { - if (skip_diff_buffer) { - for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { - if (!wininfo_other_tab_diff(wip) - && (!need_options - || wip->wi_optset - || (wip->wi_win != NULL - && wip->wi_win->w_buffer == buf))) { - break; - } + if (skip_diff_buffer) { + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { + if (!wininfo_other_tab_diff(wip) + && (!need_options + || wip->wi_optset + || (wip->wi_win != NULL + && wip->wi_win->w_buffer == buf))) { + break; } - } else { - wip = buf->b_wininfo; } + } else { + wip = buf->b_wininfo; } return wip; } @@ -2688,9 +2817,9 @@ void buflist_list(exarg_T *eap) continue; } if (buf_spname(buf) != NULL) { - STRLCPY(NameBuff, buf_spname(buf), MAXPATHL); + xstrlcpy(NameBuff, buf_spname(buf), MAXPATHL); } else { - home_replace(buf, buf->b_fname, (char *)NameBuff, MAXPATHL, true); + home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, true); } if (message_filtered(NameBuff)) { @@ -2706,7 +2835,7 @@ void buflist_list(exarg_T *eap) } msg_putchar('\n'); - len = vim_snprintf((char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"", + len = vim_snprintf(IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"", buf->b_fnum, buf->b_p_bl ? ' ' : 'u', buf == curbuf ? '%' : (curwin->w_alt_fnum == buf->b_fnum ? '#' : ' '), @@ -2720,18 +2849,18 @@ void buflist_list(exarg_T *eap) } // put "line 999" in column 40 or after the file name - i = 40 - vim_strsize((char *)IObuff); + i = 40 - vim_strsize(IObuff); do { IObuff[len++] = ' '; } while (--i > 0 && len < IOSIZE - 18); if (vim_strchr(eap->arg, 't') && buf->b_last_used) { undo_fmt_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used); } else { - vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), _("line %" PRId64), + vim_snprintf(IObuff + len, (size_t)(IOSIZE - len), _("line %" PRId64), buf == curbuf ? (int64_t)curwin->w_cursor.lnum : (int64_t)buflist_findlnum(buf)); } - msg_outtrans((char *)IObuff); + msg_outtrans(IObuff); line_breakcheck(); } @@ -2833,18 +2962,20 @@ int setfname(buf_T *buf, char *ffname_arg, char *sfname_arg, bool message) void buf_set_name(int fnum, char *name) { buf_T *buf = buflist_findnr(fnum); - if (buf != NULL) { - if (buf->b_sfname != buf->b_ffname) { - xfree(buf->b_sfname); - } - xfree(buf->b_ffname); - buf->b_ffname = xstrdup(name); - buf->b_sfname = NULL; - // Allocate ffname and expand into full path. Also resolves .lnk - // files on Win32. - fname_expand(buf, &buf->b_ffname, &buf->b_sfname); - buf->b_fname = buf->b_sfname; + if (buf == NULL) { + return; + } + + if (buf->b_sfname != buf->b_ffname) { + xfree(buf->b_sfname); } + xfree(buf->b_ffname); + buf->b_ffname = xstrdup(name); + buf->b_sfname = NULL; + // Allocate ffname and expand into full path. Also resolves .lnk + // files on Win32. + fname_expand(buf, &buf->b_ffname, &buf->b_sfname); + buf->b_fname = buf->b_sfname; } /// Take care of what needs to be done when the name of buffer "buf" has changed. @@ -2957,7 +3088,7 @@ static bool otherfile_buf(buf_T *buf, char *ffname, FileID *file_id_p, bool file if (ffname == NULL || *ffname == NUL || buf->b_ffname == NULL) { return true; } - if (FNAMECMP(ffname, buf->b_ffname) == 0) { + if (path_fnamecmp(ffname, buf->b_ffname) == 0) { return false; } { @@ -3029,14 +3160,14 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate) if (fullname > 1) { // 2 CTRL-G: include buffer number vim_snprintf(buffer, IOSIZE, "buf %d: ", curbuf->b_fnum); - p = buffer + STRLEN(buffer); + p = buffer + strlen(buffer); } else { p = buffer; } *p++ = '"'; if (buf_spname(curbuf) != NULL) { - STRLCPY(p, buf_spname(curbuf), IOSIZE - (p - buffer)); + xstrlcpy(p, buf_spname(curbuf), (size_t)(IOSIZE - (p - buffer))); } else { if (!fullname && curbuf->b_fname != NULL) { name = curbuf->b_fname; @@ -3079,7 +3210,7 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate) vim_snprintf_add(buffer, IOSIZE, NGETTEXT("%" PRId64 " line --%d%%--", "%" PRId64 " lines --%d%%--", - (unsigned long)curbuf->b_ml.ml_line_count), + curbuf->b_ml.ml_line_count), (int64_t)curbuf->b_ml.ml_line_count, n); } else { vim_snprintf_add(buffer, IOSIZE, @@ -3088,7 +3219,7 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate) (int64_t)curbuf->b_ml.ml_line_count, n); validate_virtcol(); - len = STRLEN(buffer); + len = strlen(buffer); col_print(buffer + len, IOSIZE - len, (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); } @@ -3161,19 +3292,11 @@ void maketitle(void) if (*p_titlestring != NUL) { if (stl_syntax & STL_IN_TITLE) { - int use_sandbox = false; - const int called_emsg_before = called_emsg; - - use_sandbox = was_set_insecurely(curwin, "titlestring", 0); - build_stl_str_hl(curwin, buf, sizeof(buf), - (char *)p_titlestring, use_sandbox, - 0, maxlen, NULL, NULL); + build_stl_str_hl(curwin, buf, sizeof(buf), p_titlestring, + "titlestring", 0, 0, maxlen, NULL, NULL, NULL); title_str = buf; - if (called_emsg > called_emsg_before) { - set_string_option_direct("titlestring", -1, "", OPT_FREE, SID_ERROR); - } } else { - title_str = (char *)p_titlestring; + title_str = p_titlestring; } } else { // Format: "fname + (path) (1 of 2) - VIM". @@ -3220,7 +3343,7 @@ void maketitle(void) (SPACE_FOR_DIR - (size_t)(buf_p - buf)), true); #ifdef BACKSLASH_IN_FILENAME // Avoid "c:/name" to be reduced to "c". - if (isalpha((uint8_t)buf_p) && *(buf_p + 1) == ':') { + if (isalpha((uint8_t)(*buf_p)) && *(buf_p + 1) == ':') { buf_p += 2; } #endif @@ -3275,18 +3398,10 @@ void maketitle(void) icon_str = buf; if (*p_iconstring != NUL) { if (stl_syntax & STL_IN_ICON) { - int use_sandbox = false; - const int called_emsg_before = called_emsg; - - use_sandbox = was_set_insecurely(curwin, "iconstring", 0); - build_stl_str_hl(curwin, icon_str, sizeof(buf), - (char *)p_iconstring, use_sandbox, - 0, 0, NULL, NULL); - if (called_emsg > called_emsg_before) { - set_string_option_direct("iconstring", -1, "", OPT_FREE, SID_ERROR); - } + build_stl_str_hl(curwin, icon_str, sizeof(buf), p_iconstring, + "iconstring", 0, 0, 0, NULL, NULL, NULL); } else { - icon_str = (char *)p_iconstring; + icon_str = p_iconstring; } } else { char *buf_p; @@ -3297,7 +3412,7 @@ void maketitle(void) } *icon_str = NUL; // Truncate name at 100 bytes. - len = (int)STRLEN(buf_p); + len = (int)strlen(buf_p); if (len > 100) { len -= 100; len += utf_cp_tail_off(buf_p, buf_p + len) + 1; @@ -3327,7 +3442,7 @@ static bool value_change(char *str, char **last) FUNC_ATTR_WARN_UNUSED_RESULT { if ((str == NULL) != (*last == NULL) - || (str != NULL && *last != NULL && STRCMP(str, *last) != 0)) { + || (str != NULL && *last != NULL && strcmp(str, *last) != 0)) { xfree(*last); if (str == NULL) { *last = NULL; @@ -3378,9 +3493,9 @@ void get_rel_pos(win_T *wp, char *buf, int buflen) } below = wp->w_buffer->b_ml.ml_line_count - wp->w_botline + 1; if (below <= 0) { - STRLCPY(buf, (above == 0 ? _("All") : _("Bot")), buflen); + xstrlcpy(buf, (above == 0 ? _("All") : _("Bot")), (size_t)buflen); } else if (above <= 0) { - STRLCPY(buf, _("Top"), buflen); + xstrlcpy(buf, _("Top"), (size_t)buflen); } else { vim_snprintf(buf, (size_t)buflen, "%2d%%", above > 1000000L ? (int)(above / ((above + below) / 100L)) @@ -3404,7 +3519,7 @@ bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file) return false; } - char *p = buf + STRLEN(buf); // go to the end of the buffer + char *p = buf + strlen(buf); // go to the end of the buffer // Early out if the string is getting too long if (p - buf + 35 >= buflen) { @@ -3440,7 +3555,7 @@ void fname_expand(buf_T *buf, char **ffname, char **sfname) } *ffname = fix_fname((*ffname)); // expand to full path -#ifdef WIN32 +#ifdef MSWIN if (!buf->b_p_bin) { // If the file name is a shortcut file, use the file it links to. char *rfname = os_resolve_shortcut((const char *)(*ffname)); @@ -3693,10 +3808,10 @@ static int chk_modeline(linenr_T lnum, int flags) int retval = OK; prev = -1; - for (s = (char *)ml_get(lnum); *s != NUL; s++) { + for (s = ml_get(lnum); *s != NUL; s++) { if (prev == -1 || ascii_isspace(prev)) { - if ((prev != -1 && STRNCMP(s, "ex:", (size_t)3) == 0) - || STRNCMP(s, "vi:", (size_t)3) == 0) { + if ((prev != -1 && strncmp(s, "ex:", (size_t)3) == 0) + || strncmp(s, "vi:", (size_t)3) == 0) { break; } // Accept both "vim" and "Vim". @@ -3712,9 +3827,9 @@ static int chk_modeline(linenr_T lnum, int flags) if (*e == ':' && (s[0] != 'V' - || STRNCMP(skipwhite((char *)e + 1), "set", 3) == 0) + || strncmp(skipwhite(e + 1), "set", 3) == 0) && (s[3] == ':' - || (VIM_VERSION_100 >= vers && isdigit(s[3])) + || (VIM_VERSION_100 >= vers && isdigit((uint8_t)s[3])) || (VIM_VERSION_100 < vers && s[3] == '<') || (VIM_VERSION_100 > vers && s[3] == '>') || (VIM_VERSION_100 == vers && s[3] == '='))) { @@ -3761,8 +3876,8 @@ static int chk_modeline(linenr_T lnum, int flags) // "vi:set opt opt opt: foo" -- foo not interpreted // "vi:opt opt opt: foo" -- foo interpreted // Accept "se" for compatibility with Elvis. - if (STRNCMP(s, "set ", (size_t)4) == 0 - || STRNCMP(s, "se ", (size_t)3) == 0) { + if (strncmp(s, "set ", (size_t)4) == 0 + || strncmp(s, "se ", (size_t)3) == 0) { if (*e != ':') { // no terminating ':'? break; } @@ -3826,7 +3941,8 @@ bool bt_terminal(const buf_T *const buf) } /// @return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" -/// buffer. This means the buffer name is not a file name. +/// buffer. This means the buffer name may not be a file name, +/// at least not for writing the buffer. bool bt_nofilename(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -3836,6 +3952,17 @@ bool bt_nofilename(const buf_T *const buf) || buf->b_p_bt[0] == 'p'); } +/// @return true if "buf" is a "nofile", "quickfix", "terminal" or "prompt" +/// buffer. This means the buffer is not to be read from a file. +static bool bt_nofileread(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') + || buf->b_p_bt[0] == 't' + || buf->b_p_bt[0] == 'q' + || buf->b_p_bt[0] == 'p'); +} + /// @return true if "buf" has 'buftype' set to "nofile". bool bt_nofile(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT @@ -3909,30 +4036,6 @@ char *buf_spname(buf_T *buf) return NULL; } -/// Find a window for buffer "buf". -/// If found true is returned and "wp" and "tp" are set to -/// the window and tabpage. -/// If not found, false is returned. -/// -/// @param buf buffer to find a window for -/// @param[out] wp stores the found window -/// @param[out] tp stores the found tabpage -/// -/// @return true if a window was found for the buffer. -bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp) -{ - *wp = NULL; - *tp = NULL; - FOR_ALL_TAB_WINDOWS(tp2, wp2) { - if (wp2->w_buffer == buf) { - *tp = tp2; - *wp = wp2; - return true; - } - } - return false; -} - static int buf_signcols_inner(buf_T *buf, int maximum) { sign_entry_T *sign; // a sign in the sign list @@ -4034,7 +4137,7 @@ void buf_signcols_add_check(buf_T *buf, sign_entry_T *added) buf->b_signcols.max++; } buf->b_signcols.size++; - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); return; } @@ -4055,7 +4158,7 @@ void buf_signcols_add_check(buf_T *buf, sign_entry_T *added) buf->b_signcols.size = linesum; buf->b_signcols.max = linesum; buf->b_signcols.sentinel = added->se_lnum; - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); } } @@ -4074,7 +4177,7 @@ int buf_signcols(buf_T *buf, int maximum) if (signcols != buf->b_signcols.size) { buf->b_signcols.size = signcols; buf->b_signcols.max = maximum; - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); } buf->b_signcols.valid = true; @@ -4096,13 +4199,15 @@ char *buf_get_fname(const buf_T *buf) /// Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. void set_buflisted(int on) { - if (on != curbuf->b_p_bl) { - curbuf->b_p_bl = on; - if (on) { - apply_autocmds(EVENT_BUFADD, NULL, NULL, false, curbuf); - } else { - apply_autocmds(EVENT_BUFDELETE, NULL, NULL, false, curbuf); - } + if (on == curbuf->b_p_bl) { + return; + } + + curbuf->b_p_bl = on; + if (on) { + apply_autocmds(EVENT_BUFADD, NULL, NULL, false, curbuf); + } else { + apply_autocmds(EVENT_BUFDELETE, NULL, NULL, false, curbuf); } } @@ -4127,7 +4232,7 @@ bool buf_contents_changed(buf_T *buf) exarg_T ea; prep_exarg(&ea, buf); - // set curwin/curbuf to buf and save a few things + // Set curwin/curbuf to buf and save a few things. aco_save_T aco; aucmd_prepbuf(&aco, newbuf); @@ -4139,7 +4244,7 @@ bool buf_contents_changed(buf_T *buf) if (buf->b_ml.ml_line_count == curbuf->b_ml.ml_line_count) { differ = false; for (linenr_T lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) { - if (STRCMP(ml_get_buf(buf, lnum, false), ml_get(lnum)) != 0) { + if (strcmp(ml_get_buf(buf, lnum, false), ml_get(lnum)) != 0) { differ = true; break; } @@ -4183,14 +4288,19 @@ void wipe_buffer(buf_T *buf, bool aucmd) /// @param bufnr Buffer to switch to, or 0 to create a new buffer. /// /// @see curbufIsChanged() -void buf_open_scratch(handle_T bufnr, char *bufname) +/// +/// @return FAIL for failure, OK otherwise +int buf_open_scratch(handle_T bufnr, char *bufname) { - (void)do_ecmd((int)bufnr, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL); + if (do_ecmd((int)bufnr, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL) == FAIL) { + return FAIL; + } apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf); (void)setfname(curbuf, bufname, NULL, true); apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); - set_option_value("bh", 0L, "hide", OPT_LOCAL); - set_option_value("bt", 0L, "nofile", OPT_LOCAL); - set_option_value("swf", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("bh", 0L, "hide", OPT_LOCAL); + set_option_value_give_err("bt", 0L, "nofile", OPT_LOCAL); + set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL); RESET_BINDING(curwin); + return OK; } diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index d56a70dc7e..610d9e37ec 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -1,13 +1,20 @@ #ifndef NVIM_BUFFER_H #define NVIM_BUFFER_H +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> + +#include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/func_attr.h" #include "nvim/grid_defs.h" // for StlClickRecord #include "nvim/macros.h" #include "nvim/memline.h" -#include "nvim/pos.h" // for linenr_T +#include "nvim/memline_defs.h" +#include "nvim/pos.h" // Values for buflist_getfile() enum getf_values { @@ -69,8 +76,7 @@ EXTERN char *msg_qflist INIT(= N_("[Quickfix List]")); # include "buffer.h.generated.h" #endif -static inline void buf_set_changedtick(buf_T *const buf, - const varnumber_T changedtick) +static inline void buf_set_changedtick(buf_T *buf, varnumber_T changedtick) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; /// Set b:changedtick, also checking b: for consistency in debug build @@ -102,7 +108,7 @@ static inline void buf_set_changedtick(buf_T *const buf, const varnumber_T chang } } -static inline varnumber_T buf_get_changedtick(const buf_T *const buf) +static inline varnumber_T buf_get_changedtick(const buf_T *buf) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -116,7 +122,7 @@ static inline varnumber_T buf_get_changedtick(const buf_T *const buf) return buf->changedtick_di.di_tv.vval.v_number; } -static inline void buf_inc_changedtick(buf_T *const buf) +static inline void buf_inc_changedtick(buf_T *buf) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; /// Increment b:changedtick value diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 12ebd13439..fa76377c4e 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -3,11 +3,8 @@ #include <stdbool.h> #include <stdint.h> -// for FILE #include <stdio.h> -#include "grid_defs.h" - typedef struct file_buffer buf_T; // Forward declaration // Reference to a buffer that stores the value of buf_free_count. @@ -18,55 +15,44 @@ typedef struct { int br_buf_free_count; } bufref_T; -// for garray_T +#include "klib/kvec.h" +#include "nvim/api/private/defs.h" +#include "nvim/eval/typval.h" #include "nvim/garray.h" -// for ScreenGrid #include "nvim/grid_defs.h" -// for HLF_COUNT -#include "nvim/highlight_defs.h" -// for pos_T, lpos_T and linenr_T -#include "nvim/pos.h" -// for the number window-local and buffer-local options -#include "nvim/option_defs.h" -// for jump list and tag stack sizes in a buffer and mark types -#include "nvim/mark_defs.h" -// for u_header_T -#include "nvim/undo_defs.h" -// for hashtab_T #include "nvim/hashtab.h" -// for dict_T -#include "nvim/eval/typval.h" -// for String -#include "nvim/api/private/defs.h" -// for Map(K, V) +#include "nvim/highlight_defs.h" #include "nvim/map.h" -// for kvec -#include "nvim/lib/kvec.h" -// for marktree +#include "nvim/mark_defs.h" #include "nvim/marktree.h" +// for float window title +#include "nvim/extmark_defs.h" +// for click definitions +#include "nvim/option_defs.h" +#include "nvim/pos.h" +#include "nvim/statusline_defs.h" +#include "nvim/undo_defs.h" #define GETFILE_SUCCESS(x) ((x) <= 0) #define MODIFIABLE(buf) (buf->b_p_ma) -/* - * Flags for w_valid. - * These are set when something in a window structure becomes invalid, except - * when the cursor is moved. Call check_cursor_moved() before testing one of - * the flags. - * These are reset when that thing has been updated and is valid again. - * - * Every function that invalidates one of these must call one of the - * invalidate_* functions. - * - * w_valid is supposed to be used only in screen.c. From other files, use the - * functions that set or reset the flags. - * - * VALID_BOTLINE VALID_BOTLINE_AP - * on on w_botline valid - * off on w_botline approximated - * off off w_botline not valid - * on off not possible - */ +// Flags for w_valid. +// These are set when something in a window structure becomes invalid, except +// when the cursor is moved. Call check_cursor_moved() before testing one of +// the flags. +// These are reset when that thing has been updated and is valid again. +// +// Every function that invalidates one of these must call one of the +// invalidate_* functions. +// +// w_valid is supposed to be used only in screen.c. From other files, use the +// functions that set or reset the flags. +// +// VALID_BOTLINE VALID_BOTLINE_AP +// on on w_botline valid +// off on w_botline approximated +// off off w_botline not valid +// on off not possible #define VALID_WROW 0x01 // w_wrow (window row) is valid #define VALID_WCOL 0x02 // w_wcol (window col) is valid #define VALID_VIRTCOL 0x04 // w_virtcol (file col) is valid @@ -100,43 +86,32 @@ typedef struct wininfo_S wininfo_T; typedef struct frame_S frame_T; typedef uint64_t disptick_T; // display tick type -// for struct memline (it needs memfile_T) #include "nvim/memline_defs.h" - -// for regprog_T. Needs win_T and buf_T. +#include "nvim/os/fs_defs.h" #include "nvim/regexp_defs.h" -// for synstate_T (needs reg_extmatch_T, win_T, buf_T) -#include "nvim/syntax_defs.h" -// for sign_entry_T -#include "nvim/os/fs_defs.h" // for FileID #include "nvim/sign_defs.h" -#include "nvim/terminal.h" // for Terminal +#include "nvim/syntax_defs.h" +#include "nvim/terminal.h" -/* - * The taggy struct is used to store the information about a :tag command. - */ +// The taggy struct is used to store the information about a :tag command. typedef struct taggy { - char_u *tagname; // tag name + char *tagname; // tag name fmark_T fmark; // cursor position BEFORE ":tag" int cur_match; // match number int cur_fnum; // buffer number used for cur_match - char_u *user_data; // used with tagfunc + char *user_data; // used with tagfunc } taggy_T; typedef struct buffblock buffblock_T; typedef struct buffheader buffheader_T; -/* - * structure used to store one block of the stuff/redo/recording buffers - */ +// structure used to store one block of the stuff/redo/recording buffers struct buffblock { buffblock_T *b_next; // pointer to next buffblock - char_u b_str[1]; // contents (actually longer) + char b_str[1]; // contents (actually longer) }; -/* - * header used for the stuff buffer and the redo buffer - */ +// header used for the stuff buffer and the redo buffer struct buffheader { buffblock_T bh_first; // first (dummy) block of list buffblock_T *bh_curr; // buffblock for appending @@ -149,49 +124,47 @@ typedef struct { buffheader_T sr_old_redobuff; } save_redo_T; -/* - * Structure that contains all options that are local to a window. - * Used twice in a window: for the current buffer and for all buffers. - * Also used in wininfo_T. - */ +// Structure that contains all options that are local to a window. +// Used twice in a window: for the current buffer and for all buffers. +// Also used in wininfo_T. typedef struct { int wo_arab; #define w_p_arab w_onebuf_opt.wo_arab // 'arabic' int wo_bri; #define w_p_bri w_onebuf_opt.wo_bri // 'breakindent' - char_u *wo_briopt; + char *wo_briopt; #define w_p_briopt w_onebuf_opt.wo_briopt // 'breakindentopt' int wo_diff; #define w_p_diff w_onebuf_opt.wo_diff // 'diff' - char_u *wo_fdc; + char *wo_fdc; #define w_p_fdc w_onebuf_opt.wo_fdc // 'foldcolumn' - char_u *wo_fdc_save; + char *wo_fdc_save; #define w_p_fdc_save w_onebuf_opt.wo_fdc_save // 'fdc' saved for diff mode int wo_fen; #define w_p_fen w_onebuf_opt.wo_fen // 'foldenable' int wo_fen_save; // 'foldenable' saved for diff mode #define w_p_fen_save w_onebuf_opt.wo_fen_save - char_u *wo_fdi; + char *wo_fdi; #define w_p_fdi w_onebuf_opt.wo_fdi // 'foldignore' long wo_fdl; #define w_p_fdl w_onebuf_opt.wo_fdl // 'foldlevel' long wo_fdl_save; // 'foldlevel' state saved for diff mode #define w_p_fdl_save w_onebuf_opt.wo_fdl_save - char_u *wo_fdm; + char *wo_fdm; #define w_p_fdm w_onebuf_opt.wo_fdm // 'foldmethod' - char_u *wo_fdm_save; + char *wo_fdm_save; #define w_p_fdm_save w_onebuf_opt.wo_fdm_save // 'fdm' saved for diff mode long wo_fml; #define w_p_fml w_onebuf_opt.wo_fml // 'foldminlines' long wo_fdn; #define w_p_fdn w_onebuf_opt.wo_fdn // 'foldnestmax' - char_u *wo_fde; + char *wo_fde; #define w_p_fde w_onebuf_opt.wo_fde // 'foldexpr' - char_u *wo_fdt; + char *wo_fdt; #define w_p_fdt w_onebuf_opt.wo_fdt // 'foldtext' - char_u *wo_fmr; + char *wo_fmr; #define w_p_fmr w_onebuf_opt.wo_fmr // 'foldmarker' int wo_lbr; #define w_p_lbr w_onebuf_opt.wo_lbr // 'linebreak' @@ -201,7 +174,7 @@ typedef struct { #define w_p_nu w_onebuf_opt.wo_nu // 'number' int wo_rnu; #define w_p_rnu w_onebuf_opt.wo_rnu // 'relativenumber' - char_u *wo_ve; + char *wo_ve; #define w_p_ve w_onebuf_opt.wo_ve // 'virtualedit' unsigned wo_ve_flags; #define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit' @@ -215,7 +188,7 @@ typedef struct { #define w_p_pvw w_onebuf_opt.wo_pvw // 'previewwindow' int wo_rl; #define w_p_rl w_onebuf_opt.wo_rl // 'rightleft' - char_u *wo_rlc; + char *wo_rlc; #define w_p_rlc w_onebuf_opt.wo_rlc // 'rightleftcmd' long wo_scr; #define w_p_scr w_onebuf_opt.wo_scr // 'scroll' @@ -225,13 +198,15 @@ typedef struct { #define w_p_cuc w_onebuf_opt.wo_cuc // 'cursorcolumn' int wo_cul; #define w_p_cul w_onebuf_opt.wo_cul // 'cursorline' - char_u *wo_culopt; + char *wo_culopt; #define w_p_culopt w_onebuf_opt.wo_culopt // 'cursorlineopt' - char_u *wo_cc; + char *wo_cc; #define w_p_cc w_onebuf_opt.wo_cc // 'colorcolumn' - char_u *wo_sbr; + char *wo_sbr; #define w_p_sbr w_onebuf_opt.wo_sbr // 'showbreak' - char_u *wo_stl; + char *wo_stc; +#define w_p_stc w_onebuf_opt.wo_stc // 'statuscolumn' + char *wo_stl; #define w_p_stl w_onebuf_opt.wo_stl // 'statusline' char *wo_wbr; #define w_p_wbr w_onebuf_opt.wo_wbr // 'winbar' @@ -245,7 +220,7 @@ typedef struct { #define w_p_wrap w_onebuf_opt.wo_wrap // 'wrap' int wo_wrap_save; // 'wrap' state saved for diff mode #define w_p_wrap_save w_onebuf_opt.wo_wrap_save - char_u *wo_cocu; // 'concealcursor' + char *wo_cocu; // 'concealcursor' #define w_p_cocu w_onebuf_opt.wo_cocu long wo_cole; // 'conceallevel' #define w_p_cole w_onebuf_opt.wo_cole @@ -253,14 +228,14 @@ typedef struct { #define w_p_crb w_onebuf_opt.wo_crb // 'cursorbind' int wo_crb_save; // 'cursorbind' state saved for diff mode #define w_p_crb_save w_onebuf_opt.wo_crb_save - char_u *wo_scl; + char *wo_scl; #define w_p_scl w_onebuf_opt.wo_scl // 'signcolumn' - char_u *wo_winhl; + char *wo_winhl; #define w_p_winhl w_onebuf_opt.wo_winhl // 'winhighlight' - char_u *wo_fcs; -#define w_p_fcs w_onebuf_opt.wo_fcs // 'fillchars' - char_u *wo_lcs; + char *wo_lcs; #define w_p_lcs w_onebuf_opt.wo_lcs // 'listchars' + char *wo_fcs; +#define w_p_fcs w_onebuf_opt.wo_fcs // 'fillchars' long wo_winbl; #define w_p_winbl w_onebuf_opt.wo_winbl // 'winblend' @@ -268,16 +243,14 @@ typedef struct { #define w_p_script_ctx w_onebuf_opt.wo_script_ctx } winopt_T; -/* - * Window info stored with a buffer. - * - * Two types of info are kept for a buffer which are associated with a - * specific window: - * 1. Each window can have a different line number associated with a buffer. - * 2. The window-local options for a buffer work in a similar way. - * The window-info is kept in a list at b_wininfo. It is kept in - * most-recently-used order. - */ +// Window info stored with a buffer. +// +// Two types of info are kept for a buffer which are associated with a +// specific window: +// 1. Each window can have a different line number associated with a buffer. +// 2. The window-local options for a buffer work in a similar way. +// The window-info is kept in a list at b_wininfo. It is kept in +// most-recently-used order. struct wininfo_S { wininfo_T *wi_next; // next entry or NULL for last entry wininfo_T *wi_prev; // previous entry or NULL for first entry @@ -290,12 +263,10 @@ struct wininfo_S { int wi_changelistidx; // copy of w_changelistidx }; -/* - * Argument list: Array of file names. - * Used for the global argument list and the argument lists local to a window. - * - * TODO: move struct arglist to another header - */ +// Argument list: Array of file names. +// Used for the global argument list and the argument lists local to a window. +// +// TODO(neovim): move struct arglist to another header typedef struct arglist { garray_T al_ga; // growarray with the array of file names int al_refcount; // number of windows using this arglist @@ -308,8 +279,8 @@ typedef struct arglist { // // TODO(Felipe): move aentry_T to another header typedef struct argentry { - char_u *ae_fname; // file name as specified - int ae_fnum; // buffer number with expanded file name + char *ae_fname; // file name as specified + int ae_fnum; // buffer number with expanded file name } aentry_T; #define ALIST(win) (win)->w_alist @@ -321,12 +292,10 @@ typedef struct argentry { #define ARGCOUNT (ALIST(curwin)->al_ga.ga_len) #define WARGCOUNT(wp) (ALIST(wp)->al_ga.ga_len) -/* - * Used for the typeahead buffer: typebuf. - */ +// Used for the typeahead buffer: typebuf. typedef struct { - char_u *tb_buf; // buffer for typed characters - char_u *tb_noremap; // mapping flags for characters in tb_buf[] + uint8_t *tb_buf; // buffer for typed characters + uint8_t *tb_noremap; // mapping flags for characters in tb_buf[] int tb_buflen; // size of tb_buf[] int tb_off; // current position in tb_buf[] int tb_len; // number of valid bytes in tb_buf[] @@ -347,16 +316,14 @@ typedef struct { String save_inputbuf; } tasave_T; -/* - * Structure used for mappings and abbreviations. - */ +// Structure used for mappings and abbreviations. typedef struct mapblock mapblock_T; struct mapblock { - mapblock_T *m_next; // next mapblock in list - char_u *m_keys; // mapped from, lhs - char_u *m_str; // mapped to, rhs - char_u *m_orig_str; // rhs as entered by the user - LuaRef m_luaref; // lua function reference as rhs + mapblock_T *m_next; // next mapblock in list + char *m_keys; // mapped from, lhs + char *m_str; // mapped to, rhs + char *m_orig_str; // rhs as entered by the user + LuaRef m_luaref; // lua function reference as rhs int m_keylen; // strlen(m_keys) int m_mode; // valid mode int m_simplified; // m_keys was simplified, do no use this map @@ -366,8 +333,8 @@ struct mapblock { char m_nowait; // <nowait> used char m_expr; // <expr> used, m_str is an expression sctx_T m_script_ctx; // SCTX where map was defined - char *m_desc; // description of keymap - bool m_replace_keycodes; // replace termcodes in lua function + char *m_desc; // description of mapping + bool m_replace_keycodes; // replace keycodes in result of expression }; /// Used for highlighting in the status line. @@ -414,9 +381,7 @@ struct stl_item { typedef struct qf_info_S qf_info_T; -/* - * Used for :syntime: timing of executing a syntax pattern. - */ +// Used for :syntime: timing of executing a syntax pattern. typedef struct { proftime_T total; // total time used proftime_T slowest; // time of slowest call @@ -424,10 +389,8 @@ typedef struct { long match; // nr of times matched } syn_time_T; -/* - * These are items normally related to a buffer. But when using ":ownsyntax" - * a window may have its own instance. - */ +// These are items normally related to a buffer. But when using ":ownsyntax" +// a window may have its own instance. typedef struct { hashtab_T b_keywtab; // syntax keywords hash table hashtab_T b_keywtab_ic; // idem, ignore case @@ -440,14 +403,14 @@ typedef struct { garray_T b_syn_clusters; // table for syntax clusters int b_spell_cluster_id; // @Spell cluster ID or 0 int b_nospell_cluster_id; // @NoSpell cluster ID or 0 - int b_syn_containedin; // TRUE when there is an item with a + int b_syn_containedin; // true when there is an item with a // "containedin" argument int b_syn_sync_flags; // flags about how to sync int16_t b_syn_sync_id; // group to sync on linenr_T b_syn_sync_minlines; // minimal sync lines offset linenr_T b_syn_sync_maxlines; // maximal sync lines offset linenr_T b_syn_sync_linebreaks; // offset for multi-line pattern - char_u *b_syn_linecont_pat; // line continuation pattern + char *b_syn_linecont_pat; // line continuation pattern regprog_T *b_syn_linecont_prog; // line continuation program syn_time_T b_syn_linecont_time; int b_syn_linecont_ic; // ignore-case flag for above @@ -476,17 +439,20 @@ typedef struct { disptick_T b_sst_lasttick; // last display tick // for spell checking - garray_T b_langp; // list of pointers to slang_T, see spell.c - bool b_spell_ismw[256]; // flags: is midword char - char_u *b_spell_ismw_mb; // multi-byte midword chars - char_u *b_p_spc; // 'spellcapcheck' + garray_T b_langp; // list of pointers to slang_T, see spell.c + bool b_spell_ismw[256]; // flags: is midword char + char *b_spell_ismw_mb; // multi-byte midword chars + char *b_p_spc; // 'spellcapcheck' regprog_T *b_cap_prog; // program for 'spellcapcheck' - char_u *b_p_spf; // 'spellfile' - char_u *b_p_spl; // 'spelllang' - char_u *b_p_spo; // 'spelloptions' - int b_cjk; // all CJK letters as OK - char_u b_syn_chartab[32]; // syntax iskeyword option - char_u *b_syn_isk; // iskeyword option + char *b_p_spf; // 'spellfile' + char *b_p_spl; // 'spelllang' + char *b_p_spo; // 'spelloptions' +#define SPO_CAMEL 0x1 +#define SPO_NPBUFFER 0x2 + unsigned b_p_spo_flags; // 'spelloptions' flags + int b_cjk; // all CJK letters as OK + uint8_t b_syn_chartab[32]; // syntax iskeyword option + char *b_syn_isk; // iskeyword option } synblock_T; /// Type used for changedtick_di member in buf_T @@ -514,13 +480,11 @@ EXTERN int curbuf_splice_pending INIT(= 0); // Maximum number of maphash blocks we will have #define MAX_MAPHASH 256 -/* - * buffer: structure that holds information about one file - * - * Several windows can share a single Buffer - * A buffer is unallocated if there is no memfile for it. - * A buffer is new if the associated file has never been loaded yet. - */ +// buffer: structure that holds information about one file +// +// Several windows can share a single Buffer +// A buffer is unallocated if there is no memfile for it. +// A buffer is new if the associated file has never been loaded yet. struct file_buffer { handle_T handle; // unique id for the buffer (buffer number) @@ -570,15 +534,13 @@ struct file_buffer { varnumber_T b_last_changedtick_pum; // b:changedtick when TextChangedP was // last triggered. - bool b_saving; /* Set to true if we are in the middle of - saving the buffer. */ + bool b_saving; // Set to true if we are in the middle of + // saving the buffer. - /* - * Changes to a buffer require updating of the display. To minimize the - * work, remember changes made and update everything at once. - */ - bool b_mod_set; /* true when there are changes since the last - time the display was updated */ + // Changes to a buffer require updating of the display. To minimize the + // work, remember changes made and update everything at once. + bool b_mod_set; // true when there are changes since the last + // time the display was updated linenr_T b_mod_top; // topmost lnum that was changed linenr_T b_mod_bot; // lnum below last changed line, AFTER the // change @@ -600,7 +562,7 @@ struct file_buffer { fmark_T b_namedm[NMARKS]; // current named marks (mark.c) - // These variables are set when VIsual_active becomes FALSE + // These variables are set when VIsual_active becomes false visualinfo_T b_visual; int b_visual_mode_eval; // b_visual.vi_mode for visualmode() @@ -609,17 +571,13 @@ struct file_buffer { fmark_T b_last_insert; // where Insert mode was left fmark_T b_last_change; // position of last change: '. mark - /* - * the changelist contains old change positions - */ + // the changelist contains old change positions fmark_T b_changelist[JUMPLISTSIZE]; int b_changelistlen; // number of active entries bool b_new_change; // set by u_savecommon() - /* - * Character table, only used in charset.c for 'iskeyword' - * bitset with 4*64=256 bits: 1 bit per character 0-255. - */ + // Character table, only used in charset.c for 'iskeyword' + // bitset with 4*64=256 bits: 1 bit per character 0-255. uint64_t b_chartab[4]; // Table used for mappings local to a buffer. @@ -629,18 +587,14 @@ struct file_buffer { mapblock_T *b_first_abbr; // User commands local to the buffer. garray_T b_ucmds; - /* - * start and end of an operator, also used for '[ and '] - */ + // start and end of an operator, also used for '[ and '] pos_T b_op_start; pos_T b_op_start_orig; // used for Insstart_orig pos_T b_op_end; bool b_marks_read; // Have we read ShaDa marks yet? - /* - * The following only used in undo.c. - */ + // The following only used in undo.c. u_header_T *b_u_oldhead; // pointer to oldest header u_header_T *b_u_newhead; // pointer to newest header; may not be valid // if b_u_curhead is not NULL @@ -649,14 +603,12 @@ struct file_buffer { bool b_u_synced; // entry lists are synced long b_u_seq_last; // last used undo sequence number long b_u_save_nr_last; // counter for last file write - long b_u_seq_cur; // hu_seq of header below which we are now + long b_u_seq_cur; // uh_seq of header below which we are now time_t b_u_time_cur; // uh_time of header below which we are now long b_u_save_nr_cur; // file write nr after which we are now - /* - * variables for "U" command in undo.c - */ - char_u *b_u_line_ptr; // saved line for "U" command + // variables for "U" command in undo.c + char *b_u_line_ptr; // saved line for "U" command linenr_T b_u_line_lnum; // line number of line in u_line colnr_T b_u_line_colnr; // optional column number @@ -675,84 +627,87 @@ struct file_buffer { #define KEYMAP_LOADED 2 // 'keymap' mappings have been loaded garray_T b_kmap_ga; // the keymap table - /* - * Options local to a buffer. - * They are here because their value depends on the type of file - * or contents of the file being edited. - */ + // Options local to a buffer. + // They are here because their value depends on the type of file + // or contents of the file being edited. bool b_p_initialized; // set when options initialized LastSet b_p_script_ctx[BV_COUNT]; // SCTXs for buffer-local options int b_p_ai; ///< 'autoindent' int b_p_ai_nopaste; ///< b_p_ai saved for paste mode - char_u *b_p_bkc; ///< 'backupco + char *b_p_bkc; ///< 'backupco unsigned int b_bkc_flags; ///< flags for 'backupco int b_p_ci; ///< 'copyindent' int b_p_bin; ///< 'binary' int b_p_bomb; ///< 'bomb' - char_u *b_p_bh; ///< 'bufhidden' - char_u *b_p_bt; ///< 'buftype' + char *b_p_bh; ///< 'bufhidden' + char *b_p_bt; ///< 'buftype' int b_has_qf_entry; ///< quickfix exists for buffer int b_p_bl; ///< 'buflisted' long b_p_channel; ///< 'channel' int b_p_cin; ///< 'cindent' - char_u *b_p_cino; ///< 'cinoptions' - char_u *b_p_cink; ///< 'cinkeys' - char_u *b_p_cinw; ///< 'cinwords' - char_u *b_p_cinsd; ///< 'cinscopedecls' - char_u *b_p_com; ///< 'comments' - char_u *b_p_cms; ///< 'commentstring' - char_u *b_p_cpt; ///< 'complete' + char *b_p_cino; ///< 'cinoptions' + char *b_p_cink; ///< 'cinkeys' + char *b_p_cinw; ///< 'cinwords' + char *b_p_cinsd; ///< 'cinscopedecls' + char *b_p_com; ///< 'comments' + char *b_p_cms; ///< 'commentstring' + char *b_p_cpt; ///< 'complete' #ifdef BACKSLASH_IN_FILENAME - char_u *b_p_csl; ///< 'completeslash' + char *b_p_csl; ///< 'completeslash' #endif - char_u *b_p_cfu; ///< 'completefunc' - char_u *b_p_ofu; ///< 'omnifunc' - char_u *b_p_tfu; ///< 'tagfunc' - char_u *b_p_umf; ///< 'usermarkfunc' + char *b_p_umf; ///< 'usermarkfunc' + char *b_p_cfu; ///< 'completefunc' + Callback b_cfu_cb; ///< 'completefunc' callback + char *b_p_ofu; ///< 'omnifunc' + Callback b_ofu_cb; ///< 'omnifunc' callback + char *b_p_tfu; ///< 'tagfunc' + Callback b_tfu_cb; ///< 'tagfunc' callback + int b_p_eof; ///< 'endoffile' int b_p_eol; ///< 'endofline' int b_p_fixeol; ///< 'fixendofline' int b_p_et; ///< 'expandtab' int b_p_et_nobin; ///< b_p_et saved for binary mode int b_p_et_nopaste; ///< b_p_et saved for paste mode - char_u *b_p_fenc; ///< 'fileencoding' - char_u *b_p_ff; ///< 'fileformat' - char_u *b_p_ft; ///< 'filetype' - char_u *b_p_fo; ///< 'formatoptions' - char_u *b_p_flp; ///< 'formatlistpat' + char *b_p_fenc; ///< 'fileencoding' + char *b_p_ff; ///< 'fileformat' + char *b_p_ft; ///< 'filetype' + char *b_p_fo; ///< 'formatoptions' + char *b_p_flp; ///< 'formatlistpat' int b_p_inf; ///< 'infercase' - char_u *b_p_isk; ///< 'iskeyword' - char_u *b_p_def; ///< 'define' local value - char_u *b_p_inc; ///< 'include' - char_u *b_p_inex; ///< 'includeexpr' + char *b_p_isk; ///< 'iskeyword' + char *b_p_def; ///< 'define' local value + char *b_p_inc; ///< 'include' + char *b_p_inex; ///< 'includeexpr' uint32_t b_p_inex_flags; ///< flags for 'includeexpr' - char_u *b_p_inde; ///< 'indentexpr' + char *b_p_inde; ///< 'indentexpr' uint32_t b_p_inde_flags; ///< flags for 'indentexpr' - char_u *b_p_indk; ///< 'indentkeys' - char_u *b_p_fp; ///< 'formatprg' - char_u *b_p_fex; ///< 'formatexpr' + char *b_p_indk; ///< 'indentkeys' + char *b_p_fp; ///< 'formatprg' + char *b_p_fex; ///< 'formatexpr' uint32_t b_p_fex_flags; ///< flags for 'formatexpr' - char_u *b_p_kp; ///< 'keywordprg' + char *b_p_kp; ///< 'keywordprg' int b_p_lisp; ///< 'lisp' - char_u *b_p_menc; ///< 'makeencoding' - char_u *b_p_mps; ///< 'matchpairs' + char *b_p_lop; ///< 'lispoptions' + char *b_p_menc; ///< 'makeencoding' + char *b_p_mps; ///< 'matchpairs' int b_p_ml; ///< 'modeline' int b_p_ml_nobin; ///< b_p_ml saved for binary mode int b_p_ma; ///< 'modifiable' - char_u *b_p_nf; ///< 'nrformats' + char *b_p_nf; ///< 'nrformats' int b_p_pi; ///< 'preserveindent' - char_u *b_p_qe; ///< 'quoteescape' + char *b_p_qe; ///< 'quoteescape' int b_p_ro; ///< 'readonly' long b_p_sw; ///< 'shiftwidth' long b_p_scbk; ///< 'scrollback' int b_p_si; ///< 'smartindent' long b_p_sts; ///< 'softtabstop' long b_p_sts_nopaste; ///< b_p_sts saved for paste mode - char_u *b_p_sua; ///< 'suffixesadd' + char *b_p_sua; ///< 'suffixesadd' int b_p_swf; ///< 'swapfile' long b_p_smc; ///< 'synmaxcol' - char_u *b_p_syn; ///< 'syntax' + char *b_p_syn; ///< 'syntax' long b_p_ts; ///< 'tabstop' long b_p_tw; ///< 'textwidth' long b_p_tw_nobin; ///< b_p_tw saved for binary mode @@ -760,29 +715,30 @@ struct file_buffer { long b_p_wm; ///< 'wrapmargin' long b_p_wm_nobin; ///< b_p_wm saved for binary mode long b_p_wm_nopaste; ///< b_p_wm saved for paste mode - char_u *b_p_vsts; ///< 'varsofttabstop' - long *b_p_vsts_array; ///< 'varsofttabstop' in internal format - char_u *b_p_vsts_nopaste; ///< b_p_vsts saved for paste mode - char_u *b_p_vts; ///< 'vartabstop' - long *b_p_vts_array; ///< 'vartabstop' in internal format - char_u *b_p_keymap; ///< 'keymap' + char *b_p_vsts; ///< 'varsofttabstop' + long *b_p_vsts_array; ///< 'varsofttabstop' in internal format + char *b_p_vsts_nopaste; ///< b_p_vsts saved for paste mode + char *b_p_vts; ///< 'vartabstop' + long *b_p_vts_array; ///< 'vartabstop' in internal format + char *b_p_keymap; ///< 'keymap' // local values for options which are normally global - char_u *b_p_gp; ///< 'grepprg' local value - char_u *b_p_mp; ///< 'makeprg' local value - char_u *b_p_efm; ///< 'errorformat' local value - char_u *b_p_ep; ///< 'equalprg' local value - char_u *b_p_path; ///< 'path' local value + char *b_p_gp; ///< 'grepprg' local value + char *b_p_mp; ///< 'makeprg' local value + char *b_p_efm; ///< 'errorformat' local value + char *b_p_ep; ///< 'equalprg' local value + char *b_p_path; ///< 'path' local value int b_p_ar; ///< 'autoread' local value - char_u *b_p_tags; ///< 'tags' local value - char_u *b_p_tc; ///< 'tagcase' local value + char *b_p_tags; ///< 'tags' local value + char *b_p_tc; ///< 'tagcase' local value unsigned b_tc_flags; ///< flags for 'tagcase' - char_u *b_p_dict; ///< 'dictionary' local value - char_u *b_p_tsr; ///< 'thesaurus' local value - char_u *b_p_tsrfu; ///< 'thesaurusfunc' local value + char *b_p_dict; ///< 'dictionary' local value + char *b_p_tsr; ///< 'thesaurus' local value + char *b_p_tsrfu; ///< 'thesaurusfunc' local value + Callback b_tsrfu_cb; ///< 'thesaurusfunc' callback long b_p_ul; ///< 'undolevels' local value int b_p_udf; ///< 'undofile' - char_u *b_p_lw; ///< 'lispwords' local value + char *b_p_lw; ///< 'lispwords' local value // end of buffer options @@ -825,9 +781,10 @@ struct file_buffer { int b_ind_cpp_extern_c; int b_ind_pragma; - linenr_T b_no_eol_lnum; /* non-zero lnum when last line of next binary - * write should not have an end-of-line */ + linenr_T b_no_eol_lnum; // non-zero lnum when last line of next binary + // write should not have an end-of-line + int b_start_eof; // last line had eof (CTRL-Z) when it was read int b_start_eol; // last line had eol when it was read int b_start_ffc; // first char of 'ff' when edit started char *b_start_fenc; // 'fileencoding' when edit started or NULL @@ -837,20 +794,18 @@ struct file_buffer { ScopeDictDictItem b_bufvar; ///< Variable for "b:" Dictionary. dict_T *b_vars; ///< b: scope dictionary. - /* When a buffer is created, it starts without a swap file. b_may_swap is - * then set to indicate that a swap file may be opened later. It is reset - * if a swap file could not be opened. - */ + // When a buffer is created, it starts without a swap file. b_may_swap is + // then set to indicate that a swap file may be opened later. It is reset + // if a swap file could not be opened. bool b_may_swap; - bool b_did_warn; /* Set to true if user has been warned on first - change of a read-only file */ - - /* Two special kinds of buffers: - * help buffer - used for help files, won't use a swap file. - * spell buffer - used for spell info, never displayed and doesn't have a - * file name. - */ - bool b_help; // TRUE for help file buffer (when set b_p_bt + bool b_did_warn; // Set to true if user has been warned on first + // change of a read-only file + + // Two special kinds of buffers: + // help buffer - used for help files, won't use a swap file. + // spell buffer - used for spell info, never displayed and doesn't have a + // file name. + bool b_help; // true for help file buffer (when set b_p_bt // is "help") bool b_spell; // True for a spell file buffer, most fields // are not used! Use the B_SPELL macro to @@ -909,30 +864,28 @@ struct file_buffer { int b_diff_failed; // internal diff failed for this buffer }; -/* - * Stuff for diff mode. - */ +// Stuff for diff mode. #define DB_COUNT 8 // up to four buffers can be diff'ed -/* - * Each diffblock defines where a block of lines starts in each of the buffers - * and how many lines it occupies in that buffer. When the lines are missing - * in the buffer the df_count[] is zero. This is all counted in - * buffer lines. - * There is always at least one unchanged line in between the diffs. - * Otherwise it would have been included in the diff above or below it. - * df_lnum[] + df_count[] is the lnum below the change. When in one buffer - * lines have been inserted, in the other buffer df_lnum[] is the line below - * the insertion and df_count[] is zero. When appending lines at the end of - * the buffer, df_lnum[] is one beyond the end! - * This is using a linked list, because the number of differences is expected - * to be reasonable small. The list is sorted on lnum. - */ +// Each diffblock defines where a block of lines starts in each of the buffers +// and how many lines it occupies in that buffer. When the lines are missing +// in the buffer the df_count[] is zero. This is all counted in +// buffer lines. +// There is always at least one unchanged line in between the diffs. +// Otherwise it would have been included in the diff above or below it. +// df_lnum[] + df_count[] is the lnum below the change. When in one buffer +// lines have been inserted, in the other buffer df_lnum[] is the line below +// the insertion and df_count[] is zero. When appending lines at the end of +// the buffer, df_lnum[] is one beyond the end! +// This is using a linked list, because the number of differences is expected +// to be reasonable small. The list is sorted on lnum. typedef struct diffblock_S diff_T; struct diffblock_S { diff_T *df_next; linenr_T df_lnum[DB_COUNT]; // line number in buffer linenr_T df_count[DB_COUNT]; // nr of inserted/changed lines + bool is_linematched; // has the linematch algorithm ran on this diff hunk to divide it into + // smaller diff hunks? }; #define SNAP_HELP_IDX 0 @@ -953,7 +906,8 @@ struct tabpage_S { win_T *tp_firstwin; ///< first window in this Tab page win_T *tp_lastwin; ///< last window in this Tab page long tp_old_Rows_avail; ///< ROWS_AVAIL when Tab page was left - long tp_old_Columns; ///< Columns when Tab page was left + long tp_old_Columns; ///< Columns when Tab page was left, -1 when + ///< calling win_new_screen_cols() postponed long tp_ch_used; ///< value of 'cmdheight' when frame size was set diff_T *tp_first_diff; @@ -967,30 +921,26 @@ struct tabpage_S { char *tp_prevdir; ///< Previous directory. }; -/* - * Structure to cache info for displayed lines in w_lines[]. - * Each logical line has one entry. - * The entry tells how the logical line is currently displayed in the window. - * This is updated when displaying the window. - * When the display is changed (e.g., when clearing the screen) w_lines_valid - * is changed to exclude invalid entries. - * When making changes to the buffer, wl_valid is reset to indicate wl_size - * may not reflect what is actually in the buffer. When wl_valid is FALSE, - * the entries can only be used to count the number of displayed lines used. - * wl_lnum and wl_lastlnum are invalid too. - */ +// Structure to cache info for displayed lines in w_lines[]. +// Each logical line has one entry. +// The entry tells how the logical line is currently displayed in the window. +// This is updated when displaying the window. +// When the display is changed (e.g., when clearing the screen) w_lines_valid +// is changed to exclude invalid entries. +// When making changes to the buffer, wl_valid is reset to indicate wl_size +// may not reflect what is actually in the buffer. When wl_valid is false, +// the entries can only be used to count the number of displayed lines used. +// wl_lnum and wl_lastlnum are invalid too. typedef struct w_line { linenr_T wl_lnum; // buffer line number for logical line uint16_t wl_size; // height in screen lines - char wl_valid; // TRUE values are valid for text in buffer - char wl_folded; // TRUE when this is a range of folded lines + char wl_valid; // true values are valid for text in buffer + char wl_folded; // true when this is a range of folded lines linenr_T wl_lastlnum; // last buffer line number for logical line } wline_T; -/* - * Windows are kept in a tree of frames. Each frame has a column (FR_COL) - * or row (FR_ROW) layout or is a leaf, which has a window. - */ +// Windows are kept in a tree of frames. Each frame has a column (FR_COL) +// or row (FR_ROW) layout or is a leaf, which has a window. struct frame_S { char fr_layout; // FR_LEAF, FR_COL or FR_ROW int fr_width; @@ -1004,19 +954,18 @@ struct frame_S { // for first // fr_child and fr_win are mutually exclusive frame_T *fr_child; // first contained frame - win_T *fr_win; // window that fills this frame + win_T *fr_win; // window that fills this frame; for a snapshot + // set to the current window }; #define FR_LEAF 0 // frame is a leaf #define FR_ROW 1 // frame with a row of windows #define FR_COL 2 // frame with a column of windows -/* - * Struct used for highlighting 'hlsearch' matches, matches defined by - * ":match" and matches defined by match functions. - * For 'hlsearch' there is one pattern for all windows. For ":match" and the - * match functions there is a different pattern for each window. - */ +// Struct used for highlighting 'hlsearch' matches, matches defined by +// ":match" and matches defined by match functions. +// For 'hlsearch' there is one pattern for all windows. For ":match" and the +// match functions there is a different pattern for each window. typedef struct { regmmatch_T rm; // points to the regexp program; contains last found // match (may continue in next line) @@ -1032,9 +981,6 @@ typedef struct { proftime_T tm; // for a time limit } match_T; -/// number of positions supported by matchaddpos() -#define MAXPOSMATCH 8 - /// Same as lpos_T, but with additional field len. typedef struct { linenr_T lnum; ///< line number @@ -1042,31 +988,28 @@ typedef struct { int len; ///< length: 0 - to the end of line } llpos_T; -/// posmatch_T provides an array for storing match items for matchaddpos() -/// function. -typedef struct posmatch posmatch_T; -struct posmatch { - llpos_T pos[MAXPOSMATCH]; ///< array of positions - int cur; ///< internal position counter - linenr_T toplnum; ///< top buffer line - linenr_T botlnum; ///< bottom buffer line -}; - -/* - * matchitem_T provides a linked list for storing match items for ":match" and - * the match functions. - */ +/// matchitem_T provides a linked list for storing match items for ":match", +/// matchadd() and matchaddpos(). typedef struct matchitem matchitem_T; struct matchitem { - matchitem_T *next; - int id; ///< match ID - int priority; ///< match priority - char *pattern; ///< pattern to highlight - regmmatch_T match; ///< regexp program for pattern - posmatch_T pos; ///< position matches - match_T hl; ///< struct for doing the actual highlighting - int hlg_id; ///< highlight group ID - int conceal_char; ///< cchar for Conceal highlighting + matchitem_T *mit_next; + int mit_id; ///< match ID + int mit_priority; ///< match priority + + // Either a pattern is defined (mit_pattern is not NUL) or a list of + // positions is given (mit_pos is not NULL and mit_pos_count > 0). + char *mit_pattern; ///< pattern to highlight + regmmatch_T mit_match; ///< regexp program for pattern + + llpos_T *mit_pos_array; ///< array of positions + int mit_pos_count; ///< nr of entries in mit_pos + int mit_pos_cur; ///< internal position counter + linenr_T mit_toplnum; ///< top buffer line + linenr_T mit_botlnum; ///< bottom buffer line + + match_T mit_hl; ///< struct for doing the actual highlighting + int mit_hlg_id; ///< highlight group ID + int mit_conceal_char; ///< cchar for Conceal highlighting }; typedef int FloatAnchor; @@ -1086,16 +1029,23 @@ typedef enum { kFloatRelativeEditor = 0, kFloatRelativeWindow = 1, kFloatRelativeCursor = 2, + kFloatRelativeMouse = 3, } FloatRelative; EXTERN const char *const float_relative_str[] INIT(= { "editor", "win", - "cursor" }); + "cursor", "mouse" }); typedef enum { kWinStyleUnused = 0, kWinStyleMinimal, /// Minimal UI: no number column, eob markers, etc } WinStyle; +typedef enum { + kAlignLeft = 0, + kAlignCenter = 1, + kAlignRight = 2, +} AlignTextPos; + typedef struct { Window window; lpos_T bufpos; @@ -1108,10 +1058,14 @@ typedef struct { int zindex; WinStyle style; bool border; + bool title; bool shadow; schar_T border_chars[8]; int border_hl_ids[8]; int border_attr[8]; + AlignTextPos title_pos; + VirtText title_chunks; + int title_width; bool noautocmd; } FloatConfig; @@ -1222,16 +1176,15 @@ struct window_S { int diff; int msgsep; int eob; + int lastline; } w_p_fcs_chars; - /* - * "w_topline", "w_leftcol" and "w_skipcol" specify the offsets for - * displaying the buffer. - */ - linenr_T w_topline; /* buffer line number of the line at the - top of the window */ - char w_topline_was_set; /* flag set to TRUE when topline is set, - e.g. by winrestview() */ + // "w_topline", "w_leftcol" and "w_skipcol" specify the offsets for + // displaying the buffer. + linenr_T w_topline; // buffer line number of the line at the + // top of the window + char w_topline_was_set; // flag set to true when topline is set, + // e.g. by winrestview() int w_topfill; // number of filler lines above w_topline int w_old_topfill; // w_topfill at last redraw bool w_botfill; // true when filler lines are actually @@ -1243,9 +1196,11 @@ struct window_S { colnr_T w_skipcol; // starting column when a single line // doesn't fit in the window - // four fields that are only used when there is a WinScrolled autocommand + // six fields that are only used when there is a WinScrolled autocommand linenr_T w_last_topline; ///< last known value for w_topline + int w_last_topfill; ///< last known value for w_topfill colnr_T w_last_leftcol; ///< last known value for w_leftcol + colnr_T w_last_skipcol; ///< last known value for w_skipcol int w_last_width; ///< last known value for w_width int w_last_height; ///< last known value for w_height @@ -1256,6 +1211,8 @@ struct window_S { int w_winrow; // first row of window in screen int w_height; // number of rows in window, excluding // status/command line(s) + int w_prev_winrow; // previous winrow used for 'splitkeep' + int w_prev_height; // previous height used for 'splitkeep' int w_status_height; // number of status lines (0 or 1) int w_winbar_height; // number of window bars (0 or 1) int w_wincol; // Leftmost column of window in screen. @@ -1281,26 +1238,20 @@ struct window_S { int w_height_outer; int w_width_outer; - /* - * === start of cached values ==== - */ - /* - * Recomputing is minimized by storing the result of computations. - * Use functions in screen.c to check if they are valid and to update. - * w_valid is a bitfield of flags, which indicate if specific values are - * valid or need to be recomputed. - */ + // === start of cached values ==== + + // Recomputing is minimized by storing the result of computations. + // Use functions in screen.c to check if they are valid and to update. + // w_valid is a bitfield of flags, which indicate if specific values are + // valid or need to be recomputed. int w_valid; - pos_T w_valid_cursor; /* last known position of w_cursor, used - to adjust w_valid */ + pos_T w_valid_cursor; // last known position of w_cursor, used to adjust w_valid colnr_T w_valid_leftcol; // last known w_leftcol bool w_viewport_invalid; - /* - * w_cline_height is the number of physical lines taken by the buffer line - * that the cursor is on. We use this to avoid extra calls to plines_win(). - */ + // w_cline_height is the number of physical lines taken by the buffer line + // that the cursor is on. We use this to avoid extra calls to plines_win(). int w_cline_height; // current size of cursor line bool w_cline_folded; // cursor line is folded @@ -1313,11 +1264,9 @@ struct window_S { // more than one screen line or when // w_leftcol is non-zero - /* - * w_wrow and w_wcol specify the cursor position in the window. - * This is related to positions in the window, not in the display or - * buffer, thus w_wrow is relative to w_winrow. - */ + // w_wrow and w_wcol specify the cursor position in the window. + // This is related to positions in the window, not in the display or + // buffer, thus w_wrow is relative to w_winrow. int w_wrow, w_wcol; // cursor position in window linenr_T w_botline; // number of the line below the bottom of @@ -1326,16 +1275,14 @@ struct window_S { int w_filler_rows; // number of filler rows at the end of the // window - /* - * Info about the lines currently in the window is remembered to avoid - * recomputing it every time. The allocated size of w_lines[] is Rows. - * Only the w_lines_valid entries are actually valid. - * When the display is up-to-date w_lines[0].wl_lnum is equal to w_topline - * and w_lines[w_lines_valid - 1].wl_lnum is equal to w_botline. - * Between changing text and updating the display w_lines[] represents - * what is currently displayed. wl_valid is reset to indicated this. - * This is used for efficient redrawing. - */ + // Info about the lines currently in the window is remembered to avoid + // recomputing it every time. The allocated size of w_lines[] is Rows. + // Only the w_lines_valid entries are actually valid. + // When the display is up-to-date w_lines[0].wl_lnum is equal to w_topline + // and w_lines[w_lines_valid - 1].wl_lnum is equal to w_botline. + // Between changing text and updating the display w_lines[] represents + // what is currently displayed. wl_valid is reset to indicated this. + // This is used for efficient redrawing. int w_lines_valid; // number of valid entries wline_T *w_lines; @@ -1348,17 +1295,16 @@ struct window_S { // column being used int w_scwidth; // width of 'signcolumn' - /* - * === end of cached values === - */ + // === end of cached values === int w_redr_type; // type of redraw to be performed on win int w_upd_rows; // number of window lines to update when - // w_redr_type is REDRAW_TOP + // w_redr_type is UPD_REDRAW_TOP linenr_T w_redraw_top; // when != 0: first line needing redraw linenr_T w_redraw_bot; // when != 0: last line needing redraw bool w_redr_status; // if true statusline/winbar must be redrawn bool w_redr_border; // if true border must be redrawn + bool w_redr_statuscol; // if true 'statuscolumn' must be redrawn // remember what is shown in the ruler for this window (if 'ruler' set) pos_T w_ru_cursor; // cursor position shown in ruler @@ -1366,7 +1312,7 @@ struct window_S { linenr_T w_ru_topline; // topline shown in ruler linenr_T w_ru_line_count; // line count used for ruler int w_ru_topfill; // topfill shown in ruler - char w_ru_empty; // TRUE if ruler shows 0-1 (empty line) + char w_ru_empty; // true if ruler shows 0-1 (empty line) int w_alt_fnum; // alternate file (for # and CTRL-^) @@ -1399,6 +1345,7 @@ struct window_S { int w_briopt_shift; // additional shift for breakindent bool w_briopt_sbr; // sbr in 'briopt' int w_briopt_list; // additional indent for lists + int w_briopt_vcol; // indent for specific column // transform a pointer to a "onebuf" option into a "allbuf" option #define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T)) @@ -1408,17 +1355,13 @@ struct window_S { ScopeDictDictItem w_winvar; ///< Variable for "w:" dictionary. dict_T *w_vars; ///< Dictionary with w: variables. - /* - * The w_prev_pcmark field is used to check whether we really did jump to - * a new line after setting the w_pcmark. If not, then we revert to - * using the previous w_pcmark. - */ + // The w_prev_pcmark field is used to check whether we really did jump to + // a new line after setting the w_pcmark. If not, then we revert to + // using the previous w_pcmark. pos_T w_pcmark; // previous context mark pos_T w_prev_pcmark; // previous w_pcmark - /* - * the jumplist contains old cursor positions - */ + // the jumplist contains old cursor positions xfmark_T w_jumplist[JUMPLISTSIZE]; int w_jumplistlen; // number of active entries int w_jumplistidx; // current position @@ -1428,12 +1371,10 @@ struct window_S { matchitem_T *w_match_head; // head of match list int w_next_match_id; // next match ID - /* - * the tagstack grows from 0 upwards: - * entry 0: older - * entry 1: newer - * entry 2: newest - */ + // the tagstack grows from 0 upwards: + // entry 0: older + // entry 1: newer + // entry 2: newest taggy_T w_tagstack[TAGSTACKSIZE]; // the tag stack int w_tagstackidx; // idx just below active entry int w_tagstacklen; // number of tags on stack @@ -1444,17 +1385,14 @@ struct window_S { bool w_floating; ///< whether the window is floating FloatConfig w_float_config; - /* - * w_fraction is the fractional row of the cursor within the window, from - * 0 at the top row to FRACTION_MULT at the last row. - * w_prev_fraction_row was the actual cursor row when w_fraction was last - * calculated. - */ + // w_fraction is the fractional row of the cursor within the window, from + // 0 at the top row to FRACTION_MULT at the last row. + // w_prev_fraction_row was the actual cursor row when w_fraction was last + // calculated. int w_fraction; int w_prev_fraction_row; - linenr_T w_nrwidth_line_count; /* line count when ml_nrwidth_width - * was computed. */ + linenr_T w_nrwidth_line_count; // line count when ml_nrwidth_width was computed. int w_nrwidth_width; // nr of chars to print line count. qf_info_T *w_llist; // Location list for this window @@ -1471,10 +1409,37 @@ struct window_S { StlClickDefinition *w_winbar_click_defs; // Size of the w_winbar_click_defs array size_t w_winbar_click_defs_size; + + // Status column click definitions + StlClickDefinition *w_statuscol_click_defs; + // Size of the w_statuscol_click_defs array + size_t w_statuscol_click_defs_size; +}; + +/// Struct to hold info for 'statuscolumn' +typedef struct statuscol statuscol_T; + +struct statuscol { + int width; ///< width of the status column + int cur_attr; ///< current attributes in text + int num_attr; ///< attributes used for line number + int fold_attr; ///< attributes used for fold column + int sign_attr[SIGN_SHOW_MAX + 1]; ///< attributes used for signs + int truncate; ///< truncated width + bool draw; ///< draw statuscolumn or not + char fold_text[9 * 4 + 1]; ///< text in fold column (%C) + char *sign_text[SIGN_SHOW_MAX + 1]; ///< text in sign column (%s) + char text[MAXPATHL]; ///< text in status column + char *textp; ///< current position in text + char *text_end; ///< end of text (the NUL byte) + stl_hlrec_t *hlrec; ///< highlight groups + stl_hlrec_t *hlrecp; ///< current highlight group }; /// Macros defined in Vim, but not in Neovim +// uncrustify:off #define CHANGEDTICK(buf) \ (=== Include buffer.h & use buf_(get|set|inc) _changedtick ===) +// uncrustify:on -#endif // NVIM_BUFFER_DEFS_H +#endif // NVIM_BUFFER_DEFS_H diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 14973502ab..075ac2adbf 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -1,17 +1,31 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> + +#include "klib/kvec.h" +#include "lauxlib.h" +#include "nvim/api/buffer.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/assert.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/extmark.h" +#include "nvim/globals.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" #include "nvim/memline.h" +#include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/pos.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "buffer_updates.c.generated.h" +# include "buffer_updates.c.generated.h" // IWYU pragma: export #endif // Register a channel. Return True if the channel was added, or already added. @@ -34,12 +48,10 @@ bool buf_updates_register(buf_T *buf, uint64_t channel_id, BufUpdateCallbacks cb // count how many channels are currently watching the buffer size_t size = kv_size(buf->update_channels); - if (size) { - for (size_t i = 0; i < size; i++) { - if (kv_A(buf->update_channels, i) == channel_id) { - // buffer is already registered ... nothing to do - return true; - } + for (size_t i = 0; i < size; i++) { + if (kv_A(buf->update_channels, i) == channel_id) { + // buffer is already registered ... nothing to do + return true; } } @@ -69,13 +81,14 @@ bool buf_updates_register(buf_T *buf, uint64_t channel_id, BufUpdateCallbacks cb linedata.size = line_count; linedata.items = xcalloc(line_count, sizeof(Object)); - buf_collect_lines(buf, line_count, 1, true, &linedata, NULL); + buf_collect_lines(buf, line_count, 1, 0, true, &linedata, NULL, NULL); } args.items[4] = ARRAY_OBJ(linedata); args.items[5] = BOOLEAN_OBJ(false); rpc_send_event(channel_id, "nvim_buf_lines_event", args); + api_free_array(args); // TODO(bfredl): no } else { buf_updates_changedtick_single(buf, channel_id); } @@ -91,10 +104,8 @@ bool buf_updates_active(buf_T *buf) void buf_updates_send_end(buf_T *buf, uint64_t channelid) { - Array args = ARRAY_DICT_INIT; - args.size = 1; - args.items = xcalloc(args.size, sizeof(Object)); - args.items[0] = BUFFER_OBJ(buf->handle); + MAXSIZE_TEMP_ARRAY(args, 1); + ADD_C(args, BUFFER_OBJ(buf->handle)); rpc_send_event(channelid, "nvim_buf_detach_event", args); } @@ -135,6 +146,15 @@ void buf_updates_unregister(buf_T *buf, uint64_t channelid) } } +void buf_free_callbacks(buf_T *buf) +{ + kv_destroy(buf->update_channels); + for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) { + buffer_update_callbacks_free(kv_A(buf->update_callbacks, i)); + } + kv_destroy(buf->update_callbacks); +} + void buf_updates_unload(buf_T *buf, bool can_reload) { size_t size = kv_size(buf->update_channels); @@ -230,8 +250,8 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added, STATIC_ASSERT(SIZE_MAX >= MAXLNUM, "size_t smaller than MAXLNUM"); linedata.size = (size_t)num_added; linedata.items = xcalloc((size_t)num_added, sizeof(Object)); - buf_collect_lines(buf, (size_t)num_added, firstline, true, &linedata, - NULL); + buf_collect_lines(buf, (size_t)num_added, firstline, 0, true, &linedata, + NULL, NULL); } args.items[4] = ARRAY_OBJ(linedata); args.items[5] = BOOLEAN_OBJ(false); @@ -241,6 +261,7 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added, // the end. badchannelid = channelid; } + api_free_array(args); // TODO(bfredl): no } // We can only ever remove one dead channel at a time. This is OK because the @@ -285,14 +306,13 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added, args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits); } textlock++; - Object res = nlua_call_ref(cb.on_lines, "lines", args, true, NULL); + Object res = nlua_call_ref(cb.on_lines, "lines", args, false, NULL); textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { buffer_update_callbacks_free(cb); keep = false; } - api_free_object(res); } if (keep) { kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i); @@ -335,7 +355,7 @@ void buf_updates_send_splice(buf_T *buf, int start_row, colnr_T start_col, bcoun ADD_C(args, INTEGER_OBJ(new_byte)); textlock++; - Object res = nlua_call_ref(cb.on_bytes, "bytes", args, true, NULL); + Object res = nlua_call_ref(cb.on_bytes, "bytes", args, false, NULL); textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { @@ -371,14 +391,13 @@ void buf_updates_changedtick(buf_T *buf) textlock++; Object res = nlua_call_ref(cb.on_changedtick, "changedtick", - args, true, NULL); + args, false, NULL); textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { buffer_update_callbacks_free(cb); keep = false; } - api_free_object(res); } if (keep) { kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i); @@ -389,15 +408,13 @@ void buf_updates_changedtick(buf_T *buf) void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id) { - Array args = ARRAY_DICT_INIT; - args.size = 2; - args.items = xcalloc(args.size, sizeof(Object)); + MAXSIZE_TEMP_ARRAY(args, 2); // the first argument is always the buffer handle - args.items[0] = BUFFER_OBJ(buf->handle); + ADD_C(args, BUFFER_OBJ(buf->handle)); // next argument is b:changedtick - args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf)); + ADD_C(args, INTEGER_OBJ(buf_get_changedtick(buf))); // don't try and clean up dead channels here rpc_send_event(channel_id, "nvim_buf_changedtick_event", args); diff --git a/src/nvim/buffer_updates.h b/src/nvim/buffer_updates.h index 3c2635be71..961fec879b 100644 --- a/src/nvim/buffer_updates.h +++ b/src/nvim/buffer_updates.h @@ -1,8 +1,8 @@ #ifndef NVIM_BUFFER_UPDATES_H #define NVIM_BUFFER_UPDATES_H -#include "nvim/buffer_defs.h" // for buf_T -#include "nvim/extmark.h" // for bcount_t +#include "nvim/buffer_defs.h" +#include "nvim/extmark.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "buffer_updates.h.generated.h" diff --git a/src/nvim/change.c b/src/nvim/change.c index 5184dc0689..06696610b0 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -3,8 +3,16 @@ /// change.c: functions related to changing text +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> + +#include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/charset.h" @@ -13,21 +21,35 @@ #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/extmark.h" -#include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" +#include "nvim/highlight_defs.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/insexpand.h" +#include "nvim/macros.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/os/time.h" #include "nvim/plines.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" +#include "nvim/strings.h" +#include "nvim/textformat.h" #include "nvim/ui.h" #include "nvim/undo.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "change.c.generated.h" @@ -96,8 +118,7 @@ void changed(void) // Create a swap file if that is wanted. // Don't do this for "nofile" and "nowrite" buffer types. - if (curbuf->b_may_swap - && !bt_dontwrite(curbuf)) { + if (curbuf->b_may_swap && !bt_dontwrite(curbuf)) { bool save_need_wait_return = need_wait_return; need_wait_return = false; @@ -107,7 +128,7 @@ void changed(void) // Wait two seconds, to make sure the user reads this unexpected // message. Since we could be anywhere, call wait_return() now, // and don't let the emsg() set msg_scroll. - if (need_wait_return && emsg_silent == 0) { + if (need_wait_return && emsg_silent == 0 && !in_assert_fails) { ui_flush(); os_delay(2002L, true); wait_return(true); @@ -223,8 +244,8 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == curbuf) { // Mark this window to be redrawn later. - if (wp->w_redr_type < VALID) { - wp->w_redr_type = VALID; + if (wp->w_redr_type < UPD_VALID) { + wp->w_redr_type = UPD_VALID; } // Check if a change in the buffer has invalidated the cached @@ -301,17 +322,17 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T // requires a redraw. if (wp->w_p_rnu && xtra != 0) { wp->w_last_cursor_lnum_rnu = 0; - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } // Cursor line highlighting probably need to be updated with - // "VALID" if it's below the change. + // "UPD_VALID" if it's below the change. // If the cursor line is inside the change we need to redraw more. if (wp->w_p_cul) { if (xtra == 0) { - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } else if (lnum <= wp->w_last_cursorline) { - redraw_later(wp, SOME_VALID); + redraw_later(wp, UPD_SOME_VALID); } } } @@ -319,8 +340,8 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T // Call update_screen() later, which checks out what needs to be redrawn, // since it notices b_mod_set and then uses b_mod_*. - if (must_redraw < VALID) { - must_redraw = VALID; + if (must_redraw < UPD_VALID) { + must_redraw = UPD_VALID; } // when the cursor line is changed always trigger CursorMoved @@ -364,7 +385,7 @@ void changed_bytes(linenr_T lnum, colnr_T col) if (curwin->w_p_diff) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_diff && wp != curwin) { - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); linenr_T wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) { changedOneline(wp->w_buffer, wlnum); @@ -397,14 +418,7 @@ void appended_lines(linenr_T lnum, linenr_T count) /// Like appended_lines(), but adjust marks first. void appended_lines_mark(linenr_T lnum, long count) { - // Skip mark_adjust when adding a line after the last one, there can't - // be marks there. But it's still needed in diff mode. - if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { - mark_adjust(lnum + 1, (linenr_T)MAXLNUM, (linenr_T)count, 0L, kExtmarkUndo); - } else { - extmark_adjust(curbuf, lnum + 1, (linenr_T)MAXLNUM, (linenr_T)count, 0L, - kExtmarkUndo); - } + mark_adjust(lnum + 1, (linenr_T)MAXLNUM, (linenr_T)count, 0L, kExtmarkUndo); changed_lines(lnum + 1, 0, lnum + 1, (linenr_T)count, true); } @@ -493,7 +507,7 @@ void changed_lines(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra, bo FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_diff && wp != curwin) { - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) { changed_lines_buf(wp->w_buffer, wlnum, @@ -534,16 +548,69 @@ void unchanged(buf_T *buf, int ff, bool always_inc_changedtick) } } +/// Save the current values of 'fileformat' and 'fileencoding', so that we know +/// the file must be considered changed when the value is different. +void save_file_ff(buf_T *buf) +{ + buf->b_start_ffc = (unsigned char)(*buf->b_p_ff); + buf->b_start_eof = buf->b_p_eof; + buf->b_start_eol = buf->b_p_eol; + buf->b_start_bomb = buf->b_p_bomb; + + // Only use free/alloc when necessary, they take time. + if (buf->b_start_fenc == NULL + || strcmp(buf->b_start_fenc, buf->b_p_fenc) != 0) { + xfree(buf->b_start_fenc); + buf->b_start_fenc = xstrdup(buf->b_p_fenc); + } +} + +/// Return true if 'fileformat' and/or 'fileencoding' has a different value +/// from when editing started (save_file_ff() called). +/// Also when 'endofline' was changed and 'binary' is set, or when 'bomb' was +/// changed and 'binary' is not set. +/// Also when 'endofline' was changed and 'fixeol' is not set. +/// When "ignore_empty" is true don't consider a new, empty buffer to be +/// changed. +bool file_ff_differs(buf_T *buf, bool ignore_empty) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + // In a buffer that was never loaded the options are not valid. + if (buf->b_flags & BF_NEVERLOADED) { + return false; + } + if (ignore_empty + && (buf->b_flags & BF_NEW) + && buf->b_ml.ml_line_count == 1 + && *ml_get_buf(buf, (linenr_T)1, false) == NUL) { + return false; + } + if (buf->b_start_ffc != *buf->b_p_ff) { + return true; + } + if ((buf->b_p_bin || !buf->b_p_fixeol) + && (buf->b_start_eof != buf->b_p_eof || buf->b_start_eol != buf->b_p_eol)) { + return true; + } + if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb) { + return true; + } + if (buf->b_start_fenc == NULL) { + return *buf->b_p_fenc != NUL; + } + return strcmp(buf->b_start_fenc, buf->b_p_fenc) != 0; +} + /// Insert string "p" at the cursor position. Stops at a NUL byte. /// Handles Replace mode and multi-byte characters. -void ins_bytes(char_u *p) +void ins_bytes(char *p) { - ins_bytes_len(p, STRLEN(p)); + ins_bytes_len(p, strlen(p)); } /// Insert string "p" with length "len" at the cursor position. /// Handles Replace mode and multi-byte characters. -void ins_bytes_len(char_u *p, size_t len) +void ins_bytes_len(char *p, size_t len) { size_t n; for (size_t i = 0; i < len; i += n) { @@ -560,18 +627,18 @@ void ins_bytes_len(char_u *p, size_t len) /// convert bytes to a character. void ins_char(int c) { - char_u buf[MB_MAXBYTES + 1]; - size_t n = (size_t)utf_char2bytes(c, (char *)buf); + char buf[MB_MAXBYTES + 1]; + size_t n = (size_t)utf_char2bytes(c, buf); // When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte. // Happens for CTRL-Vu9900. if (buf[0] == 0) { buf[0] = '\n'; } - ins_char_bytes((char_u *)buf, n); + ins_char_bytes(buf, n); } -void ins_char_bytes(char_u *buf, size_t charlen) +void ins_char_bytes(char *buf, size_t charlen) { // Break tabs if needed. if (virtual_active() && curwin->w_cursor.coladd > 0) { @@ -580,8 +647,8 @@ void ins_char_bytes(char_u *buf, size_t charlen) size_t col = (size_t)curwin->w_cursor.col; linenr_T lnum = curwin->w_cursor.lnum; - char_u *oldp = ml_get(lnum); - size_t linelen = STRLEN(oldp) + 1; // length of old line including NUL + char *oldp = ml_get(lnum); + size_t linelen = strlen(oldp) + 1; // length of old line including NUL // The lengths default to the values for when not replacing. size_t oldlen = 0; // nr of bytes inserted @@ -610,7 +677,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) if (vcol > new_vcol && oldp[col + oldlen] == TAB) { break; } - oldlen += (size_t)utfc_ptr2len((char *)oldp + col + oldlen); + oldlen += (size_t)utfc_ptr2len(oldp + col + oldlen); // Deleted a bit too much, insert spaces. if (vcol > new_vcol) { newlen += (size_t)(vcol - new_vcol); @@ -619,7 +686,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) curwin->w_p_list = old_list; } else if (oldp[col] != NUL) { // normal replace - oldlen = (size_t)utfc_ptr2len((char *)oldp + col); + oldlen = (size_t)utfc_ptr2len(oldp + col); } // Push the replaced bytes onto the replace stack, so that they can be @@ -632,7 +699,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } } - char_u *newp = xmalloc(linelen + newlen - oldlen); + char *newp = xmalloc(linelen + newlen - oldlen); // Copy bytes before the cursor. if (col > 0) { @@ -640,7 +707,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } // Copy bytes after the changed character(s). - char_u *p = newp + col; + char *p = newp + col; if (linelen > col + oldlen) { memmove(p + newlen, oldp + col + oldlen, (size_t)(linelen - col - oldlen)); @@ -655,7 +722,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } // Replace the line in the buffer. - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); // mark the buffer as changed and prepare for displaying inserted_bytes(lnum, (colnr_T)col, (int)oldlen, (int)newlen); @@ -665,7 +732,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) if (p_sm && (State & MODE_INSERT) && msg_silent == 0 && !ins_compl_active()) { - showmatch(utf_ptr2char((char *)buf)); + showmatch(utf_ptr2char(buf)); } if (!p_ri || (State & REPLACE_FLAG)) { @@ -678,9 +745,9 @@ void ins_char_bytes(char_u *buf, size_t charlen) /// Insert a string at the cursor position. /// Note: Does NOT handle Replace mode. /// Caller must have prepared for undo. -void ins_str(char_u *s) +void ins_str(char *s) { - int newlen = (int)STRLEN(s); + int newlen = (int)strlen(s); linenr_T lnum = curwin->w_cursor.lnum; if (virtual_active() && curwin->w_cursor.coladd > 0) { @@ -688,10 +755,10 @@ void ins_str(char_u *s) } colnr_T col = curwin->w_cursor.col; - char_u *oldp = ml_get(lnum); - int oldlen = (int)STRLEN(oldp); + char *oldp = ml_get(lnum); + int oldlen = (int)strlen(oldp); - char_u *newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1); + char *newp = xmalloc((size_t)oldlen + (size_t)newlen + 1); if (col > 0) { memmove(newp, oldp, (size_t)col); } @@ -699,7 +766,7 @@ void ins_str(char_u *s) int bytes = oldlen - col + 1; assert(bytes >= 0); memmove(newp + col + newlen, oldp + col, (size_t)bytes); - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); inserted_bytes(lnum, col, 0, newlen); curwin->w_cursor.col += newlen; } @@ -723,9 +790,9 @@ int del_char(bool fixpos) int del_chars(long count, int fixpos) { int bytes = 0; - char_u *p = get_cursor_pos_ptr(); + char *p = get_cursor_pos_ptr(); for (long i = 0; i < count && *p != NUL; i++) { - int l = utfc_ptr2len((char *)p); + int l = utfc_ptr2len(p); bytes += l; p += l; } @@ -746,8 +813,8 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) linenr_T lnum = curwin->w_cursor.lnum; colnr_T col = curwin->w_cursor.col; bool fixpos = fixpos_arg; - char_u *oldp = ml_get(lnum); - colnr_T oldlen = (colnr_T)STRLEN(oldp); + char *oldp = ml_get(lnum); + colnr_T oldlen = (colnr_T)strlen(oldp); // Can't do anything when the cursor is on the NUL after the line. if (col >= oldlen) { @@ -766,7 +833,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) // If 'delcombine' is set and deleting (less than) one character, only // delete the last combining character. if (p_deco && use_delcombine - && utfc_ptr2len((char *)oldp + col) >= count) { + && utfc_ptr2len(oldp + col) >= count) { int cc[MAX_MCO]; (void)utfc_ptr2char(oldp + col, cc); @@ -775,7 +842,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) int n = col; do { col = n; - count = utf_ptr2len((char *)oldp + n); + count = utf_ptr2len(oldp + n); n += count; } while (utf_composinglike(oldp + col, oldp + n)); fixpos = false; @@ -801,7 +868,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) // If the old line has been allocated the deletion can be done in the // existing line. Otherwise a new line has to be allocated. bool was_alloced = ml_line_alloced(); // check if oldp was allocated - char_u *newp; + char *newp; if (was_alloced) { ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, oldlen); newp = oldp; // use same allocated memory @@ -811,7 +878,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) } memmove(newp + col, oldp + col + count, (size_t)movelen); if (!was_alloced) { - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); } // mark the buffer as changed and prepare for displaying @@ -823,10 +890,10 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) /// Copy the indent from ptr to the current line (and fill to size). /// Leaves the cursor on the first non-blank in the line. /// @return true if the line was changed. -int copy_indent(int size, char_u *src) +int copy_indent(int size, char *src) { - char_u *p = NULL; - char_u *line = NULL; + char *p = NULL; + char *line = NULL; int ind_len; int line_len = 0; int tab_pad; @@ -838,7 +905,7 @@ int copy_indent(int size, char_u *src) ind_len = 0; int ind_done = 0; int ind_col = 0; - char_u *s = src; + char *s = src; // Count/copy the usable portion of the source line. while (todo > 0 && ascii_iswhite(*s)) { @@ -911,7 +978,7 @@ int copy_indent(int size, char_u *src) if (p == NULL) { // Allocate memory for the result: the copied indent, new indent // and the rest of the line. - line_len = (int)STRLEN(get_cursor_line_ptr()) + 1; + line_len = (int)strlen(get_cursor_line_ptr()) + 1; assert(ind_len + line_len >= 0); size_t line_size; STRICT_ADD(ind_len, line_len, &line_size, size_t); @@ -924,7 +991,7 @@ int copy_indent(int size, char_u *src) memmove(p, get_cursor_line_ptr(), (size_t)line_len); // Replace the line - ml_replace(curwin->w_cursor.lnum, (char *)line, false); + ml_replace(curwin->w_cursor.lnum, line, false); // Put the cursor after the indent. curwin->w_cursor.col = ind_len; @@ -948,15 +1015,15 @@ int copy_indent(int size, char_u *src) /// "second_line_indent": indent for after ^^D in Insert mode or if flag /// OPENLINE_COM_LIST /// "did_do_comment" is set to true when intentionally putting the comment -/// leader in fromt of the new line. +/// leader in front of the new line. /// /// @param dir FORWARD or BACKWARD /// /// @return true on success, false on failure int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) { - char_u *next_line = NULL; // copy of the next line - char_u *p_extra = NULL; // what goes to next line + char *next_line = NULL; // copy of the next line + char *p_extra = NULL; // what goes to next line colnr_T less_cols = 0; // less columns for mark in new line colnr_T less_cols_off = 0; // columns to skip for mark adjust pos_T old_cursor; // old cursor position @@ -969,9 +1036,9 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) int comment_start = 0; // start index of the comment leader char *lead_flags; // position in 'comments' for comment leader char *leader = NULL; // copy of comment leader - char_u *allocated = NULL; // allocated memory + char *allocated = NULL; // allocated memory char *p; - char_u saved_char = NUL; // init for GCC + char saved_char = NUL; // init for GCC pos_T *pos; bool do_si = may_do_si(); bool do_cindent; @@ -985,7 +1052,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) colnr_T mincol = curwin->w_cursor.col + 1; // make a copy of the current line so we can mess with it - char_u *saved_line = vim_strsave(get_cursor_line_ptr()); + char *saved_line = xstrdup(get_cursor_line_ptr()); if (State & VREPLACE_FLAG) { // With MODE_VREPLACE we make a copy of the next line, which we will be @@ -996,9 +1063,9 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // the line, replacing what was there before and pushing the right // stuff onto the replace stack. -- webb. if (curwin->w_cursor.lnum < orig_line_count) { - next_line = vim_strsave(ml_get(curwin->w_cursor.lnum + 1)); + next_line = xstrdup(ml_get(curwin->w_cursor.lnum + 1)); } else { - next_line = vim_strsave((char_u *)""); + next_line = xstrdup(""); } // In MODE_VREPLACE state, a NL replaces the rest of the line, and @@ -1008,9 +1075,9 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // autoindent etc) a bit later. replace_push(NUL); // Call twice because BS over NL expects it replace_push(NUL); - p = (char *)saved_line + curwin->w_cursor.col; + p = saved_line + curwin->w_cursor.col; while (*p != NUL) { - p += replace_push_mb((char_u *)p); + p += replace_push_mb(p); } saved_line[curwin->w_cursor.col] = NUL; } @@ -1018,10 +1085,10 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if ((State & MODE_INSERT) && (State & VREPLACE_FLAG) == 0) { p_extra = saved_line + curwin->w_cursor.col; if (do_si) { // need first char after new line break - p = skipwhite((char *)p_extra); + p = skipwhite(p_extra); first_char = (unsigned char)(*p); } - extra_len = (int)STRLEN(p_extra); + extra_len = (int)strlen(p_extra); saved_char = *p_extra; *p_extra = NUL; } @@ -1058,7 +1125,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) char *ptr; old_cursor = curwin->w_cursor; - ptr = (char *)saved_line; + ptr = saved_line; if (flags & OPENLINE_DO_COM) { lead_len = get_leader_len(ptr, NULL, false, true); } else { @@ -1068,7 +1135,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Skip preprocessor directives, unless they are recognised as comments. if (lead_len == 0 && ptr[0] == '#') { while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) { - ptr = (char *)ml_get(--curwin->w_cursor.lnum); + ptr = ml_get(--curwin->w_cursor.lnum); } newindent = get_indent(); } @@ -1104,7 +1171,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } } else { // Not a comment line // Find last non-blank in line - p = ptr + STRLEN(ptr) - 1; + p = ptr + strlen(ptr) - 1; while (p > ptr && ascii_iswhite(*p)) { p--; } @@ -1130,7 +1197,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if ((pos = findmatch(NULL, '(')) != NULL) { curwin->w_cursor.lnum = pos->lnum; newindent = get_indent(); - ptr = (char *)get_cursor_line_ptr(); + ptr = get_cursor_line_ptr(); } } // If last character is '{' do indent, without @@ -1142,7 +1209,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Don't do this if the previous line ended in ';' or // '}'. } else if (last_char != ';' && last_char != '}' - && cin_is_cinword((char_u *)ptr)) { + && cin_is_cinword(ptr)) { did_si = true; } } @@ -1154,12 +1221,12 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) while ((ptr[0] == '#' || was_backslashed) && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - if (*ptr && ptr[STRLEN(ptr) - 1] == '\\') { + if (*ptr && ptr[strlen(ptr) - 1] == '\\') { was_backslashed = true; } else { was_backslashed = false; } - ptr = (char *)ml_get(++curwin->w_cursor.lnum); + ptr = ml_get(++curwin->w_cursor.lnum); } if (was_backslashed) { newindent = 0; // Got to end of file @@ -1192,13 +1259,13 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // This may then be inserted in front of the new line. end_comment_pending = NUL; if (flags & OPENLINE_DO_COM) { - lead_len = get_leader_len((char *)saved_line, &lead_flags, dir == BACKWARD, true); + lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true); if (lead_len == 0 && curbuf->b_p_cin && do_cindent && dir == FORWARD && (!has_format_option(FO_NO_OPEN_COMS) || (flags & OPENLINE_FORMAT))) { // Check for a line comment after code. comment_start = check_linecomment(saved_line); if (comment_start != MAXCOL) { - lead_len = get_leader_len((char *)saved_line + comment_start, &lead_flags, false, true); + lead_len = get_leader_len(saved_line + comment_start, &lead_flags, false, true); if (lead_len != 0) { lead_len += comment_start; if (did_do_comment != NULL) { @@ -1213,13 +1280,13 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if (lead_len > 0) { char *lead_repl = NULL; // replaces comment leader int lead_repl_len = 0; // length of *lead_repl - char_u lead_middle[COM_MAX_LEN]; // middle-comment string - char_u lead_end[COM_MAX_LEN]; // end-comment string - char_u *comment_end = NULL; // where lead_end has been found + char lead_middle[COM_MAX_LEN]; // middle-comment string + char lead_end[COM_MAX_LEN]; // end-comment string + char *comment_end = NULL; // where lead_end has been found int extra_space = false; // append extra space int current_flag; int require_blank = false; // requires blank after middle - char_u *p2; + char *p2; // If the comment leader has the start, middle or end flag, it may not // be used or may be replaced with the middle leader. @@ -1261,15 +1328,15 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) size_t n = copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ","); if (end_comment_pending == -1) { // we can set it now - end_comment_pending = lead_end[n - 1]; + end_comment_pending = (unsigned char)lead_end[n - 1]; } // If the end of the comment is in the same line, don't use // the comment leader. if (dir == FORWARD) { - for (p = (char *)saved_line + lead_len; *p; p++) { - if (STRNCMP(p, lead_end, n) == 0) { - comment_end = (char_u *)p; + for (p = saved_line + lead_len; *p; p++) { + if (strncmp(p, lead_end, n) == 0) { + comment_end = p; lead_len = 0; break; } @@ -1280,7 +1347,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if (lead_len > 0) { if (current_flag == COM_START) { lead_repl = (char *)lead_middle; - lead_repl_len = (int)STRLEN(lead_middle); + lead_repl_len = (int)strlen(lead_middle); } // If we have hit RETURN immediately after the start @@ -1302,17 +1369,17 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Remember where the end is, might want to use it to find the // start (for C-comments). if (dir == FORWARD) { - comment_end = (char_u *)skipwhite((char *)saved_line); + comment_end = skipwhite(saved_line); lead_len = 0; break; } // Doing "O" on the end of a comment inserts the middle leader. // Find the string for the middle leader, searching backwards. - while (p > (char *)curbuf->b_p_com && *p != ',') { + while (p > curbuf->b_p_com && *p != ',') { p--; } - for (lead_repl = p; lead_repl > (char *)curbuf->b_p_com + for (lead_repl = p; lead_repl > curbuf->b_p_com && lead_repl[-1] != ':'; lead_repl--) {} lead_repl_len = (int)(p - lead_repl); @@ -1321,7 +1388,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) extra_space = true; // Check whether we allow automatic ending of comments - for (p2 = (char_u *)p; *p2 && *p2 != ':'; p2++) { + for (p2 = p; *p2 && *p2 != ':'; p2++) { if (*p2 == COM_AUTO_END) { end_comment_pending = -1; // means we want to set it } @@ -1331,7 +1398,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) while (*p2 && *p2 != ',') { p2++; } - end_comment_pending = p2[-1]; + end_comment_pending = (unsigned char)p2[-1]; } break; } @@ -1357,9 +1424,9 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) + 1; assert(bytes >= 0); leader = xmalloc((size_t)bytes); - allocated = (char_u *)leader; // remember to free it later + allocated = leader; // remember to free it later - STRLCPY(leader, saved_line, lead_len + 1); + xstrlcpy(leader, saved_line, (size_t)lead_len + 1); // TODO(vim): handle multi-byte and double width chars for (int li = 0; li < comment_start; li++) { @@ -1391,7 +1458,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Compute the length of the replaced characters in // screen characters, not bytes. { - int repl_size = vim_strnsize((char_u *)lead_repl, lead_repl_len); + int repl_size = vim_strnsize(lead_repl, lead_repl_len); int old_size = 0; char *endp = p; int l; @@ -1414,7 +1481,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // blank-out any other chars from the old leader. while (--p >= leader) { - int l = utf_head_off((char_u *)leader, (char_u *)p); + int l = utf_head_off(leader, p); if (l > 1) { p -= l; @@ -1436,13 +1503,13 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // screen characters, not bytes. Move the part that is // not to be overwritten. { - int repl_size = vim_strnsize((char_u *)lead_repl, lead_repl_len); + int repl_size = vim_strnsize(lead_repl, lead_repl_len); int i; int l; for (i = 0; i < lead_len && p[i] != NUL; i += l) { l = utfc_ptr2len(p + i); - if (vim_strnsize((char_u *)p, i + l) > repl_size) { + if (vim_strnsize(p, i + l) > repl_size) { break; } } @@ -1485,7 +1552,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // Recompute the indent, it may have changed. if (curbuf->b_p_ai || do_si) { - newindent = get_indent_str_vtab((char_u *)leader, + newindent = get_indent_str_vtab(leader, curbuf->b_p_ts, curbuf->b_p_vts_array, false); } @@ -1568,7 +1635,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) { while ((*p_extra == ' ' || *p_extra == '\t') - && !utf_iscomposing(utf_ptr2char((char *)p_extra + 1))) { + && !utf_iscomposing(utf_ptr2char(p_extra + 1))) { if (REPLACE_NORMAL(State)) { replace_push(*p_extra); } @@ -1582,7 +1649,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } if (p_extra == NULL) { - p_extra = (char_u *)""; // append empty line + p_extra = ""; // append empty line } // concatenate leader and p_extra, if there is a leader @@ -1590,7 +1657,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if (flags & OPENLINE_COM_LIST && second_line_indent > 0) { int i; int padding = second_line_indent - - (newindent + (int)STRLEN(leader)); + - (newindent + (int)strlen(leader)); // Here whitespace is inserted after the comment char. // Below, set_indent(newindent, SIN_INSERT) will insert the @@ -1602,7 +1669,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) } } STRCAT(leader, p_extra); - p_extra = (char_u *)leader; + p_extra = leader; did_ai = true; // So truncating blanks works with comments less_cols -= lead_len; } else { @@ -1615,18 +1682,12 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) curwin->w_cursor.lnum--; } if ((State & VREPLACE_FLAG) == 0 || old_cursor.lnum >= orig_line_count) { - if (ml_append(curwin->w_cursor.lnum, (char *)p_extra, (colnr_T)0, false) == FAIL) { + if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) { goto theend; } // Postpone calling changed_lines(), because it would mess up folding // with markers. - // Skip mark_adjust when adding a line after the last one, there can't - // be marks there. But still needed in diff mode. - if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count - || curwin->w_p_diff) { - mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, - kExtmarkNOOP); - } + mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, kExtmarkNOOP); did_append = true; } else { // In MODE_VREPLACE state we are starting to replace the next line. @@ -1637,7 +1698,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) (void)u_save_cursor(); // errors are ignored! vr_lines_changed++; } - ml_replace(curwin->w_cursor.lnum, (char *)p_extra, true); + ml_replace(curwin->w_cursor.lnum, p_extra, true); changed_bytes(curwin->w_cursor.lnum, 0); // TODO(vigoux): extmark_splice_cols here?? curwin->w_cursor.lnum--; @@ -1702,9 +1763,9 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) { truncate_spaces(saved_line); } - ml_replace(curwin->w_cursor.lnum, (char *)saved_line, false); + ml_replace(curwin->w_cursor.lnum, saved_line, false); - int new_len = (int)STRLEN(saved_line); + int new_len = (int)strlen(saved_line); // TODO(vigoux): maybe there is issues there with expandtabs ? int cols_spliced = 0; @@ -1744,7 +1805,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) if (did_append) { changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true); // bail out and just get the final length of the line we just manipulated - bcount_t extra = (bcount_t)STRLEN(ml_get(curwin->w_cursor.lnum)); + bcount_t extra = (bcount_t)strlen(ml_get(curwin->w_cursor.lnum)); extmark_splice(curbuf, (int)curwin->w_cursor.lnum - 1, 0, 0, 0, 0, 1, 0, 1 + extra, kExtmarkUndo); } @@ -1763,19 +1824,19 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) vreplace_mode = 0; } - // May do lisp indenting. - if (!p_paste - && leader == NULL - && curbuf->b_p_lisp - && curbuf->b_p_ai) { - fixthisline(get_lisp_indent); - ai_col = (colnr_T)getwhitecols_curline(); - } - - // May do indenting after opening a new line. - if (do_cindent) { - do_c_expr_indent(); - ai_col = (colnr_T)getwhitecols_curline(); + if (!p_paste) { + if (leader == NULL + && !use_indentexpr_for_lisp() + && curbuf->b_p_lisp + && curbuf->b_p_ai) { + // do lisp indenting + fixthisline(get_lisp_indent); + ai_col = (colnr_T)getwhitecols_curline(); + } else if (do_cindent || (curbuf->b_p_ai && use_indentexpr_for_lisp())) { + // do 'cindent' or 'indentexpr' indenting + do_c_expr_indent(); + ai_col = (colnr_T)getwhitecols_curline(); + } } if (vreplace_mode != 0) { @@ -1787,10 +1848,10 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) // stuff onto the replace stack (via ins_char()). if (State & VREPLACE_FLAG) { // Put new line in p_extra - p_extra = vim_strsave(get_cursor_line_ptr()); + p_extra = xstrdup(get_cursor_line_ptr()); // Put back original line - ml_replace(curwin->w_cursor.lnum, (char *)next_line, false); + ml_replace(curwin->w_cursor.lnum, next_line, false); // Insert new stuff into line again curwin->w_cursor.col = 0; @@ -1814,16 +1875,16 @@ theend: /// If "fixpos" is true fix the cursor position when done. void truncate_line(int fixpos) { - char_u *newp; + char *newp; linenr_T lnum = curwin->w_cursor.lnum; colnr_T col = curwin->w_cursor.col; if (col == 0) { - newp = vim_strsave((char_u *)""); + newp = xstrdup(""); } else { - newp = vim_strnsave(ml_get(lnum), (size_t)col); + newp = xstrnsave(ml_get(lnum), (size_t)col); } - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); // mark the buffer as changed and prepare for displaying changed_bytes(lnum, curwin->w_cursor.col); @@ -1900,7 +1961,7 @@ int get_leader_len(char *line, char **flags, bool backward, bool include_space) while (line[i] != NUL) { // scan through the 'comments' option for a match int found_one = false; - for (list = (char *)curbuf->b_p_com; *list;) { + for (list = curbuf->b_p_com; *list;) { // Get one option part into part_buf[]. Advance "list" to next // one. Put "string" at start of string. if (!got_com && flags != NULL) { @@ -2033,11 +2094,11 @@ int get_last_leader_offset(char *line, char **flags) char part_buf[COM_MAX_LEN]; // buffer for one option part // Repeat to match several nested comment strings. - int i = (int)STRLEN(line); + int i = (int)strlen(line); while (--i >= lower_check_bound) { // scan through the 'comments' option for a match int found_one = false; - for (list = (char *)curbuf->b_p_com; *list;) { + for (list = curbuf->b_p_com; *list;) { char *flags_save = list; // Get one option part into part_buf[]. Advance list to next one. @@ -2120,9 +2181,9 @@ int get_last_leader_offset(char *line, char **flags) while (ascii_iswhite(*com_leader)) { com_leader++; } - len1 = (int)STRLEN(com_leader); + len1 = (int)strlen(com_leader); - for (list = (char *)curbuf->b_p_com; *list;) { + for (list = curbuf->b_p_com; *list;) { char *flags_save = list; (void)copy_option_part(&list, part_buf2, COM_MAX_LEN, ","); @@ -2134,7 +2195,7 @@ int get_last_leader_offset(char *line, char **flags) while (ascii_iswhite(*string)) { string++; } - len2 = (int)STRLEN(string); + len2 = (int)strlen(string); if (len2 == 0) { continue; } @@ -2143,7 +2204,7 @@ int get_last_leader_offset(char *line, char **flags) // beginning the com_leader. for (off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) { off--; - if (!STRNCMP(string + off, com_leader, len2 - off)) { + if (!strncmp(string + off, com_leader, (size_t)(len2 - off))) { if (i - off < lower_check_bound) { lower_check_bound = i - off; } diff --git a/src/nvim/change.h b/src/nvim/change.h index fdfa8a29ec..d1d016c630 100644 --- a/src/nvim/change.h +++ b/src/nvim/change.h @@ -1,8 +1,8 @@ #ifndef NVIM_CHANGE_H #define NVIM_CHANGE_H -#include "nvim/buffer_defs.h" // for buf_T -#include "nvim/pos.h" // for linenr_T +#include "nvim/buffer_defs.h" +#include "nvim/pos.h" // flags for open_line() #define OPENLINE_DELSPACES 0x01 // delete spaces after cursor diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 5910053025..65bb87bc2c 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -1,24 +1,42 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> +#include <inttypes.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include "lauxlib.h" #include "nvim/api/private/converter.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/ui.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/channel.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" +#include "nvim/eval/typval.h" +#include "nvim/event/loop.h" +#include "nvim/event/rstream.h" #include "nvim/event/socket.h" +#include "nvim/event/wstream.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" +#include "nvim/main.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" -#include "nvim/os/fs.h" +#include "nvim/os/os_defs.h" #include "nvim/os/shell.h" -#ifdef WIN32 +#include "nvim/rbuffer.h" +#ifdef MSWIN # include "nvim/os/os_win_console.h" # include "nvim/os/pty_conpty_win.h" #endif -#include "nvim/ascii.h" #include "nvim/path.h" static bool did_stdio = false; @@ -197,7 +215,7 @@ void channel_create_event(Channel *chan, const char *ext_source) // external events should be included. source = ext_source; } else { - eval_fmt_source_name_line((char *)IObuff, sizeof(IObuff)); + eval_fmt_source_name_line(IObuff, sizeof(IObuff)); source = (const char *)IObuff; } @@ -359,6 +377,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader } else { has_out = rpc || callback_reader_set(chan->on_data); has_err = callback_reader_set(chan->on_stderr); + proc->fwd_err = chan->on_stderr.fwd_err; } switch (stdin_mode) { @@ -492,7 +511,7 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, const char **err int stdin_dup_fd = STDIN_FILENO; int stdout_dup_fd = STDOUT_FILENO; -#ifdef WIN32 +#ifdef MSWIN // Strangely, ConPTY doesn't work if stdin and stdout are pipes. So replace // stdin and stdout with CONIN$ and CONOUT$, respectively. if (embedded_mode && os_has_conpty_working()) { @@ -501,6 +520,13 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, const char **err stdout_dup_fd = os_dup(STDOUT_FILENO); os_replace_stdout_and_stderr_to_conout(); } +#else + if (embedded_mode) { + stdin_dup_fd = dup(STDIN_FILENO); + stdout_dup_fd = dup(STDOUT_FILENO); + dup2(STDERR_FILENO, STDOUT_FILENO); + dup2(STDERR_FILENO, STDIN_FILENO); + } #endif rstream_init_fd(&main_loop, &channel->stream.stdio.in, stdin_dup_fd, 0); wstream_init_fd(&main_loop, &channel->stream.stdio.out, stdout_dup_fd, 0); diff --git a/src/nvim/channel.h b/src/nvim/channel.h index 0f1b481792..5743eaead5 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -1,13 +1,26 @@ #ifndef NVIM_CHANNEL_H #define NVIM_CHANNEL_H +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/event/libuv_process.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/process.h" #include "nvim/event/socket.h" +#include "nvim/event/stream.h" +#include "nvim/garray.h" +#include "nvim/macros.h" #include "nvim/main.h" +#include "nvim/map.h" +#include "nvim/map_defs.h" #include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/os/pty_process.h" +#include "nvim/terminal.h" +#include "nvim/types.h" #define CHAN_STDIO 1 #define CHAN_STDERR 2 @@ -53,6 +66,7 @@ typedef struct { garray_T buffer; bool eof; bool buffered; + bool fwd_err; const char *type; } CallbackReader; @@ -60,6 +74,7 @@ typedef struct { .self = NULL, \ .buffer = GA_EMPTY_INIT_VALUE, \ .buffered = false, \ + .fwd_err = false, \ .type = NULL }) static inline bool callback_reader_set(CallbackReader reader) { diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 6238d85b3a..5aec9ccf9d 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -6,26 +6,35 @@ /// Code related to character sets. #include <assert.h> +#include <errno.h> #include <inttypes.h> +#include <limits.h> +#include <stdlib.h> #include <string.h> -#include <wctype.h> +#include "auto/config.h" +#include "klib/kvec.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" -#include "nvim/func_attr.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/garray.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/indent.h" -#include "nvim/main.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/option.h" -#include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/plines.h" +#include "nvim/pos.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/vim.h" @@ -137,7 +146,7 @@ int buf_init_chartab(buf_T *buf, int global) // options Each option is a list of characters, character numbers or // ranges, separated by commas, e.g.: "200-210,x,#-178,-" for (i = global ? 0 : 3; i <= 3; i++) { - const char_u *p; + const char *p; if (i == 0) { // first round: 'isident' p = p_isi; @@ -245,7 +254,7 @@ int buf_init_chartab(buf_T *buf, int global) c++; } - c = *p; + c = (uint8_t)(*p); p = skip_to_option_part(p); if ((c == ',') && (*p == NUL)) { @@ -267,8 +276,8 @@ int buf_init_chartab(buf_T *buf, int global) /// @param bufsize void trans_characters(char *buf, int bufsize) { - char_u *trs; // translated character - int len = (int)STRLEN(buf); // length of string needing translation + char *trs; // translated character + int len = (int)strlen(buf); // length of string needing translation int room = bufsize - len; // room in buffer after string while (*buf != 0) { @@ -277,8 +286,8 @@ void trans_characters(char *buf, int bufsize) if ((trs_len = utfc_ptr2len(buf)) > 1) { len -= trs_len; } else { - trs = transchar_byte((uint8_t)(*buf)); - trs_len = (int)STRLEN(trs); + trs = (char *)transchar_byte((uint8_t)(*buf)); + trs_len = (int)strlen(trs); if (trs_len > 1) { room -= trs_len - 1; @@ -313,7 +322,7 @@ size_t transstr_len(const char *const s, bool untab) const size_t l = (size_t)utfc_ptr2len(p); if (l > 1) { int pcc[MAX_MCO + 1]; - pcc[0] = utfc_ptr2char((const char_u *)p, &pcc[1]); + pcc[0] = utfc_ptr2char(p, &pcc[1]); if (vim_isprintc(pcc[0])) { len += l; @@ -359,7 +368,7 @@ size_t transstr_buf(const char *const s, char *const buf, const size_t len, bool break; // Exceeded `buf` size. } int pcc[MAX_MCO + 1]; - pcc[0] = utfc_ptr2char((const char_u *)p, &pcc[1]); + pcc[0] = utfc_ptr2char(p, &pcc[1]); if (vim_isprintc(pcc[0])) { memmove(buf_p, p, l); @@ -411,12 +420,28 @@ char *transstr(const char *const s, bool untab) return buf; } +size_t kv_transstr(StringBuilder *str, const char *const s, bool untab) + FUNC_ATTR_NONNULL_ARG(1) +{ + if (!s) { + return 0; + } + + // Compute the length of the result, taking account of unprintable + // multi-byte characters. + const size_t len = transstr_len(s, untab); + kv_ensure_space(*str, len + 1); + transstr_buf(s, str->items + str->size, len + 1, untab); + str->size += len; // do not include NUL byte + return len; +} + /// Convert the string "str[orglen]" to do ignore-case comparing. /// Use the current locale. /// /// When "buf" is NULL, return an allocated string. /// Otherwise, put the result in buf, limited by buflen, and return buf. -char_u *str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) +char *str_foldcase(char *str, int orglen, char *buf, int buflen) FUNC_ATTR_NONNULL_RET { garray_T ga; @@ -424,7 +449,7 @@ char_u *str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) int len = orglen; #define GA_CHAR(i) ((char *)ga.ga_data)[i] -#define GA_PTR(i) ((char_u *)ga.ga_data + (i)) +#define GA_PTR(i) ((char *)ga.ga_data + (i)) #define STR_CHAR(i) (buf == NULL ? GA_CHAR(i) : buf[i]) #define STR_PTR(i) (buf == NULL ? GA_PTR(i) : buf + (i)) @@ -452,8 +477,8 @@ char_u *str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) // Make each character lower case. i = 0; while (STR_CHAR(i) != NUL) { - int c = utf_ptr2char((char *)STR_PTR(i)); - int olen = utf_ptr2len((char *)STR_PTR(i)); + int c = utf_ptr2char(STR_PTR(i)); + int olen = utf_ptr2len(STR_PTR(i)); int lc = mb_tolower(c); // Only replace the character when it is not an invalid @@ -487,15 +512,15 @@ char_u *str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) } } } - (void)utf_char2bytes(lc, (char *)STR_PTR(i)); + (void)utf_char2bytes(lc, STR_PTR(i)); } // skip to next multi-byte char - i += utfc_ptr2len((char *)STR_PTR(i)); + i += utfc_ptr2len(STR_PTR(i)); } if (buf == NULL) { - return (char_u *)ga.ga_data; + return ga.ga_data; } return buf; } @@ -515,9 +540,9 @@ static char_u transchar_charbuf[11]; /// @param[in] c Character to translate. /// /// @return translated character into a static buffer. -char_u *transchar(int c) +char *transchar(int c) { - return transchar_buf(curbuf, c); + return (char *)transchar_buf(curbuf, c); } char_u *transchar_buf(const buf_T *buf, int c) @@ -559,7 +584,7 @@ char_u *transchar_byte(const int c) transchar_nonprint(curbuf, transchar_charbuf, c); return transchar_charbuf; } - return transchar(c); + return (char_u *)transchar(c); } /// Convert non-printable characters to 2..4 printable ones @@ -650,7 +675,7 @@ static inline unsigned nr2hex(unsigned n) /// /// @param b /// -/// @reeturn Number of display cells. +/// @return Number of display cells. int byte2cells(int b) FUNC_ATTR_PURE { @@ -709,7 +734,7 @@ int ptr2cells(const char *p_in) /// @return number of character cells. int vim_strsize(char *s) { - return vim_strnsize((char_u *)s, MAXCOL); + return vim_strnsize(s, MAXCOL); } /// Return the number of character cells string "s[len]" will take on the @@ -721,13 +746,13 @@ int vim_strsize(char *s) /// @param len /// /// @return Number of character cells. -int vim_strnsize(char_u *s, int len) +int vim_strnsize(char *s, int len) { assert(s != NULL); int size = 0; while (*s != NUL && --len >= 0) { - int l = utfc_ptr2len((char *)s); - size += ptr2cells((char *)s); + int l = utfc_ptr2len(s); + size += ptr2cells(s); s += l; len -= l - 1; } @@ -786,7 +811,7 @@ bool vim_iswordc_buf(const int c, buf_T *const buf) /// @param p pointer to the multi-byte character /// /// @return true if "p" points to a keyword character. -bool vim_iswordp(const char_u *const p) +bool vim_iswordp(const char *const p) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { return vim_iswordp_buf(p, curbuf); @@ -799,13 +824,13 @@ bool vim_iswordp(const char_u *const p) /// @param buf buffer whose keywords to use /// /// @return true if "p" points to a keyword character. -bool vim_iswordp_buf(const char_u *const p, buf_T *const buf) +bool vim_iswordp_buf(const char *const p, buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int c = *p; + int c = (uint8_t)(*p); if (MB_BYTE2LEN(c) > 1) { - c = utf_ptr2char((char *)p); + c = utf_ptr2char(p); } return vim_iswordc_buf(c, buf); } @@ -829,8 +854,8 @@ bool vim_isfilec(int c) bool vim_isfilec_or_wc(int c) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - char_u buf[2]; - buf[0] = (char_u)c; + char buf[2]; + buf[0] = (char)c; buf[1] = NUL; return vim_isfilec(c) || c == ']' || path_has_wildcard(buf); } @@ -905,15 +930,15 @@ bool in_win_border(win_T *wp, colnr_T vcol) /// @param end void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) { - char_u *ptr; // points to current char - char_u *posptr; // points to char at pos->col + char *ptr; // points to current char + char *posptr; // points to char at pos->col int incr; int head; long *vts = wp->w_buffer->b_p_vts_array; int ts = (int)wp->w_buffer->b_p_ts; colnr_T vcol = 0; - char_u *line = ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); // start of the line + char *line = ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); // start of the line if (pos->col == MAXCOL) { // continue until the NUL @@ -930,17 +955,21 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en posptr -= utf_head_off(line, posptr); } + chartabsize_T cts; + init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line); + // This function is used very often, do some speed optimizations. // When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set - // use a simple loop. + // 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) { + && !wp->w_p_bri + && !cts.cts_has_virt_text) { for (;;) { head = 0; - int c = *ptr; + int c = (uint8_t)(*ptr); // make sure we don't go past the end of the line if (c == NUL) { @@ -956,7 +985,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en // 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((char *)ptr); + incr = utf_ptr2cells(ptr); } else { incr = g_chartab[c] & CT_CELL_MASK; } @@ -966,7 +995,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en // cells wide. if ((incr == 2) && wp->w_p_wrap - && (MB_BYTE2LEN(*ptr) > 1) + && (MB_BYTE2LEN((uint8_t)(*ptr)) > 1) && in_win_border(wp, vcol)) { incr++; head = 1; @@ -984,25 +1013,29 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en } else { for (;;) { // A tab gets expanded, depending on the current column + // Other things also take up space. head = 0; - incr = win_lbr_chartabsize(wp, line, ptr, vcol, &head); + incr = win_lbr_chartabsize(&cts, &head); // make sure we don't go past the end of the line - if (*ptr == NUL) { + if (*cts.cts_ptr == NUL) { // NUL at end of line only takes one column incr = 1; break; } - if ((posptr != NULL) && (ptr >= posptr)) { + if ((posptr != NULL) && (cts.cts_ptr >= posptr)) { // character at pos->col break; } - vcol += incr; - MB_PTR_ADV(ptr); + cts.cts_vcol += incr; + MB_PTR_ADV(cts.cts_ptr); } + vcol = cts.cts_vcol; + ptr = cts.cts_ptr; } + clear_chartabsize_arg(&cts); if (start != NULL) { *start = vcol + head; @@ -1013,6 +1046,8 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en } if (cursor != NULL) { + // cursor is after inserted text + vcol += cts.cts_cur_text_width; if ((*ptr == TAB) && (State & MODE_NORMAL) && !wp->w_p_list @@ -1066,10 +1101,10 @@ void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *e colnr_T endadd = 0; // Cannot put the cursor on part of a wide character. - char_u *ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); + char *ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); - if (pos->col < (colnr_T)STRLEN(ptr)) { - int c = utf_ptr2char((char *)ptr + pos->col); + if (pos->col < (colnr_T)strlen(ptr)) { + int c = utf_ptr2char(ptr + pos->col); if ((c != TAB) && vim_isprintc(c)) { endadd = (colnr_T)(char2cells(c) - 1); if (coladd > endadd) { @@ -1147,7 +1182,7 @@ char *skipwhite(const char *const p) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - return (char *)skipwhite_len((char_u *)p, STRLEN(p)); + return skipwhite_len(p, strlen(p)); } /// Like `skipwhite`, but skip up to `len` characters. @@ -1158,14 +1193,14 @@ char *skipwhite(const char *const p) /// /// @return Pointer to character after the skipped whitespace, or the `len`-th /// character in the string. -char_u *skipwhite_len(const char_u *p, size_t len) +char *skipwhite_len(const char *p, size_t len) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { for (; len > 0 && ascii_iswhite(*p); len--) { p++; } - return (char_u *)p; + return (char *)p; } // getwhitecols: return the number of whitespace @@ -1175,10 +1210,10 @@ intptr_t getwhitecols_curline(void) return getwhitecols(get_cursor_line_ptr()); } -intptr_t getwhitecols(const char_u *p) +intptr_t getwhitecols(const char *p) FUNC_ATTR_PURE { - return (char_u *)skipwhite((char *)p) - p; + return skipwhite(p) - p; } /// Skip over digits @@ -1222,10 +1257,10 @@ const char *skipbin(const char *q) /// /// @return Pointer to the character after the skipped digits and hex /// characters. -char_u *skiphex(char_u *q) +char *skiphex(char *q) FUNC_ATTR_PURE { - char_u *p = q; + char *p = q; while (ascii_isxdigit(*p)) { // skip to next non-digit p++; @@ -1238,10 +1273,10 @@ char_u *skiphex(char_u *q) /// @param q /// /// @return Pointer to the digit or (NUL after the string). -char_u *skiptodigit(char_u *q) +char *skiptodigit(char *q) FUNC_ATTR_PURE { - char_u *p = q; + char *p = q; while (*p != NUL && !ascii_isdigit(*p)) { // skip to next digit p++; @@ -1272,10 +1307,10 @@ const char *skiptobin(const char *q) /// @param q /// /// @return Pointer to the hex character or (NUL after the string). -char_u *skiptohex(char_u *q) +char *skiptohex(char *q) FUNC_ATTR_PURE { - char_u *p = q; + char *p = q; while (*p != NUL && !ascii_isxdigit(*p)) { // skip to next digit p++; @@ -1288,13 +1323,13 @@ char_u *skiptohex(char_u *q) /// @param[in] p Text to skip over. /// /// @return Pointer to the next whitespace or NUL character. -char_u *skiptowhite(const char_u *p) +char *skiptowhite(const char *p) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE { while (*p != ' ' && *p != '\t' && *p != NUL) { p++; } - return (char_u *)p; + return (char *)p; } /// skiptowhite_esc: Like skiptowhite(), but also skip escaped chars @@ -1319,16 +1354,16 @@ char *skiptowhite_esc(char *p) /// @param[in] p Text to skip over. /// /// @return Pointer to the next '\n' or NUL character. -char_u *skip_to_newline(const char_u *const p) +char *skip_to_newline(const char *const p) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - return (char_u *)xstrchrnul((const char *)p, NL); + return xstrchrnul(p, NL); } /// Gets a number from a string and skips over it, signalling overflow. /// -/// @param[out] pp A pointer to a pointer to char_u. +/// @param[out] pp A pointer to a pointer to char. /// It will be advanced past the read number. /// @param[out] nr Number read from the string. /// @@ -1345,7 +1380,7 @@ bool try_getdigits(char **pp, intmax_t *nr) /// Gets a number from a string and skips over it. /// -/// @param[out] pp Pointer to a pointer to char_u. +/// @param[out] pp Pointer to a pointer to char. /// It will be advanced past the read number. /// @param strict Abort on overflow. /// @param def Default value, if parsing fails or overflow occurs. @@ -1412,10 +1447,10 @@ int32_t getdigits_int32(char **pp, bool strict, long def) /// Check that "lbuf" is empty or only contains blanks. /// /// @param lbuf line buffer to check -bool vim_isblankline(char_u *lbuf) +bool vim_isblankline(char *lbuf) FUNC_ATTR_PURE { - char_u *p = (char_u *)skipwhite((char *)lbuf); + char *p = skipwhite(lbuf); return *p == NUL || *p == '\r' || *p == '\n'; } @@ -1453,14 +1488,14 @@ bool vim_isblankline(char_u *lbuf) /// @param strict If true, fail if the number has unexpected trailing /// alphanumeric chars: *len is set to 0 and nothing else is /// returned. -void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, +void vim_str2nr(const char *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen, const bool strict) FUNC_ATTR_NONNULL_ARG(1) { - const char *ptr = (const char *)start; + const char *ptr = start; #define STRING_ENDED(ptr) \ - (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) + (!(maxlen == 0 || (int)((ptr) - start) < maxlen)) int pre = 0; // default is decimal const bool negative = (ptr[0] == '-'); uvarnumber_T un = 0; @@ -1512,7 +1547,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, cons } else if ((what & (STR2NR_HEX | STR2NR_OCT | STR2NR_OOCT | STR2NR_BIN)) && !STRING_ENDED(ptr + 1) && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { - pre = (char_u)ptr[1]; + pre = (uint8_t)ptr[1]; // Detect hexadecimal: 0x or 0X followed by hex digit. if ((what & STR2NR_HEX) && !STRING_ENDED(ptr + 2) @@ -1600,7 +1635,7 @@ vim_str2nr_hex: vim_str2nr_proceed: // Check for an alphanumeric character immediately following, that is // most likely a typo. - if (strict && ptr - (const char *)start != maxlen && ASCII_ISALNUM(*ptr)) { + if (strict && ptr - start != maxlen && ASCII_ISALNUM(*ptr)) { return; } @@ -1609,7 +1644,7 @@ vim_str2nr_proceed: } if (len != NULL) { - *len = (int)(ptr - (const char *)start); + *len = (int)(ptr - start); } if (nptr != NULL) { @@ -1654,8 +1689,9 @@ int hex2nr(int c) } /// Convert two hex characters to a byte. -/// Return -1 if one of the characters is not hex. -int hexhex2nr(char_u *p) +/// +/// @return -1 if one of the characters is not hex. +int hexhex2nr(const char *p) FUNC_ATTR_PURE { if (!ascii_isxdigit(p[0]) || !ascii_isxdigit(p[1])) { @@ -1677,17 +1713,17 @@ int hexhex2nr(char_u *p) /// characters. /// /// @param str file path string to check -bool rem_backslash(const char_u *str) +bool rem_backslash(const char *str) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { #ifdef BACKSLASH_IN_FILENAME return str[0] == '\\' - && str[1] < 0x80 + && (uint8_t)str[1] < 0x80 && (str[1] == ' ' || (str[1] != NUL && str[1] != '*' && str[1] != '?' - && !vim_isfilec(str[1]))); + && !vim_isfilec((uint8_t)str[1]))); #else // ifdef BACKSLASH_IN_FILENAME return str[0] == '\\' && str[1] != NUL; @@ -1697,7 +1733,7 @@ bool rem_backslash(const char_u *str) /// Halve the number of backslashes in a file name argument. /// /// @param p -void backslash_halve(char_u *p) +void backslash_halve(char *p) { for (; *p; p++) { if (rem_backslash(p)) { @@ -1711,11 +1747,11 @@ void backslash_halve(char_u *p) /// @param p /// /// @return String with the number of backslashes halved. -char_u *backslash_halve_save(const char_u *p) +char *backslash_halve_save(const char *p) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { // TODO(philix): simplify and improve backslash_halve_save algorithm - char_u *res = vim_strsave(p); + char *res = xstrdup(p); backslash_halve(res); return res; } diff --git a/src/nvim/charset.h b/src/nvim/charset.h index c4e5d9522b..e1ef06ef1d 100644 --- a/src/nvim/charset.h +++ b/src/nvim/charset.h @@ -1,10 +1,13 @@ #ifndef NVIM_CHARSET_H #define NVIM_CHARSET_H +#include <stdbool.h> + #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" #include "nvim/option_defs.h" #include "nvim/pos.h" +#include "nvim/strings.h" #include "nvim/types.h" /// Return the folded-case equivalent of the given character diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c index e82e98ba4e..be815151ef 100644 --- a/src/nvim/cmdexpand.c +++ b/src/nvim/cmdexpand.c @@ -3,9 +3,20 @@ // cmdexpand.c: functions for command-line completion +#include <assert.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "auto/config.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" @@ -13,27 +24,42 @@ #include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid.h" +#include "nvim/hashtab.h" #include "nvim/help.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" -#include "nvim/if_cscope.h" +#include "nvim/keycodes.h" +#include "nvim/locale.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/mapping.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" #include "nvim/menu.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/os/os.h" +#include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/regexp.h" -#include "nvim/screen.h" +#include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/sign.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" @@ -47,7 +73,7 @@ typedef char *(*CompleteListItemGetter)(expand_T *, int); /// Type used by call_user_expand_func -typedef void *(*user_expand_func_T)(const char_u *, int, typval_T *); +typedef void *(*user_expand_func_T)(const char *, int, typval_T *); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "cmdexpand.c.generated.h" @@ -63,10 +89,48 @@ static int compl_match_arraysize; static int compl_startcol; static int compl_selected; +#define SHOW_MATCH(m) (showtail ? showmatches_gettail(matches[m], false) : matches[m]) + +/// Returns true if fuzzy completion is supported for a given cmdline completion +/// context. +static bool cmdline_fuzzy_completion_supported(const expand_T *const xp) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + return (wop_flags & WOP_FUZZY) + && xp->xp_context != EXPAND_BOOL_SETTINGS + && xp->xp_context != EXPAND_COLORS + && xp->xp_context != EXPAND_COMPILER + && xp->xp_context != EXPAND_DIRECTORIES + && xp->xp_context != EXPAND_FILES + && xp->xp_context != EXPAND_FILES_IN_PATH + && xp->xp_context != EXPAND_FILETYPE + && xp->xp_context != EXPAND_HELP + && xp->xp_context != EXPAND_LUA + && xp->xp_context != EXPAND_OLD_SETTING + && xp->xp_context != EXPAND_OWNSYNTAX + && xp->xp_context != EXPAND_PACKADD + && xp->xp_context != EXPAND_SHELLCMD + && xp->xp_context != EXPAND_TAGS + && xp->xp_context != EXPAND_TAGS_LISTFILES + && xp->xp_context != EXPAND_USER_LIST + && xp->xp_context != EXPAND_USER_LUA; +} + +/// Returns true if fuzzy completion for cmdline completion is enabled and +/// "fuzzystr" is not empty. If search pattern is empty, then don't use fuzzy +/// matching. +bool cmdline_fuzzy_complete(const char *const fuzzystr) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + return (wop_flags & WOP_FUZZY) && *fuzzystr != NUL; +} + +/// Sort function for the completion matches. +/// <SNR> functions should be sorted to the end. static int sort_func_compare(const void *s1, const void *s2) { - char_u *p1 = *(char_u **)s1; - char_u *p2 = *(char_u **)s2; + char *p1 = *(char **)s1; + char *p2 = *(char **)s2; if (*p1 != '<' && *p2 == '<') { return -1; @@ -74,74 +138,79 @@ static int sort_func_compare(const void *s1, const void *s2) if (*p1 == '<' && *p2 != '<') { return 1; } - return STRCMP(p1, p2); + return strcmp(p1, p2); } -static void ExpandEscape(expand_T *xp, char_u *str, int numfiles, char **files, int options) +/// Escape special characters in the cmdline completion matches. +static void wildescape(expand_T *xp, const char *str, int numfiles, char **files) { - int i; - char_u *p; + char *p; const int vse_what = xp->xp_context == EXPAND_BUFFERS ? VSE_BUFFER : VSE_NONE; - // May change home directory back to "~" - if (options & WILD_HOME_REPLACE) { - tilde_replace(str, numfiles, files); - } - - if (options & WILD_ESCAPE) { - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_FILES_IN_PATH - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS - || xp->xp_context == EXPAND_DIRECTORIES) { - // Insert a backslash into a file name before a space, \, %, # - // and wildmatch characters, except '~'. - for (i = 0; i < numfiles; i++) { - // for ":set path=" we need to escape spaces twice - if (xp->xp_backslash == XP_BS_THREE) { - p = vim_strsave_escaped((char_u *)files[i], (char_u *)" "); - xfree(files[i]); - files[i] = (char *)p; + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_FILES_IN_PATH + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS + || xp->xp_context == EXPAND_DIRECTORIES) { + // Insert a backslash into a file name before a space, \, %, # + // and wildmatch characters, except '~'. + for (int i = 0; i < numfiles; i++) { + // for ":set path=" we need to escape spaces twice + if (xp->xp_backslash == XP_BS_THREE) { + p = vim_strsave_escaped(files[i], " "); + xfree(files[i]); + files[i] = p; #if defined(BACKSLASH_IN_FILENAME) - p = vim_strsave_escaped(files[i], (char_u *)" "); - xfree(files[i]); - files[i] = p; + p = vim_strsave_escaped(files[i], " "); + xfree(files[i]); + files[i] = p; #endif - } + } #ifdef BACKSLASH_IN_FILENAME - p = (char_u *)vim_strsave_fnameescape((const char *)files[i], vse_what); + p = vim_strsave_fnameescape(files[i], vse_what); #else - p = (char_u *)vim_strsave_fnameescape((const char *)files[i], - xp->xp_shell ? VSE_SHELL : vse_what); + p = vim_strsave_fnameescape(files[i], xp->xp_shell ? VSE_SHELL : vse_what); #endif - xfree(files[i]); - files[i] = (char *)p; + xfree(files[i]); + files[i] = p; - // If 'str' starts with "\~", replace "~" at start of - // files[i] with "\~". - if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') { - escape_fname(&files[i]); - } + // If 'str' starts with "\~", replace "~" at start of + // files[i] with "\~". + if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') { + escape_fname(&files[i]); } - xp->xp_backslash = XP_BS_NONE; + } + xp->xp_backslash = XP_BS_NONE; - // If the first file starts with a '+' escape it. Otherwise it - // could be seen as "+cmd". - if (*files[0] == '+') { - escape_fname(&files[0]); - } - } else if (xp->xp_context == EXPAND_TAGS) { - // Insert a backslash before characters in a tag name that - // would terminate the ":tag" command. - for (i = 0; i < numfiles; i++) { - p = vim_strsave_escaped((char_u *)files[i], (char_u *)"\\|\""); - xfree(files[i]); - files[i] = (char *)p; - } + // If the first file starts with a '+' escape it. Otherwise it + // could be seen as "+cmd". + if (*files[0] == '+') { + escape_fname(&files[0]); + } + } else if (xp->xp_context == EXPAND_TAGS) { + // Insert a backslash before characters in a tag name that + // would terminate the ":tag" command. + for (int i = 0; i < numfiles; i++) { + p = vim_strsave_escaped(files[i], "\\|\""); + xfree(files[i]); + files[i] = p; } } } +/// Escape special characters in the cmdline completion matches. +static void ExpandEscape(expand_T *xp, char *str, int numfiles, char **files, int options) +{ + // May change home directory back to "~" + if (options & WILD_HOME_REPLACE) { + tilde_replace(str, numfiles, files); + } + + if (options & WILD_ESCAPE) { + wildescape(xp, str, numfiles, files); + } +} + /// Return FAIL if this is not an appropriate context in which to do /// completion of anything, return OK if it is (even if there are no matches). /// For the caller, this means that the character is just passed through like a @@ -153,8 +222,8 @@ int nextwild(expand_T *xp, int type, int options, bool escape) { CmdlineInfo *const ccline = get_cmdline_info(); int i, j; - char_u *p1; - char_u *p2; + char *p1; + char *p2; int difflen; if (xp->xp_numfiles == -1) { @@ -171,34 +240,43 @@ int nextwild(expand_T *xp, int type, int options, bool escape) return FAIL; } - if (!(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { + // If cmd_silent is set then don't show the dots, because redrawcmd() below + // won't remove them. + if (!cmd_silent && !(ui_has(kUICmdline) || ui_has(kUIWildmenu))) { msg_puts("..."); // show that we are busy ui_flush(); } - i = (int)((char_u *)xp->xp_pattern - ccline->cmdbuff); + i = (int)(xp->xp_pattern - ccline->cmdbuff); assert(ccline->cmdpos >= i); xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i; - if (type == WILD_NEXT || type == WILD_PREV) { + if (type == WILD_NEXT || type == WILD_PREV + || type == WILD_PAGEUP || type == WILD_PAGEDOWN + || type == WILD_PUM_WANT) { // Get next/previous match for a previous expanded pattern. p2 = ExpandOne(xp, NULL, NULL, 0, type); } else { + if (cmdline_fuzzy_completion_supported(xp)) { + // If fuzzy matching, don't modify the search string + p1 = xstrdup(xp->xp_pattern); + } else { + p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + } // Translate string into pattern and expand it. - p1 = addstar((char_u *)xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); const int use_options = (options | WILD_HOME_REPLACE | WILD_ADD_SLASH | WILD_SILENT | (escape ? WILD_ESCAPE : 0) | (p_wic ? WILD_ICASE : 0)); - p2 = ExpandOne(xp, p1, vim_strnsave(&ccline->cmdbuff[i], xp->xp_pattern_len), + p2 = ExpandOne(xp, p1, xstrnsave(&ccline->cmdbuff[i], xp->xp_pattern_len), use_options, type); xfree(p1); // xp->xp_pattern might have been modified by ExpandOne (for example, // in lua completion), so recompute the pattern index and length - i = (int)((char_u *)xp->xp_pattern - ccline->cmdbuff); + i = (int)(xp->xp_pattern - ccline->cmdbuff); xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i; // Longest match: make sure it is not shorter, happens with :help. @@ -209,23 +287,23 @@ int nextwild(expand_T *xp, int type, int options, bool escape) break; } } - if ((int)STRLEN(p2) < j) { + if ((int)strlen(p2) < j) { XFREE_CLEAR(p2); } } } if (p2 != NULL && !got_int) { - difflen = (int)STRLEN(p2) - (int)(xp->xp_pattern_len); + difflen = (int)strlen(p2) - (int)(xp->xp_pattern_len); if (ccline->cmdlen + difflen + 4 > ccline->cmdbufflen) { realloc_cmdbuff(ccline->cmdlen + difflen + 4); - xp->xp_pattern = (char *)ccline->cmdbuff + i; + xp->xp_pattern = ccline->cmdbuff + i; } assert(ccline->cmdpos <= ccline->cmdlen); memmove(&ccline->cmdbuff[ccline->cmdpos + difflen], &ccline->cmdbuff[ccline->cmdpos], (size_t)ccline->cmdlen - (size_t)ccline->cmdpos + 1); - memmove(&ccline->cmdbuff[i], p2, STRLEN(p2)); + memmove(&ccline->cmdbuff[i], p2, strlen(p2)); ccline->cmdlen += difflen; ccline->cmdpos += difflen; } @@ -250,19 +328,54 @@ int nextwild(expand_T *xp, int type, int options, bool escape) return OK; } +/// Create and display a cmdline completion popup menu with items from +/// "matches". +static int cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches, int numMatches, + int showtail) +{ + assert(numMatches >= 0); + // Add all the completion matches + compl_match_arraysize = numMatches; + compl_match_array = xmalloc(sizeof(pumitem_T) * (size_t)compl_match_arraysize); + for (int i = 0; i < numMatches; i++) { + compl_match_array[i] = (pumitem_T){ + .pum_text = SHOW_MATCH(i), + .pum_info = NULL, + .pum_extra = NULL, + .pum_kind = NULL, + }; + } + + // Compute the popup menu starting column + char *endpos = showtail ? showmatches_gettail(xp->xp_pattern, true) : xp->xp_pattern; + if (ui_has(kUICmdline)) { + compl_startcol = (int)(endpos - ccline->cmdbuff); + } else { + compl_startcol = cmd_screencol((int)(endpos - ccline->cmdbuff)); + } + + // no default selection + compl_selected = -1; + + cmdline_pum_display(true); + + return EXPAND_OK; +} + void cmdline_pum_display(bool changed_array) { pum_display(compl_match_array, compl_match_arraysize, compl_selected, changed_array, compl_startcol); } +/// Returns true if the cmdline completion popup menu is being displayed. bool cmdline_pum_active(void) { // compl_match_array != NULL should already imply pum_visible() in Nvim. return compl_match_array != NULL; } -/// Remove the cmdline completion popup menu +/// Remove the cmdline completion popup menu (if present), free the list of items. void cmdline_pum_remove(void) { pum_undisplay(true); @@ -275,7 +388,420 @@ void cmdline_pum_cleanup(CmdlineInfo *cclp) wildmenu_cleanup(cclp); } -/// Do wildcard expansion on the string 'str'. +/// Return the number of characters that should be skipped in the wildmenu +/// These are backslashes used for escaping. Do show backslashes in help tags. +static int skip_wildmenu_char(expand_T *xp, char *s) +{ + if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP) + || ((xp->xp_context == EXPAND_MENUS || xp->xp_context == EXPAND_MENUNAMES) + && (s[0] == '\t' || (s[0] == '\\' && s[1] != NUL)))) { +#ifndef BACKSLASH_IN_FILENAME + // TODO(bfredl): Why in the actual fuck are we special casing the + // shell variety deep in the redraw logic? Shell special snowflakiness + // should already be eliminated multiple layers before reaching the + // screen infracstructure. + if (xp->xp_shell && csh_like_shell() && s[1] == '\\' && s[2] == '!') { + return 2; + } +#endif + return 1; + } + return 0; +} + +/// Get the length of an item as it will be shown in the status line. +static int wildmenu_match_len(expand_T *xp, char *s) +{ + int len = 0; + + int emenu = (xp->xp_context == EXPAND_MENUS + || xp->xp_context == EXPAND_MENUNAMES); + + // Check for menu separators - replace with '|'. + if (emenu && menu_is_separator(s)) { + return 1; + } + + while (*s != NUL) { + s += skip_wildmenu_char(xp, s); + len += ptr2cells(s); + MB_PTR_ADV(s); + } + + return len; +} + +/// Show wildchar matches in the status line. +/// Show at least the "match" item. +/// We start at item "first_match" in the list and show all matches that fit. +/// +/// If inversion is possible we use it. Else '=' characters are used. +/// +/// @param matches list of matches +static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, int showtail) +{ + int row; + char *buf; + int len; + int clen; // length in screen cells + int fillchar; + int attr; + int i; + bool highlight = true; + char *selstart = NULL; + int selstart_col = 0; + char *selend = NULL; + static int first_match = 0; + bool add_left = false; + char *s; + int emenu; + int l; + + if (matches == NULL) { // interrupted completion? + return; + } + + buf = xmalloc((size_t)Columns * MB_MAXBYTES + 1); + + if (match == -1) { // don't show match but original text + match = 0; + highlight = false; + } + // count 1 for the ending ">" + clen = wildmenu_match_len(xp, SHOW_MATCH(match)) + 3; + if (match == 0) { + first_match = 0; + } else if (match < first_match) { + // jumping left, as far as we can go + first_match = match; + add_left = true; + } else { + // check if match fits on the screen + for (i = first_match; i < match; i++) { + clen += wildmenu_match_len(xp, SHOW_MATCH(i)) + 2; + } + if (first_match > 0) { + clen += 2; + } + // jumping right, put match at the left + if ((long)clen > Columns) { + first_match = match; + // if showing the last match, we can add some on the left + clen = 2; + for (i = match; i < num_matches; i++) { + clen += wildmenu_match_len(xp, SHOW_MATCH(i)) + 2; + if ((long)clen >= Columns) { + break; + } + } + if (i == num_matches) { + add_left = true; + } + } + } + if (add_left) { + while (first_match > 0) { + clen += wildmenu_match_len(xp, SHOW_MATCH(first_match - 1)) + 2; + if ((long)clen >= Columns) { + break; + } + first_match--; + } + } + + fillchar = fillchar_status(&attr, curwin); + + if (first_match == 0) { + *buf = NUL; + len = 0; + } else { + STRCPY(buf, "< "); + len = 2; + } + clen = len; + + i = first_match; + while (clen + wildmenu_match_len(xp, SHOW_MATCH(i)) + 2 < Columns) { + if (i == match) { + selstart = buf + len; + selstart_col = clen; + } + + s = SHOW_MATCH(i); + // Check for menu separators - replace with '|' + emenu = (xp->xp_context == EXPAND_MENUS + || xp->xp_context == EXPAND_MENUNAMES); + if (emenu && menu_is_separator(s)) { + STRCPY(buf + len, transchar('|')); + l = (int)strlen(buf + len); + len += l; + clen += l; + } else { + for (; *s != NUL; s++) { + s += skip_wildmenu_char(xp, s); + clen += ptr2cells(s); + if ((l = utfc_ptr2len(s)) > 1) { + strncpy(buf + len, s, (size_t)l); // NOLINT(runtime/printf) + s += l - 1; + len += l; + } else { + STRCPY(buf + len, transchar_byte((uint8_t)(*s))); + len += (int)strlen(buf + len); + } + } + } + if (i == match) { + selend = buf + len; + } + + *(buf + len++) = ' '; + *(buf + len++) = ' '; + clen += 2; + if (++i == num_matches) { + break; + } + } + + if (i != num_matches) { + *(buf + len++) = '>'; + clen++; + } + + buf[len] = NUL; + + row = cmdline_row - 1; + if (row >= 0) { + if (wild_menu_showing == 0 || wild_menu_showing == WM_LIST) { + if (msg_scrolled > 0) { + // Put the wildmenu just above the command line. If there is + // no room, scroll the screen one line up. + if (cmdline_row == Rows - 1) { + msg_scroll_up(false, false); + msg_scrolled++; + } else { + cmdline_row++; + row++; + } + wild_menu_showing = WM_SCROLLED; + } else { + // Create status line if needed by setting 'laststatus' to 2. + // Set 'winminheight' to zero to avoid that the window is + // resized. + if (lastwin->w_status_height == 0 && global_stl_height() == 0) { + save_p_ls = (int)p_ls; + save_p_wmh = (int)p_wmh; + p_ls = 2; + p_wmh = 0; + last_status(false); + } + wild_menu_showing = WM_SHOWN; + } + } + + // Tricky: wildmenu can be drawn either over a status line, or at empty + // scrolled space in the message output + ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED) + ? &msg_grid_adj : &default_grid; + + grid_puts(grid, buf, row, 0, attr); + if (selstart != NULL && highlight) { + *selend = NUL; + grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); + } + + grid_fill(grid, row, row + 1, clen, Columns, + fillchar, fillchar, attr); + } + + win_redraw_last_status(topframe); + xfree(buf); +} + +/// Get the next or prev cmdline completion match. The index of the match is set +/// in "p_findex" +static char *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char *orig_save) +{ + if (xp->xp_numfiles <= 0) { + return NULL; + } + + int findex = *p_findex; + + if (mode == WILD_PREV) { + if (findex == -1) { + findex = xp->xp_numfiles; + } + findex--; + } else if (mode == WILD_NEXT) { + findex++; + } else if (mode == WILD_PAGEUP) { + if (findex == 0) { + // at the first entry, don't select any entries + findex = -1; + } else if (findex == -1) { + // no entry is selected. select the last entry + findex = xp->xp_numfiles - 1; + } else { + // go up by the pum height + int ht = pum_get_height(); + if (ht > 3) { + ht -= 2; + } + findex -= ht; + if (findex < 0) { + // few entries left, select the first entry + findex = 0; + } + } + } else if (mode == WILD_PAGEDOWN) { + if (findex == xp->xp_numfiles - 1) { + // at the last entry, don't select any entries + findex = -1; + } else if (findex == -1) { + // no entry is selected. select the first entry + findex = 0; + } else { + // go down by the pum height + int ht = pum_get_height(); + if (ht > 3) { + ht -= 2; + } + findex += ht; + if (findex >= xp->xp_numfiles) { + // few entries left, select the last entry + findex = xp->xp_numfiles - 1; + } + } + } else { // mode == WILD_PUM_WANT + assert(pum_want.active); + findex = pum_want.item; + } + + // When wrapping around, return the original string, set findex to -1. + if (findex < 0) { + if (orig_save == NULL) { + findex = xp->xp_numfiles - 1; + } else { + findex = -1; + } + } + if (findex >= xp->xp_numfiles) { + if (orig_save == NULL) { + findex = 0; + } else { + findex = -1; + } + } + if (compl_match_array) { + compl_selected = findex; + cmdline_pum_display(false); + } else if (p_wmnu) { + redraw_wildmenu(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail); + } + *p_findex = findex; + + return xstrdup(findex == -1 ? orig_save : xp->xp_files[findex]); +} + +/// Start the command-line expansion and get the matches. +static char *ExpandOne_start(int mode, expand_T *xp, char *str, int options) +{ + int non_suf_match; // number without matching suffix + char *ss = NULL; + + // Do the expansion. + if (ExpandFromContext(xp, str, &xp->xp_files, &xp->xp_numfiles, options) == FAIL) { +#ifdef FNAME_ILLEGAL + // Illegal file name has been silently skipped. But when there + // are wildcards, the real problem is that there was no match, + // causing the pattern to be added, which has illegal characters. + if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) { + semsg(_(e_nomatch2), str); + } +#endif + } else if (xp->xp_numfiles == 0) { + if (!(options & WILD_SILENT)) { + semsg(_(e_nomatch2), str); + } + } else { + // Escape the matches for use on the command line. + ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options); + + // Check for matching suffixes in file names. + if (mode != WILD_ALL && mode != WILD_ALL_KEEP && mode != WILD_LONGEST) { + if (xp->xp_numfiles) { + non_suf_match = xp->xp_numfiles; + } else { + non_suf_match = 1; + } + if ((xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES) + && xp->xp_numfiles > 1) { + // More than one match; check suffix. + // The files will have been sorted on matching suffix in + // expand_wildcards, only need to check the first two. + non_suf_match = 0; + for (int i = 0; i < 2; i++) { + if (match_suffix(xp->xp_files[i])) { + non_suf_match++; + } + } + } + if (non_suf_match != 1) { + // Can we ever get here unless it's while expanding + // interactively? If not, we can get rid of this all + // together. Don't really want to wait for this message + // (and possibly have to hit return to continue!). + if (!(options & WILD_SILENT)) { + emsg(_(e_toomany)); + } else if (!(options & WILD_NO_BEEP)) { + beep_flush(); + } + } + if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) { + ss = xstrdup(xp->xp_files[0]); + } + } + } + + return ss; +} + +/// Return the longest common part in the list of cmdline completion matches. +static char *find_longest_match(expand_T *xp, int options) +{ + size_t len = 0; + + for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) { + mb_len = (size_t)utfc_ptr2len(&xp->xp_files[0][len]); + int c0 = utf_ptr2char(&xp->xp_files[0][len]); + int i; + for (i = 1; i < xp->xp_numfiles; i++) { + int ci = utf_ptr2char(&xp->xp_files[i][len]); + + if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS)) { + if (mb_tolower(c0) != mb_tolower(ci)) { + break; + } + } else if (c0 != ci) { + break; + } + } + if (i < xp->xp_numfiles) { + if (!(options & WILD_NO_BEEP)) { + vim_beep(BO_WILD); + } + break; + } + } + + return xstrndup(xp->xp_files[0], len); +} + +/// Do wildcard expansion on the string "str". /// Chars that should not be expanded must be preceded with a backslash. /// Return a pointer to allocated memory containing the new string. /// Return NULL for failure. @@ -295,6 +821,11 @@ void cmdline_pum_cleanup(CmdlineInfo *cclp) /// mode = WILD_ALL: return all matches concatenated /// mode = WILD_LONGEST: return longest matched part /// mode = WILD_ALL_KEEP: get all matches, keep matches +/// mode = WILD_APPLY: apply the item selected in the cmdline completion +/// popup menu and close the menu. +/// mode = WILD_CANCEL: cancel and close the cmdline completion popup and +/// use the original text. +/// mode = WILD_PUM_WANT: use the match at index pum_want.item /// /// options = WILD_LIST_NOTFOUND: list entries without a match /// options = WILD_HOME_REPLACE: do home_replace() for buffer names @@ -309,63 +840,27 @@ void cmdline_pum_cleanup(CmdlineInfo *cclp) /// The variables xp->xp_context and xp->xp_backslash must have been set! /// /// @param orig allocated copy of original of expanded string -char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode) +char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode) { - char_u *ss = NULL; + char *ss = NULL; static int findex; - static char_u *orig_save = NULL; // kept value of orig + static char *orig_save = NULL; // kept value of orig int orig_saved = false; int i; - int non_suf_match; // number without matching suffix // first handle the case of using an old match - if (mode == WILD_NEXT || mode == WILD_PREV) { - if (xp->xp_numfiles > 0) { - if (mode == WILD_PREV) { - if (findex == -1) { - findex = xp->xp_numfiles; - } - findex--; - } else { // mode == WILD_NEXT - findex++; - } - - // When wrapping around, return the original string, set findex to - // -1. - if (findex < 0) { - if (orig_save == NULL) { - findex = xp->xp_numfiles - 1; - } else { - findex = -1; - } - } - if (findex >= xp->xp_numfiles) { - if (orig_save == NULL) { - findex = 0; - } else { - findex = -1; - } - } - if (compl_match_array) { - compl_selected = findex; - cmdline_pum_display(false); - } else if (p_wmnu) { - redraw_wildmenu(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail); - } - if (findex == -1) { - return vim_strsave(orig_save); - } - return vim_strsave((char_u *)xp->xp_files[findex]); - } else { - return NULL; - } + if (mode == WILD_NEXT || mode == WILD_PREV + || mode == WILD_PAGEUP || mode == WILD_PAGEDOWN + || mode == WILD_PUM_WANT) { + return get_next_or_prev_match(mode, xp, &findex, orig_save); } if (mode == WILD_CANCEL) { - ss = vim_strsave(orig_save ? orig_save : (char_u *)""); + ss = xstrdup(orig_save ? orig_save : ""); } else if (mode == WILD_APPLY) { - ss = vim_strsave(findex == -1 ? (orig_save ? orig_save : (char_u *)"") : - (char_u *)xp->xp_files[findex]); + ss = xstrdup(findex == -1 + ? (orig_save ? orig_save : "") + : xp->xp_files[findex]); } // free old names @@ -373,6 +868,11 @@ char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode FreeWild(xp->xp_numfiles, xp->xp_files); xp->xp_numfiles = -1; XFREE_CLEAR(orig_save); + + // The entries from xp_files may be used in the PUM, remove it. + if (compl_match_array != NULL) { + cmdline_pum_remove(); + } } findex = 0; @@ -385,93 +885,12 @@ char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode orig_save = orig; orig_saved = true; - // Do the expansion. - if (ExpandFromContext(xp, str, &xp->xp_numfiles, &xp->xp_files, options) == FAIL) { -#ifdef FNAME_ILLEGAL - // Illegal file name has been silently skipped. But when there - // are wildcards, the real problem is that there was no match, - // causing the pattern to be added, which has illegal characters. - if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) { - semsg(_(e_nomatch2), str); - } -#endif - } else if (xp->xp_numfiles == 0) { - if (!(options & WILD_SILENT)) { - semsg(_(e_nomatch2), str); - } - } else { - // Escape the matches for use on the command line. - ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options); - - // Check for matching suffixes in file names. - if (mode != WILD_ALL && mode != WILD_ALL_KEEP - && mode != WILD_LONGEST) { - if (xp->xp_numfiles) { - non_suf_match = xp->xp_numfiles; - } else { - non_suf_match = 1; - } - if ((xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_DIRECTORIES) - && xp->xp_numfiles > 1) { - // More than one match; check suffix. - // The files will have been sorted on matching suffix in - // expand_wildcards, only need to check the first two. - non_suf_match = 0; - for (i = 0; i < 2; i++) { - if (match_suffix((char_u *)xp->xp_files[i])) { - non_suf_match++; - } - } - } - if (non_suf_match != 1) { - // Can we ever get here unless it's while expanding - // interactively? If not, we can get rid of this all - // together. Don't really want to wait for this message - // (and possibly have to hit return to continue!). - if (!(options & WILD_SILENT)) { - emsg(_(e_toomany)); - } else if (!(options & WILD_NO_BEEP)) { - beep_flush(); - } - } - if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) { - ss = vim_strsave((char_u *)xp->xp_files[0]); - } - } - } + ss = ExpandOne_start(mode, xp, str, options); } // Find longest common part if (mode == WILD_LONGEST && xp->xp_numfiles > 0) { - size_t len = 0; - - for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) { - mb_len = (size_t)utfc_ptr2len(&xp->xp_files[0][len]); - int c0 = utf_ptr2char(&xp->xp_files[0][len]); - for (i = 1; i < xp->xp_numfiles; i++) { - int ci = utf_ptr2char(&xp->xp_files[i][len]); - - if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES - || xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS)) { - if (mb_tolower(c0) != mb_tolower(ci)) { - break; - } - } else if (c0 != ci) { - break; - } - } - if (i < xp->xp_numfiles) { - if (!(options & WILD_NO_BEEP)) { - vim_beep(BO_WILD); - } - break; - } - } - - ss = (char_u *)xstrndup(xp->xp_files[0], len); + ss = find_longest_match(xp, options); findex = -1; // next p_wc gets first one } @@ -481,7 +900,7 @@ char_u *ExpandOne(expand_T *xp, char_u *str, char_u *orig, int options, int mode if (mode == WILD_ALL && xp->xp_numfiles > 0 && !got_int) { size_t len = 0; for (i = 0; i < xp->xp_numfiles; i++) { - len += STRLEN(xp->xp_files[i]) + 1; + len += strlen(xp->xp_files[i]) + 1; } ss = xmalloc(len); *ss = NUL; @@ -523,36 +942,99 @@ void ExpandCleanup(expand_T *xp) } } +/// Display one line of completion matches. Multiple matches are displayed in +/// each line (used by wildmode=list and CTRL-D) +/// +/// @param matches list of completion match names +/// @param numMatches number of completion matches in "matches" +/// @param lines number of output lines +/// @param linenr line number of matches to display +/// @param maxlen maximum number of characters in each line +/// @param showtail display only the tail of the full path of a file name +/// @param dir_attr highlight attribute to use for directory names +static void showmatches_oneline(expand_T *xp, char **matches, int numMatches, int lines, int linenr, + int maxlen, int showtail, int dir_attr) +{ + char *p; + int lastlen = 999; + for (int j = linenr; j < numMatches; j += lines) { + if (xp->xp_context == EXPAND_TAGS_LISTFILES) { + msg_outtrans_attr(matches[j], HL_ATTR(HLF_D)); + p = matches[j] + strlen(matches[j]) + 1; + msg_advance(maxlen + 1); + msg_puts((const char *)p); + msg_advance(maxlen + 3); + msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D)); + break; + } + for (int i = maxlen - lastlen; --i >= 0;) { + msg_putchar(' '); + } + bool isdir; + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS) { + // highlight directories + if (xp->xp_numfiles != -1) { + // Expansion was done before and special characters + // were escaped, need to halve backslashes. Also + // $HOME has been replaced with ~/. + char *exp_path = expand_env_save_opt(matches[j], true); + char *path = exp_path != NULL ? exp_path : matches[j]; + char *halved_slash = backslash_halve_save(path); + isdir = os_isdir(halved_slash); + xfree(exp_path); + if (halved_slash != path) { + xfree(halved_slash); + } + } else { + // Expansion was done here, file names are literal. + isdir = os_isdir(matches[j]); + } + if (showtail) { + p = SHOW_MATCH(j); + } else { + home_replace(NULL, matches[j], NameBuff, MAXPATHL, true); + p = NameBuff; + } + } else { + isdir = false; + p = SHOW_MATCH(j); + } + lastlen = msg_outtrans_attr(p, isdir ? dir_attr : 0); + } + if (msg_col > 0) { // when not wrapped around + msg_clr_eos(); + msg_putchar('\n'); + } +} + /// Show all matches for completion on the command line. /// Returns EXPAND_NOTHING when the character that triggered expansion should /// be inserted like a normal character. int showmatches(expand_T *xp, int wildmenu) { CmdlineInfo *const ccline = get_cmdline_info(); -#define L_SHOWFILE(m) (showtail \ - ? sm_gettail(files_found[m], false) : files_found[m]) - int num_files; - char **files_found; - int i, j, k; + int numMatches; + char **matches; + int i, j; int maxlen; int lines; int columns; - char_u *p; - int lastlen; int attr; int showtail; if (xp->xp_numfiles == -1) { set_expand_context(xp); i = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos, - &num_files, &files_found); + &numMatches, &matches); showtail = expand_showtail(xp); if (i != EXPAND_OK) { return i; } } else { - num_files = xp->xp_numfiles; - files_found = xp->xp_files; + numMatches = xp->xp_numfiles; + matches = xp->xp_files; showtail = cmd_showtail; } @@ -562,26 +1044,8 @@ int showmatches(expand_T *xp, int wildmenu) || ui_has(kUIWildmenu); if (compl_use_pum) { - assert(num_files >= 0); - compl_match_arraysize = num_files; - compl_match_array = xmalloc(sizeof(pumitem_T) * (size_t)compl_match_arraysize); - for (i = 0; i < num_files; i++) { - compl_match_array[i] = (pumitem_T){ - .pum_text = (char_u *)L_SHOWFILE(i), - .pum_info = NULL, - .pum_extra = NULL, - .pum_kind = NULL, - }; - } - char_u *endpos = (char_u *)(showtail ? sm_gettail(xp->xp_pattern, true) : xp->xp_pattern); - if (ui_has(kUICmdline)) { - compl_startcol = (int)(endpos - ccline->cmdbuff); - } else { - compl_startcol = cmd_screencol((int)(endpos - ccline->cmdbuff)); - } - compl_selected = -1; - cmdline_pum_display(true); - return EXPAND_OK; + // cmdline completion popup menu (with wildoptions=pum) + return cmdline_pum_create(ccline, xp, matches, numMatches, showtail); } if (!wildmenu) { @@ -597,18 +1061,18 @@ int showmatches(expand_T *xp, int wildmenu) if (got_int) { got_int = false; // only int. the completion, not the cmd line } else if (wildmenu) { - redraw_wildmenu(xp, num_files, files_found, -1, showtail); + redraw_wildmenu(xp, numMatches, matches, -1, showtail); } else { // find the length of the longest file name maxlen = 0; - for (i = 0; i < num_files; i++) { + for (i = 0; i < numMatches; i++) { if (!showtail && (xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_SHELLCMD || xp->xp_context == EXPAND_BUFFERS)) { - home_replace(NULL, files_found[i], (char *)NameBuff, MAXPATHL, true); - j = vim_strsize((char *)NameBuff); + home_replace(NULL, matches[i], NameBuff, MAXPATHL, true); + j = vim_strsize(NameBuff); } else { - j = vim_strsize(L_SHOWFILE(i)); + j = vim_strsize(SHOW_MATCH(i)); } if (j > maxlen) { maxlen = j; @@ -616,7 +1080,7 @@ int showmatches(expand_T *xp, int wildmenu) } if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - lines = num_files; + lines = numMatches; } else { // compute the number of columns and lines for the listing maxlen += 2; // two spaces between file names @@ -624,7 +1088,7 @@ int showmatches(expand_T *xp, int wildmenu) if (columns < 1) { columns = 1; } - lines = (num_files + columns - 1) / columns; + lines = (numMatches + columns - 1) / columns; } attr = HL_ATTR(HLF_D); // find out highlighting for directories @@ -638,57 +1102,7 @@ int showmatches(expand_T *xp, int wildmenu) // list the files line by line for (i = 0; i < lines; i++) { - lastlen = 999; - for (k = i; k < num_files; k += lines) { - if (xp->xp_context == EXPAND_TAGS_LISTFILES) { - msg_outtrans_attr((char_u *)files_found[k], HL_ATTR(HLF_D)); - p = (char_u *)files_found[k] + STRLEN(files_found[k]) + 1; - msg_advance(maxlen + 1); - msg_puts((const char *)p); - msg_advance(maxlen + 3); - msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D)); - break; - } - for (j = maxlen - lastlen; --j >= 0;) { - msg_putchar(' '); - } - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_SHELLCMD - || xp->xp_context == EXPAND_BUFFERS) { - // highlight directories - if (xp->xp_numfiles != -1) { - // Expansion was done before and special characters - // were escaped, need to halve backslashes. Also - // $HOME has been replaced with ~/. - char_u *exp_path = expand_env_save_opt((char_u *)files_found[k], true); - char_u *path = exp_path != NULL ? exp_path : (char_u *)files_found[k]; - char_u *halved_slash = backslash_halve_save(path); - j = os_isdir(halved_slash); - xfree(exp_path); - if (halved_slash != path) { - xfree(halved_slash); - } - } else { - // Expansion was done here, file names are literal. - j = os_isdir((char_u *)files_found[k]); - } - if (showtail) { - p = (char_u *)L_SHOWFILE(k); - } else { - home_replace(NULL, files_found[k], (char *)NameBuff, MAXPATHL, true); - p = NameBuff; - } - } else { - j = false; - p = (char_u *)L_SHOWFILE(k); - } - lastlen = msg_outtrans_attr(p, j ? attr : 0); - } - if (msg_col > 0) { // when not wrapped around - msg_clr_eos(); - msg_putchar('\n'); - } - ui_flush(); // show one line at a time + showmatches_oneline(xp, matches, numMatches, lines, i, maxlen, showtail, attr); if (got_int) { got_int = false; break; @@ -701,21 +1115,21 @@ int showmatches(expand_T *xp, int wildmenu) } if (xp->xp_numfiles == -1) { - FreeWild(num_files, files_found); + FreeWild(numMatches, matches); } return EXPAND_OK; } -/// Private path_tail for showmatches() (and redraw_wildmenu()): -/// Find tail of file name path, but ignore trailing "/". -char *sm_gettail(char *s, bool eager) +/// path_tail() version for showmatches() and redraw_wildmenu(): +/// Return the tail of file name path "s", ignoring a trailing "/". +static char *showmatches_gettail(char *s, bool eager) { - char_u *p; - char_u *t = (char_u *)s; + char *p; + char *t = s; bool had_sep = false; - for (p = (char_u *)s; *p != NUL;) { + for (p = s; *p != NUL;) { if (vim_ispathsep(*p) #ifdef BACKSLASH_IN_FILENAME && !rem_backslash(p) @@ -732,7 +1146,7 @@ char *sm_gettail(char *s, bool eager) } MB_PTR_ADV(p); } - return (char *)t; + return t; } /// Return true if we only need to show the tail of completion matches. @@ -740,8 +1154,8 @@ char *sm_gettail(char *s, bool eager) /// returned. static bool expand_showtail(expand_T *xp) { - char_u *s; - char_u *end; + char *s; + char *end; // When not completing file names a "/" may mean something different. if (xp->xp_context != EXPAND_FILES @@ -750,17 +1164,17 @@ static bool expand_showtail(expand_T *xp) return false; } - end = (char_u *)path_tail(xp->xp_pattern); - if (end == (char_u *)xp->xp_pattern) { // there is no path separator + end = path_tail(xp->xp_pattern); + if (end == xp->xp_pattern) { // there is no path separator return false; } - for (s = (char_u *)xp->xp_pattern; s < end; s++) { + for (s = xp->xp_pattern; s < end; s++) { // Skip escaped wildcards. Only when the backslash is not a path // separator, on DOS the '*' "path\*\file" must not be skipped. if (rem_backslash(s)) { s++; - } else if (vim_strchr("*?[", *s) != NULL) { + } else if (vim_strchr("*?[", (uint8_t)(*s)) != NULL) { return false; } } @@ -775,13 +1189,13 @@ static bool expand_showtail(expand_T *xp) /// the name into allocated memory and prepend "^". /// /// @param context EXPAND_FILES etc. -char_u *addstar(char_u *fname, size_t len, int context) +char *addstar(char *fname, size_t len, int context) FUNC_ATTR_NONNULL_RET { - char_u *retval; + char *retval; size_t i, j; size_t new_len; - char_u *tail; + char *tail; int ends_in_star; if (context != EXPAND_FILES @@ -803,7 +1217,7 @@ char_u *addstar(char_u *fname, size_t len, int context) || context == EXPAND_PACKADD || ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS) && fname[0] == '/')) { - retval = vim_strnsave(fname, len); + retval = xstrnsave(fname, len); } else { new_len = len + 2; // +2 for '^' at start, NUL at end for (i = 0; i < len; i++) { @@ -865,7 +1279,7 @@ char_u *addstar(char_u *fname, size_t len, int context) } } else { retval = xmalloc(len + 4); - STRLCPY(retval, fname, len + 1); + xstrlcpy(retval, fname, len + 1); // Don't add a star to *, ~, ~user, $var or `cmd`. // * would become **, which walks the whole tree. @@ -873,7 +1287,7 @@ char_u *addstar(char_u *fname, size_t len, int context) // $ could be anywhere in the tail. // ` could be anywhere in the file name. // When the name ends in '$' don't add a star, remove the '$'. - tail = (char_u *)path_tail((char *)retval); + tail = path_tail(retval); ends_in_star = (len > 0 && retval[len - 1] == '*'); #ifndef BACKSLASH_IN_FILENAME for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) { @@ -885,8 +1299,8 @@ char_u *addstar(char_u *fname, size_t len, int context) #endif if ((*retval != '~' || tail != retval) && !ends_in_star - && vim_strchr((char *)tail, '$') == NULL - && vim_strchr((char *)retval, '`') == NULL) { + && vim_strchr(tail, '$') == NULL + && vim_strchr(retval, '`') == NULL) { retval[len++] = '*'; } else if (len > 0 && retval[len - 1] == '$') { len--; @@ -951,65 +1365,26 @@ void set_expand_context(expand_T *xp) set_cmd_context(xp, ccline->cmdbuff, ccline->cmdlen, ccline->cmdpos, true); } -/// This is all pretty much copied from do_one_cmd(), with all the extra stuff -/// we don't need/want deleted. Maybe this could be done better if we didn't -/// repeat all this stuff. The only problem is that they may not stay -/// perfectly compatible with each other, but then the command line syntax -/// probably won't change that much -- webb. +/// Sets the index of a built-in or user defined command "cmd" in eap->cmdidx. +/// For user defined commands, the completion context is set in "xp" and the +/// completion flags in "complp". /// -/// @param buff buffer for command string -static const char *set_one_cmd_context(expand_T *xp, const char *buff) +/// @return a pointer to the text after the command or NULL for failure. +static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, int *complp) { + const char *p = NULL; size_t len = 0; - exarg_T ea; - int context = EXPAND_NOTHING; - bool forceit = false; - bool usefilter = false; // Filter instead of file name. - - ExpandInit(xp); - xp->xp_pattern = (char *)buff; - xp->xp_line = (char *)buff; - xp->xp_context = EXPAND_COMMANDS; // Default until we get past command - ea.argt = 0; - - // 2. skip comment lines and leading space, colons or bars - const char *cmd; - for (cmd = buff; vim_strchr(" \t:|", *cmd) != NULL; cmd++) {} - xp->xp_pattern = (char *)cmd; - - if (*cmd == NUL) { - return NULL; - } - if (*cmd == '"') { // ignore comment lines - xp->xp_context = EXPAND_NOTHING; - return NULL; - } - - // 3. parse a range specifier of the form: addr [,addr] [;addr] .. - cmd = (const char *)skip_range(cmd, &xp->xp_context); - - // 4. parse command - xp->xp_pattern = (char *)cmd; - if (*cmd == NUL) { - return NULL; - } - if (*cmd == '"') { - xp->xp_context = EXPAND_NOTHING; - return NULL; - } - - if (*cmd == '|' || *cmd == '\n') { - return cmd + 1; // There's another command - } + const bool fuzzy = cmdline_fuzzy_complete(cmd); // Isolate the command and search for it in the command table. // Exceptions: - // - the 'k' command can directly be followed by any character, but - // do accept "keepmarks", "keepalt" and "keepjumps". + // - the 'k' command can directly be followed by any character, but do + // accept "keepmarks", "keepalt" and "keepjumps". As fuzzy matching can + // find matches anywhere in the command name, do this only for command + // expansion based on regular expression and not for fuzzy matching. // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' - const char *p; - if (*cmd == 'k' && cmd[1] != 'e') { - ea.cmdidx = CMD_k; + if (!fuzzy && (*cmd == 'k' && cmd[1] != 'e')) { + eap->cmdidx = CMD_k; p = cmd + 1; } else { p = cmd; @@ -1030,7 +1405,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) } } // check for non-alpha command - if (p == cmd && vim_strchr("@*!=><&~#", *p) != NULL) { + if (p == cmd && vim_strchr("@*!=><&~#", (uint8_t)(*p)) != NULL) { p++; } len = (size_t)(p - cmd); @@ -1040,9 +1415,13 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) return NULL; } - ea.cmdidx = excmd_get_cmdidx(cmd, len); + eap->cmdidx = excmd_get_cmdidx(cmd, len); - if (cmd[0] >= 'A' && cmd[0] <= 'Z') { + // User defined commands support alphanumeric characters. + // Also when doing fuzzy expansion for non-shell commands, support + // alphanumeric characters. + if ((cmd[0] >= 'A' && cmd[0] <= 'Z') + || (fuzzy && eap->cmdidx != CMD_bang && *p != NUL)) { while (ASCII_ISALNUM(*p) || *p == '*') { // Allow * wild card p++; } @@ -1055,227 +1434,358 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) return NULL; } - if (ea.cmdidx == CMD_SIZE) { - if (*cmd == 's' && vim_strchr("cgriI", cmd[1]) != NULL) { - ea.cmdidx = CMD_substitute; + if (eap->cmdidx == CMD_SIZE) { + if (*cmd == 's' && vim_strchr("cgriI", (uint8_t)cmd[1]) != NULL) { + eap->cmdidx = CMD_substitute; p = cmd + 1; } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { - ea.cmd = (char *)cmd; - p = (const char *)find_ucmd(&ea, (char *)p, NULL, xp, &context); + eap->cmd = (char *)cmd; + p = (const char *)find_ucmd(eap, (char *)p, NULL, xp, complp); if (p == NULL) { - ea.cmdidx = CMD_SIZE; // Ambiguous user command. + eap->cmdidx = CMD_SIZE; // Ambiguous user command. } } } - if (ea.cmdidx == CMD_SIZE) { + if (eap->cmdidx == CMD_SIZE) { // Not still touching the command and it was an illegal one xp->xp_context = EXPAND_UNSUCCESSFUL; return NULL; } - xp->xp_context = EXPAND_NOTHING; // Default now that we're past command + return p; +} - if (*p == '!') { // forced commands - forceit = true; - p++; - } +/// Set the completion context for a command argument with wild card characters. +static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool usefilter, + expand_T *xp, int *complp) +{ + bool in_quote = false; + const char *bow = NULL; // Beginning of word. + size_t len = 0; - // 5. parse arguments - if (!IS_USER_CMDIDX(ea.cmdidx)) { - ea.argt = excmd_get_argt(ea.cmdidx); + // Allow spaces within back-quotes to count as part of the argument + // being expanded. + xp->xp_pattern = skipwhite(arg); + const char *p = (const char *)xp->xp_pattern; + while (*p != NUL) { + int c = utf_ptr2char(p); + if (c == '\\' && p[1] != NUL) { + p++; + } else if (c == '`') { + if (!in_quote) { + xp->xp_pattern = (char *)p; + bow = p + 1; + } + in_quote = !in_quote; + // An argument can contain just about everything, except + // characters that end the command and white space. + } else if (c == '|' || c == '\n' || c == '"' || ascii_iswhite(c)) { + len = 0; // avoid getting stuck when space is in 'isfname' + while (*p != NUL) { + c = utf_ptr2char(p); + if (c == '`' || vim_isfilec_or_wc(c)) { + break; + } + len = (size_t)utfc_ptr2len(p); + MB_PTR_ADV(p); + } + if (in_quote) { + bow = p; + } else { + xp->xp_pattern = (char *)p; + } + p -= len; + } + MB_PTR_ADV(p); } - const char *arg = (const char *)skipwhite(p); + // If we are still inside the quotes, and we passed a space, just + // expand from there. + if (bow != NULL && in_quote) { + xp->xp_pattern = (char *)bow; + } + xp->xp_context = EXPAND_FILES; - // Skip over ++argopt argument - if ((ea.argt & EX_ARGOPT) && *arg != NUL && strncmp(arg, "++", 2) == 0) { - p = arg; - while (*p && !ascii_isspace(*p)) { - MB_PTR_ADV(p); + // For a shell command more chars need to be escaped. + if (usefilter || eap->cmdidx == CMD_bang || eap->cmdidx == CMD_terminal) { +#ifndef BACKSLASH_IN_FILENAME + xp->xp_shell = true; +#endif + // When still after the command name expand executables. + if (xp->xp_pattern == skipwhite(arg)) { + xp->xp_context = EXPAND_SHELLCMD; } - arg = (const char *)skipwhite(p); } - if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { - if (*arg == '>') { // Append. - if (*++arg == '>') { - arg++; + // Check for environment variable. + if (*xp->xp_pattern == '$') { + for (p = (const char *)xp->xp_pattern + 1; *p != NUL; p++) { + if (!vim_isIDc((uint8_t)(*p))) { + break; } - arg = (const char *)skipwhite(arg); - } else if (*arg == '!' && ea.cmdidx == CMD_write) { // :w !filter - arg++; - usefilter = true; } - } - - if (ea.cmdidx == CMD_read) { - usefilter = forceit; // :r! filter if forced - if (*arg == '!') { // :r !filter - arg++; - usefilter = true; + if (*p == NUL) { + xp->xp_context = EXPAND_ENV_VARS; + xp->xp_pattern++; + // Avoid that the assignment uses EXPAND_FILES again. + if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST) { + *complp = EXPAND_ENV_VARS; + } } } - - if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { - while (*arg == *cmd) { // allow any number of '>' or '<' - arg++; + // Check for user names. + if (*xp->xp_pattern == '~') { + for (p = (const char *)xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) {} + // Complete ~user only if it partially matches a user name. + // A full match ~user<Tab> will be replaced by user's home + // directory i.e. something like ~user<Tab> -> /home/user/ + if (*p == NUL && p > (const char *)xp->xp_pattern + 1 + && match_user(xp->xp_pattern + 1) >= 1) { + xp->xp_context = EXPAND_USER; + xp->xp_pattern++; } - arg = (const char *)skipwhite(arg); } +} - // Does command allow "+command"? - if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') { - // Check if we're in the +command - p = arg + 1; - arg = (const char *)skip_cmd_arg((char *)arg, false); +/// Set the completion context for the :filter command. Returns a pointer to the +/// next command after the :filter command. +static const char *set_context_in_filter_cmd(expand_T *xp, const char *arg) +{ + if (*arg != NUL) { + arg = (const char *)skip_vimgrep_pat((char *)arg, NULL, NULL); + } + if (arg == NULL || *arg == NUL) { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + return (const char *)skipwhite(arg); +} - // Still touching the command after '+'? - if (*arg == NUL) { - return p; +/// Set the completion context for the :match command. Returns a pointer to the +/// next command after the :match command. +static const char *set_context_in_match_cmd(expand_T *xp, const char *arg) +{ + if (*arg == NUL || !ends_excmd(*arg)) { + // also complete "None" + set_context_in_echohl_cmd(xp, arg); + arg = (const char *)skipwhite(skiptowhite(arg)); + if (*arg != NUL) { + xp->xp_context = EXPAND_NOTHING; + arg = (const char *)skip_regexp((char *)arg + 1, (uint8_t)(*arg), magic_isset()); } + } + return (const char *)find_nextcmd(arg); +} - // Skip space(s) after +command to get to the real argument. - arg = (const char *)skipwhite(arg); +/// Returns a pointer to the next command after a :global or a :v command. +/// Returns NULL if there is no next command. +static const char *find_cmd_after_global_cmd(const char *arg) +{ + const int delim = (uint8_t)(*arg); // Get the delimiter. + if (delim) { + arg++; // Skip delimiter if there is one. } - // Check for '|' to separate commands and '"' to start comments. - // Don't do this for ":read !cmd" and ":write !cmd". - if ((ea.argt & EX_TRLBAR) && !usefilter) { - p = arg; - // ":redir @" is not the start of a comment - if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') { - p += 2; + while (arg[0] != NUL && (uint8_t)arg[0] != delim) { + if (arg[0] == '\\' && arg[1] != NUL) { + arg++; } - while (*p) { - if (*p == Ctrl_V) { - if (p[1] != NUL) { - p++; - } - } else if ((*p == '"' && !(ea.argt & EX_NOTRLCOM)) - || *p == '|' - || *p == '\n') { - if (*(p - 1) != '\\') { - if (*p == '|' || *p == '\n') { - return p + 1; - } - return NULL; // It's a comment + arg++; + } + if (arg[0] != NUL) { + return arg + 1; + } + + return NULL; +} + +/// Returns a pointer to the next command after a :substitute or a :& command. +/// Returns NULL if there is no next command. +static const char *find_cmd_after_substitute_cmd(const char *arg) +{ + const int delim = (uint8_t)(*arg); + if (delim) { + // Skip "from" part. + arg++; + arg = (const char *)skip_regexp((char *)arg, delim, magic_isset()); + + if (arg[0] != NUL && arg[0] == delim) { + // Skip "to" part. + arg++; + while (arg[0] != NUL && (uint8_t)arg[0] != delim) { + if (arg[0] == '\\' && arg[1] != NUL) { + arg++; } + arg++; + } + if (arg[0] != NUL) { // Skip delimiter. + arg++; } - MB_PTR_ADV(p); } } + while (arg[0] && strchr("|\"#", arg[0]) == NULL) { + arg++; + } + if (arg[0] != NUL) { + return arg; + } - if (!(ea.argt & EX_EXTRA) && *arg != NUL && strchr("|\"", *arg) == NULL) { - // no arguments allowed but there is something + return NULL; +} + +/// Returns a pointer to the next command after a :isearch/:dsearch/:ilist +/// :dlist/:ijump/:psearch/:djump/:isplit/:dsplit command. +/// Returns NULL if there is no next command. +static const char *find_cmd_after_isearch_cmd(expand_T *xp, const char *arg) +{ + // Skip count. + arg = (const char *)skipwhite(skipdigits(arg)); + if (*arg != '/') { return NULL; } - // Find start of last argument (argument just before cursor): - p = buff; - xp->xp_pattern = (char *)p; - len = strlen(buff); - while (*p && p < buff + len) { - if (*p == ' ' || *p == TAB) { - // Argument starts after a space. - xp->xp_pattern = (char *)++p; - } else { - if (*p == '\\' && *(p + 1) != NUL) { - p++; // skip over escaped character - } - MB_PTR_ADV(p); + // Match regexp, not just whole words. + for (++arg; *arg && *arg != '/'; arg++) { + if (*arg == '\\' && arg[1] != NUL) { + arg++; } } + if (*arg) { + arg = (const char *)skipwhite(arg + 1); - if (ea.argt & EX_XFILE) { - int in_quote = false; - const char *bow = NULL; // Beginning of word. - - // Allow spaces within back-quotes to count as part of the argument - // being expanded. - xp->xp_pattern = skipwhite(arg); - p = (const char *)xp->xp_pattern; - while (*p != NUL) { - int c = utf_ptr2char(p); - if (c == '\\' && p[1] != NUL) { - p++; - } else if (c == '`') { - if (!in_quote) { - xp->xp_pattern = (char *)p; - bow = p + 1; - } - in_quote = !in_quote; - // An argument can contain just about everything, except - // characters that end the command and white space. - } else if (c == '|' || c == '\n' || c == '"' || ascii_iswhite(c)) { - len = 0; // avoid getting stuck when space is in 'isfname' - while (*p != NUL) { - c = utf_ptr2char(p); - if (c == '`' || vim_isfilec_or_wc(c)) { - break; - } - len = (size_t)utfc_ptr2len(p); - MB_PTR_ADV(p); - } - if (in_quote) { - bow = p; - } else { - xp->xp_pattern = (char *)p; - } - p -= len; - } - MB_PTR_ADV(p); + // Check for trailing illegal characters. + if (*arg == NUL || strchr("|\"\n", *arg) == NULL) { + xp->xp_context = EXPAND_NOTHING; + } else { + return arg; } + } - // If we are still inside the quotes, and we passed a space, just - // expand from there. - if (bow != NULL && in_quote) { - xp->xp_pattern = (char *)bow; - } - xp->xp_context = EXPAND_FILES; + return NULL; +} - // For a shell command more chars need to be escaped. - if (usefilter || ea.cmdidx == CMD_bang || ea.cmdidx == CMD_terminal) { -#ifndef BACKSLASH_IN_FILENAME - xp->xp_shell = true; -#endif - // When still after the command name expand executables. - if (xp->xp_pattern == skipwhite(arg)) { - xp->xp_context = EXPAND_SHELLCMD; - } +/// Set the completion context for the :unlet command. Always returns NULL. +static const char *set_context_in_unlet_cmd(expand_T *xp, const char *arg) +{ + while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) { + arg = (const char *)xp->xp_pattern + 1; + } + + xp->xp_context = EXPAND_USER_VARS; + xp->xp_pattern = (char *)arg; + + if (*xp->xp_pattern == '$') { + xp->xp_context = EXPAND_ENV_VARS; + xp->xp_pattern++; + } + + return NULL; +} + +/// Set the completion context for the :language command. Always returns NULL. +static const char *set_context_in_lang_cmd(expand_T *xp, const char *arg) +{ + const char *p = (const char *)skiptowhite(arg); + if (*p == NUL) { + xp->xp_context = EXPAND_LANGUAGE; + xp->xp_pattern = (char *)arg; + } else { + if (strncmp(arg, "messages", (size_t)(p - arg)) == 0 + || strncmp(arg, "ctype", (size_t)(p - arg)) == 0 + || strncmp(arg, "time", (size_t)(p - arg)) == 0 + || strncmp(arg, "collate", (size_t)(p - arg)) == 0) { + xp->xp_context = EXPAND_LOCALES; + xp->xp_pattern = skipwhite(p); + } else { + xp->xp_context = EXPAND_NOTHING; } + } - // Check for environment variable. - if (*xp->xp_pattern == '$') { - for (p = (const char *)xp->xp_pattern + 1; *p != NUL; p++) { - if (!vim_isIDc((uint8_t)(*p))) { - break; - } - } - if (*p == NUL) { - xp->xp_context = EXPAND_ENV_VARS; - xp->xp_pattern++; - // Avoid that the assignment uses EXPAND_FILES again. - if (context != EXPAND_USER_DEFINED && context != EXPAND_USER_LIST) { - context = EXPAND_ENV_VARS; - } + return NULL; +} + +static enum { + EXP_BREAKPT_ADD, ///< expand ":breakadd" sub-commands + EXP_BREAKPT_DEL, ///< expand ":breakdel" sub-commands + EXP_PROFDEL, ///< expand ":profdel" sub-commands +} breakpt_expand_what; + +/// Set the completion context for the :breakadd command. Always returns NULL. +static const char *set_context_in_breakadd_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx) +{ + xp->xp_context = EXPAND_BREAKPOINT; + xp->xp_pattern = (char *)arg; + + if (cmdidx == CMD_breakadd) { + breakpt_expand_what = EXP_BREAKPT_ADD; + } else if (cmdidx == CMD_breakdel) { + breakpt_expand_what = EXP_BREAKPT_DEL; + } else { + breakpt_expand_what = EXP_PROFDEL; + } + + const char *p = skipwhite(arg); + if (*p == NUL) { + return NULL; + } + const char *subcmd_start = p; + + if (strncmp("file ", p, 5) == 0 || strncmp("func ", p, 5) == 0) { + // :breakadd file [lnum] <filename> + // :breakadd func [lnum] <funcname> + p += 4; + p = skipwhite(p); + + // skip line number (if specified) + if (ascii_isdigit(*p)) { + p = skipdigits(p); + if (*p != ' ') { + xp->xp_context = EXPAND_NOTHING; + return NULL; } + p = skipwhite(p); } - // Check for user names. - if (*xp->xp_pattern == '~') { - for (p = (const char *)xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) {} - // Complete ~user only if it partially matches a user name. - // A full match ~user<Tab> will be replaced by user's home - // directory i.e. something like ~user<Tab> -> /home/user/ - if (*p == NUL && p > (const char *)xp->xp_pattern + 1 - && match_user((char_u *)xp->xp_pattern + 1) >= 1) { - xp->xp_context = EXPAND_USER; - xp->xp_pattern++; - } + if (strncmp("file", subcmd_start, 4) == 0) { + xp->xp_context = EXPAND_FILES; + } else { + xp->xp_context = EXPAND_USER_FUNC; } + xp->xp_pattern = (char *)p; + } else if (strncmp("expr ", p, 5) == 0) { + // :breakadd expr <expression> + xp->xp_context = EXPAND_EXPRESSION; + xp->xp_pattern = skipwhite(p + 5); + } + + return NULL; +} + +static const char *set_context_in_scriptnames_cmd(expand_T *xp, const char *arg) +{ + xp->xp_context = EXPAND_NOTHING; + xp->xp_pattern = NULL; + + char *p = skipwhite(arg); + if (ascii_isdigit(*p)) { + return NULL; } - // 6. switch on command name - switch (ea.cmdidx) { + xp->xp_context = EXPAND_SCRIPTNAMES; + xp->xp_pattern = p; + + return NULL; +} + +/// Set the completion context in "xp" for command "cmd" with index "cmdidx". +/// The argument to the command is "arg" and the argument flags is "argt". +/// For user-defined commands and for environment variables, "context" has the +/// completion type. +/// +/// @return a pointer to the next command, or NULL if there is no next command. +static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expand_T *xp, + const char *arg, uint32_t argt, int context, bool forceit) +{ + switch (cmdidx) { case CMD_find: case CMD_sfind: case CMD_tabfind: @@ -1313,6 +1823,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_folddoclosed: case CMD_folddoopen: case CMD_hide: + case CMD_horizontal: case CMD_keepalt: case CMD_keepjumps: case CMD_keepmarks: @@ -1335,27 +1846,10 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) return arg; case CMD_filter: - if (*arg != NUL) { - arg = (const char *)skip_vimgrep_pat((char *)arg, NULL, NULL); - } - if (arg == NULL || *arg == NUL) { - xp->xp_context = EXPAND_NOTHING; - return NULL; - } - return (const char *)skipwhite(arg); + return set_context_in_filter_cmd(xp, arg); case CMD_match: - if (*arg == NUL || !ends_excmd(*arg)) { - // also complete "None" - set_context_in_echohl_cmd(xp, arg); - arg = (const char *)skipwhite((char *)skiptowhite((const char_u *)arg)); - if (*arg != NUL) { - xp->xp_context = EXPAND_NOTHING; - arg = (const char *)skip_regexp((char_u *)arg + 1, (uint8_t)(*arg), - p_magic, NULL); - } - } - return (const char *)find_nextcmd((char_u *)arg); + return set_context_in_match_cmd(xp, arg); // All completion for the +cmdline_compl feature goes here. @@ -1368,49 +1862,11 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) break; case CMD_global: - case CMD_vglobal: { - const int delim = (uint8_t)(*arg); // Get the delimiter. - if (delim) { - arg++; // Skip delimiter if there is one. - } - - while (arg[0] != NUL && (uint8_t)arg[0] != delim) { - if (arg[0] == '\\' && arg[1] != NUL) { - arg++; - } - arg++; - } - if (arg[0] != NUL) { - return arg + 1; - } - break; - } + case CMD_vglobal: + return find_cmd_after_global_cmd(arg); case CMD_and: - case CMD_substitute: { - const int delim = (uint8_t)(*arg); - if (delim) { - // Skip "from" part. - arg++; - arg = (const char *)skip_regexp((char_u *)arg, delim, p_magic, NULL); - } - // Skip "to" part. - while (arg[0] != NUL && (uint8_t)arg[0] != delim) { - if (arg[0] == '\\' && arg[1] != NUL) { - arg++; - } - arg++; - } - if (arg[0] != NUL) { // Skip delimiter. - arg++; - } - while (arg[0] && strchr("|\"#", arg[0]) == NULL) { - arg++; - } - if (arg[0] != NUL) { - return arg; - } - break; - } + case CMD_substitute: + return find_cmd_after_substitute_cmd(arg); case CMD_isearch: case CMD_dsearch: case CMD_ilist: @@ -1420,26 +1876,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_djump: case CMD_isplit: case CMD_dsplit: - // Skip count. - arg = (const char *)skipwhite(skipdigits(arg)); - if (*arg == '/') { // Match regexp, not just whole words. - for (++arg; *arg && *arg != '/'; arg++) { - if (*arg == '\\' && arg[1] != NUL) { - arg++; - } - } - if (*arg) { - arg = (const char *)skipwhite(arg + 1); - - // Check for trailing illegal characters. - if (*arg && strchr("|\"\n", *arg) == NULL) { - xp->xp_context = EXPAND_NOTHING; - } else { - return arg; - } - } - } - break; + return find_cmd_after_isearch_cmd(xp, arg); case CMD_autocmd: return (const char *)set_context_in_autocmd(xp, (char *)arg, false); @@ -1447,13 +1884,13 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_doautoall: return (const char *)set_context_in_autocmd(xp, (char *)arg, true); case CMD_set: - set_context_in_set_cmd(xp, (char_u *)arg, 0); + set_context_in_set_cmd(xp, (char *)arg, 0); break; case CMD_setglobal: - set_context_in_set_cmd(xp, (char_u *)arg, OPT_GLOBAL); + set_context_in_set_cmd(xp, (char *)arg, OPT_GLOBAL); break; case CMD_setlocal: - set_context_in_set_cmd(xp, (char_u *)arg, OPT_LOCAL); + set_context_in_set_cmd(xp, (char *)arg, OPT_LOCAL); break; case CMD_tag: case CMD_stag: @@ -1498,24 +1935,11 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_lexpr: case CMD_laddexpr: case CMD_lgetexpr: - set_context_for_expression(xp, (char *)arg, ea.cmdidx); + set_context_for_expression(xp, (char *)arg, cmdidx); break; case CMD_unlet: - while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) { - arg = (const char *)xp->xp_pattern + 1; - } - - xp->xp_context = EXPAND_USER_VARS; - xp->xp_pattern = (char *)arg; - - if (*xp->xp_pattern == '$') { - xp->xp_context = EXPAND_ENV_VARS; - xp->xp_pattern++; - } - - break; - + return set_context_in_unlet_cmd(xp, arg); case CMD_function: case CMD_delfunction: xp->xp_context = EXPAND_USER_FUNC; @@ -1528,13 +1952,8 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_highlight: set_context_in_highlight_cmd(xp, arg); break; - case CMD_cscope: - case CMD_lcscope: - case CMD_scscope: - set_context_in_cscope_cmd(xp, arg, ea.cmdidx); - break; case CMD_sign: - set_context_in_sign_cmd(xp, (char_u *)arg); + set_context_in_sign_cmd(xp, (char *)arg); break; case CMD_bdelete: case CMD_bwipeout: @@ -1559,34 +1978,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_USER: case CMD_USER_BUF: - if (context != EXPAND_NOTHING) { - // EX_XFILE: file names are handled above. - if (!(ea.argt & EX_XFILE)) { - if (context == EXPAND_MENUS) { - return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit); - } else if (context == EXPAND_COMMANDS) { - return arg; - } else if (context == EXPAND_MAPPINGS) { - return (const char *)set_context_in_map_cmd(xp, "map", (char_u *)arg, forceit, - false, false, - CMD_map); - } - // Find start of last argument. - p = arg; - while (*p) { - if (*p == ' ') { - // argument starts after a space - arg = p + 1; - } else if (*p == '\\' && *(p + 1) != NUL) { - p++; // skip over escaped character - } - MB_PTR_ADV(p); - } - xp->xp_pattern = (char *)arg; - } - xp->xp_context = context; - } - break; + return set_context_in_user_cmdarg(cmd, arg, argt, context, xp, forceit); case CMD_map: case CMD_noremap: @@ -1606,8 +1998,8 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_snoremap: case CMD_xmap: case CMD_xnoremap: - return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, false, - false, ea.cmdidx); + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false, + false, cmdidx); case CMD_unmap: case CMD_nunmap: case CMD_vunmap: @@ -1617,8 +2009,8 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_lunmap: case CMD_sunmap: case CMD_xunmap: - return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, false, - true, ea.cmdidx); + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false, + true, cmdidx); case CMD_mapclear: case CMD_nmapclear: case CMD_vmapclear: @@ -1638,13 +2030,13 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) case CMD_cnoreabbrev: case CMD_iabbrev: case CMD_inoreabbrev: - return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, true, - false, ea.cmdidx); + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true, + false, cmdidx); case CMD_unabbreviate: case CMD_cunabbrev: case CMD_iunabbrev: - return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char_u *)arg, forceit, true, - true, ea.cmdidx); + return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true, + true, cmdidx); case CMD_menu: case CMD_noremenu: case CMD_unmenu: @@ -1702,22 +2094,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) #ifdef HAVE_WORKING_LIBINTL case CMD_language: - p = (const char *)skiptowhite((const char_u *)arg); - if (*p == NUL) { - xp->xp_context = EXPAND_LANGUAGE; - xp->xp_pattern = (char *)arg; - } else { - if (strncmp(arg, "messages", (size_t)(p - arg)) == 0 - || strncmp(arg, "ctype", (size_t)(p - arg)) == 0 - || strncmp(arg, "time", (size_t)(p - arg)) == 0 - || strncmp(arg, "collate", (size_t)(p - arg)) == 0) { - xp->xp_context = EXPAND_LOCALES; - xp->xp_pattern = skipwhite(p); - } else { - xp->xp_context = EXPAND_NOTHING; - } - } - break; + return set_context_in_lang_cmd(xp, arg); #endif case CMD_profile: set_context_in_profile_cmd(xp, arg); @@ -1753,6 +2130,14 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) xp->xp_pattern = (char *)arg; break; + case CMD_breakadd: + case CMD_profdel: + case CMD_breakdel: + return set_context_in_breakadd_cmd(xp, arg, cmdidx); + + case CMD_scriptnames: + return set_context_in_scriptnames_cmd(xp, arg); + case CMD_lua: xp->xp_context = EXPAND_LUA; break; @@ -1763,14 +2148,192 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff) return NULL; } +/// This is all pretty much copied from do_one_cmd(), with all the extra stuff +/// we don't need/want deleted. Maybe this could be done better if we didn't +/// repeat all this stuff. The only problem is that they may not stay +/// perfectly compatible with each other, but then the command line syntax +/// probably won't change that much -- webb. +/// +/// @param buff buffer for command string +static const char *set_one_cmd_context(expand_T *xp, const char *buff) +{ + size_t len = 0; + exarg_T ea; + int context = EXPAND_NOTHING; + bool forceit = false; + bool usefilter = false; // Filter instead of file name. + + ExpandInit(xp); + xp->xp_pattern = (char *)buff; + xp->xp_line = (char *)buff; + xp->xp_context = EXPAND_COMMANDS; // Default until we get past command + ea.argt = 0; + + // 1. skip comment lines and leading space, colons or bars + const char *cmd; + for (cmd = buff; vim_strchr(" \t:|", (uint8_t)(*cmd)) != NULL; cmd++) {} + xp->xp_pattern = (char *)cmd; + + if (*cmd == NUL) { + return NULL; + } + if (*cmd == '"') { // ignore comment lines + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + + // 3. skip over a range specifier of the form: addr [,addr] [;addr] .. + cmd = (const char *)skip_range(cmd, &xp->xp_context); + xp->xp_pattern = (char *)cmd; + if (*cmd == NUL) { + return NULL; + } + if (*cmd == '"') { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + + if (*cmd == '|' || *cmd == '\n') { + return cmd + 1; // There's another command + } + + // Get the command index. + const char *p = set_cmd_index(cmd, &ea, xp, &context); + if (p == NULL) { + return NULL; + } + + xp->xp_context = EXPAND_NOTHING; // Default now that we're past command + + if (*p == '!') { // forced commands + forceit = true; + p++; + } + + // 6. parse arguments + if (!IS_USER_CMDIDX(ea.cmdidx)) { + ea.argt = excmd_get_argt(ea.cmdidx); + } + + const char *arg = (const char *)skipwhite(p); + + // Skip over ++argopt argument + if ((ea.argt & EX_ARGOPT) && *arg != NUL && strncmp(arg, "++", 2) == 0) { + p = arg; + while (*p && !ascii_isspace(*p)) { + MB_PTR_ADV(p); + } + arg = (const char *)skipwhite(p); + } + + if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { + if (*arg == '>') { // append + if (*++arg == '>') { + arg++; + } + arg = (const char *)skipwhite(arg); + } else if (*arg == '!' && ea.cmdidx == CMD_write) { // :w !filter + arg++; + usefilter = true; + } + } + + if (ea.cmdidx == CMD_read) { + usefilter = forceit; // :r! filter if forced + if (*arg == '!') { // :r !filter + arg++; + usefilter = true; + } + } + + if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { + while (*arg == *cmd) { // allow any number of '>' or '<' + arg++; + } + arg = (const char *)skipwhite(arg); + } + + // Does command allow "+command"? + if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') { + // Check if we're in the +command + p = arg + 1; + arg = (const char *)skip_cmd_arg((char *)arg, false); + + // Still touching the command after '+'? + if (*arg == NUL) { + return p; + } + + // Skip space(s) after +command to get to the real argument. + arg = (const char *)skipwhite(arg); + } + + // Check for '|' to separate commands and '"' to start comments. + // Don't do this for ":read !cmd" and ":write !cmd". + if ((ea.argt & EX_TRLBAR) && !usefilter) { + p = arg; + // ":redir @" is not the start of a comment + if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') { + p += 2; + } + while (*p) { + if (*p == Ctrl_V) { + if (p[1] != NUL) { + p++; + } + } else if ((*p == '"' && !(ea.argt & EX_NOTRLCOM)) + || *p == '|' + || *p == '\n') { + if (*(p - 1) != '\\') { + if (*p == '|' || *p == '\n') { + return p + 1; + } + return NULL; // It's a comment + } + } + MB_PTR_ADV(p); + } + } + + if (!(ea.argt & EX_EXTRA) && *arg != NUL && strchr("|\"", *arg) == NULL) { + // no arguments allowed but there is something + return NULL; + } + + // Find start of last argument (argument just before cursor): + p = buff; + xp->xp_pattern = (char *)p; + len = strlen(buff); + while (*p && p < buff + len) { + if (*p == ' ' || *p == TAB) { + // argument starts after a space + xp->xp_pattern = (char *)++p; + } else { + if (*p == '\\' && *(p + 1) != NUL) { + p++; // skip over escaped character + } + MB_PTR_ADV(p); + } + } + + if (ea.argt & EX_XFILE) { + set_context_for_wildcard_arg(&ea, arg, usefilter, xp, &context); + } + + // Switch on command name. + return set_context_by_cmdname(cmd, ea.cmdidx, xp, arg, ea.argt, context, forceit); +} + +/// Set the completion context in "xp" for command "str" +/// /// @param str start of command line /// @param len length of command line (excl. NUL) /// @param col position of cursor /// @param use_ccline use ccline for info -void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline) +void set_cmd_context(expand_T *xp, char *str, int len, int col, int use_ccline) { CmdlineInfo *const ccline = get_cmdline_info(); - char_u old_char = NUL; + char old_char = NUL; // Avoid a UMR warning from Purify, only save the character if it has been // written before. @@ -1782,11 +2345,11 @@ void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline if (use_ccline && ccline->cmdfirstc == '=') { // pass CMD_SIZE because there is no real command - set_context_for_expression(xp, (char *)str, CMD_SIZE); + set_context_for_expression(xp, str, CMD_SIZE); } else if (use_ccline && ccline->input_fn) { xp->xp_context = ccline->xp_context; - xp->xp_pattern = (char *)ccline->cmdbuff; - xp->xp_arg = (char *)ccline->xp_arg; + xp->xp_pattern = ccline->cmdbuff; + xp->xp_arg = ccline->xp_arg; } else { while (nextcomm != NULL) { nextcomm = set_one_cmd_context(xp, nextcomm); @@ -1795,7 +2358,7 @@ void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline // Store the string here so that call_user_expand_func() can get to them // easily. - xp->xp_line = (char *)str; + xp->xp_line = str; xp->xp_col = col; str[col] = old_char; @@ -1815,9 +2378,9 @@ void set_cmd_context(expand_T *xp, char_u *str, int len, int col, int use_ccline /// @param col position of cursor /// @param matchcount return: nr of matches /// @param matches return: array of pointers to matches -int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char ***matches) +int expand_cmdline(expand_T *xp, const char *str, int col, int *matchcount, char ***matches) { - char_u *file_str = NULL; + char *file_str = NULL; int options = WILD_ADD_SLASH|WILD_SILENT; if (xp->xp_context == EXPAND_UNSUCCESSFUL) { @@ -1830,16 +2393,21 @@ int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char *** } // add star to file name, or convert to regexp if not exp. files. - assert((str + col) - (char_u *)xp->xp_pattern >= 0); - xp->xp_pattern_len = (size_t)((str + col) - (char_u *)xp->xp_pattern); - file_str = addstar((char_u *)xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + assert((str + col) - xp->xp_pattern >= 0); + xp->xp_pattern_len = (size_t)((str + col) - xp->xp_pattern); + if (cmdline_fuzzy_completion_supported(xp)) { + // If fuzzy matching, don't modify the search string + file_str = xstrdup(xp->xp_pattern); + } else { + file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + } if (p_wic) { options += WILD_ICASE; } // find all files that match the description - if (ExpandFromContext(xp, file_str, matchcount, matches, options) == FAIL) { + if (ExpandFromContext(xp, file_str, matches, matchcount, options) == FAIL) { *matchcount = 0; *matches = NULL; } @@ -1848,6 +2416,66 @@ int expand_cmdline(expand_T *xp, char_u *str, int col, int *matchcount, char *** return EXPAND_OK; } +/// Expand file or directory names. +static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *numMatches, + int flags, int options) +{ + bool free_pat = false; + + // for ":set path=" and ":set tags=" halve backslashes for escaped space + if (xp->xp_backslash != XP_BS_NONE) { + free_pat = true; + pat = xstrdup(pat); + for (int i = 0; pat[i]; i++) { + if (pat[i] == '\\') { + if (xp->xp_backslash == XP_BS_THREE + && pat[i + 1] == '\\' + && pat[i + 2] == '\\' + && pat[i + 3] == ' ') { + STRMOVE(pat + i, pat + i + 3); + } + if (xp->xp_backslash == XP_BS_ONE + && pat[i + 1] == ' ') { + STRMOVE(pat + i, pat + i + 1); + } + } + } + } + + if (xp->xp_context == EXPAND_FILES) { + flags |= EW_FILE; + } else if (xp->xp_context == EXPAND_FILES_IN_PATH) { + flags |= (EW_FILE | EW_PATH); + } else { + flags = (flags | EW_DIR) & ~EW_FILE; + } + if (options & WILD_ICASE) { + flags |= EW_ICASE; + } + + // Expand wildcards, supporting %:h and the like. + int ret = expand_wildcards_eval(&pat, numMatches, matches, flags); + if (free_pat) { + xfree(pat); + } +#ifdef BACKSLASH_IN_FILENAME + if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) { + for (int j = 0; j < *numMatches; j++) { + char *ptr = (*matches)[j]; + while (*ptr != NUL) { + if (p_csl[0] == 's' && *ptr == '\\') { + *ptr = '/'; + } else if (p_csl[0] == 'b' && *ptr == '/') { + *ptr = '\\'; + } + ptr += utfc_ptr2len(ptr); + } + } + } +#endif + return ret; +} + /// Function given to ExpandGeneric() to obtain the possible arguments of the /// ":behave {mswin,xterm}" command. static char *get_behave_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) @@ -1862,6 +2490,45 @@ static char *get_behave_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) } /// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":breakadd {expr, file, func, here}" command. +/// ":breakdel {func, file, here}" command. +static char *get_breakadd_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + char *opts[] = { "expr", "file", "func", "here" }; + + if (idx >= 0 && idx <= 3) { + // breakadd {expr, file, func, here} + if (breakpt_expand_what == EXP_BREAKPT_ADD) { + return opts[idx]; + } else if (breakpt_expand_what == EXP_BREAKPT_DEL) { + // breakdel {func, file, here} + if (idx <= 2) { + return opts[idx + 1]; + } + } else { + // profdel {func, file} + if (idx <= 1) { + return opts[idx + 1]; + } + } + } + return NULL; +} + +/// Function given to ExpandGeneric() to obtain the possible arguments for the +/// ":scriptnames" command. +static char *get_scriptnames_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) +{ + if (!SCRIPT_ID_VALID(idx + 1)) { + return NULL; + } + + scriptitem_T *si = &SCRIPT_ITEM(idx + 1); + home_replace(NULL, si->sn_name, NameBuff, MAXPATHL, true); + return NameBuff; +} + +/// Function given to ExpandGeneric() to obtain the possible arguments of the /// ":messages {clear}" command. static char *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) { @@ -1905,16 +2572,72 @@ static char *get_healthcheck_names(expand_T *xp FUNC_ATTR_UNUSED, int idx) return NULL; } -/// Do the expansion based on xp->xp_context and "pat". -/// -/// @param options WILD_ flags -static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char ***file, int options) +/// Do the expansion based on xp->xp_context and "rmp". +static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches, int *numMatches) { - regmatch_T regmatch; - int ret; - int flags; + typedef CompleteListItemGetter ExpandFunc; + static struct expgen { + int context; + ExpandFunc func; + int ic; + int escaped; + } tab[] = { + { EXPAND_COMMANDS, get_command_name, false, true }, + { EXPAND_BEHAVE, get_behave_arg, true, true }, + { EXPAND_MAPCLEAR, get_mapclear_arg, true, true }, + { EXPAND_MESSAGES, get_messages_arg, true, true }, + { EXPAND_HISTORY, get_history_arg, true, true }, + { EXPAND_USER_COMMANDS, get_user_commands, false, true }, + { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true }, + { EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, false, true }, + { EXPAND_USER_NARGS, get_user_cmd_nargs, false, true }, + { EXPAND_USER_COMPLETE, get_user_cmd_complete, false, true }, + { EXPAND_USER_VARS, get_user_var_name, false, true }, + { EXPAND_FUNCTIONS, get_function_name, false, true }, + { EXPAND_USER_FUNC, get_user_func_name, false, true }, + { EXPAND_EXPRESSION, get_expr_name, false, true }, + { EXPAND_MENUS, get_menu_name, false, true }, + { EXPAND_MENUNAMES, get_menu_names, false, true }, + { EXPAND_SYNTAX, get_syntax_name, true, true }, + { EXPAND_SYNTIME, get_syntime_arg, true, true }, + { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, false }, + { EXPAND_EVENTS, expand_get_event_name, true, false }, + { EXPAND_AUGROUP, expand_get_augroup_name, true, false }, + { EXPAND_SIGN, get_sign_name, true, true }, + { EXPAND_PROFILE, get_profile_name, true, true }, +#ifdef HAVE_WORKING_LIBINTL + { EXPAND_LANGUAGE, get_lang_arg, true, false }, + { EXPAND_LOCALES, get_locales, true, false }, +#endif + { EXPAND_ENV_VARS, get_env_name, true, true }, + { EXPAND_USER, get_users, true, false }, + { EXPAND_ARGLIST, get_arglist_name, true, false }, + { EXPAND_BREAKPOINT, get_breakadd_arg, true, true }, + { EXPAND_SCRIPTNAMES, get_scriptnames_arg, true, false }, + { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false }, + }; + int ret = FAIL; + + // Find a context in the table and call the ExpandGeneric() with the + // right function to do the expansion. + for (int i = 0; i < (int)ARRAY_SIZE(tab); i++) { + if (xp->xp_context == tab[i].context) { + if (tab[i].ic) { + rmp->rm_ic = true; + } + ExpandGeneric(pat, xp, rmp, matches, numMatches, tab[i].func, tab[i].escaped); + ret = OK; + break; + } + } + + return ret; +} - flags = EW_DIR; // include directories +/// Map wild expand options to flags for expand_wildcards() +static int map_wildopts_to_ewflags(int options) +{ + int flags = EW_DIR; // include directories if (options & WILD_LIST_NOTFOUND) { flags |= EW_NOTFOUND; } @@ -1934,214 +2657,123 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char ***f flags |= EW_ALLLINKS; } + return flags; +} + +/// Do the expansion based on xp->xp_context and "pat". +/// +/// @param options WILD_ flags +static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numMatches, int options) +{ + regmatch_T regmatch = { .rm_ic = false }; + int ret; + int flags = map_wildopts_to_ewflags(options); + const bool fuzzy = cmdline_fuzzy_complete(pat) + && cmdline_fuzzy_completion_supported(xp); + if (xp->xp_context == EXPAND_FILES || xp->xp_context == EXPAND_DIRECTORIES || xp->xp_context == EXPAND_FILES_IN_PATH) { - // Expand file or directory names. - bool free_pat = false; - int i; - - // for ":set path=" and ":set tags=" halve backslashes for escaped space - if (xp->xp_backslash != XP_BS_NONE) { - free_pat = true; - pat = vim_strsave(pat); - for (i = 0; pat[i]; i++) { - if (pat[i] == '\\') { - if (xp->xp_backslash == XP_BS_THREE - && pat[i + 1] == '\\' - && pat[i + 2] == '\\' - && pat[i + 3] == ' ') { - STRMOVE(pat + i, pat + i + 3); - } - if (xp->xp_backslash == XP_BS_ONE - && pat[i + 1] == ' ') { - STRMOVE(pat + i, pat + i + 1); - } - } - } - } - - if (xp->xp_context == EXPAND_FILES) { - flags |= EW_FILE; - } else if (xp->xp_context == EXPAND_FILES_IN_PATH) { - flags |= (EW_FILE | EW_PATH); - } else { - flags = (flags | EW_DIR) & ~EW_FILE; - } - if (options & WILD_ICASE) { - flags |= EW_ICASE; - } - - // Expand wildcards, supporting %:h and the like. - ret = expand_wildcards_eval(&pat, num_file, file, flags); - if (free_pat) { - xfree(pat); - } -#ifdef BACKSLASH_IN_FILENAME - if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) { - for (int i = 0; i < *num_file; i++) { - char_u *ptr = (*file)[i]; - while (*ptr != NUL) { - if (p_csl[0] == 's' && *ptr == '\\') { - *ptr = '/'; - } else if (p_csl[0] == 'b' && *ptr == '/') { - *ptr = '\\'; - } - ptr += utfc_ptr2len(ptr); - } - } - } -#endif - return ret; + return expand_files_and_dirs(xp, pat, matches, numMatches, flags, options); } - *file = NULL; - *num_file = 0; + *matches = NULL; + *numMatches = 0; if (xp->xp_context == EXPAND_HELP) { // With an empty argument we would get all the help tags, which is // very slow. Get matches for "help" instead. - if (find_help_tags(*pat == NUL ? "help" : (char *)pat, - num_file, file, false) == OK) { - cleanup_help_tags(*num_file, *file); + if (find_help_tags(*pat == NUL ? "help" : pat, + numMatches, matches, false) == OK) { + cleanup_help_tags(*numMatches, *matches); return OK; } return FAIL; } if (xp->xp_context == EXPAND_SHELLCMD) { - *file = NULL; - expand_shellcmd(pat, num_file, file, flags); + expand_shellcmd(pat, matches, numMatches, flags); return OK; } if (xp->xp_context == EXPAND_OLD_SETTING) { - ExpandOldSetting(num_file, file); + ExpandOldSetting(numMatches, matches); return OK; } if (xp->xp_context == EXPAND_BUFFERS) { - return ExpandBufnames((char *)pat, num_file, file, options); + return ExpandBufnames(pat, numMatches, matches, options); } if (xp->xp_context == EXPAND_DIFF_BUFFERS) { - return ExpandBufnames((char *)pat, num_file, file, options | BUF_DIFF_FILTER); + return ExpandBufnames(pat, numMatches, matches, options | BUF_DIFF_FILTER); } if (xp->xp_context == EXPAND_TAGS || xp->xp_context == EXPAND_TAGS_LISTFILES) { - return expand_tags(xp->xp_context == EXPAND_TAGS, pat, num_file, file); + return expand_tags(xp->xp_context == EXPAND_TAGS, pat, numMatches, matches); } if (xp->xp_context == EXPAND_COLORS) { char *directories[] = { "colors", NULL }; - return ExpandRTDir(pat, DIP_START + DIP_OPT + DIP_LUA, num_file, file, directories); + return ExpandRTDir(pat, DIP_START + DIP_OPT, numMatches, matches, directories); } if (xp->xp_context == EXPAND_COMPILER) { char *directories[] = { "compiler", NULL }; - return ExpandRTDir(pat, DIP_LUA, num_file, file, directories); + return ExpandRTDir(pat, 0, numMatches, matches, directories); } if (xp->xp_context == EXPAND_OWNSYNTAX) { char *directories[] = { "syntax", NULL }; - return ExpandRTDir(pat, 0, num_file, file, directories); + return ExpandRTDir(pat, 0, numMatches, matches, directories); } if (xp->xp_context == EXPAND_FILETYPE) { char *directories[] = { "syntax", "indent", "ftplugin", NULL }; - return ExpandRTDir(pat, DIP_LUA, num_file, file, directories); + return ExpandRTDir(pat, 0, numMatches, matches, directories); } if (xp->xp_context == EXPAND_USER_LIST) { - return ExpandUserList(xp, num_file, file); + return ExpandUserList(xp, matches, numMatches); } if (xp->xp_context == EXPAND_USER_LUA) { - return ExpandUserLua(xp, num_file, file); + return ExpandUserLua(xp, numMatches, matches); } if (xp->xp_context == EXPAND_PACKADD) { - return ExpandPackAddDir(pat, num_file, file); + return ExpandPackAddDir(pat, numMatches, matches); } // When expanding a function name starting with s:, match the <SNR>nr_ // prefix. char *tofree = NULL; - if (xp->xp_context == EXPAND_USER_FUNC && STRNCMP(pat, "^s:", 3) == 0) { - const size_t len = STRLEN(pat) + 20; + if (xp->xp_context == EXPAND_USER_FUNC && strncmp(pat, "^s:", 3) == 0) { + const size_t len = strlen(pat) + 20; tofree = xmalloc(len); snprintf(tofree, len, "^<SNR>\\d\\+_%s", pat + 3); - pat = (char_u *)tofree; + pat = tofree; } if (xp->xp_context == EXPAND_LUA) { ILOG("PAT %s", pat); - return nlua_expand_pat(xp, pat, num_file, file); + return nlua_expand_pat(xp, pat, numMatches, matches); } - regmatch.regprog = vim_regcomp((char *)pat, p_magic ? RE_MAGIC : 0); - if (regmatch.regprog == NULL) { - return FAIL; - } + if (!fuzzy) { + regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) { + return FAIL; + } - // set ignore-case according to p_ic, p_scs and pat - regmatch.rm_ic = ignorecase(pat); + // set ignore-case according to p_ic, p_scs and pat + regmatch.rm_ic = ignorecase(pat); + } if (xp->xp_context == EXPAND_SETTINGS || xp->xp_context == EXPAND_BOOL_SETTINGS) { - ret = ExpandSettings(xp, ®match, num_file, file); + ret = ExpandSettings(xp, ®match, pat, numMatches, matches, fuzzy); } else if (xp->xp_context == EXPAND_MAPPINGS) { - ret = ExpandMappings(®match, num_file, file); + ret = ExpandMappings(pat, ®match, numMatches, matches); } else if (xp->xp_context == EXPAND_USER_DEFINED) { - ret = ExpandUserDefined(xp, ®match, num_file, file); + ret = ExpandUserDefined(pat, xp, ®match, matches, numMatches); } else { - typedef CompleteListItemGetter ExpandFunc; - static struct expgen { - int context; - ExpandFunc func; - int ic; - int escaped; - } tab[] = { - { EXPAND_COMMANDS, get_command_name, false, true }, - { EXPAND_BEHAVE, get_behave_arg, true, true }, - { EXPAND_MAPCLEAR, get_mapclear_arg, true, true }, - { EXPAND_MESSAGES, get_messages_arg, true, true }, - { EXPAND_HISTORY, get_history_arg, true, true }, - { EXPAND_USER_COMMANDS, get_user_commands, false, true }, - { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true }, - { EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, false, true }, - { EXPAND_USER_NARGS, get_user_cmd_nargs, false, true }, - { EXPAND_USER_COMPLETE, get_user_cmd_complete, false, true }, - { EXPAND_USER_VARS, get_user_var_name, false, true }, - { EXPAND_FUNCTIONS, get_function_name, false, true }, - { EXPAND_USER_FUNC, get_user_func_name, false, true }, - { EXPAND_EXPRESSION, get_expr_name, false, true }, - { EXPAND_MENUS, get_menu_name, false, true }, - { EXPAND_MENUNAMES, get_menu_names, false, true }, - { EXPAND_SYNTAX, get_syntax_name, true, true }, - { EXPAND_SYNTIME, get_syntime_arg, true, true }, - { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true }, - { EXPAND_EVENTS, expand_get_event_name, true, false }, - { EXPAND_AUGROUP, expand_get_augroup_name, true, false }, - { EXPAND_CSCOPE, get_cscope_name, true, true }, - { EXPAND_SIGN, get_sign_name, true, true }, - { EXPAND_PROFILE, get_profile_name, true, true }, -#ifdef HAVE_WORKING_LIBINTL - { EXPAND_LANGUAGE, get_lang_arg, true, false }, - { EXPAND_LOCALES, get_locales, true, false }, -#endif - { EXPAND_ENV_VARS, get_env_name, true, true }, - { EXPAND_USER, get_users, true, false }, - { EXPAND_ARGLIST, get_arglist_name, true, false }, - { EXPAND_CHECKHEALTH, get_healthcheck_names, true, false }, - }; - - // Find a context in the table and call the ExpandGeneric() with the - // right function to do the expansion. - ret = FAIL; - for (int i = 0; i < (int)ARRAY_SIZE(tab); i++) { - if (xp->xp_context == tab[i].context) { - if (tab[i].ic) { - regmatch.rm_ic = true; - } - ExpandGeneric(xp, ®match, num_file, file, tab[i].func, tab[i].escaped); - ret = OK; - break; - } - } + ret = ExpandOther(pat, xp, ®match, matches, numMatches); } - vim_regfree(regmatch.regprog); + if (!fuzzy) { + vim_regfree(regmatch.regprog); + } xfree(tofree); return ret; @@ -2154,102 +2786,173 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char ***f /// program. Matching strings are copied into an array, which is returned. /// /// @param func returns a string from the list -static void ExpandGeneric(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***file, - CompleteListItemGetter func, int escaped) +static void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regmatch, + char ***matches, int *numMatches, CompleteListItemGetter func, + int escaped) { - int i; - size_t count = 0; - char_u *str; + const bool fuzzy = cmdline_fuzzy_complete(pat); + *matches = NULL; + *numMatches = 0; - // count the number of matching names - for (i = 0;; i++) { - str = (char_u *)(*func)(xp, i); - if (str == NULL) { // end of list - break; - } - if (*str == NUL) { // skip empty strings - continue; - } - if (vim_regexec(regmatch, (char *)str, (colnr_T)0)) { - count++; - } - } - if (count == 0) { - return; + garray_T ga; + if (!fuzzy) { + ga_init(&ga, sizeof(char *), 30); + } else { + ga_init(&ga, sizeof(fuzmatch_str_T), 30); } - assert(count < INT_MAX); - *num_file = (int)count; - *file = xmalloc(count * sizeof(char_u *)); - // copy the matching names into allocated memory - count = 0; - for (i = 0;; i++) { - str = (char_u *)(*func)(xp, i); + for (int i = 0;; i++) { + char *str = (*func)(xp, i); if (str == NULL) { // End of list. break; } if (*str == NUL) { // Skip empty strings. continue; } - if (vim_regexec(regmatch, (char *)str, (colnr_T)0)) { - if (escaped) { - str = vim_strsave_escaped(str, (char_u *)" \t\\."); + + bool match; + int score = 0; + if (xp->xp_pattern[0] != NUL) { + if (!fuzzy) { + match = vim_regexec(regmatch, str, (colnr_T)0); } else { - str = vim_strsave(str); + score = fuzzy_match_str(str, pat); + match = (score != 0); } - (*file)[count++] = (char *)str; - if (func == get_menu_names) { - // Test for separator added by get_menu_names(). - str += STRLEN(str) - 1; - if (*str == '\001') { - *str = '.'; - } + } else { + match = true; + } + + if (!match) { + continue; + } + + if (escaped) { + str = vim_strsave_escaped(str, " \t\\."); + } else { + str = xstrdup(str); + } + + if (fuzzy) { + GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){ + .idx = ga.ga_len, + .str = str, + .score = score, + })); + } else { + GA_APPEND(char *, &ga, str); + } + + if (func == get_menu_names) { + // Test for separator added by get_menu_names(). + str += strlen(str) - 1; + if (*str == '\001') { + *str = '.'; } } } - // Sort the results. Keep menu's in the specified order. - if (xp->xp_context != EXPAND_MENUNAMES && xp->xp_context != EXPAND_MENUS) { - if (xp->xp_context == EXPAND_EXPRESSION - || xp->xp_context == EXPAND_FUNCTIONS - || xp->xp_context == EXPAND_USER_FUNC) { + if (ga.ga_len == 0) { + return; + } + + // Sort the matches when using regular expression matching and sorting + // applies to the completion context. Menus and scriptnames should be kept + // in the specified order. + const bool sort_matches = !fuzzy + && xp->xp_context != EXPAND_MENUNAMES + && xp->xp_context != EXPAND_MENUS + && xp->xp_context != EXPAND_SCRIPTNAMES; + + // <SNR> functions should be sorted to the end. + const bool funcsort = xp->xp_context == EXPAND_EXPRESSION + || xp->xp_context == EXPAND_FUNCTIONS + || xp->xp_context == EXPAND_USER_FUNC; + + // Sort the matches. + if (sort_matches) { + if (funcsort) { // <SNR> functions should be sorted to the end. - qsort((void *)(*file), (size_t)(*num_file), sizeof(char_u *), - sort_func_compare); + qsort(ga.ga_data, (size_t)ga.ga_len, sizeof(char *), sort_func_compare); } else { - sort_strings(*file, *num_file); + sort_strings(ga.ga_data, ga.ga_len); } } + if (!fuzzy) { + *matches = ga.ga_data; + *numMatches = ga.ga_len; + } else { + fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, funcsort); + *numMatches = ga.ga_len; + } + // Reset the variables used for special highlight names expansion, so that // they don't show up when getting normal highlight names by ID. reset_expand_highlight(); } +/// Expand shell command matches in one directory of $PATH. +static void expand_shellcmd_onedir(char *buf, char *s, size_t l, char *pat, char ***matches, + int *numMatches, int flags, hashtab_T *ht, garray_T *gap) +{ + xstrlcpy(buf, s, l + 1); + add_pathsep(buf); + l = strlen(buf); + xstrlcpy(buf + l, pat, MAXPATHL - l); + + // Expand matches in one directory of $PATH. + int ret = expand_wildcards(1, &buf, numMatches, matches, flags); + if (ret != OK) { + return; + } + + ga_grow(gap, *numMatches); + + for (int i = 0; i < *numMatches; i++) { + char *name = (*matches)[i]; + + if (strlen(name) > l) { + // Check if this name was already found. + hash_T hash = hash_hash(name + l); + hashitem_T *hi = + hash_lookup(ht, (const char *)(name + l), strlen(name + l), hash); + if (HASHITEM_EMPTY(hi)) { + // Remove the path that was prepended. + STRMOVE(name, name + l); + ((char **)gap->ga_data)[gap->ga_len++] = name; + hash_add_item(ht, hi, name, hash); + name = NULL; + } + } + xfree(name); + } + xfree(*matches); +} + /// Complete a shell command. /// -/// @param filepat is a pattern to match with command names. -/// @param[out] num_file is pointer to number of matches. -/// @param[out] file is pointer to array of pointers to matches. -/// *file will either be set to NULL or point to -/// allocated memory. -/// @param flagsarg is a combination of EW_* flags. -static void expand_shellcmd(char_u *filepat, int *num_file, char ***file, int flagsarg) +/// @param filepat is a pattern to match with command names. +/// @param[out] matches is pointer to array of pointers to matches. +/// *matches will either be set to NULL or point to +/// allocated memory. +/// @param[out] numMatches is pointer to number of matches. +/// @param flagsarg is a combination of EW_* flags. +static void expand_shellcmd(char *filepat, char ***matches, int *numMatches, int flagsarg) FUNC_ATTR_NONNULL_ALL { - char_u *pat; + char *pat; int i; - char_u *path = NULL; + char *path = NULL; garray_T ga; char *buf = xmalloc(MAXPATHL); size_t l; - char_u *s, *e; + char *s, *e; int flags = flagsarg; - int ret; bool did_curdir = false; // for ":set path=" and ":set tags=" halve backslashes for escaped space - pat = vim_strsave(filepat); + pat = xstrdup(filepat); for (i = 0; pat[i]; i++) { if (pat[i] == '\\' && pat[i + 1] == ' ') { STRMOVE(pat + i, pat + i + 1); @@ -2261,29 +2964,29 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char ***file, int fl bool mustfree = false; // Track memory allocation for *path. if (pat[0] == '.' && (vim_ispathsep(pat[1]) || (pat[1] == '.' && vim_ispathsep(pat[2])))) { - path = (char_u *)"."; + path = "."; } else { // For an absolute name we don't use $PATH. if (!path_is_absolute(pat)) { - path = (char_u *)vim_getenv("PATH"); + path = vim_getenv("PATH"); } if (path == NULL) { - path = (char_u *)""; + path = ""; } else { mustfree = true; } } // Go over all directories in $PATH. Expand matches in that directory and - // collect them in "ga". When "." is not in $PATH also expaned for the + // collect them in "ga". When "." is not in $PATH also expand for the // current directory, to find "subdir/cmd". ga_init(&ga, (int)sizeof(char *), 10); hashtab_T found_ht; hash_init(&found_ht); for (s = path;; s = e) { - e = (char_u *)vim_strchr((char *)s, ENV_SEPCHAR); + e = vim_strchr(s, ENV_SEPCHAR); if (e == NULL) { - e = s + STRLEN(s); + e = s + strlen(s); } if (*s == NUL) { @@ -2293,7 +2996,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char ***file, int fl // Find directories in the current directory, path is empty. did_curdir = true; flags |= EW_DIR; - } else if (STRNCMP(s, ".", e - s) == 0) { + } else if (strncmp(s, ".", (size_t)(e - s)) == 0) { did_curdir = true; flags |= EW_DIR; } else { @@ -2305,44 +3008,13 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char ***file, int fl if (l > MAXPATHL - 5) { break; } - STRLCPY(buf, s, l + 1); - add_pathsep(buf); - l = STRLEN(buf); - STRLCPY(buf + l, pat, MAXPATHL - l); - - // Expand matches in one directory of $PATH. - ret = expand_wildcards(1, &buf, num_file, file, flags); - if (ret == OK) { - ga_grow(&ga, *num_file); - { - for (i = 0; i < *num_file; i++) { - char_u *name = (char_u *)(*file)[i]; - - if (STRLEN(name) > l) { - // Check if this name was already found. - hash_T hash = hash_hash(name + l); - hashitem_T *hi = - hash_lookup(&found_ht, (const char *)(name + l), - STRLEN(name + l), hash); - if (HASHITEM_EMPTY(hi)) { - // Remove the path that was prepended. - STRMOVE(name, name + l); - ((char_u **)ga.ga_data)[ga.ga_len++] = name; - hash_add_item(&found_ht, hi, name, hash); - name = NULL; - } - } - xfree(name); - } - xfree(*file); - } - } + expand_shellcmd_onedir(buf, s, l, pat, matches, numMatches, flags, &found_ht, &ga); if (*e != NUL) { e++; } } - *file = ga.ga_data; - *num_file = ga.ga_len; + *matches = ga.ga_data; + *numMatches = ga.ga_len; xfree(buf); xfree(pat); @@ -2354,39 +3026,36 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char ***file, int fl /// Call "user_expand_func()" to invoke a user defined Vim script function and /// return the result (either a string, a List or NULL). -static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T *xp, int *num_file, - char ***file) +static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T *xp) FUNC_ATTR_NONNULL_ALL { CmdlineInfo *const ccline = get_cmdline_info(); - char_u keep = 0; + char keep = 0; typval_T args[4]; - char_u *pat = NULL; + char *pat = NULL; const sctx_T save_current_sctx = current_sctx; if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) { return NULL; } - *num_file = 0; - *file = NULL; if (ccline->cmdbuff != NULL) { keep = ccline->cmdbuff[ccline->cmdlen]; ccline->cmdbuff[ccline->cmdlen] = 0; } - pat = vim_strnsave((char_u *)xp->xp_pattern, xp->xp_pattern_len); + pat = xstrnsave(xp->xp_pattern, xp->xp_pattern_len); args[0].v_type = VAR_STRING; args[1].v_type = VAR_STRING; args[2].v_type = VAR_NUMBER; args[3].v_type = VAR_UNKNOWN; - args[0].vval.v_string = (char *)pat; + args[0].vval.v_string = pat; args[1].vval.v_string = xp->xp_line; args[2].vval.v_number = xp->xp_col; current_sctx = xp->xp_script_ctx; - void *const ret = user_expand_func((char_u *)xp->xp_arg, 3, args); + void *const ret = user_expand_func(xp->xp_arg, 3, args); current_sctx = save_current_sctx; if (ccline->cmdbuff != NULL) { @@ -2397,33 +3066,60 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T return ret; } -/// Expand names with a function defined by the user. -static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***file) +/// Expand names with a function defined by the user (EXPAND_USER_DEFINED and +/// EXPAND_USER_LIST). +static int ExpandUserDefined(const char *const pat, expand_T *xp, regmatch_T *regmatch, + char ***matches, int *numMatches) { - char_u *e; - garray_T ga; - - char_u *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp, num_file, - file); + const bool fuzzy = cmdline_fuzzy_complete(pat); + *matches = NULL; + *numMatches = 0; + char *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp); if (retstr == NULL) { return FAIL; } - ga_init(&ga, (int)sizeof(char *), 3); - for (char_u *s = retstr; *s != NUL; s = e) { - e = (char_u *)vim_strchr((char *)s, '\n'); + garray_T ga; + if (!fuzzy) { + ga_init(&ga, (int)sizeof(char *), 3); + } else { + ga_init(&ga, (int)sizeof(fuzmatch_str_T), 3); + } + + for (char *s = retstr, *e; *s != NUL; s = e) { + e = vim_strchr(s, '\n'); if (e == NULL) { - e = s + STRLEN(s); + e = s + strlen(s); } - const char_u keep = *e; + const char keep = *e; *e = NUL; - const bool skip = xp->xp_pattern[0] - && vim_regexec(regmatch, (char *)s, (colnr_T)0) == 0; + bool match; + int score = 0; + if (xp->xp_pattern[0] != NUL) { + if (!fuzzy) { + match = vim_regexec(regmatch, s, (colnr_T)0); + } else { + score = fuzzy_match_str(s, pat); + match = (score != 0); + } + } else { + match = true; // match everything + } + *e = keep; - if (!skip) { - GA_APPEND(char_u *, &ga, vim_strnsave(s, (size_t)(e - s))); + + if (match) { + if (!fuzzy) { + GA_APPEND(char *, &ga, xstrnsave(s, (size_t)(e - s))); + } else { + GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){ + .idx = ga.ga_len, + .str = xstrnsave(s, (size_t)(e - s)), + .score = score, + })); + } } if (*e != NUL) { @@ -2431,16 +3127,27 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, } } xfree(retstr); - *file = ga.ga_data; - *num_file = ga.ga_len; + + if (ga.ga_len == 0) { + return OK; + } + + if (!fuzzy) { + *matches = ga.ga_data; + *numMatches = ga.ga_len; + } else { + fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, false); + *numMatches = ga.ga_len; + } return OK; } /// Expand names with a list returned by a function defined by the user. -static int ExpandUserList(expand_T *xp, int *num_file, char ***file) +static int ExpandUserList(expand_T *xp, char ***matches, int *numMatches) { - list_T *const retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp, num_file, - file); + *matches = NULL; + *numMatches = 0; + list_T *const retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp); if (retlist == NULL) { return FAIL; } @@ -2458,8 +3165,8 @@ static int ExpandUserList(expand_T *xp, int *num_file, char ***file) }); tv_list_unref(retlist); - *file = ga.ga_data; - *num_file = ga.ga_len; + *matches = ga.ga_data; + *numMatches = ga.ga_len; return OK; } @@ -2494,25 +3201,25 @@ static int ExpandUserLua(expand_T *xp, int *num_file, char ***file) /// Expand `file` for all comma-separated directories in `path`. /// Adds matches to `ga`. -void globpath(char *path, char_u *file, garray_T *ga, int expand_options) +void globpath(char *path, char *file, garray_T *ga, int expand_options) { expand_T xpc; ExpandInit(&xpc); xpc.xp_context = EXPAND_FILES; - char_u *buf = xmalloc(MAXPATHL); + char *buf = xmalloc(MAXPATHL); // Loop over all entries in {path}. while (*path != NUL) { // Copy one item of the path to buf[] and concatenate the file name. - copy_option_part(&path, (char *)buf, MAXPATHL, ","); - if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) { - add_pathsep((char *)buf); + copy_option_part(&path, buf, MAXPATHL, ","); + if (strlen(buf) + strlen(file) + 2 < MAXPATHL) { + add_pathsep(buf); STRCAT(buf, file); // NOLINT char **p; int num_p = 0; - (void)ExpandFromContext(&xpc, buf, &num_p, &p, + (void)ExpandFromContext(&xpc, buf, &p, &num_p, WILD_SILENT | expand_options); if (num_p > 0) { ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options); @@ -2567,145 +3274,158 @@ static void cmdline_del(CmdlineInfo *cclp, int from) cclp->cmdpos = from; } -/// Handle a key pressed when wild menu is displayed -int wildmenu_process_key(CmdlineInfo *cclp, int key, expand_T *xp) +/// Handle a key pressed when the wild menu for the menu names +/// (EXPAND_MENUNAMES) is displayed. +static int wildmenu_process_key_menunames(CmdlineInfo *cclp, int key, expand_T *xp) { - int c = key; - - // Special translations for 'wildmenu' - if (xp->xp_context == EXPAND_MENUNAMES) { - // Hitting <Down> after "emenu Name.": complete submenu - if (c == K_DOWN && cclp->cmdpos > 0 - && cclp->cmdbuff[cclp->cmdpos - 1] == '.') { - c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - } else if (c == K_UP) { - // Hitting <Up>: Remove one submenu name in front of the - // cursor - int found = false; - - int j = (int)((char_u *)xp->xp_pattern - cclp->cmdbuff); - int i = 0; - while (--j > 0) { - // check for start of menu name - if (cclp->cmdbuff[j] == ' ' - && cclp->cmdbuff[j - 1] != '\\') { + // Hitting <Down> after "emenu Name.": complete submenu + if (key == K_DOWN && cclp->cmdpos > 0 + && cclp->cmdbuff[cclp->cmdpos - 1] == '.') { + key = (int)p_wc; + KeyTyped = true; // in case the key was mapped + } else if (key == K_UP) { + // Hitting <Up>: Remove one submenu name in front of the + // cursor + int found = false; + + int j = (int)(xp->xp_pattern - cclp->cmdbuff); + int i = 0; + while (--j > 0) { + // check for start of menu name + if (cclp->cmdbuff[j] == ' ' + && cclp->cmdbuff[j - 1] != '\\') { + i = j + 1; + break; + } + // check for start of submenu name + if (cclp->cmdbuff[j] == '.' + && cclp->cmdbuff[j - 1] != '\\') { + if (found) { i = j + 1; break; + } else { + found = true; } - - // check for start of submenu name - if (cclp->cmdbuff[j] == '.' - && cclp->cmdbuff[j - 1] != '\\') { - if (found) { - i = j + 1; - break; - } else { - found = true; - } - } - } - if (i > 0) { - cmdline_del(cclp, i); } - c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - xp->xp_context = EXPAND_NOTHING; } + if (i > 0) { + cmdline_del(cclp, i); + } + key = (int)p_wc; + KeyTyped = true; // in case the key was mapped + xp->xp_context = EXPAND_NOTHING; } - if (xp->xp_context == EXPAND_FILES - || xp->xp_context == EXPAND_DIRECTORIES - || xp->xp_context == EXPAND_SHELLCMD) { - char_u upseg[5]; - - upseg[0] = PATHSEP; - upseg[1] = '.'; - upseg[2] = '.'; - upseg[3] = PATHSEP; - upseg[4] = NUL; - - if (c == K_DOWN - && cclp->cmdpos > 0 - && cclp->cmdbuff[cclp->cmdpos - 1] == PATHSEP - && (cclp->cmdpos < 3 - || cclp->cmdbuff[cclp->cmdpos - 2] != '.' - || cclp->cmdbuff[cclp->cmdpos - 3] != '.')) { - // go down a directory - c = (int)p_wc; - KeyTyped = true; // in case the key was mapped - } else if (STRNCMP(xp->xp_pattern, upseg + 1, 3) == 0 - && c == K_DOWN) { - // If in a direct ancestor, strip off one ../ to go down - int found = false; - - int j = cclp->cmdpos; - int i = (int)((char_u *)xp->xp_pattern - cclp->cmdbuff); - while (--j > i) { - j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); - if (vim_ispathsep(cclp->cmdbuff[j])) { - found = true; - break; - } - } - if (found - && cclp->cmdbuff[j - 1] == '.' - && cclp->cmdbuff[j - 2] == '.' - && (vim_ispathsep(cclp->cmdbuff[j - 3]) || j == i + 2)) { - cmdline_del(cclp, j - 2); - c = (int)p_wc; - KeyTyped = true; // in case the key was mapped + + return key; +} + +/// Handle a key pressed when the wild menu for file names (EXPAND_FILES) or +/// directory names (EXPAND_DIRECTORIES) or shell command names +/// (EXPAND_SHELLCMD) is displayed. +static int wildmenu_process_key_filenames(CmdlineInfo *cclp, int key, expand_T *xp) +{ + char upseg[5]; + upseg[0] = PATHSEP; + upseg[1] = '.'; + upseg[2] = '.'; + upseg[3] = PATHSEP; + upseg[4] = NUL; + + if (key == K_DOWN + && cclp->cmdpos > 0 + && cclp->cmdbuff[cclp->cmdpos - 1] == PATHSEP + && (cclp->cmdpos < 3 + || cclp->cmdbuff[cclp->cmdpos - 2] != '.' + || cclp->cmdbuff[cclp->cmdpos - 3] != '.')) { + // go down a directory + key = (int)p_wc; + KeyTyped = true; // in case the key was mapped + } else if (strncmp(xp->xp_pattern, upseg + 1, 3) == 0 && key == K_DOWN) { + // If in a direct ancestor, strip off one ../ to go down + int found = false; + + int j = cclp->cmdpos; + int i = (int)(xp->xp_pattern - cclp->cmdbuff); + while (--j > i) { + j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); + if (vim_ispathsep(cclp->cmdbuff[j])) { + found = true; + break; } - } else if (c == K_UP) { - // go up a directory - int found = false; - - int j = cclp->cmdpos - 1; - int i = (int)((char_u *)xp->xp_pattern - cclp->cmdbuff); - while (--j > i) { - j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); - if (vim_ispathsep(cclp->cmdbuff[j]) + } + if (found + && cclp->cmdbuff[j - 1] == '.' + && cclp->cmdbuff[j - 2] == '.' + && (vim_ispathsep(cclp->cmdbuff[j - 3]) || j == i + 2)) { + cmdline_del(cclp, j - 2); + key = (int)p_wc; + KeyTyped = true; // in case the key was mapped + } + } else if (key == K_UP) { + // go up a directory + int found = false; + + int j = cclp->cmdpos - 1; + int i = (int)(xp->xp_pattern - cclp->cmdbuff); + while (--j > i) { + j -= utf_head_off(cclp->cmdbuff, cclp->cmdbuff + j); + if (vim_ispathsep(cclp->cmdbuff[j]) #ifdef BACKSLASH_IN_FILENAME - && vim_strchr((const char_u *)" *?[{`$%#", cclp->cmdbuff[j + 1]) - == NULL + && vim_strchr(" *?[{`$%#", (uint8_t)cclp->cmdbuff[j + 1]) == NULL #endif - ) { - if (found) { - i = j + 1; - break; - } else { - found = true; - } + ) { + if (found) { + i = j + 1; + break; + } else { + found = true; } } + } - if (!found) { - j = i; - } else if (STRNCMP(cclp->cmdbuff + j, upseg, 4) == 0) { - j += 4; - } else if (STRNCMP(cclp->cmdbuff + j, upseg + 1, 3) == 0 - && j == i) { - j += 3; - } else { - j = 0; - } - - if (j > 0) { - // TODO(tarruda): this is only for DOS/Unix systems - need to put in - // machine-specific stuff here and in upseg init - cmdline_del(cclp, j); - put_on_cmdline(upseg + 1, 3, false); - } else if (cclp->cmdpos > i) { - cmdline_del(cclp, i); - } + if (!found) { + j = i; + } else if (strncmp(cclp->cmdbuff + j, upseg, 4) == 0) { + j += 4; + } else if (strncmp(cclp->cmdbuff + j, upseg + 1, 3) == 0 + && j == i) { + j += 3; + } else { + j = 0; + } - // Now complete in the new directory. Set KeyTyped in case the - // Up key came from a mapping. - c = (int)p_wc; - KeyTyped = true; + if (j > 0) { + // TODO(tarruda): this is only for DOS/Unix systems - need to put in + // machine-specific stuff here and in upseg init + cmdline_del(cclp, j); + put_on_cmdline(upseg + 1, 3, false); + } else if (cclp->cmdpos > i) { + cmdline_del(cclp, i); } + + // Now complete in the new directory. Set KeyTyped in case the + // Up key came from a mapping. + key = (int)p_wc; + KeyTyped = true; } - return c; + return key; +} + +/// Handle a key pressed when wild menu is displayed +int wildmenu_process_key(CmdlineInfo *cclp, int key, expand_T *xp) +{ + // Special translations for 'wildmenu' + if (xp->xp_context == EXPAND_MENUNAMES) { + return wildmenu_process_key_menunames(cclp, key, xp); + } + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_SHELLCMD) { + return wildmenu_process_key_filenames(cclp, key, xp); + } + + return key; } /// Free expanded names when finished walking through the matches @@ -2732,7 +3452,7 @@ void wildmenu_cleanup(CmdlineInfo *cclp) p_ls = save_p_ls; p_wmh = save_p_wmh; last_status(false); - update_screen(VALID); // redraw the screen NOW + update_screen(); // redraw the screen NOW redrawcmd(); save_p_ls = -1; wild_menu_showing = 0; @@ -2751,9 +3471,9 @@ void wildmenu_cleanup(CmdlineInfo *cclp) } /// "getcompletion()" function -void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char_u *pat; + char *pat; expand_T xpc; bool filtered = false; int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH @@ -2786,14 +3506,14 @@ void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (strcmp(type, "cmdline") == 0) { set_one_cmd_context(&xpc, pattern); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - xpc.xp_col = (int)STRLEN(pattern); + xpc.xp_pattern_len = strlen(xpc.xp_pattern); + xpc.xp_col = (int)strlen(pattern); goto theend; } ExpandInit(&xpc); xpc.xp_pattern = (char *)pattern; - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + xpc.xp_pattern_len = strlen(xpc.xp_pattern); xpc.xp_context = cmdcomplete_str_to_type(type); if (xpc.xp_context == EXPAND_NOTHING) { semsg(_(e_invarg2), type); @@ -2802,21 +3522,22 @@ void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (xpc.xp_context == EXPAND_MENUS) { set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); - } - - if (xpc.xp_context == EXPAND_CSCOPE) { - set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + xpc.xp_pattern_len = strlen(xpc.xp_pattern); } if (xpc.xp_context == EXPAND_SIGN) { - set_context_in_sign_cmd(&xpc, (char_u *)xpc.xp_pattern); - xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + set_context_in_sign_cmd(&xpc, xpc.xp_pattern); + xpc.xp_pattern_len = strlen(xpc.xp_pattern); } theend: - pat = addstar((char_u *)xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); + if (cmdline_fuzzy_completion_supported(&xpc)) { + // when fuzzy matching, don't modify the search string + pat = xstrdup(xpc.xp_pattern); + } else { + pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); + } + ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); tv_list_alloc_ret(rettv, xpc.xp_numfiles); diff --git a/src/nvim/cmdexpand.h b/src/nvim/cmdexpand.h index 93e91af169..810e289f7c 100644 --- a/src/nvim/cmdexpand.h +++ b/src/nvim/cmdexpand.h @@ -19,6 +19,9 @@ enum { WILD_ALL_KEEP = 8, WILD_CANCEL = 9, WILD_APPLY = 10, + WILD_PAGEUP = 11, + WILD_PAGEDOWN = 12, + WILD_PUM_WANT = 13, }; enum { diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c index 13f97ba1e8..2df82d9355 100644 --- a/src/nvim/cmdhist.c +++ b/src/nvim/cmdhist.c @@ -3,14 +3,30 @@ // cmdhist.c: Functions for the history of the command-line. +#include <assert.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/cmdhist.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" +#include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/strings.h" -#include "nvim/ui.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -90,14 +106,14 @@ static char *(history_names[]) = { /// arguments of the ":history command. char *get_history_arg(expand_T *xp, int idx) { - static char_u compl[2] = { NUL, NUL }; - char *short_names = ":=@>?/"; - int short_names_count = (int)STRLEN(short_names); - int history_name_count = ARRAY_SIZE(history_names) - 1; + const char *short_names = ":=@>?/"; + const int short_names_count = (int)strlen(short_names); + const int history_name_count = ARRAY_SIZE(history_names) - 1; if (idx < short_names_count) { - compl[0] = (char_u)short_names[idx]; - return (char *)compl; + xp->xp_buf[0] = short_names[idx]; + xp->xp_buf[1] = NUL; + return xp->xp_buf; } if (idx < short_names_count + history_name_count) { return history_names[idx - short_names_count]; @@ -116,56 +132,58 @@ void init_history(void) int newlen = (int)p_hi; int oldlen = hislen; + if (newlen == oldlen) { // history length didn't change + return; + } + // If history tables size changed, reallocate them. // Tables are circular arrays (current position marked by hisidx[type]). // On copying them to the new arrays, we take the chance to reorder them. - if (newlen != oldlen) { - for (int type = 0; type < HIST_COUNT; type++) { - histentry_T *temp = (newlen - ? xmalloc((size_t)newlen * sizeof(*temp)) - : NULL); - - int j = hisidx[type]; - if (j >= 0) { - // old array gets partitioned this way: - // [0 , i1 ) --> newest entries to be deleted - // [i1 , i1 + l1) --> newest entries to be copied - // [i1 + l1 , i2 ) --> oldest entries to be deleted - // [i2 , i2 + l2) --> oldest entries to be copied - int l1 = MIN(j + 1, newlen); // how many newest to copy - int l2 = MIN(newlen, oldlen) - l1; // how many oldest to copy - int i1 = j + 1 - l1; // copy newest from here - int i2 = MAX(l1, oldlen - newlen + l1); // copy oldest from here - - // copy as much entries as they fit to new table, reordering them - if (newlen) { - // copy oldest entries - memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp)); - // copy newest entries - memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp)); - } - - // delete entries that don't fit in newlen, if any - for (int i = 0; i < i1; i++) { - hist_free_entry(history[type] + i); - } - for (int i = i1 + l1; i < i2; i++) { - hist_free_entry(history[type] + i); - } + for (int type = 0; type < HIST_COUNT; type++) { + histentry_T *temp = (newlen > 0 + ? xmalloc((size_t)newlen * sizeof(*temp)) + : NULL); + + int j = hisidx[type]; + if (j >= 0) { + // old array gets partitioned this way: + // [0 , i1 ) --> newest entries to be deleted + // [i1 , i1 + l1) --> newest entries to be copied + // [i1 + l1 , i2 ) --> oldest entries to be deleted + // [i2 , i2 + l2) --> oldest entries to be copied + int l1 = MIN(j + 1, newlen); // how many newest to copy + int l2 = MIN(newlen, oldlen) - l1; // how many oldest to copy + int i1 = j + 1 - l1; // copy newest from here + int i2 = MAX(l1, oldlen - newlen + l1); // copy oldest from here + + // copy as much entries as they fit to new table, reordering them + if (newlen) { + // copy oldest entries + memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp)); + // copy newest entries + memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp)); } - // clear remaining space, if any - int l3 = j < 0 ? 0 : MIN(newlen, oldlen); // number of copied entries - if (newlen) { - memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp)); + // delete entries that don't fit in newlen, if any + for (int i = 0; i < i1; i++) { + hist_free_entry(history[type] + i); + } + for (int i = i1 + l1; i < i2; i++) { + hist_free_entry(history[type] + i); } + } - hisidx[type] = l3 - 1; - xfree(history[type]); - history[type] = temp; + // clear remaining space, if any + int l3 = j < 0 ? 0 : MIN(newlen, oldlen); // number of copied entries + if (newlen > 0) { + memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp)); } - hislen = newlen; + + hisidx[type] = l3 - 1; + xfree(history[type]); + history[type] = temp; } + hislen = newlen; } static inline void hist_free_entry(histentry_T *hisptr) @@ -186,7 +204,7 @@ static inline void clear_hist_entry(histentry_T *hisptr) /// If 'move_to_front' is true, matching entry is moved to end of history. /// /// @param move_to_front Move the entry to the front if it exists -static int in_history(int type, char_u *str, int move_to_front, int sep) +static int in_history(int type, char *str, int move_to_front, int sep) { int last_i = -1; @@ -201,9 +219,9 @@ static int in_history(int type, char_u *str, int move_to_front, int sep) // For search history, check that the separator character matches as // well. - char_u *p = history[type][i].hisstr; - if (STRCMP(str, p) == 0 - && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) { + char *p = history[type][i].hisstr; + if (strcmp(str, p) == 0 + && (type != HIST_SEARCH || sep == p[strlen(p) + 1])) { if (!move_to_front) { return true; } @@ -215,24 +233,25 @@ static int in_history(int type, char_u *str, int move_to_front, int sep) } } while (i != hisidx[type]); - if (last_i >= 0) { - list_T *const list = history[type][i].additional_elements; - str = history[type][i].hisstr; - while (i != hisidx[type]) { - if (++i >= hislen) { - i = 0; - } - history[type][last_i] = history[type][i]; - last_i = i; + if (last_i < 0) { + return false; + } + + list_T *const list = history[type][i].additional_elements; + str = history[type][i].hisstr; + while (i != hisidx[type]) { + if (++i >= hislen) { + i = 0; } - tv_list_unref(list); - history[type][i].hisnum = ++hisnum[type]; - history[type][i].hisstr = str; - history[type][i].timestamp = os_time(); - history[type][i].additional_elements = NULL; - return true; - } - return false; + history[type][last_i] = history[type][i]; + last_i = i; + } + tv_list_unref(list); + history[type][i].hisnum = ++hisnum[type]; + history[type][i].hisstr = str; + history[type][i].timestamp = os_time(); + history[type][i].additional_elements = NULL; + return true; } /// Convert history name to its HIST_ equivalent @@ -261,7 +280,7 @@ static HistoryType get_histtype(const char *const name, const size_t len, const } } - if (vim_strchr(":=@>?/", name[0]) != NULL && len == 1) { + if (vim_strchr(":=@>?/", (uint8_t)name[0]) != NULL && len == 1) { return hist_char2type(name[0]); } @@ -276,7 +295,7 @@ static int last_maptick = -1; // last seen maptick /// @param histype may be one of the HIST_ values. /// @param in_map consider maptick when inside a mapping /// @param sep separator character used (search hist) -void add_to_history(int histype, char_u *new_entry, int in_map, int sep) +void add_to_history(int histype, char *new_entry, int in_map, int sep) { histentry_T *hisptr; @@ -304,24 +323,27 @@ void add_to_history(int histype, char_u *new_entry, int in_map, int sep) } last_maptick = -1; } - if (!in_history(histype, new_entry, true, sep)) { - if (++hisidx[histype] == hislen) { - hisidx[histype] = 0; - } - hisptr = &history[histype][hisidx[histype]]; - hist_free_entry(hisptr); - - // Store the separator after the NUL of the string. - size_t len = STRLEN(new_entry); - hisptr->hisstr = vim_strnsave(new_entry, len + 2); - hisptr->timestamp = os_time(); - hisptr->additional_elements = NULL; - hisptr->hisstr[len + 1] = (char_u)sep; - - hisptr->hisnum = ++hisnum[histype]; - if (histype == HIST_SEARCH && in_map) { - last_maptick = maptick; - } + + if (in_history(histype, new_entry, true, sep)) { + return; + } + + if (++hisidx[histype] == hislen) { + hisidx[histype] = 0; + } + hisptr = &history[histype][hisidx[histype]]; + hist_free_entry(hisptr); + + // Store the separator after the NUL of the string. + size_t len = strlen(new_entry); + hisptr->hisstr = xstrnsave(new_entry, len + 2); + hisptr->timestamp = os_time(); + hisptr->additional_elements = NULL; + hisptr->hisstr[len + 1] = (char)sep; + + hisptr->hisnum = ++hisnum[histype]; + if (histype == HIST_SEARCH && in_map) { + last_maptick = maptick; } } @@ -382,13 +404,13 @@ static int calc_hist_idx(int histype, int num) /// Get a history entry by its index. /// /// @param histype may be one of the HIST_ values. -static char_u *get_history_entry(int histype, int idx) +static char *get_history_entry(int histype, int idx) { idx = calc_hist_idx(histype, idx); if (idx >= 0) { return history[histype][idx].hisstr; } else { - return (char_u *)""; + return ""; } } @@ -415,49 +437,50 @@ int clr_history(const int histype) /// Remove all entries matching {str} from a history. /// /// @param histype may be one of the HIST_ values. -static int del_history_entry(int histype, char_u *str) +static int del_history_entry(int histype, char *str) { + if (hislen == 0 || histype < 0 || histype >= HIST_COUNT || *str == NUL + || hisidx[histype] < 0) { + return false; + } + + const int idx = hisidx[histype]; regmatch_T regmatch; - histentry_T *hisptr; - int idx; - int i; - int last; - bool found = false; + regmatch.regprog = vim_regcomp(str, RE_MAGIC + RE_STRING); + if (regmatch.regprog == NULL) { + return false; + } - regmatch.regprog = NULL; regmatch.rm_ic = false; // always match case - if (hislen != 0 - && histype >= 0 - && histype < HIST_COUNT - && *str != NUL - && (idx = hisidx[histype]) >= 0 - && (regmatch.regprog = vim_regcomp((char *)str, RE_MAGIC + RE_STRING)) != NULL) { - i = last = idx; - do { - hisptr = &history[histype][i]; - if (hisptr->hisstr == NULL) { - break; - } - if (vim_regexec(®match, (char *)hisptr->hisstr, (colnr_T)0)) { - found = true; - hist_free_entry(hisptr); - } else { - if (i != last) { - history[histype][last] = *hisptr; - clear_hist_entry(hisptr); - } - if (--last < 0) { - last += hislen; - } + + bool found = false; + int i = idx, last = idx; + do { + histentry_T *hisptr = &history[histype][i]; + if (hisptr->hisstr == NULL) { + break; + } + if (vim_regexec(®match, hisptr->hisstr, (colnr_T)0)) { + found = true; + hist_free_entry(hisptr); + } else { + if (i != last) { + history[histype][last] = *hisptr; + clear_hist_entry(hisptr); } - if (--i < 0) { - i += hislen; + if (--last < 0) { + last += hislen; } - } while (i != idx); - if (history[histype][idx].hisstr == NULL) { - hisidx[histype] = -1; } + if (--i < 0) { + i += hislen; + } + } while (i != idx); + + if (history[histype][idx].hisstr == NULL) { + hisidx[histype] = -1; } + vim_regfree(regmatch.regprog); return found; } @@ -494,7 +517,7 @@ static int del_history_idx(int histype, int idx) } /// "histadd()" function -void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_histadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { HistoryType histype; @@ -504,20 +527,23 @@ void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID; - if (histype != HIST_INVALID) { - char buf[NUMBUFLEN]; - str = tv_get_string_buf(&argvars[1], buf); - if (*str != NUL) { - init_history(); - add_to_history(histype, (char_u *)str, false, NUL); - rettv->vval.v_number = true; - return; - } + if (histype == HIST_INVALID) { + return; + } + + char buf[NUMBUFLEN]; + str = tv_get_string_buf(&argvars[1], buf); + if (*str == NUL) { + return; } + + init_history(); + add_to_history(histype, (char *)str, false, NUL); + rettv->vval.v_number = true; } /// "histdel()" function -void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_histdel(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int n; const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error @@ -534,13 +560,13 @@ void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) // string given: remove all matching entries char buf[NUMBUFLEN]; n = del_history_entry(get_histtype(str, strlen(str), false), - (char_u *)tv_get_string_buf(&argvars[1], buf)); + (char *)tv_get_string_buf(&argvars[1], buf)); } rettv->vval.v_number = n; } /// "histget()" function -void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_histget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { HistoryType type; int idx; @@ -556,13 +582,13 @@ void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) idx = (int)tv_get_number_chk(&argvars[1], NULL); } // -1 on type error - rettv->vval.v_string = (char *)vim_strsave(get_history_entry(type, idx)); + rettv->vval.v_string = xstrdup(get_history_entry(type, idx)); } rettv->v_type = VAR_STRING; } /// "histnr()" function -void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_histnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const histname = tv_get_string_chk(&argvars[0]); HistoryType i = histname == NULL @@ -585,7 +611,7 @@ void ex_history(exarg_T *eap) int idx; int i, j, k; char *end; - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; if (hislen == 0) { msg(_("'history' option is zero")); @@ -593,12 +619,12 @@ void ex_history(exarg_T *eap) } if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) { - end = (char *)arg; + end = arg; while (ASCII_ISALPHA(*end) - || vim_strchr(":=@>/?", *end) != NULL) { + || vim_strchr(":=@>/?", (uint8_t)(*end)) != NULL) { end++; } - histype1 = get_histtype((const char *)arg, (size_t)(end - (char *)arg), false); + histype1 = get_histtype(arg, (size_t)(end - arg), false); if (histype1 == HIST_INVALID) { if (STRNICMP(arg, "all", end - (char *)arg) == 0) { histype1 = 0; @@ -611,7 +637,7 @@ void ex_history(exarg_T *eap) histype2 = histype1; } } else { - end = (char *)arg; + end = arg; } if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) { semsg(_(e_trailing_arg), end); @@ -622,7 +648,7 @@ void ex_history(exarg_T *eap) STRCPY(IObuff, "\n # "); assert(history_names[histype1] != NULL); STRCAT(STRCAT(IObuff, history_names[histype1]), " history"); - msg_puts_title((char *)IObuff); + msg_puts_title(IObuff); idx = hisidx[histype1]; hist = history[histype1]; j = hisidx1; @@ -641,16 +667,15 @@ void ex_history(exarg_T *eap) if (hist[i].hisstr != NULL && hist[i].hisnum >= j && hist[i].hisnum <= k) { msg_putchar('\n'); - snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ', + snprintf(IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ', hist[i].hisnum); - if (vim_strsize((char *)hist[i].hisstr) > Columns - 10) { - trunc_string((char *)hist[i].hisstr, (char *)IObuff + STRLEN(IObuff), - Columns - 10, IOSIZE - (int)STRLEN(IObuff)); + if (vim_strsize(hist[i].hisstr) > Columns - 10) { + trunc_string(hist[i].hisstr, IObuff + strlen(IObuff), + Columns - 10, IOSIZE - (int)strlen(IObuff)); } else { STRCAT(IObuff, hist[i].hisstr); } - msg_outtrans((char *)IObuff); - ui_flush(); + msg_outtrans(IObuff); } if (i == idx) { break; diff --git a/src/nvim/cmdhist.h b/src/nvim/cmdhist.h index 797b79a5f0..f86a2f855c 100644 --- a/src/nvim/cmdhist.h +++ b/src/nvim/cmdhist.h @@ -2,6 +2,7 @@ #define NVIM_CMDHIST_H #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/os/time.h" @@ -22,7 +23,7 @@ typedef enum { /// History entry definition typedef struct hist_entry { int hisnum; ///< Entry identifier number. - char_u *hisstr; ///< Actual entry, separator char after the NUL. + char *hisstr; ///< Actual entry, separator char after the NUL. Timestamp timestamp; ///< Time when entry was added. list_T *additional_elements; ///< Additional entries from ShaDa file. } histentry_T; diff --git a/src/nvim/context.c b/src/nvim/context.c index e3ae9355bf..9de6c16536 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -3,14 +3,26 @@ // Context: snapshot of the entire editor state as one big object/map +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + #include "nvim/api/private/converter.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/api/vimscript.h" #include "nvim/context.h" #include "nvim/eval/encode.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_docmd.h" +#include "nvim/gettext.h" +#include "nvim/hashtab.h" +#include "nvim/keycodes.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/shada.h" @@ -131,7 +143,7 @@ bool ctx_restore(Context *ctx, const int flags) } char *op_shada; - get_option_value("shada", NULL, &op_shada, OPT_GLOBAL); + get_option_value("shada", NULL, &op_shada, NULL, OPT_GLOBAL); set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL); if (flags & kCtxRegs) { @@ -251,12 +263,12 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly) Error err = ERROR_INIT; HASHTAB_ITER(func_tbl_get(), hi, { - const char_u *const name = hi->hi_key; - bool islambda = (STRNCMP(name, "<lambda>", 8) == 0); - bool isscript = (name[0] == K_SPECIAL); + const char *const name = hi->hi_key; + bool islambda = (strncmp(name, "<lambda>", 8) == 0); + bool isscript = ((uint8_t)name[0] == K_SPECIAL); if (!islambda && (!scriptonly || isscript)) { - size_t cmd_len = sizeof("func! ") + STRLEN(name); + size_t cmd_len = sizeof("func! ") + strlen(name); char *cmd = xmalloc(cmd_len); snprintf(cmd, cmd_len, "func! %s", name); String func_body = nvim_exec(VIML_INTERNAL_CALL, cstr_as_string(cmd), @@ -345,7 +357,7 @@ Dictionary ctx_to_dict(Context *ctx) PUT(rv, "jumps", ARRAY_OBJ(sbuf_to_array(ctx->jumps))); PUT(rv, "bufs", ARRAY_OBJ(sbuf_to_array(ctx->bufs))); PUT(rv, "gvars", ARRAY_OBJ(sbuf_to_array(ctx->gvars))); - PUT(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs))); + PUT(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs, NULL))); return rv; } @@ -381,7 +393,7 @@ int ctx_from_dict(Dictionary dict, Context *ctx) ctx->gvars = array_to_sbuf(item.value.data.array); } else if (strequal(item.key.data, "funcs")) { types |= kCtxFuncs; - ctx->funcs = copy_object(item.value).data.array; + ctx->funcs = copy_object(item.value, NULL).data.array; } } diff --git a/src/nvim/context.h b/src/nvim/context.h index c2deca12c9..7a1224d876 100644 --- a/src/nvim/context.h +++ b/src/nvim/context.h @@ -2,9 +2,11 @@ #define NVIM_CONTEXT_H #include <msgpack.h> +#include <msgpack/sbuffer.h> +#include <stddef.h> +#include "klib/kvec.h" #include "nvim/api/private/defs.h" -#include "nvim/lib/kvec.h" typedef struct { msgpack_sbuffer regs; ///< Registers. diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index d4670dedc7..b1dbc68ea3 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -1,24 +1,31 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" -#include "nvim/extmark.h" #include "nvim/fold.h" +#include "nvim/globals.h" +#include "nvim/macros.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" +#include "nvim/pos.h" #include "nvim/state.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -67,7 +74,7 @@ int coladvance_force(colnr_T wcol) /// Try to advance the Cursor to the specified screen column. /// If virtual editing: fine tune the cursor position. /// Note that all virtual positions off the end of a line should share -/// a curwin->w_cursor.col value (n.b. this is equal to STRLEN(line)), +/// a curwin->w_cursor.col value (n.b. this is equal to strlen(line)), /// beginning at coladd 0. /// /// @return OK if desired column is reached, FAIL if not @@ -101,10 +108,10 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a || (VIsual_active && *p_sel != 'o') || ((get_ve_flags() & VE_ONEMORE) && wcol < MAXCOL); - char_u *line = ml_get_buf(curbuf, pos->lnum, false); + char *line = ml_get_buf(curbuf, pos->lnum, false); if (wcol >= MAXCOL) { - idx = (int)STRLEN(line) - 1 + one_more; + idx = (int)strlen(line) - 1 + one_more; col = wcol; if ((addspaces || finetune) && !VIsual_active) { @@ -137,14 +144,18 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a } } - char_u *ptr = line; - while (col <= wcol && *ptr != NUL) { + 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(curwin, line, ptr, col, &head); - MB_PTR_ADV(ptr); - col += csize; + csize = win_lbr_chartabsize(&cts, &head); + MB_PTR_ADV(cts.cts_ptr); + cts.cts_vcol += csize; } - idx = (int)(ptr - line); + col = cts.cts_vcol; + idx = (int)(cts.cts_ptr - line); + clear_chartabsize_arg(&cts); + // Handle all the special cases. The virtual_active() check // is needed to ensure that a virtual position off the end of // a line has the correct indexing. The one_more comparison @@ -167,19 +178,19 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a int correct = wcol - col; size_t newline_size; STRICT_ADD(idx, correct, &newline_size, size_t); - char_u *newline = xmallocz(newline_size); + char *newline = xmallocz(newline_size); memcpy(newline, line, (size_t)idx); memset(newline + idx, ' ', (size_t)correct); - ml_replace(pos->lnum, (char *)newline, false); + ml_replace(pos->lnum, newline, false); inserted_bytes(pos->lnum, (colnr_T)idx, 0, correct); idx += correct; col = wcol; } else { // Break a tab - int linelen = (int)STRLEN(line); + int linelen = (int)strlen(line); int correct = wcol - col - csize + 1; // negative!! - char_u *newline; + char *newline; if (-correct > csize) { return FAIL; @@ -197,7 +208,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a STRICT_SUB(n, 1, &n, size_t); memcpy(newline + idx + csize, line + idx + 1, n); - ml_replace(pos->lnum, (char *)newline, false); + ml_replace(pos->lnum, newline, false); inserted_bytes(pos->lnum, idx, 1, csize); idx += (csize - 1 + correct); col += correct; @@ -304,8 +315,8 @@ void check_pos(buf_T *buf, pos_T *pos) } if (pos->col > 0) { - char_u *line = ml_get_buf(buf, pos->lnum, false); - colnr_T len = (colnr_T)STRLEN(line); + char *line = ml_get_buf(buf, pos->lnum, false); + colnr_T len = (colnr_T)strlen(line); if (pos->col > len) { pos->col = len; } @@ -342,7 +353,7 @@ void check_cursor_col_win(win_T *win) colnr_T oldcoladd = win->w_cursor.col + win->w_cursor.coladd; unsigned int cur_ve_flags = get_ve_flags(); - colnr_T len = (colnr_T)STRLEN(ml_get_buf(win->w_buffer, win->w_cursor.lnum, false)); + colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, win->w_cursor.lnum, false)); if (len == 0) { win->w_cursor.col = 0; } else if (win->w_cursor.col >= len) { @@ -408,7 +419,7 @@ void check_visual_pos(void) VIsual.col = 0; VIsual.coladd = 0; } else { - int len = (int)STRLEN(ml_get(VIsual.lnum)); + int len = (int)strlen(ml_get(VIsual.lnum)); if (VIsual.col > len) { VIsual.col = len; @@ -445,12 +456,13 @@ bool leftcol_changed(void) // If the cursor is right or left of the screen, move it to last or first // character. - if (curwin->w_virtcol > (colnr_T)(lastcol - p_siso)) { + long siso = get_sidescrolloff_value(curwin); + if (curwin->w_virtcol > (colnr_T)(lastcol - siso)) { retval = true; - coladvance((colnr_T)(lastcol - p_siso)); - } else if (curwin->w_virtcol < curwin->w_leftcol + p_siso) { + coladvance((colnr_T)(lastcol - siso)); + } else if (curwin->w_virtcol < curwin->w_leftcol + siso) { retval = true; - coladvance((colnr_T)(curwin->w_leftcol + p_siso)); + coladvance((colnr_T)(curwin->w_leftcol + siso)); } // If the start of the character under the cursor is not on the screen, @@ -471,13 +483,13 @@ bool leftcol_changed(void) if (retval) { curwin->w_set_curswant = true; } - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); return retval; } int gchar_cursor(void) { - return utf_ptr2char((char *)get_cursor_pos_ptr()); + return utf_ptr2char(get_cursor_pos_ptr()); } /// Write a character at the current cursor position. @@ -485,18 +497,17 @@ int gchar_cursor(void) void pchar_cursor(char_u c) { *(ml_get_buf(curbuf, curwin->w_cursor.lnum, true) - + curwin->w_cursor.col) = c; + + curwin->w_cursor.col) = (char)c; } /// @return pointer to cursor line. -char_u *get_cursor_line_ptr(void) +char *get_cursor_line_ptr(void) { return ml_get_buf(curbuf, curwin->w_cursor.lnum, false); } /// @return pointer to cursor position. -char_u *get_cursor_pos_ptr(void) +char *get_cursor_pos_ptr(void) { - return ml_get_buf(curbuf, curwin->w_cursor.lnum, false) + - curwin->w_cursor.col; + return ml_get_buf(curbuf, curwin->w_cursor.lnum, false) + curwin->w_cursor.col; } diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index 9c33b1a806..f21e632036 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -1,15 +1,23 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <assert.h> +#include <stdbool.h> #include <stdint.h> +#include <string.h> +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/cursor_shape.h" #include "nvim/ex_getln.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/highlight_group.h" +#include "nvim/log.h" +#include "nvim/macros.h" +#include "nvim/memory.h" +#include "nvim/option_defs.h" #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -19,8 +27,7 @@ #endif /// Handling of cursor and mouse pointer shapes in various modes. -cursorentry_T shape_table[SHAPE_IDX_COUNT] = -{ +cursorentry_T shape_table[SHAPE_IDX_COUNT] = { // Values are set by 'guicursor' and 'mouseshape'. // Adjust the SHAPE_IDX_ defines when changing this! { "normal", 0, 0, 0, 700L, 400L, 250L, 0, 0, "n", SHAPE_CURSOR + SHAPE_MOUSE }, @@ -120,7 +127,7 @@ char *parse_shape_opt(int what) } } // Repeat for all comma separated parts. - char *modep = (char *)p_guicursor; + char *modep = p_guicursor; while (modep != NULL && *modep != NUL) { colonp = vim_strchr(modep, ':'); commap = vim_strchr(modep, ','); @@ -220,7 +227,7 @@ char *parse_shape_opt(int what) endp = vim_strchr(p, '-'); if (commap == NULL) { // last part if (endp == NULL) { - endp = p + STRLEN(p); // find end of part + endp = p + strlen(p); // find end of part } } else if (endp > commap || endp == NULL) { endp = commap; diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index a061bd961b..f7e70a78ce 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -5,17 +5,33 @@ /// /// Vim script debugger functions +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/debugger.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" +#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/os/os.h" +#include "nvim/path.h" #include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/runtime.h" @@ -33,7 +49,7 @@ static char *debug_newval = NULL; struct debuggy { int dbg_nr; ///< breakpoint number int dbg_type; ///< DBG_FUNC or DBG_FILE or DBG_EXPR - char_u *dbg_name; ///< function, expression or file name + char *dbg_name; ///< function, expression or file name regprog_T *dbg_prog; ///< regexp program linenr_T dbg_lnum; ///< line number in function or file int dbg_forceit; ///< ! used @@ -47,7 +63,7 @@ struct debuggy { /// Debug mode. Repeatedly get Ex commands, until told to continue normal /// execution. -void do_debug(char_u *cmd) +void do_debug(char *cmd) { int save_msg_scroll = msg_scroll; int save_State = State; @@ -60,7 +76,7 @@ void do_debug(char_u *cmd) bool typeahead_saved = false; int save_ignore_script = 0; int n; - char_u *cmdline = NULL; + char *cmdline = NULL; char *p; char *tail = NULL; static int last_cmd = 0; @@ -114,6 +130,7 @@ void do_debug(char_u *cmd) for (;;) { msg_scroll = true; need_wait_return = false; + // Save the current typeahead buffer and replace it with an empty one. // This makes sure we get input from the user here and don't interfere // with the commands being executed. Reset "ex_normal_busy" to avoid @@ -128,10 +145,15 @@ void do_debug(char_u *cmd) ignore_script = true; } + // don't debug any function call, e.g. from an expression mapping + n = debug_break_level; + debug_break_level = -1; + xfree(cmdline); - cmdline = (char_u *)getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL, - CALLBACK_NONE); + cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL, + CALLBACK_NONE); + debug_break_level = n; if (typeahead_saved) { restore_typeahead(&typeaheadbuf); ignore_script = save_ignore_script; @@ -144,7 +166,7 @@ void do_debug(char_u *cmd) // If this is a debug command, set "last_cmd". // If not, reset "last_cmd". // For a blank line use previous command. - p = skipwhite((char *)cmdline); + p = skipwhite(cmdline); if (*p != NUL) { switch (*p) { case 'c': @@ -246,7 +268,7 @@ void do_debug(char_u *cmd) do_showbacktrace(cmd); } else { p = skipwhite(p); - do_setdebugtracelevel((char_u *)p); + do_setdebugtracelevel(p); } continue; case CMD_UP: @@ -266,7 +288,7 @@ void do_debug(char_u *cmd) // don't debug this command n = debug_break_level; debug_break_level = -1; - (void)do_cmdline((char *)cmdline, getexline, NULL, DOCMD_VERBOSE|DOCMD_EXCRESET); + (void)do_cmdline(cmdline, getexline, NULL, DOCMD_VERBOSE|DOCMD_EXCRESET); debug_break_level = n; } lines_left = Rows - 1; @@ -275,7 +297,7 @@ void do_debug(char_u *cmd) RedrawingDisabled--; no_wait_return--; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); need_wait_return = false; msg_scroll = save_msg_scroll; lines_left = Rows - 1; @@ -295,20 +317,22 @@ static int get_maxbacktrace_level(char *sname) { int maxbacktrace = 0; - if (sname != NULL) { - char *p = sname; - char *q; - while ((q = strstr(p, "..")) != NULL) { - p = q + 2; - maxbacktrace++; - } + if (sname == NULL) { + return 0; + } + + char *p = sname; + char *q; + while ((q = strstr(p, "..")) != NULL) { + p = q + 2; + maxbacktrace++; } return maxbacktrace; } -static void do_setdebugtracelevel(char_u *arg) +static void do_setdebugtracelevel(char *arg) { - int level = atoi((char *)arg); + int level = atoi(arg); if (*arg == '+' || level < 0) { debug_backtrace_level += level; } else { @@ -335,7 +359,7 @@ static void do_checkbacktracelevel(void) } } -static void do_showbacktrace(char_u *cmd) +static void do_showbacktrace(char *cmd) { char *sname = estack_sfile(ESTACK_NONE); int max = get_maxbacktrace_level(sname); @@ -379,7 +403,7 @@ void ex_debug(exarg_T *eap) debug_break_level = debug_break_level_save; } -static char_u *debug_breakpoint_name = NULL; +static char *debug_breakpoint_name = NULL; static linenr_T debug_breakpoint_lnum; /// When debugging or a breakpoint is set on a skipped command, no debug prompt @@ -388,7 +412,7 @@ static linenr_T debug_breakpoint_lnum; /// a skipped command decides itself that a debug prompt should be displayed, it /// can do so by calling dbg_check_skipped(). static int debug_skipped; -static char_u *debug_skipped_name; +static char *debug_skipped_name; /// Go to debug mode when a breakpoint was encountered or "ex_nesting_level" is /// at or below the break level. But only when the line is actually @@ -402,8 +426,8 @@ void dbg_check_breakpoint(exarg_T *eap) if (!eap->skip) { char *p; // replace K_SNR with "<SNR>" - if (debug_breakpoint_name[0] == K_SPECIAL - && debug_breakpoint_name[1] == KS_EXTRA + if ((uint8_t)debug_breakpoint_name[0] == K_SPECIAL + && (uint8_t)debug_breakpoint_name[1] == KS_EXTRA && debug_breakpoint_name[2] == KE_SNR) { p = "<SNR>"; } else { @@ -414,7 +438,7 @@ void dbg_check_breakpoint(exarg_T *eap) debug_breakpoint_name + (*p == NUL ? 0 : 3), (int64_t)debug_breakpoint_lnum); debug_breakpoint_name = NULL; - do_debug((char_u *)eap->cmd); + do_debug(eap->cmd); } else { debug_skipped = true; debug_skipped_name = debug_breakpoint_name; @@ -422,7 +446,7 @@ void dbg_check_breakpoint(exarg_T *eap) } } else if (ex_nesting_level <= debug_break_level) { if (!eap->skip) { - do_debug((char_u *)eap->cmd); + do_debug(eap->cmd); } else { debug_skipped = true; debug_skipped_name = NULL; @@ -436,20 +460,21 @@ void dbg_check_breakpoint(exarg_T *eap) /// @return true when the debug mode is entered this time. bool dbg_check_skipped(exarg_T *eap) { - if (debug_skipped) { - // Save the value of got_int and reset it. We don't want a previous - // interruption cause flushing the input buffer. - int prev_got_int = got_int; - got_int = false; - debug_breakpoint_name = debug_skipped_name; - // eap->skip is true - eap->skip = false; - dbg_check_breakpoint(eap); - eap->skip = true; - got_int |= prev_got_int; - return true; - } - return false; + if (!debug_skipped) { + return false; + } + + // Save the value of got_int and reset it. We don't want a previous + // interruption cause flushing the input buffer. + int prev_got_int = got_int; + got_int = false; + debug_breakpoint_name = debug_skipped_name; + // eap->skip is true + eap->skip = false; + dbg_check_breakpoint(eap); + eap->skip = true; + got_int |= prev_got_int; + return true; } static garray_T dbg_breakp = { 0, 0, sizeof(struct debuggy), 4, NULL }; @@ -470,7 +495,7 @@ static typval_T *eval_expr_no_emsg(struct debuggy *const bp) { // Disable error messages, a bad expression would make Vim unusable. emsg_off++; - typval_T *const tv = eval_expr((char *)bp->dbg_name); + typval_T *const tv = eval_expr(bp->dbg_name); emsg_off--; return tv; } @@ -482,9 +507,9 @@ static typval_T *eval_expr_no_emsg(struct debuggy *const bp) /// /// @param arg /// @param gap either &dbg_breakp or &prof_ga -static int dbg_parsearg(char_u *arg, garray_T *gap) +static int dbg_parsearg(char *arg, garray_T *gap) { - char *p = (char *)arg; + char *p = arg; char *q; bool here = false; @@ -493,18 +518,18 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) struct debuggy *bp = &DEBUGGY(gap, gap->ga_len); // Find "func" or "file". - if (STRNCMP(p, "func", 4) == 0) { + if (strncmp(p, "func", 4) == 0) { bp->dbg_type = DBG_FUNC; - } else if (STRNCMP(p, "file", 4) == 0) { + } else if (strncmp(p, "file", 4) == 0) { bp->dbg_type = DBG_FILE; - } else if (gap != &prof_ga && STRNCMP(p, "here", 4) == 0) { + } else if (gap != &prof_ga && strncmp(p, "here", 4) == 0) { if (curbuf->b_ffname == NULL) { emsg(_(e_noname)); return FAIL; } bp->dbg_type = DBG_FILE; here = true; - } else if (gap != &prof_ga && STRNCMP(p, "expr", 4) == 0) { + } else if (gap != &prof_ga && strncmp(p, "expr", 4) == 0) { bp->dbg_type = DBG_EXPR; } else { semsg(_(e_invarg2), p); @@ -531,11 +556,11 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) } if (bp->dbg_type == DBG_FUNC) { - bp->dbg_name = vim_strsave((char_u *)p); + bp->dbg_name = xstrdup(p); } else if (here) { - bp->dbg_name = vim_strsave((char_u *)curbuf->b_ffname); + bp->dbg_name = xstrdup(curbuf->b_ffname); } else if (bp->dbg_type == DBG_EXPR) { - bp->dbg_name = vim_strsave((char_u *)p); + bp->dbg_name = xstrdup(p); bp->dbg_val = eval_expr_no_emsg(bp); } else { // Expand the file name in the same way as do_source(). This means @@ -551,10 +576,10 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) return FAIL; } if (*p != '*') { - bp->dbg_name = (char_u *)fix_fname(p); + bp->dbg_name = fix_fname(p); xfree(p); } else { - bp->dbg_name = (char_u *)p; + bp->dbg_name = p; } } @@ -572,33 +597,35 @@ void ex_breakadd(exarg_T *eap) gap = &prof_ga; } - if (dbg_parsearg((char_u *)eap->arg, gap) == OK) { - struct debuggy *bp = &DEBUGGY(gap, gap->ga_len); - bp->dbg_forceit = eap->forceit; + if (dbg_parsearg(eap->arg, gap) != OK) { + return; + } - if (bp->dbg_type != DBG_EXPR) { - char *pat = file_pat_to_reg_pat((char *)bp->dbg_name, NULL, NULL, false); - if (pat != NULL) { - bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING); - xfree(pat); + struct debuggy *bp = &DEBUGGY(gap, gap->ga_len); + bp->dbg_forceit = eap->forceit; + + if (bp->dbg_type != DBG_EXPR) { + char *pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, false); + if (pat != NULL) { + bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + xfree(pat); + } + if (pat == NULL || bp->dbg_prog == NULL) { + xfree(bp->dbg_name); + } else { + if (bp->dbg_lnum == 0) { // default line number is 1 + bp->dbg_lnum = 1; } - if (pat == NULL || bp->dbg_prog == NULL) { - xfree(bp->dbg_name); - } else { - if (bp->dbg_lnum == 0) { // default line number is 1 - bp->dbg_lnum = 1; - } - if (eap->cmdidx != CMD_profile) { - DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp; - debug_tick++; - } - gap->ga_len++; + if (eap->cmdidx != CMD_profile) { + DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp; + debug_tick++; } - } else { - // DBG_EXPR - DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp; - debug_tick++; + gap->ga_len++; } + } else { + // DBG_EXPR + DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp; + debug_tick++; } } @@ -639,14 +666,14 @@ void ex_breakdel(exarg_T *eap) del_all = true; } else { // ":breakdel {func|file|expr} [lnum] {name}" - if (dbg_parsearg((char_u *)eap->arg, gap) == FAIL) { + if (dbg_parsearg(eap->arg, gap) == FAIL) { return; } bp = &DEBUGGY(gap, gap->ga_len); for (int i = 0; i < gap->ga_len; i++) { bpi = &DEBUGGY(gap, i); if (bp->dbg_type == bpi->dbg_type - && STRCMP(bp->dbg_name, bpi->dbg_name) == 0 + && strcmp(bp->dbg_name, bpi->dbg_name) == 0 && (bp->dbg_lnum == bpi->dbg_lnum || (bp->dbg_lnum == 0 && (best_lnum == 0 @@ -660,32 +687,33 @@ void ex_breakdel(exarg_T *eap) if (todel < 0) { semsg(_("E161: Breakpoint not found: %s"), eap->arg); - } else { - while (!GA_EMPTY(gap)) { - xfree(DEBUGGY(gap, todel).dbg_name); - if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR - && DEBUGGY(gap, todel).dbg_val != NULL) { - tv_free(DEBUGGY(gap, todel).dbg_val); - } - vim_regfree(DEBUGGY(gap, todel).dbg_prog); - gap->ga_len--; - if (todel < gap->ga_len) { - memmove(&DEBUGGY(gap, todel), &DEBUGGY(gap, todel + 1), - (size_t)(gap->ga_len - todel) * sizeof(struct debuggy)); - } - if (eap->cmdidx == CMD_breakdel) { - debug_tick++; - } - if (!del_all) { - break; - } - } + return; + } - // If all breakpoints were removed clear the array. - if (GA_EMPTY(gap)) { - ga_clear(gap); + while (!GA_EMPTY(gap)) { + xfree(DEBUGGY(gap, todel).dbg_name); + if (DEBUGGY(gap, todel).dbg_type == DBG_EXPR + && DEBUGGY(gap, todel).dbg_val != NULL) { + tv_free(DEBUGGY(gap, todel).dbg_val); + } + vim_regfree(DEBUGGY(gap, todel).dbg_prog); + gap->ga_len--; + if (todel < gap->ga_len) { + memmove(&DEBUGGY(gap, todel), &DEBUGGY(gap, todel + 1), + (size_t)(gap->ga_len - todel) * sizeof(struct debuggy)); + } + if (eap->cmdidx == CMD_breakdel) { + debug_tick++; + } + if (!del_all) { + break; } } + + // If all breakpoints were removed clear the array. + if (GA_EMPTY(gap)) { + ga_clear(gap); + } } /// ":breaklist". @@ -693,21 +721,22 @@ void ex_breaklist(exarg_T *eap) { if (GA_EMPTY(&dbg_breakp)) { msg(_("No breakpoints defined")); - } else { - for (int i = 0; i < dbg_breakp.ga_len; i++) { - struct debuggy *bp = &BREAKP(i); - if (bp->dbg_type == DBG_FILE) { - home_replace(NULL, (char *)bp->dbg_name, (char *)NameBuff, MAXPATHL, true); - } - if (bp->dbg_type != DBG_EXPR) { - smsg(_("%3d %s %s line %" PRId64), - bp->dbg_nr, - bp->dbg_type == DBG_FUNC ? "func" : "file", - bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, - (int64_t)bp->dbg_lnum); - } else { - smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name); - } + return; + } + + for (int i = 0; i < dbg_breakp.ga_len; i++) { + struct debuggy *bp = &BREAKP(i); + if (bp->dbg_type == DBG_FILE) { + home_replace(NULL, bp->dbg_name, (char *)NameBuff, MAXPATHL, true); + } + if (bp->dbg_type != DBG_EXPR) { + smsg(_("%3d %s %s line %" PRId64), + bp->dbg_nr, + bp->dbg_type == DBG_FUNC ? "func" : "file", + bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, + (int64_t)bp->dbg_lnum); + } else { + smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name); } } } @@ -718,7 +747,7 @@ void ex_breaklist(exarg_T *eap) /// @param file true for a file, false for a function /// @param fname file or function name /// @param after after this line number -linenr_T dbg_find_breakpoint(bool file, char_u *fname, linenr_T after) +linenr_T dbg_find_breakpoint(bool file, char *fname, linenr_T after) { return debuggy_find(file, fname, after, &dbg_breakp, NULL); } @@ -728,7 +757,7 @@ linenr_T dbg_find_breakpoint(bool file, char_u *fname, linenr_T after) /// @param fp[out] forceit /// /// @returns true if profiling is on for a function or sourced file. -bool has_profiling(bool file, char_u *fname, bool *fp) +bool has_profiling(bool file, char *fname, bool *fp) { return debuggy_find(file, fname, (linenr_T)0, &prof_ga, fp) != (linenr_T)0; @@ -741,11 +770,11 @@ bool has_profiling(bool file, char_u *fname, bool *fp) /// @param after after this line number /// @param gap either &dbg_breakp or &prof_ga /// @param fp if not NULL: return forceit -static linenr_T debuggy_find(bool file, char_u *fname, linenr_T after, garray_T *gap, bool *fp) +static linenr_T debuggy_find(bool file, char *fname, linenr_T after, garray_T *gap, bool *fp) { struct debuggy *bp; linenr_T lnum = 0; - char_u *name = fname; + char *name = fname; int prev_got_int; // Return quickly when there are no breakpoints. @@ -754,8 +783,8 @@ static linenr_T debuggy_find(bool file, char_u *fname, linenr_T after, garray_T } // Replace K_SNR in function name with "<SNR>". - if (!file && fname[0] == K_SPECIAL) { - name = xmalloc(STRLEN(fname) + 3); + if (!file && (uint8_t)fname[0] == K_SPECIAL) { + name = xmalloc(strlen(fname) + 3); STRCPY(name, "<SNR>"); STRCPY(name + 5, fname + 3); } @@ -786,26 +815,26 @@ static linenr_T debuggy_find(bool file, char_u *fname, linenr_T after, garray_T typval_T *const tv = eval_expr_no_emsg(bp); if (tv != NULL) { if (bp->dbg_val == NULL) { - debug_oldval = typval_tostring(NULL); + debug_oldval = typval_tostring(NULL, true); bp->dbg_val = tv; - debug_newval = typval_tostring(bp->dbg_val); + debug_newval = typval_tostring(bp->dbg_val, true); line = true; } else { if (typval_compare(tv, bp->dbg_val, EXPR_IS, false) == OK && tv->vval.v_number == false) { line = true; - debug_oldval = typval_tostring(bp->dbg_val); + debug_oldval = typval_tostring(bp->dbg_val, true); // Need to evaluate again, typval_compare() overwrites "tv". typval_T *const v = eval_expr_no_emsg(bp); - debug_newval = typval_tostring(v); + debug_newval = typval_tostring(v, true); tv_free(bp->dbg_val); bp->dbg_val = v; } tv_free(tv); } } else if (bp->dbg_val != NULL) { - debug_oldval = typval_tostring(bp->dbg_val); - debug_newval = typval_tostring(NULL); + debug_oldval = typval_tostring(bp->dbg_val, true); + debug_newval = typval_tostring(NULL, true); tv_free(bp->dbg_val); bp->dbg_val = NULL; line = true; @@ -825,7 +854,7 @@ static linenr_T debuggy_find(bool file, char_u *fname, linenr_T after, garray_T } /// Called when a breakpoint was encountered. -void dbg_breakpoint(char_u *name, linenr_T lnum) +void dbg_breakpoint(char *name, linenr_T lnum) { // We need to check if this line is actually executed in do_one_cmd() debug_breakpoint_name = name; diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index a93fb599c4..63c55ec602 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1,16 +1,18 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include "nvim/api/ui.h" +#include <assert.h> + #include "nvim/buffer.h" #include "nvim/decoration.h" #include "nvim/drawscreen.h" #include "nvim/extmark.h" +#include "nvim/fold.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" -#include "nvim/lua/executor.h" -#include "nvim/move.h" -#include "nvim/vim.h" +#include "nvim/memory.h" +#include "nvim/pos.h" +#include "nvim/sign_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "decoration.c.generated.h" @@ -69,18 +71,21 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) { if (row2 >= row1) { - if (!decor || decor->hl_id || decor_has_sign(decor) || decor->conceal) { + if (!decor + || decor->hl_id + || decor_has_sign(decor) + || decor->conceal + || decor->spell != kNone) { redraw_buf_range_later(buf, row1 + 1, row2 + 1); } } if (decor && decor_virt_pos(*decor)) { - redraw_buf_line_later(buf, row1 + 1); + redraw_buf_line_later(buf, row1 + 1, false); } if (decor && kv_size(decor->virt_lines)) { - redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, - row1 + 1 + (decor->virt_lines_above?0:1))); + redraw_buf_line_later(buf, row1 + 1 + (decor->virt_lines_above?0:1), true); } } @@ -116,6 +121,11 @@ void decor_free(Decoration *decor) } } +void decor_state_free(DecorState *state) +{ + xfree(state->active.items); +} + void clear_virttext(VirtText *text) { for (size_t i = 0; i < kv_size(*text); i++) { @@ -165,13 +175,12 @@ Decoration get_decor(mtkey_t mark) { if (mark.decor_full) { return *mark.decor_full; - } else { - Decoration fake = DECORATION_INIT; - fake.hl_id = mark.hl_id; - fake.priority = mark.priority; - fake.hl_eol = (mark.flags & MT_FLAG_HL_EOL); - return fake; } + Decoration fake = DECORATION_INIT; + fake.hl_id = mark.hl_id; + fake.priority = mark.priority; + fake.hl_eol = (mark.flags & MT_FLAG_HL_EOL); + return fake; } /// @return true if decor has a virtual position (virtual text or ui_watched) @@ -306,6 +315,7 @@ next_mark: bool conceal = 0; int conceal_char = 0; int conceal_attr = 0; + TriState spell = kNone; for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange item = kv_A(state->active, i); @@ -339,6 +349,9 @@ next_mark: conceal_attr = item.attr_id; } } + if (active && item.decor.spell != kNone) { + spell = item.decor.spell; + } if ((item.start_row == state->row && item.start_col <= col) && decor_virt_pos(item.decor) && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) { @@ -355,6 +368,7 @@ next_mark: state->conceal = conceal; state->conceal_char = conceal_char; state->conceal_attr = conceal_attr; + state->spell = spell; return attr; } @@ -537,7 +551,8 @@ void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, ns_id, mark_id); } -int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) +/// @param has_fold whether line "lnum" has a fold, or kNone when not calculated yet +int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fold) { buf_T *buf = wp->w_buffer; if (!buf->b_virt_line_blocks) { @@ -547,10 +562,14 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) } int virt_lines = 0; - int row = (int)MAX(lnum - 2, 0); + int row = MAX(lnum - 2, 0); int end_row = (int)lnum; MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, row, 0, itr); + bool below_fold = lnum > 1 && hasFoldingWin(wp, lnum - 1, NULL, NULL, true, NULL); + if (has_fold == kNone) { + has_fold = hasFoldingWin(wp, lnum, NULL, NULL, true, NULL); + } while (true) { mtkey_t mark = marktree_itr_current(itr); if (mark.pos.row < 0 || mark.pos.row >= end_row) { @@ -558,9 +577,10 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) } else if (marktree_decor_level(mark) < kDecorLevelVirtLine) { goto next_mark; } - bool above = mark.pos.row > (int)(lnum - 2); + bool above = mark.pos.row > (lnum - 2); + bool has_fold_cur = above ? has_fold : below_fold; Decoration *decor = mark.decor_full; - if (decor && decor->virt_lines_above == above) { + if (!has_fold_cur && decor && decor->virt_lines_above == above) { virt_lines += (int)kv_size(decor->virt_lines); if (lines) { kv_splice(*lines, decor->virt_lines); diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 8f28442d41..c9ec8ede7f 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -1,9 +1,17 @@ #ifndef NVIM_DECORATION_H #define NVIM_DECORATION_H +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include "klib/kvec.h" #include "nvim/buffer_defs.h" #include "nvim/extmark_defs.h" +#include "nvim/macros.h" +#include "nvim/marktree.h" #include "nvim/pos.h" +#include "nvim/types.h" // actual Decoration data is in extmark_defs.h @@ -28,7 +36,6 @@ typedef enum { EXTERN const char *const hl_mode_str[] INIT(= { "", "replace", "combine", "blend" }); -typedef kvec_t(VirtTextChunk) VirtText; #define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines; @@ -46,11 +53,12 @@ struct Decoration { bool hl_eol; bool virt_lines_above; bool conceal; + TriState spell; // TODO(bfredl): style, etc DecorPriority priority; int col; // fixed col value, like win_col int virt_text_width; // width of virt_text - char_u *sign_text; + char *sign_text; int sign_hl_id; int number_hl_id; int line_hl_id; @@ -61,8 +69,8 @@ struct Decoration { bool ui_watched; // watched for win_extmark }; #define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \ - kHlModeUnknown, false, false, false, false, DECOR_PRIORITY_BASE, \ - 0, 0, NULL, 0, 0, 0, 0, 0, false } + kHlModeUnknown, false, false, false, false, kNone, \ + DECOR_PRIORITY_BASE, 0, 0, NULL, 0, 0, 0, 0, 0, false } typedef struct { int start_row; @@ -90,6 +98,8 @@ typedef struct { bool conceal; int conceal_char; int conceal_attr; + + TriState spell; } DecorState; EXTERN DecorState decor_state INIT(= { 0 }); diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c index 14c1238fa4..ed21474935 100644 --- a/src/nvim/decoration_provider.c +++ b/src/nvim/decoration_provider.c @@ -1,20 +1,30 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> +#include <stdio.h> +#include <string.h> + +#include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/extmark.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/buffer.h" -#include "nvim/decoration.h" +#include "nvim/buffer_defs.h" #include "nvim/decoration_provider.h" +#include "nvim/globals.h" #include "nvim/highlight.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" +#include "nvim/pos.h" static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE; #define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \ { ns_id, false, LUA_NOREF, LUA_NOREF, \ LUA_NOREF, LUA_NOREF, LUA_NOREF, \ - LUA_NOREF, -1, false } + LUA_NOREF, -1, false, false } static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, Array args, bool default_true, char **perr) @@ -47,11 +57,33 @@ static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, Array return false; } +void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int end_row, int end_col, + char **err) +{ + for (size_t i = 0; i < kv_size(decor_providers); i++) { + DecorProvider *p = &kv_A(decor_providers, i); + if (!p->active) { + continue; + } + + if (p->spell_nav != LUA_NOREF) { + MAXSIZE_TEMP_ARRAY(args, 6); + ADD_C(args, INTEGER_OBJ(wp->handle)); + ADD_C(args, INTEGER_OBJ(wp->w_buffer->handle)); + ADD_C(args, INTEGER_OBJ(start_row)); + ADD_C(args, INTEGER_OBJ(start_col)); + ADD_C(args, INTEGER_OBJ(end_row)); + ADD_C(args, INTEGER_OBJ(end_col)); + decor_provider_invoke(p->ns_id, "spell", p->spell_nav, args, true, err); + } + } +} + /// For each provider invoke the 'start' callback /// /// @param[out] providers Decoration providers /// @param[out] err Provider err -void decor_providers_start(DecorProviders *providers, int type, char **err) +void decor_providers_start(DecorProviders *providers, char **err) { kvi_init(*providers); @@ -65,7 +97,6 @@ void decor_providers_start(DecorProviders *providers, int type, char **err) if (p->redraw_start != LUA_NOREF) { MAXSIZE_TEMP_ARRAY(args, 2); ADD_C(args, INTEGER_OBJ((int)display_tick)); - ADD_C(args, INTEGER_OBJ(type)); active = decor_provider_invoke(p->ns_id, "start", p->redraw_start, args, true, err); } else { active = true; @@ -116,8 +147,8 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers, /// @param row Row to invoke line callback for /// @param[out] has_decor Set when at least one provider invokes a line callback /// @param[out] err Provider error -void providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor, - char **err) +void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor, + char **err) { for (size_t k = 0; k < kv_size(*providers); k++) { DecorProvider *p = kv_A(*providers, k); @@ -172,6 +203,25 @@ void decor_providers_invoke_end(DecorProviders *providers, char **err) } } +/// Mark all cached state of per-namespace highlights as invalid. Revalidate +/// current namespace. +/// +/// Expensive! Should on be called by an already throttled validity check +/// like highlight_changed() (throttled to the next redraw or mode change) +void decor_provider_invalidate_hl(void) +{ + size_t len = kv_size(decor_providers); + for (size_t i = 0; i < len; i++) { + DecorProvider *item = &kv_A(decor_providers, i); + item->hl_cached = false; + } + + if (ns_hl_active) { + ns_hl_active = -1; + hl_check_ns(); + } +} + DecorProvider *get_decor_provider(NS ns_id, bool force) { assert(ns_id > 0); @@ -215,6 +265,7 @@ void decor_provider_clear(DecorProvider *p) NLUA_CLEAR_REF(p->redraw_win); NLUA_CLEAR_REF(p->redraw_line); NLUA_CLEAR_REF(p->redraw_end); + NLUA_CLEAR_REF(p->spell_nav); p->active = false; } diff --git a/src/nvim/decoration_provider.h b/src/nvim/decoration_provider.h index dd1ed6c581..b91ddabdfd 100644 --- a/src/nvim/decoration_provider.h +++ b/src/nvim/decoration_provider.h @@ -1,7 +1,12 @@ #ifndef NVIM_DECORATION_PROVIDER_H #define NVIM_DECORATION_PROVIDER_H +#include <stdbool.h> + +#include "klib/kvec.h" #include "nvim/buffer_defs.h" +#include "nvim/macros.h" +#include "nvim/types.h" typedef struct { NS ns_id; @@ -12,6 +17,7 @@ typedef struct { LuaRef redraw_line; LuaRef redraw_end; LuaRef hl_def; + LuaRef spell_nav; int hl_valid; bool hl_cached; } DecorProvider; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 5485d528f7..032de561b3 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -10,9 +10,15 @@ /// - Use the compiled-in xdiff library. /// - Let 'diffexpr' do the work, using files. +#include <assert.h> +#include <ctype.h> #include <inttypes.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "auto/config.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" @@ -23,9 +29,15 @@ #include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" +#include "nvim/extmark_defs.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/linematch.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -35,10 +47,13 @@ #include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/os.h" #include "nvim/os/shell.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -61,10 +76,12 @@ static bool diff_need_update = false; // ex_diffupdate needs to be called #define DIFF_INTERNAL 0x200 // use internal xdiff algorithm #define DIFF_CLOSE_OFF 0x400 // diffoff when closing window #define DIFF_FOLLOWWRAP 0x800 // follow the wrap option +#define DIFF_LINEMATCH 0x1000 // match most similar lines within diff #define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL) static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF; static long diff_algorithm = 0; +static int linematch_lines = 0; #define LBUFLEN 50 // length of line in diff file @@ -74,13 +91,13 @@ static TriState diff_a_works = kNone; // used for diff input typedef struct { - char_u *din_fname; // used for external diff + char *din_fname; // used for external diff mmfile_t din_mmfile; // used for internal diff } diffin_T; // used for diff result typedef struct { - char_u *dout_fname; // used for external diff + char *dout_fname; // used for external diff garray_T dout_ga; // used for internal diff } diffout_T; @@ -100,6 +117,12 @@ typedef struct { int dio_internal; // using internal diff } diffio_T; +typedef enum { + DIFF_ED, + DIFF_UNIFIED, + DIFF_NONE, +} diffstyle_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "diff.c.generated.h" #endif @@ -120,7 +143,7 @@ void diff_buf_delete(buf_T *buf) // don't redraw right away, more might change or buffer state // is invalid right now need_diff_redraw = true; - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } } } @@ -204,13 +227,7 @@ static void diff_buf_clear(void) /// @return Its index or DB_COUNT if not found. static int diff_buf_idx(buf_T *buf) { - int idx; - for (idx = 0; idx < DB_COUNT; idx++) { - if (curtab->tp_diffbuf[idx] == buf) { - break; - } - } - return idx; + return diff_buf_idx_tp(buf, curtab); } /// Find buffer "buf" in the list of diff buffers for tab page "tp". @@ -309,8 +326,6 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T diff_T *dp = tp->tp_first_diff; linenr_T lnum_deleted = line1; // lnum of remaining deletion - linenr_T n; - linenr_T off; for (;;) { // If the change is after the previous diff block and before the next // diff block, thus not touching an existing change, create a new diff @@ -363,7 +378,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T if (last >= line1 - 1) { // 6. change below line2: only adjust for amount_after; also when // "deleted" became zero when deleted all lines between two diffs. - if (dp->df_lnum[idx] - (deleted + inserted != 0) > line2) { + if (dp->df_lnum[idx] - (deleted + inserted != 0) > line2 - dp->is_linematched) { if (amount_after == 0) { // nothing left to change break; @@ -374,7 +389,8 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T // 2. 3. 4. 5.: inserted/deleted lines touching this diff. if (deleted > 0) { - off = 0; + linenr_T n; + linenr_T off = 0; if (dp->df_lnum[idx] >= line1) { if (last <= line2) { // 4. delete all lines of diff @@ -454,7 +470,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T } // check if this block touches the previous one, may merge them. - if ((dprev != NULL) + if ((dprev != NULL) && !dp->is_linematched && (dprev->df_lnum[idx] + dprev->df_count[idx] == dp->df_lnum[idx])) { int i; for (i = 0; i < DB_COUNT; i++) { @@ -462,9 +478,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T dprev->df_count[i] += dp->df_count[i]; } } - dprev->df_next = dp->df_next; - xfree(dp); - dp = dprev->df_next; + dp = diff_free(tp, dprev, dp); } else { // Advance to next entry. dprev = dp; @@ -485,15 +499,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T } if (i == DB_COUNT) { - diff_T *dnext = dp->df_next; - xfree(dp); - dp = dnext; - - if (dprev == NULL) { - tp->tp_first_diff = dnext; - } else { - dprev->df_next = dnext; - } + dp = diff_free(tp, dprev, dp); } else { // Advance to next entry. dprev = dp; @@ -523,6 +529,7 @@ static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp) { diff_T *dnew = xmalloc(sizeof(*dnew)); + dnew->is_linematched = false; dnew->df_next = dp; if (dprev == NULL) { tp->tp_first_diff = dnew; @@ -533,6 +540,20 @@ static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp) return dnew; } +static diff_T *diff_free(tabpage_T *tp, diff_T *dprev, diff_T *dp) +{ + diff_T *ret = dp->df_next; + xfree(dp); + + if (dprev == NULL) { + tp->tp_first_diff = ret; + } else { + dprev->df_next = ret; + } + + return ret; +} + /// Check if the diff block "dp" can be made smaller for lines at the start and /// end that are equal. Called after inserting lines. /// @@ -573,9 +594,9 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) if (dir == BACKWARD) { off_org = dp->df_count[i_org] - 1; } - char_u *line_org = vim_strsave(ml_get_buf(tp->tp_diffbuf[i_org], - dp->df_lnum[i_org] + off_org, - false)); + char *line_org = xstrdup(ml_get_buf(tp->tp_diffbuf[i_org], + dp->df_lnum[i_org] + off_org, + false)); int i_new; for (i_new = i_org + 1; i_new < DB_COUNT; i_new++) { @@ -659,7 +680,7 @@ void diff_redraw(bool dofold) continue; } - redraw_later(wp, SOME_VALID); + redraw_later(wp, UPD_SOME_VALID); if (wp != curwin) { wp_other = wp; } @@ -704,16 +725,16 @@ static void clear_diffin(diffin_T *din) if (din->din_fname == NULL) { XFREE_CLEAR(din->din_mmfile.ptr); } else { - os_remove((char *)din->din_fname); + os_remove(din->din_fname); } } static void clear_diffout(diffout_T *dout) { if (dout->dout_fname == NULL) { - ga_clear_strings(&dout->dout_ga); + ga_clear(&dout->dout_ga); } else { - os_remove((char *)dout->dout_fname); + os_remove(dout->dout_fname); } } @@ -723,15 +744,19 @@ static void clear_diffout(diffout_T *dout) /// @param din /// /// @return FAIL for failure. -static int diff_write_buffer(buf_T *buf, diffin_T *din) +static int diff_write_buffer(buf_T *buf, mmfile_t *m, linenr_T start, linenr_T end) { - long len = 0; + size_t len = 0; + + if (end < 0) { + end = buf->b_ml.ml_line_count; + } // xdiff requires one big block of memory with all the text. - for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - len += (long)STRLEN(ml_get_buf(buf, lnum, false)) + 1; + for (linenr_T lnum = start; lnum <= end; lnum++) { + len += strlen(ml_get_buf(buf, lnum, false)) + 1; } - char_u *ptr = try_malloc((size_t)len); + char *ptr = try_malloc(len); if (ptr == NULL) { // Allocating memory failed. This can happen, because we try to read // the whole buffer text into memory. Set the failed flag, the diff @@ -745,37 +770,32 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) } return FAIL; } - din->din_mmfile.ptr = (char *)ptr; - din->din_mmfile.size = len; + m->ptr = ptr; + m->size = (long)len; len = 0; - for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - for (char_u *s = ml_get_buf(buf, lnum, false); *s != NUL;) { - if (diff_flags & DIFF_ICASE) { - int c; + for (linenr_T lnum = start; lnum <= end; lnum++) { + char *s = ml_get_buf(buf, lnum, false); + if (diff_flags & DIFF_ICASE) { + while (*s != NUL) { char cbuf[MB_MAXBYTES + 1]; - if (*s == NL) { - c = NUL; - } else { - // xdiff doesn't support ignoring case, fold-case the text. - c = utf_ptr2char((char *)s); - c = utf_fold(c); - } - const int orig_len = utfc_ptr2len((char *)s); - if (utf_char2bytes(c, (char *)cbuf) != orig_len) { - // TODO(Bram): handle byte length difference - memmove(ptr + len, s, (size_t)orig_len); - } else { - memmove(ptr + len, cbuf, (size_t)orig_len); - } + // xdiff doesn't support ignoring case, fold-case the text. + int c = *s == NL ? NUL : utf_fold(utf_ptr2char(s)); + const int orig_len = utfc_ptr2len(s); + // TODO(Bram): handle byte length difference + char *s1 = (utf_char2bytes(c, cbuf) != orig_len) ? s : cbuf; + memmove(ptr + len, s1, (size_t)orig_len); s += orig_len; - len += orig_len; - } else { - ptr[len++] = *s == NL ? NUL : *s; - s++; + len += (size_t)orig_len; } + } else { + size_t slen = strlen(s); + memmove(ptr + len, s, slen); + // NUL is represented as NL; convert + memchrsub(ptr + len, NL, NUL, slen); + len += slen; } ptr[len++] = NL; } @@ -793,17 +813,17 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) static int diff_write(buf_T *buf, diffin_T *din) { if (din->din_fname == NULL) { - return diff_write_buffer(buf, din); + return diff_write_buffer(buf, &din->din_mmfile, 1, -1); } // Always use 'fileformat' set to "unix". - char_u *save_ff = buf->b_p_ff; - buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + char *save_ff = buf->b_p_ff; + buf->b_p_ff = xstrdup(FF_UNIX); const bool save_cmod_flags = cmdmod.cmod_flags; // Writing the buffer is an implementation detail of performing the diff, // so it shouldn't update the '[ and '] marks. cmdmod.cmod_flags |= CMOD_LOCKMARKS; - int r = buf_write(buf, (char *)din->din_fname, NULL, + int r = buf_write(buf, din->din_fname, NULL, (linenr_T)1, buf->b_ml.ml_line_count, NULL, false, false, false, true); cmdmod.cmod_flags = save_cmod_flags; @@ -821,7 +841,7 @@ static int diff_write(buf_T *buf, diffin_T *din) static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap) { if (dio->dio_internal) { - ga_init(&dio->dio_diff.dout_ga, sizeof(char *), 1000); + ga_init(&dio->dio_diff.dout_ga, sizeof(diffhunk_T), 100); } else { // We need three temp file names. dio->dio_orig.din_fname = vim_tempname(); @@ -992,7 +1012,7 @@ static int check_external_diff(diffio_T *diffio) TriState ok = kFalse; for (;;) { ok = kFalse; - FILE *fd = os_fopen((char *)diffio->dio_orig.din_fname, "w"); + FILE *fd = os_fopen(diffio->dio_orig.din_fname, "w"); if (fd == NULL) { io_error = true; @@ -1001,7 +1021,7 @@ static int check_external_diff(diffio_T *diffio) io_error = true; } fclose(fd); - fd = os_fopen((char *)diffio->dio_new.din_fname, "w"); + fd = os_fopen(diffio->dio_new.din_fname, "w"); if (fd == NULL) { io_error = true; @@ -1012,13 +1032,13 @@ static int check_external_diff(diffio_T *diffio) fclose(fd); fd = NULL; if (diff_file(diffio) == OK) { - fd = os_fopen((char *)diffio->dio_diff.dout_fname, "r"); + fd = os_fopen(diffio->dio_diff.dout_fname, "r"); } if (fd == NULL) { io_error = true; } else { - char_u linebuf[LBUFLEN]; + char linebuf[LBUFLEN]; for (;;) { // For normal diff there must be a line that contains @@ -1027,17 +1047,17 @@ static int check_external_diff(diffio_T *diffio) break; } - if (STRNCMP(linebuf, "1c1", 3) == 0 - || STRNCMP(linebuf, "@@ -1 +1 @@", 11) == 0) { + if (strncmp(linebuf, "1c1", 3) == 0 + || strncmp(linebuf, "@@ -1 +1 @@", 11) == 0) { ok = kTrue; } } fclose(fd); } - os_remove((char *)diffio->dio_diff.dout_fname); - os_remove((char *)diffio->dio_new.din_fname); + os_remove(diffio->dio_diff.dout_fname); + os_remove(diffio->dio_new.din_fname); } - os_remove((char *)diffio->dio_orig.din_fname); + os_remove(diffio->dio_orig.din_fname); } // When using 'diffexpr' break here. @@ -1115,9 +1135,9 @@ static int diff_file_internal(diffio_T *diffio) /// @return OK or FAIL static int diff_file(diffio_T *dio) { - char *tmp_orig = (char *)dio->dio_orig.din_fname; - char *tmp_new = (char *)dio->dio_new.din_fname; - char *tmp_diff = (char *)dio->dio_diff.dout_fname; + char *tmp_orig = dio->dio_orig.din_fname; + char *tmp_new = dio->dio_new.din_fname; + char *tmp_diff = dio->dio_diff.dout_fname; if (*p_dex != NUL) { // Use 'diffexpr' to generate the diff file. eval_diff(tmp_orig, tmp_new, tmp_diff); @@ -1126,37 +1146,37 @@ static int diff_file(diffio_T *dio) // Use xdiff for generating the diff. if (dio->dio_internal) { return diff_file_internal(dio); - } else { - const size_t len = (strlen(tmp_orig) + strlen(tmp_new) + strlen(tmp_diff) - + STRLEN(p_srr) + 27); - char *const cmd = xmalloc(len); - - // We don't want $DIFF_OPTIONS to get in the way. - if (os_getenv("DIFF_OPTIONS")) { - os_unsetenv("DIFF_OPTIONS"); - } - - // Build the diff command and execute it. Always use -a, binary - // differences are of no use. Ignore errors, diff returns - // non-zero when differences have been found. - vim_snprintf(cmd, len, "diff %s%s%s%s%s%s%s%s %s", - diff_a_works == kFalse ? "" : "-a ", - "", - (diff_flags & DIFF_IWHITE) ? "-b " : "", - (diff_flags & DIFF_IWHITEALL) ? "-w " : "", - (diff_flags & DIFF_IWHITEEOL) ? "-Z " : "", - (diff_flags & DIFF_IBLANK) ? "-B " : "", - (diff_flags & DIFF_ICASE) ? "-i " : "", - tmp_orig, tmp_new); - append_redir(cmd, len, (char *)p_srr, tmp_diff); - block_autocmds(); // Avoid ShellCmdPost stuff - (void)call_shell((char_u *)cmd, - kShellOptFilter | kShellOptSilent | kShellOptDoOut, - NULL); - unblock_autocmds(); - xfree(cmd); - return OK; } + + const size_t len = (strlen(tmp_orig) + strlen(tmp_new) + strlen(tmp_diff) + + strlen(p_srr) + 27); + char *const cmd = xmalloc(len); + + // We don't want $DIFF_OPTIONS to get in the way. + if (os_getenv("DIFF_OPTIONS")) { + os_unsetenv("DIFF_OPTIONS"); + } + + // Build the diff command and execute it. Always use -a, binary + // differences are of no use. Ignore errors, diff returns + // non-zero when differences have been found. + vim_snprintf(cmd, len, "diff %s%s%s%s%s%s%s%s %s", + diff_a_works == kFalse ? "" : "-a ", + "", + (diff_flags & DIFF_IWHITE) ? "-b " : "", + (diff_flags & DIFF_IWHITEALL) ? "-w " : "", + (diff_flags & DIFF_IWHITEEOL) ? "-Z " : "", + (diff_flags & DIFF_IBLANK) ? "-B " : "", + (diff_flags & DIFF_ICASE) ? "-i " : "", + tmp_orig, tmp_new); + append_redir(cmd, len, p_srr, tmp_diff); + block_autocmds(); // Avoid ShellCmdPost stuff + (void)call_shell(cmd, + kShellOptFilter | kShellOptSilent | kShellOptDoOut, + NULL); + unblock_autocmds(); + xfree(cmd); + return OK; } /// Create a new version of a file from the current buffer and a diff file. @@ -1167,10 +1187,10 @@ static int diff_file(diffio_T *dio) /// @param eap void ex_diffpatch(exarg_T *eap) { - char_u *buf = NULL; + char *buf = NULL; win_T *old_curwin = curwin; char *newname = NULL; // name of patched file buffer - char_u *esc_name = NULL; + char *esc_name = NULL; #ifdef UNIX char *fullname = NULL; @@ -1178,16 +1198,16 @@ void ex_diffpatch(exarg_T *eap) // We need two temp file names. // Name of original temp file. - char_u *tmp_orig = vim_tempname(); + char *tmp_orig = vim_tempname(); // Name of patched temp file. - char_u *tmp_new = vim_tempname(); + char *tmp_new = vim_tempname(); if ((tmp_orig == NULL) || (tmp_new == NULL)) { goto theend; } // Write the current buffer to "tmp_orig". - if (buf_write(curbuf, (char *)tmp_orig, NULL, + if (buf_write(curbuf, tmp_orig, NULL, (linenr_T)1, curbuf->b_ml.ml_line_count, NULL, false, false, false, true) == FAIL) { goto theend; @@ -1196,23 +1216,22 @@ void ex_diffpatch(exarg_T *eap) #ifdef UNIX // Get the absolute path of the patchfile, changing directory below. fullname = FullName_save(eap->arg, false); - esc_name = - vim_strsave_shellescape((char_u *)(fullname != NULL ? fullname : eap->arg), true, true); + esc_name = vim_strsave_shellescape(fullname != NULL ? fullname : eap->arg, true, true); #else esc_name = vim_strsave_shellescape(eap->arg, true, true); #endif - size_t buflen = STRLEN(tmp_orig) + STRLEN(esc_name) + STRLEN(tmp_new) + 16; + size_t buflen = strlen(tmp_orig) + strlen(esc_name) + strlen(tmp_new) + 16; buf = xmalloc(buflen); #ifdef UNIX - char_u dirbuf[MAXPATHL]; + char dirbuf[MAXPATHL]; // Temporarily chdir to /tmp, to avoid patching files in the current // directory when the patch file contains more than one patch. When we // have our own temp dir use that instead, it will be cleaned up when we // exit (any .rej files created). Don't change directory if we can't // return to the current. if ((os_dirname(dirbuf, MAXPATHL) != OK) - || (os_chdir((char *)dirbuf) != 0)) { + || (os_chdir(dirbuf) != 0)) { dirbuf[0] = NUL; } else { char *tempdir = vim_gettempdir(); @@ -1227,15 +1246,13 @@ void ex_diffpatch(exarg_T *eap) if (*p_pex != NUL) { // Use 'patchexpr' to generate the new file. #ifdef UNIX - eval_patch((char *)tmp_orig, - (fullname != NULL ? fullname : eap->arg), - (char *)tmp_new); + eval_patch(tmp_orig, (fullname != NULL ? fullname : eap->arg), tmp_new); #else - eval_patch((char *)tmp_orig, (char *)eap->arg, (char *)tmp_new); + eval_patch(tmp_orig, eap->arg, tmp_new); #endif } else { // Build the patch command and execute it. Ignore errors. - vim_snprintf((char *)buf, buflen, "patch -o %s %s < %s", + vim_snprintf(buf, buflen, "patch -o %s %s < %s", tmp_new, tmp_orig, esc_name); block_autocmds(); // Avoid ShellCmdPost stuff (void)call_shell(buf, kShellOptFilter, NULL); @@ -1244,7 +1261,7 @@ void ex_diffpatch(exarg_T *eap) #ifdef UNIX if (dirbuf[0] != NUL) { - if (os_chdir((char *)dirbuf) != 0) { + if (os_chdir(dirbuf) != 0) { emsg(_(e_prev_dir)); } shorten_fnames(true); @@ -1254,20 +1271,20 @@ void ex_diffpatch(exarg_T *eap) // Delete any .orig or .rej file created. STRCPY(buf, tmp_new); STRCAT(buf, ".orig"); - os_remove((char *)buf); + os_remove(buf); STRCPY(buf, tmp_new); STRCAT(buf, ".rej"); - os_remove((char *)buf); + os_remove(buf); // Only continue if the output file was created. FileInfo file_info; - bool info_ok = os_fileinfo((char *)tmp_new, &file_info); + bool info_ok = os_fileinfo(tmp_new, &file_info); uint64_t filesize = os_fileinfo_size(&file_info); if (!info_ok || filesize == 0) { emsg(_("E816: Cannot read patch output")); } else { if (curbuf->b_fname != NULL) { - newname = xstrnsave(curbuf->b_fname, STRLEN(curbuf->b_fname) + 4); + newname = xstrnsave(curbuf->b_fname, strlen(curbuf->b_fname) + 4); STRCAT(newname, ".new"); } @@ -1277,7 +1294,7 @@ void ex_diffpatch(exarg_T *eap) if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) != FAIL) { // Pretend it was a ":split fname" command eap->cmdidx = CMD_split; - eap->arg = (char *)tmp_new; + eap->arg = tmp_new; do_exedit(eap, old_curwin); // check that split worked and editing tmp_new @@ -1302,12 +1319,12 @@ void ex_diffpatch(exarg_T *eap) theend: if (tmp_orig != NULL) { - os_remove((char *)tmp_orig); + os_remove(tmp_orig); } xfree(tmp_orig); if (tmp_new != NULL) { - os_remove((char *)tmp_new); + os_remove(tmp_new); } xfree(tmp_new); xfree(newname); @@ -1334,30 +1351,33 @@ void ex_diffsplit(exarg_T *eap) // don't use a new tab page, each tab page has its own diffs cmdmod.cmod_tab = 0; - if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) != FAIL) { - // Pretend it was a ":split fname" command - eap->cmdidx = CMD_split; - curwin->w_p_diff = true; - do_exedit(eap, old_curwin); - - // split must have worked - if (curwin != old_curwin) { - // Set 'diff', 'scrollbind' on and 'wrap' off. - diff_win_options(curwin, true); - if (win_valid(old_curwin)) { - diff_win_options(old_curwin, true); + if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) == FAIL) { + return; + } - if (bufref_valid(&old_curbuf)) { - // Move the cursor position to that of the old window. - curwin->w_cursor.lnum = diff_get_corresponding_line(old_curbuf.br_buf, - old_curwin->w_cursor.lnum); - } - } - // Now that lines are folded scroll to show the cursor at the same - // relative position. - scroll_to_fraction(curwin, curwin->w_height); + // Pretend it was a ":split fname" command + eap->cmdidx = CMD_split; + curwin->w_p_diff = true; + do_exedit(eap, old_curwin); + + if (curwin == old_curwin) { // split didn't work + return; + } + + // Set 'diff', 'scrollbind' on and 'wrap' off. + diff_win_options(curwin, true); + if (win_valid(old_curwin)) { + diff_win_options(old_curwin, true); + + if (bufref_valid(&old_curbuf)) { + // Move the cursor position to that of the old window. + curwin->w_cursor.lnum = diff_get_corresponding_line(old_curbuf.br_buf, + old_curwin->w_cursor.lnum); } } + // Now that lines are folded scroll to show the cursor at the same + // relative position. + scroll_to_fraction(curwin, curwin->w_height); } // Set options to show diffs for the current window. @@ -1374,7 +1394,7 @@ static void set_diff_option(win_T *wp, int value) curwin = wp; curbuf = curwin->w_buffer; curbuf->b_ro_locked++; - set_option_value("diff", (long)value, NULL, OPT_LOCAL); + set_option_value_give_err("diff", (long)value, NULL, OPT_LOCAL); curbuf->b_ro_locked--; curwin = old_curwin; curbuf = curwin->w_buffer; @@ -1413,7 +1433,7 @@ void diff_win_options(win_T *wp, int addbuf) if (wp->w_p_diff_saved) { free_string_option(wp->w_p_fdm_save); } - wp->w_p_fdm_save = vim_strsave(wp->w_p_fdm); + wp->w_p_fdm_save = xstrdup(wp->w_p_fdm); } set_string_option_direct_in_win(wp, "fdm", -1, "diff", OPT_LOCAL | OPT_FREE, 0); @@ -1424,12 +1444,12 @@ void diff_win_options(win_T *wp, int addbuf) if (wp->w_p_diff_saved) { free_string_option(wp->w_p_fdc_save); } - wp->w_p_fdc_save = vim_strsave(wp->w_p_fdc); + wp->w_p_fdc_save = xstrdup(wp->w_p_fdc); } free_string_option(wp->w_p_fdc); - wp->w_p_fdc = (char_u *)xstrdup("2"); + wp->w_p_fdc = xstrdup("2"); assert(diff_foldcolumn >= 0 && diff_foldcolumn <= 9); - snprintf((char *)wp->w_p_fdc, STRLEN(wp->w_p_fdc) + 1, "%d", diff_foldcolumn); + snprintf(wp->w_p_fdc, strlen(wp->w_p_fdc) + 1, "%d", diff_foldcolumn); wp->w_p_fen = true; wp->w_p_fdl = 0; foldUpdateAll(wp); @@ -1448,7 +1468,7 @@ void diff_win_options(win_T *wp, int addbuf) if (addbuf) { diff_buf_add(wp->w_buffer); } - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } /// Set options not to show diffs. For the current window or all windows. @@ -1480,11 +1500,9 @@ void ex_diffoff(exarg_T *eap) } } free_string_option(wp->w_p_fdm); - wp->w_p_fdm = vim_strsave(*wp->w_p_fdm_save - ? wp->w_p_fdm_save - : (char_u *)"manual"); + wp->w_p_fdm = xstrdup(*wp->w_p_fdm_save ? wp->w_p_fdm_save : "manual"); free_string_option(wp->w_p_fdc); - wp->w_p_fdc = vim_strsave(wp->w_p_fdc_save); + wp->w_p_fdc = xstrdup(*wp->w_p_fdc_save ? wp->w_p_fdc_save : "0"); if (wp->w_p_fdl == 0) { wp->w_p_fdl = wp->w_p_fdl_save; @@ -1528,210 +1546,219 @@ void ex_diffoff(exarg_T *eap) } } -/// Read the diff output and add each entry to the diff list. -/// -/// @param idx_orig idx of original file -/// @param idx_new idx of new file -/// @dout diff output -static void diff_read(int idx_orig, int idx_new, diffio_T *dio) +static bool extract_hunk_internal(diffout_T *dout, diffhunk_T *hunk, int *line_idx) { - FILE *fd = NULL; - int line_idx = 0; - diff_T *dprev = NULL; - diff_T *dp = curtab->tp_first_diff; - diff_T *dn, *dpl; - diffout_T *dout = &dio->dio_diff; - char_u linebuf[LBUFLEN]; // only need to hold the diff line - char_u *line; - linenr_T off; - int i; - int notset = true; // block "*dp" not set yet - diffhunk_T *hunk = NULL; // init to avoid gcc warning - enum { - DIFF_ED, - DIFF_UNIFIED, - DIFF_NONE, - } diffstyle = DIFF_NONE; - - if (dout->dout_fname == NULL) { - diffstyle = DIFF_UNIFIED; - } else { - fd = os_fopen((char *)dout->dout_fname, "r"); - if (fd == NULL) { - emsg(_("E98: Cannot read diff output")); - return; - } - } - - if (!dio->dio_internal) { - hunk = xmalloc(sizeof(*hunk)); + bool eof = *line_idx >= dout->dout_ga.ga_len; + if (!eof) { + *hunk = ((diffhunk_T *)dout->dout_ga.ga_data)[(*line_idx)++]; } + return eof; +} +// Extract hunk by parsing the diff output from file and calculate the diffstyle. +static bool extract_hunk(FILE *fd, diffhunk_T *hunk, diffstyle_T *diffstyle) +{ for (;;) { - if (dio->dio_internal) { - if (line_idx >= dout->dout_ga.ga_len) { - break; // did last line - } - hunk = ((diffhunk_T **)dout->dout_ga.ga_data)[line_idx++]; - } else { - if (fd == NULL) { - if (line_idx >= dout->dout_ga.ga_len) { - break; // did last line - } - line = ((char_u **)dout->dout_ga.ga_data)[line_idx++]; + char line[LBUFLEN]; // only need to hold the diff line + if (vim_fgets(line, LBUFLEN, fd)) { + return true; // end of file + } + + if (*diffstyle == DIFF_NONE) { + // Determine diff style. + // ed like diff looks like this: + // {first}[,{last}]c{first}[,{last}] + // {first}a{first}[,{last}] + // {first}[,{last}]d{first} + // + // unified diff looks like this: + // --- file1 2018-03-20 13:23:35.783153140 +0100 + // +++ file2 2018-03-20 13:23:41.183156066 +0100 + // @@ -1,3 +1,5 @@ + if (isdigit((uint8_t)(*line))) { + *diffstyle = DIFF_ED; + } else if ((strncmp(line, "@@ ", 3) == 0)) { + *diffstyle = DIFF_UNIFIED; + } else if ((strncmp(line, "--- ", 4) == 0) // -V501 + && (vim_fgets(line, LBUFLEN, fd) == 0) // -V501 + && (strncmp(line, "+++ ", 4) == 0) + && (vim_fgets(line, LBUFLEN, fd) == 0) // -V501 + && (strncmp(line, "@@ ", 3) == 0)) { + *diffstyle = DIFF_UNIFIED; } else { - if (vim_fgets(linebuf, LBUFLEN, fd)) { - break; // end of file - } - line = linebuf; - } - - if (diffstyle == DIFF_NONE) { - // Determine diff style. - // ed like diff looks like this: - // {first}[,{last}]c{first}[,{last}] - // {first}a{first}[,{last}] - // {first}[,{last}]d{first} - // - // unified diff looks like this: - // --- file1 2018-03-20 13:23:35.783153140 +0100 - // +++ file2 2018-03-20 13:23:41.183156066 +0100 - // @@ -1,3 +1,5 @@ - if (isdigit(*line)) { - diffstyle = DIFF_ED; - } else if ((STRNCMP(line, "@@ ", 3) == 0)) { - diffstyle = DIFF_UNIFIED; - } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501 - && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 - && (STRNCMP(line, "+++ ", 4) == 0) - && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 - && (STRNCMP(line, "@@ ", 3) == 0)) { - diffstyle = DIFF_UNIFIED; - } else { - // Format not recognized yet, skip over this line. Cygwin diff - // may put a warning at the start of the file. - continue; - } + // Format not recognized yet, skip over this line. Cygwin diff + // may put a warning at the start of the file. + continue; } + } - if (diffstyle == DIFF_ED) { - if (!isdigit(*line)) { - continue; // not the start of a diff block - } - if (parse_diff_ed(line, hunk) == FAIL) { - continue; - } - } else { - assert(diffstyle == DIFF_UNIFIED); - if (STRNCMP(line, "@@ ", 3) != 0) { - continue; // not the start of a diff block - } - if (parse_diff_unified(line, hunk) == FAIL) { - continue; - } + if (*diffstyle == DIFF_ED) { + if (!isdigit((uint8_t)(*line))) { + continue; // not the start of a diff block + } + if (parse_diff_ed(line, hunk) == FAIL) { + continue; + } + } else { + assert(*diffstyle == DIFF_UNIFIED); + if (strncmp(line, "@@ ", 3) != 0) { + continue; // not the start of a diff block + } + if (parse_diff_unified(line, hunk) == FAIL) { + continue; } } - // Go over blocks before the change, for which orig and new are equal. - // Copy blocks from orig to new. - while (dp != NULL - && hunk->lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) { - if (notset) { - diff_copy_entry(dprev, dp, idx_orig, idx_new); - } - dprev = dp; - dp = dp->df_next; - notset = true; + // Successfully parsed diff output, can return + return false; + } +} + +static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_new, diffhunk_T *hunk, + bool *notsetp) +{ + diff_T *dp = *dpp; + diff_T *dprev = *dprevp; + + // Go over blocks before the change, for which orig and new are equal. + // Copy blocks from orig to new. + while (dp != NULL + && hunk->lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) { + if (*notsetp) { + diff_copy_entry(dprev, dp, idx_orig, idx_new); } + dprev = dp; + dp = dp->df_next; + *notsetp = true; + } - if ((dp != NULL) - && (hunk->lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) - && (hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig])) { - // New block overlaps with existing block(s). - // First find last block that overlaps. - for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next) { - if (hunk->lnum_orig + hunk->count_orig < dpl->df_next->df_lnum[idx_orig]) { - break; - } + if ((dp != NULL) + && (hunk->lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) + && (hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig])) { + // New block overlaps with existing block(s). + // First find last block that overlaps. + diff_T *dpl; + for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next) { + if (hunk->lnum_orig + hunk->count_orig < dpl->df_next->df_lnum[idx_orig]) { + break; } + } - // If the newly found block starts before the old one, set the - // start back a number of lines. - off = dp->df_lnum[idx_orig] - hunk->lnum_orig; + // If the newly found block starts before the old one, set the + // start back a number of lines. + linenr_T off = dp->df_lnum[idx_orig] - hunk->lnum_orig; - if (off > 0) { - for (i = idx_orig; i < idx_new; i++) { - if (curtab->tp_diffbuf[i] != NULL) { - dp->df_lnum[i] -= off; - } - } - dp->df_lnum[idx_new] = hunk->lnum_new; - dp->df_count[idx_new] = (linenr_T)hunk->count_new; - } else if (notset) { - // new block inside existing one, adjust new block - dp->df_lnum[idx_new] = hunk->lnum_new + off; - dp->df_count[idx_new] = (linenr_T)hunk->count_new - off; - } else { - // second overlap of new block with existing block - dp->df_count[idx_new] += (linenr_T)hunk->count_new - (linenr_T)hunk->count_orig - + dpl->df_lnum[idx_orig] + - dpl->df_count[idx_orig] - - (dp->df_lnum[idx_orig] + - dp->df_count[idx_orig]); - } - - // Adjust the size of the block to include all the lines to the - // end of the existing block or the new diff, whatever ends last. - off = (hunk->lnum_orig + (linenr_T)hunk->count_orig) - - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]); - - if (off < 0) { - // new change ends in existing block, adjust the end if not - // done already - if (notset) { - dp->df_count[idx_new] += -off; + if (off > 0) { + for (int i = idx_orig; i < idx_new; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + dp->df_lnum[i] -= off; } - off = 0; } + dp->df_lnum[idx_new] = hunk->lnum_new; + dp->df_count[idx_new] = (linenr_T)hunk->count_new; + } else if (*notsetp) { + // new block inside existing one, adjust new block + dp->df_lnum[idx_new] = hunk->lnum_new + off; + dp->df_count[idx_new] = (linenr_T)hunk->count_new - off; + } else { + // second overlap of new block with existing block + dp->df_count[idx_new] += (linenr_T)hunk->count_new - (linenr_T)hunk->count_orig + + dpl->df_lnum[idx_orig] + + dpl->df_count[idx_orig] + - (dp->df_lnum[idx_orig] + + dp->df_count[idx_orig]); + } + + // Adjust the size of the block to include all the lines to the + // end of the existing block or the new diff, whatever ends last. + off = (hunk->lnum_orig + (linenr_T)hunk->count_orig) + - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]); + + if (off < 0) { + // new change ends in existing block, adjust the end if not + // done already + if (*notsetp) { + dp->df_count[idx_new] += -off; + } + off = 0; + } - for (i = idx_orig; i < idx_new; i++) { - if (curtab->tp_diffbuf[i] != NULL) { - dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i] - - dp->df_lnum[i] + off; - } + for (int i = idx_orig; i < idx_new; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i] + - dp->df_lnum[i] + off; } + } - // Delete the diff blocks that have been merged into one. - dn = dp->df_next; - dp->df_next = dpl->df_next; + // Delete the diff blocks that have been merged into one. + diff_T *dn = dp->df_next; + dp->df_next = dpl->df_next; - while (dn != dp->df_next) { - dpl = dn->df_next; - xfree(dn); - dn = dpl; + while (dn != dp->df_next) { + dpl = dn->df_next; + xfree(dn); + dn = dpl; + } + } else { + // Allocate a new diffblock. + dp = diff_alloc_new(curtab, dprev, dp); + + dp->df_lnum[idx_orig] = hunk->lnum_orig; + dp->df_count[idx_orig] = (linenr_T)hunk->count_orig; + dp->df_lnum[idx_new] = hunk->lnum_new; + dp->df_count[idx_new] = (linenr_T)hunk->count_new; + + // Set values for other buffers, these must be equal to the + // original buffer, otherwise there would have been a change + // already. + for (int i = idx_orig + 1; i < idx_new; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + diff_copy_entry(dprev, dp, idx_orig, i); } - } else { - // Allocate a new diffblock. - dp = diff_alloc_new(curtab, dprev, dp); + } + } + *notsetp = false; // "*dp" has been set + *dpp = dp; + *dprevp = dprev; +} - dp->df_lnum[idx_orig] = hunk->lnum_orig; - dp->df_count[idx_orig] = (linenr_T)hunk->count_orig; - dp->df_lnum[idx_new] = hunk->lnum_new; - dp->df_count[idx_new] = (linenr_T)hunk->count_new; +/// Read the diff output and add each entry to the diff list. +/// +/// @param idx_orig idx of original file +/// @param idx_new idx of new file +/// @dout diff output +static void diff_read(int idx_orig, int idx_new, diffio_T *dio) +{ + FILE *fd = NULL; + int line_idx = 0; + diff_T *dprev = NULL; + diff_T *dp = curtab->tp_first_diff; + diffout_T *dout = &dio->dio_diff; + bool notset = true; // block "*dp" not set yet + diffstyle_T diffstyle = DIFF_NONE; - // Set values for other buffers, these must be equal to the - // original buffer, otherwise there would have been a change - // already. - for (i = idx_orig + 1; i < idx_new; i++) { - if (curtab->tp_diffbuf[i] != NULL) { - diff_copy_entry(dprev, dp, idx_orig, i); - } - } + if (!dio->dio_internal) { + fd = os_fopen(dout->dout_fname, "r"); + if (fd == NULL) { + emsg(_("E98: Cannot read diff output")); + return; + } + } + + for (;;) { + diffhunk_T hunk = { 0 }; + bool eof = dio->dio_internal + ? extract_hunk_internal(dout, &hunk, &line_idx) + : extract_hunk(fd, &hunk, &diffstyle); + + if (eof) { + break; } - notset = false; // "*dp" has been set + + process_hunk(&dp, &dprev, idx_orig, idx_new, &hunk, ¬set); } -// for remaining diff blocks orig and new are equal + // for remaining diff blocks orig and new are equal while (dp != NULL) { if (notset) { diff_copy_entry(dprev, dp, idx_orig, idx_new); @@ -1741,10 +1768,6 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio) notset = true; } - if (!dio->dio_internal) { - xfree(hunk); - } - if (fd != NULL) { fclose(fd); } @@ -1784,6 +1807,293 @@ void diff_clear(tabpage_T *tp) tp->tp_first_diff = NULL; } +/// +/// return true if the options are set to use diff linematch +/// +bool diff_linematch(diff_T *dp) +{ + if (!(diff_flags & DIFF_LINEMATCH)) { + return false; + } + // are there more than three diff buffers? + int tsize = 0; + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + // for the rare case (bug?) that the count of a diff block is negative, do + // not run the algorithm because this will try to allocate a negative + // amount of space and crash + if (dp->df_count[i] < 0) { + return false; + } + tsize += dp->df_count[i]; + } + } + // avoid allocating a huge array because it will lag + return tsize <= linematch_lines; +} + +static int get_max_diff_length(const diff_T *dp) +{ + int maxlength = 0; + for (int k = 0; k < DB_COUNT; k++) { + if (curtab->tp_diffbuf[k] != NULL) { + if (dp->df_count[k] > maxlength) { + maxlength = dp->df_count[k]; + } + } + } + return maxlength; +} + +static void find_top_diff_block(diff_T **thistopdiff, diff_T **nextblockblock, int fromidx, + int topline) +{ + diff_T *topdiff = NULL; + diff_T *localtopdiff = NULL; + int topdiffchange = 0; + + for (topdiff = curtab->tp_first_diff; topdiff != NULL; topdiff = topdiff->df_next) { + // set the top of the current overlapping diff block set as we + // iterate through all of the sets of overlapping diff blocks + if (!localtopdiff || topdiffchange) { + localtopdiff = topdiff; + topdiffchange = 0; + } + + // check if the fromwin topline is matched by the current diff. if so, set it to the top of the diff block + if (topline >= topdiff->df_lnum[fromidx] && topline <= + (topdiff->df_lnum[fromidx] + topdiff->df_count[fromidx])) { + // this line is inside the current diff block, so we will save the + // top block of the set of blocks to refer to later + if ((*thistopdiff) == NULL) { + (*thistopdiff) = localtopdiff; + } + } + + // check if the next set of overlapping diff blocks is next + if (!(topdiff->df_next && (topdiff->df_next->df_lnum[fromidx] == + (topdiff->df_lnum[fromidx] + topdiff->df_count[fromidx])))) { + // mark that the next diff block is belongs to a different set of + // overlapping diff blocks + topdiffchange = 1; + + // if we already have found that the line number is inside a diff block, + // set the marker of the next block and finish the iteration + if (*thistopdiff) { + (*nextblockblock) = topdiff->df_next; + break; + } + } + } +} + +static void count_filler_lines_and_topline(int *curlinenum_to, int *linesfiller, + const diff_T *thistopdiff, const int toidx, + int virtual_lines_passed) +{ + const diff_T *curdif = thistopdiff; + int ch_virtual_lines = 0; + int isfiller = 0; + while (virtual_lines_passed > 0) { + if (ch_virtual_lines) { + virtual_lines_passed--; + ch_virtual_lines--; + if (!isfiller) { + (*curlinenum_to)++; + } else { + (*linesfiller)++; + } + } else { + (*linesfiller) = 0; + ch_virtual_lines = get_max_diff_length(curdif); + isfiller = (curdif->df_count[toidx] ? 0 : 1); + if (isfiller) { + while (curdif && curdif->df_next && curdif->df_lnum[toidx] == + curdif->df_next->df_lnum[toidx] + && curdif->df_next->df_count[toidx] == 0) { + curdif = curdif->df_next; + ch_virtual_lines += get_max_diff_length(curdif); + } + } + if (curdif) { + curdif = curdif->df_next; + } + } + } +} + +static void calculate_topfill_and_topline(const int fromidx, const int toidx, const + int from_topline, const int from_topfill, int *topfill, + linenr_T *topline) +{ + // 1. find the position from the top of the diff block, and the start + // of the next diff block + diff_T *thistopdiff = NULL; + diff_T *nextblockblock = NULL; + int virtual_lines_passed = 0; + + find_top_diff_block(&thistopdiff, &nextblockblock, fromidx, from_topline); + + // count the virtual lines that have been passed + + diff_T *curdif = thistopdiff; + while (curdif && (curdif->df_lnum[fromidx] + curdif->df_count[fromidx]) + <= from_topline) { + virtual_lines_passed += get_max_diff_length(curdif); + + curdif = curdif->df_next; + } + + if (curdif != nextblockblock) { + virtual_lines_passed += from_topline - curdif->df_lnum[fromidx]; + } + virtual_lines_passed -= from_topfill; + + // count the same amount of virtual lines in the toidx buffer + int curlinenum_to = thistopdiff->df_lnum[toidx]; + int linesfiller = 0; + count_filler_lines_and_topline(&curlinenum_to, &linesfiller, + thistopdiff, toidx, virtual_lines_passed); + + // count the number of filler lines that would normally be above this line + int maxfiller = 0; + for (diff_T *dpfillertest = thistopdiff; dpfillertest != NULL; + dpfillertest = dpfillertest->df_next) { + if (dpfillertest->df_lnum[toidx] == curlinenum_to) { + while (dpfillertest && dpfillertest->df_lnum[toidx] == curlinenum_to) { + maxfiller += dpfillertest->df_count[toidx] ? 0 : get_max_diff_length(dpfillertest); + dpfillertest = dpfillertest->df_next; + } + break; + } + } + (*topfill) = maxfiller - linesfiller; + (*topline) = curlinenum_to; +} + +static int linematched_filler_lines(diff_T *dp, int idx, linenr_T lnum, int *linestatus) +{ + int filler_lines_d1 = 0; + while (dp && dp->df_next + && lnum == (dp->df_lnum[idx] + dp->df_count[idx]) + && dp->df_next->df_lnum[idx] == lnum) { + if (dp->df_count[idx] == 0) { + filler_lines_d1 += get_max_diff_length(dp); + } + dp = dp->df_next; + } + + if (dp->df_count[idx] == 0) { + filler_lines_d1 += get_max_diff_length(dp); + } + + if (lnum < dp->df_lnum[idx] + dp->df_count[idx]) { + int j = 0; + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + if (dp->df_count[i]) { + j++; + } + } + // is this an added line or a changed line? + if (linestatus) { + (*linestatus) = (j == 1) ? -2 : -1; + } + } + } + return filler_lines_d1; +} + +// Apply results from the linematch algorithm and apply to 'dp' by splitting it into multiple +// adjacent diff blocks. +static void apply_linematch_results(diff_T *dp, size_t decisions_length, const int *decisions) +{ + // get the start line number here in each diff buffer, and then increment + int line_numbers[DB_COUNT]; + int outputmap[DB_COUNT]; + size_t ndiffs = 0; + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + line_numbers[i] = dp->df_lnum[i]; + dp->df_count[i] = 0; + + // Keep track of the index of the diff buffer we are using here. + // We will use this to write the output of the algorithm to + // diff_T structs at the correct indexes + outputmap[ndiffs] = i; + ndiffs++; + } + } + + // write the diffs starting with the current diff block + diff_T *dp_s = dp; + for (size_t i = 0; i < decisions_length; i++) { + // Don't allocate on first iter since we can reuse the initial diffblock + if (i != 0 && (decisions[i - 1] != decisions[i])) { + // create new sub diff blocks to segment the original diff block which we + // further divided by running the linematch algorithm + dp_s = diff_alloc_new(curtab, dp_s, dp_s->df_next); + dp_s->is_linematched = true; + for (int j = 0; j < DB_COUNT; j++) { + if (curtab->tp_diffbuf[j] != NULL) { + dp_s->df_lnum[j] = line_numbers[j]; + dp_s->df_count[j] = 0; + } + } + } + for (size_t j = 0; j < ndiffs; j++) { + if (decisions[i] & (1 << j)) { + // will need to use the map here + dp_s->df_count[outputmap[j]]++; + line_numbers[outputmap[j]]++; + } + } + } + dp->is_linematched = true; +} + +static void run_linematch_algorithm(diff_T *dp) +{ + // define buffers for diff algorithm + mmfile_t diffbufs_mm[DB_COUNT]; + const char *diffbufs[DB_COUNT]; + int diff_length[DB_COUNT]; + size_t ndiffs = 0; + for (int i = 0; i < DB_COUNT; i++) { + if (curtab->tp_diffbuf[i] != NULL) { + // write the contents of the entire buffer to + // diffbufs_mm[diffbuffers_count] + diff_write_buffer(curtab->tp_diffbuf[i], &diffbufs_mm[ndiffs], + dp->df_lnum[i], dp->df_lnum[i] + dp->df_count[i] - 1); + + // we want to get the char* to the diff buffer that was just written + // we add it to the array of char*, diffbufs + diffbufs[ndiffs] = diffbufs_mm[ndiffs].ptr; + + // keep track of the length of this diff block to pass it to the linematch + // algorithm + diff_length[ndiffs] = dp->df_count[i]; + + // increment the amount of diff buffers we are passing to the algorithm + ndiffs++; + } + } + + // we will get the output of the linematch algorithm in the format of an array + // of integers (*decisions) and the length of that array (decisions_length) + int *decisions = NULL; + const bool iwhite = (diff_flags & (DIFF_IWHITEALL | DIFF_IWHITE)) > 0; + size_t decisions_length = linematch_nbuffers(diffbufs, diff_length, ndiffs, &decisions, iwhite); + + for (size_t i = 0; i < ndiffs; i++) { + XFREE_CLEAR(diffbufs_mm[i].ptr); + } + + apply_linematch_results(dp, decisions_length, decisions); + + xfree(decisions); +} + /// Check diff status for line "lnum" in buffer "buf": /// /// Returns 0 for nothing special @@ -1795,11 +2105,11 @@ void diff_clear(tabpage_T *tp) /// /// @param wp /// @param lnum +/// @param[out] linestatus /// /// @return diff status. -int diff_check(win_T *wp, linenr_T lnum) +int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus) { - diff_T *dp; buf_T *buf = wp->w_buffer; if (curtab->tp_diff_invalid) { @@ -1830,6 +2140,7 @@ int diff_check(win_T *wp, linenr_T lnum) } // search for a change that includes "lnum" in the list of diffblocks. + diff_T *dp; for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) { break; @@ -1840,6 +2151,14 @@ int diff_check(win_T *wp, linenr_T lnum) return 0; } + if (!dp->is_linematched && diff_linematch(dp)) { + run_linematch_algorithm(dp); + } + + if (dp->is_linematched) { + return linematched_filler_lines(dp, idx, lnum, linestatus); + } + if (lnum < dp->df_lnum[idx] + dp->df_count[idx]) { int zero = false; @@ -1894,15 +2213,16 @@ int diff_check(win_T *wp, linenr_T lnum) // Insert filler lines above the line just below the change. Will return // 0 when this buf had the max count. - linenr_T maxcount = 0; - for (int i = 0; i < DB_COUNT; i++) { - if ((curtab->tp_diffbuf[i] != NULL) && (dp->df_count[i] > maxcount)) { - maxcount = dp->df_count[i]; - } - } + int maxcount = get_max_diff_length(dp); return maxcount - dp->df_count[idx]; } +/// See diff_check_with_linestatus +int diff_check(win_T *wp, linenr_T lnum) +{ + return diff_check_with_linestatus(wp, lnum, NULL); +} + /// Compare two entries in diff "dp" and return true if they are equal. /// /// @param dp diff @@ -1922,8 +2242,8 @@ static bool diff_equal_entry(diff_T *dp, int idx1, int idx2) } for (int i = 0; i < dp->df_count[idx1]; i++) { - char_u *line = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx1], - dp->df_lnum[idx1] + i, false)); + char *line = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx1], + dp->df_lnum[idx1] + i, false)); int cmp = diff_cmp(line, ml_get_buf(curtab->tp_diffbuf[idx2], dp->df_lnum[idx2] + i, false)); @@ -1938,24 +2258,24 @@ static bool diff_equal_entry(diff_T *dp, int idx1, int idx2) // Compare the characters at "p1" and "p2". If they are equal (possibly // ignoring case) return true and set "len" to the number of bytes. -static bool diff_equal_char(const char_u *const p1, const char_u *const p2, int *const len) +static bool diff_equal_char(const char *const p1, const char *const p2, int *const len) { - const int l = utfc_ptr2len((char *)p1); + const int l = utfc_ptr2len(p1); - if (l != utfc_ptr2len((char *)p2)) { + if (l != utfc_ptr2len(p2)) { return false; } if (l > 1) { - if (STRNCMP(p1, p2, l) != 0 + if (strncmp(p1, p2, (size_t)l) != 0 && (!(diff_flags & DIFF_ICASE) - || utf_fold(utf_ptr2char((char *)p1)) != utf_fold(utf_ptr2char((char *)p2)))) { + || utf_fold(utf_ptr2char(p1)) != utf_fold(utf_ptr2char(p2)))) { return false; } *len = l; } else { if ((*p1 != *p2) && (!(diff_flags & DIFF_ICASE) - || TOLOWER_LOC(*p1) != TOLOWER_LOC(*p2))) { + || TOLOWER_LOC((uint8_t)(*p1)) != TOLOWER_LOC((uint8_t)(*p2)))) { return false; } *len = 1; @@ -1970,23 +2290,23 @@ static bool diff_equal_char(const char_u *const p1, const char_u *const p2, int /// @param s2 The second string /// /// @return on-zero if the two strings are different. -static int diff_cmp(char_u *s1, char_u *s2) +static int diff_cmp(char *s1, char *s2) { if ((diff_flags & DIFF_IBLANK) - && (*(char_u *)skipwhite((char *)s1) == NUL || *skipwhite((char *)s2) == NUL)) { + && (*skipwhite(s1) == NUL || *skipwhite(s2) == NUL)) { return 0; } if ((diff_flags & (DIFF_ICASE | ALL_WHITE_DIFF)) == 0) { - return STRCMP(s1, s2); + return strcmp(s1, s2); } if ((diff_flags & DIFF_ICASE) && !(diff_flags & ALL_WHITE_DIFF)) { - return mb_stricmp((const char *)s1, (const char *)s2); + return mb_stricmp(s1, s2); } - char *p1 = (char *)s1; - char *p2 = (char *)s2; + char *p1 = s1; + char *p2 = s2; // Ignore white space changes and possibly ignore case. while (*p1 != NUL && *p2 != NUL) { @@ -1998,7 +2318,7 @@ static int diff_cmp(char_u *s1, char_u *s2) p2 = skipwhite(p2); } else { int l; - if (!diff_equal_char((char_u *)p1, (char_u *)p2, &l)) { + if (!diff_equal_char(p1, p2, &l)) { break; } p1 += l; @@ -2025,7 +2345,6 @@ void diff_set_topline(win_T *fromwin, win_T *towin) { buf_T *frombuf = fromwin->w_buffer; linenr_T lnum = fromwin->w_topline; - diff_T *dp; int fromidx = diff_buf_idx(frombuf); if (fromidx == DB_COUNT) { @@ -2040,6 +2359,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin) towin->w_topfill = 0; // search for a change that includes "lnum" in the list of diffblocks. + diff_T *dp; for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { if (lnum <= dp->df_lnum[fromidx] + dp->df_count[fromidx]) { break; @@ -2062,46 +2382,51 @@ void diff_set_topline(win_T *fromwin, win_T *towin) towin->w_topline = lnum + (dp->df_lnum[toidx] - dp->df_lnum[fromidx]); if (lnum >= dp->df_lnum[fromidx]) { - // Inside a change: compute filler lines. With three or more - // buffers we need to know the largest count. - linenr_T max_count = 0; - - for (int i = 0; i < DB_COUNT; i++) { - if ((curtab->tp_diffbuf[i] != NULL) && (max_count < dp->df_count[i])) { - max_count = dp->df_count[i]; - } - } + if (diff_flags & DIFF_LINEMATCH) { + calculate_topfill_and_topline(fromidx, toidx, fromwin->w_topline, + fromwin->w_topfill, &towin->w_topfill, &towin->w_topline); + } else { + // Inside a change: compute filler lines. With three or more + // buffers we need to know the largest count. + linenr_T max_count = 0; - if (dp->df_count[toidx] == dp->df_count[fromidx]) { - // same number of lines: use same filler count - towin->w_topfill = fromwin->w_topfill; - } else if (dp->df_count[toidx] > dp->df_count[fromidx]) { - if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) { - // more lines in towin and fromwin doesn't show diff - // lines, only filler lines - if (max_count - fromwin->w_topfill >= dp->df_count[toidx]) { - // towin also only shows filler lines - towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; - towin->w_topfill = fromwin->w_topfill; - } else { - // towin still has some diff lines to show - towin->w_topline = dp->df_lnum[toidx] - + max_count - fromwin->w_topfill; + for (int i = 0; i < DB_COUNT; i++) { + if ((curtab->tp_diffbuf[i] != NULL) && (max_count < dp->df_count[i])) { + max_count = dp->df_count[i]; } } - } else if (towin->w_topline >= dp->df_lnum[toidx] - + dp->df_count[toidx]) { - // less lines in towin and no diff lines to show: compute - // filler lines - towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; - if (diff_flags & DIFF_FILLER) { + if (dp->df_count[toidx] == dp->df_count[fromidx]) { + // same number of lines: use same filler count + towin->w_topfill = fromwin->w_topfill; + } else if (dp->df_count[toidx] > dp->df_count[fromidx]) { if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) { - // fromwin is also out of diff lines - towin->w_topfill = fromwin->w_topfill; - } else { - // fromwin has some diff lines - towin->w_topfill = dp->df_lnum[fromidx] + max_count - lnum; + // more lines in towin and fromwin doesn't show diff + // lines, only filler lines + if (max_count - fromwin->w_topfill >= dp->df_count[toidx]) { + // towin also only shows filler lines + towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; + towin->w_topfill = fromwin->w_topfill; + } else { + // towin still has some diff lines to show + towin->w_topline = dp->df_lnum[toidx] + + max_count - fromwin->w_topfill; + } + } + } else if (towin->w_topline >= dp->df_lnum[toidx] + + dp->df_count[toidx]) { + // less lines in towin and no diff lines to show: compute + // filler lines + towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; + + if (diff_flags & DIFF_FILLER) { + if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) { + // fromwin is also out of diff lines + towin->w_topfill = fromwin->w_topfill; + } else { + // fromwin has some diff lines + towin->w_topfill = dp->df_lnum[fromidx] + max_count - lnum; + } } } } @@ -2136,75 +2461,80 @@ void diff_set_topline(win_T *fromwin, win_T *towin) int diffopt_changed(void) { int diff_context_new = 6; + int linematch_lines_new = 0; int diff_flags_new = 0; int diff_foldcolumn_new = 2; long diff_algorithm_new = 0; long diff_indent_heuristic = 0; - char *p = (char *)p_dip; + char *p = p_dip; while (*p != NUL) { - if (STRNCMP(p, "filler", 6) == 0) { + if (strncmp(p, "filler", 6) == 0) { p += 6; diff_flags_new |= DIFF_FILLER; - } else if ((STRNCMP(p, "context:", 8) == 0) && ascii_isdigit(p[8])) { + } else if ((strncmp(p, "context:", 8) == 0) && ascii_isdigit(p[8])) { p += 8; diff_context_new = getdigits_int(&p, false, diff_context_new); - } else if (STRNCMP(p, "iblank", 6) == 0) { + } else if (strncmp(p, "iblank", 6) == 0) { p += 6; diff_flags_new |= DIFF_IBLANK; - } else if (STRNCMP(p, "icase", 5) == 0) { + } else if (strncmp(p, "icase", 5) == 0) { p += 5; diff_flags_new |= DIFF_ICASE; - } else if (STRNCMP(p, "iwhiteall", 9) == 0) { + } else if (strncmp(p, "iwhiteall", 9) == 0) { p += 9; diff_flags_new |= DIFF_IWHITEALL; - } else if (STRNCMP(p, "iwhiteeol", 9) == 0) { + } else if (strncmp(p, "iwhiteeol", 9) == 0) { p += 9; diff_flags_new |= DIFF_IWHITEEOL; - } else if (STRNCMP(p, "iwhite", 6) == 0) { + } else if (strncmp(p, "iwhite", 6) == 0) { p += 6; diff_flags_new |= DIFF_IWHITE; - } else if (STRNCMP(p, "horizontal", 10) == 0) { + } else if (strncmp(p, "horizontal", 10) == 0) { p += 10; diff_flags_new |= DIFF_HORIZONTAL; - } else if (STRNCMP(p, "vertical", 8) == 0) { + } else if (strncmp(p, "vertical", 8) == 0) { p += 8; diff_flags_new |= DIFF_VERTICAL; - } else if ((STRNCMP(p, "foldcolumn:", 11) == 0) && ascii_isdigit(p[11])) { + } else if ((strncmp(p, "foldcolumn:", 11) == 0) && ascii_isdigit(p[11])) { p += 11; diff_foldcolumn_new = getdigits_int(&p, false, diff_foldcolumn_new); - } else if (STRNCMP(p, "hiddenoff", 9) == 0) { + } else if (strncmp(p, "hiddenoff", 9) == 0) { p += 9; diff_flags_new |= DIFF_HIDDEN_OFF; - } else if (STRNCMP(p, "closeoff", 8) == 0) { + } else if (strncmp(p, "closeoff", 8) == 0) { p += 8; diff_flags_new |= DIFF_CLOSE_OFF; - } else if (STRNCMP(p, "followwrap", 10) == 0) { + } else if (strncmp(p, "followwrap", 10) == 0) { p += 10; diff_flags_new |= DIFF_FOLLOWWRAP; - } else if (STRNCMP(p, "indent-heuristic", 16) == 0) { + } else if (strncmp(p, "indent-heuristic", 16) == 0) { p += 16; diff_indent_heuristic = XDF_INDENT_HEURISTIC; - } else if (STRNCMP(p, "internal", 8) == 0) { + } else if (strncmp(p, "internal", 8) == 0) { p += 8; diff_flags_new |= DIFF_INTERNAL; - } else if (STRNCMP(p, "algorithm:", 10) == 0) { + } else if (strncmp(p, "algorithm:", 10) == 0) { p += 10; - if (STRNCMP(p, "myers", 5) == 0) { + if (strncmp(p, "myers", 5) == 0) { p += 5; diff_algorithm_new = 0; - } else if (STRNCMP(p, "minimal", 7) == 0) { + } else if (strncmp(p, "minimal", 7) == 0) { p += 7; diff_algorithm_new = XDF_NEED_MINIMAL; - } else if (STRNCMP(p, "patience", 8) == 0) { + } else if (strncmp(p, "patience", 8) == 0) { p += 8; diff_algorithm_new = XDF_PATIENCE_DIFF; - } else if (STRNCMP(p, "histogram", 9) == 0) { + } else if (strncmp(p, "histogram", 9) == 0) { p += 9; diff_algorithm_new = XDF_HISTOGRAM_DIFF; } else { return FAIL; } + } else if ((strncmp(p, "linematch:", 10) == 0) && ascii_isdigit(p[10])) { + p += 10; + linematch_lines_new = getdigits_int(&p, false, linematch_lines_new); + diff_flags_new |= DIFF_LINEMATCH; } if ((*p != ',') && (*p != NUL)) { @@ -2233,6 +2563,7 @@ int diffopt_changed(void) diff_flags = diff_flags_new; diff_context = diff_context_new == 0 ? 1 : diff_context_new; + linematch_lines = linematch_lines_new; diff_foldcolumn = diff_foldcolumn_new; diff_algorithm = diff_algorithm_new; @@ -2283,16 +2614,8 @@ bool diffopt_filler(void) bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - char_u *line_new; - int si_org; - int si_new; - int ei_org; - int ei_new; - bool added = true; - int l; - // Make a copy of the line, the next ml_get() will invalidate it. - char_u *line_org = vim_strsave(ml_get_buf(wp->w_buffer, lnum, false)); + char *line_org = xstrdup(ml_get_buf(wp->w_buffer, lnum, false)); int idx = diff_buf_idx(wp->w_buffer); if (idx == DB_COUNT) { @@ -2308,6 +2631,13 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) break; } } + if (dp != NULL && dp->is_linematched) { + while (dp && dp->df_next + && lnum == dp->df_count[idx] + dp->df_lnum[idx] + && dp->df_next->df_lnum[idx] == lnum) { + dp = dp->df_next; + } + } if ((dp == NULL) || (diff_check_sanity(curtab, dp) == FAIL)) { xfree(line_org); @@ -2315,6 +2645,12 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) return false; } + int si_org; + int si_new; + int ei_org; + int ei_new; + bool added = true; + linenr_T off = lnum - dp->df_lnum[idx]; int i; for (i = 0; i < DB_COUNT; i++) { @@ -2324,8 +2660,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) continue; } added = false; - line_new = ml_get_buf(curtab->tp_diffbuf[i], - dp->df_lnum[i] + off, false); + char *line_new = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off, false); // Search for start of difference si_org = si_new = 0; @@ -2337,9 +2672,10 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) || ((diff_flags & DIFF_IWHITEALL) && (ascii_iswhite(line_org[si_org]) || ascii_iswhite(line_new[si_new])))) { - si_org = (int)((char_u *)skipwhite((char *)line_org + si_org) - line_org); - si_new = (int)((char_u *)skipwhite((char *)line_new + si_new) - line_new); + si_org = (int)(skipwhite(line_org + si_org) - line_org); + si_new = (int)(skipwhite(line_new + si_new) - line_new); } else { + int l; if (!diff_equal_char(line_org + si_org, line_new + si_new, &l)) { break; } @@ -2359,8 +2695,8 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) // Search for end of difference, if any. if ((line_org[si_org] != NUL) || (line_new[si_new] != NUL)) { - ei_org = (int)STRLEN(line_org); - ei_new = (int)STRLEN(line_new); + ei_org = (int)strlen(line_org); + ei_new = (int)strlen(line_new); while (ei_org >= *startp && ei_new >= si_new @@ -2380,12 +2716,13 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) ei_new--; } } else { - const char_u *p1 = line_org + ei_org; - const char_u *p2 = line_new + ei_new; + const char *p1 = line_org + ei_org; + const char *p2 = line_new + ei_new; p1 -= utf_head_off(line_org, p1); p2 -= utf_head_off(line_new, p2); + int l; if (!diff_equal_char(p1, p2, &l)) { break; } @@ -2415,16 +2752,14 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) bool diff_infold(win_T *wp, linenr_T lnum) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) { - bool other = false; - // Return if 'diff' isn't set. if (!wp->w_p_diff) { return false; } int idx = -1; - int i; - for (i = 0; i < DB_COUNT; i++) { + bool other = false; + for (int i = 0; i < DB_COUNT; i++) { if (curtab->tp_diffbuf[i] == wp->w_buffer) { idx = i; } else if (curtab->tp_diffbuf[i] != NULL) { @@ -2464,13 +2799,13 @@ bool diff_infold(win_T *wp, linenr_T lnum) /// "dp" and "do" commands. void nv_diffgetput(bool put, size_t count) { - exarg_T ea; - char buf[30]; - if (bt_prompt(curbuf)) { vim_beep(BO_OPER); return; } + + exarg_T ea; + char buf[30]; if (count == 0) { ea.arg = ""; } else { @@ -2506,24 +2841,7 @@ static bool valid_diff(diff_T *diff) /// @param eap void ex_diffgetput(exarg_T *eap) { - linenr_T lnum; - linenr_T count; - linenr_T off = 0; - diff_T *dp; - diff_T *dfree; - int i; - int added; - char_u *p; - aco_save_T aco; - buf_T *buf; - linenr_T start_skip; - linenr_T end_skip; - linenr_T new_count; - int buf_empty; - int found_not_ma = false; int idx_other; - int idx_from; - int idx_to; // Find the current buffer in the list of diff buffers. int idx_cur = diff_buf_idx(curbuf); @@ -2533,6 +2851,7 @@ void ex_diffgetput(exarg_T *eap) } if (*eap->arg == NUL) { + int found_not_ma = false; // No argument: Find the other buffer in the list of diff buffers. for (idx_other = 0; idx_other < DB_COUNT; idx_other++) { if ((curtab->tp_diffbuf[idx_other] != curbuf) @@ -2555,7 +2874,7 @@ void ex_diffgetput(exarg_T *eap) } // Check that there isn't a third buffer in the list - for (i = idx_other + 1; i < DB_COUNT; i++) { + for (int i = idx_other + 1; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != curbuf) && (curtab->tp_diffbuf[i] != NULL) && ((eap->cmdidx != CMD_diffput) @@ -2567,25 +2886,26 @@ void ex_diffgetput(exarg_T *eap) } } else { // Buffer number or pattern given. Ignore trailing white space. - p = (char_u *)eap->arg + STRLEN(eap->arg); - while (p > (char_u *)eap->arg && ascii_iswhite(p[-1])) { + char *p = eap->arg + strlen(eap->arg); + while (p > eap->arg && ascii_iswhite(p[-1])) { p--; } - for (i = 0; ascii_isdigit(eap->arg[i]) && (char_u *)eap->arg + i < p; i++) {} + int i; + for (i = 0; ascii_isdigit(eap->arg[i]) && eap->arg + i < p; i++) {} - if ((char_u *)eap->arg + i == p) { + if (eap->arg + i == p) { // digits only i = (int)atol(eap->arg); } else { - i = buflist_findpat(eap->arg, (char *)p, false, true, false); + i = buflist_findpat(eap->arg, p, false, true, false); if (i < 0) { // error message already given return; } } - buf = buflist_findnr(i); + buf_T *buf = buflist_findnr(i); if (buf == NULL) { semsg(_("E102: Can't find buffer \"%s\""), eap->arg); @@ -2620,20 +2940,19 @@ void ex_diffgetput(exarg_T *eap) } } - if (eap->cmdidx == CMD_diffget) { - idx_from = idx_other; - idx_to = idx_cur; - } else { - idx_from = idx_cur; - idx_to = idx_other; + aco_save_T aco; + if (eap->cmdidx != CMD_diffget) { // Need to make the other buffer the current buffer to be able to make // changes in it. - // set curwin/curbuf to buf and save a few things + // Set curwin/curbuf to buf and save a few things. aucmd_prepbuf(&aco, curtab->tp_diffbuf[idx_other]); } + const int idx_from = eap->cmdidx == CMD_diffget ? idx_other : idx_cur; + const int idx_to = eap->cmdidx == CMD_diffget ? idx_cur : idx_other; + // May give the warning for a changed buffer here, which can trigger the // FileChangedRO autocommand, which may do nasty things and mess // everything up. @@ -2645,26 +2964,83 @@ void ex_diffgetput(exarg_T *eap) } } + diffgetput(eap->addr_count, idx_cur, idx_from, idx_to, eap->line1, eap->line2); + + // restore curwin/curbuf and a few other things + if (eap->cmdidx != CMD_diffget) { + // Syncing undo only works for the current buffer, but we change + // another buffer. Sync undo if the command was typed. This isn't + // 100% right when ":diffput" is used in a function or mapping. + if (KeyTyped) { + u_sync(false); + } + aucmd_restbuf(&aco); + } + +theend: + diff_busy = false; + + if (diff_need_update) { + ex_diffupdate(NULL); + } + + // Check that the cursor is on a valid character and update its + // position. When there were filler lines the topline has become + // invalid. + check_cursor(); + changed_line_abv_curs(); + + if (diff_need_update) { + // redraw already done by ex_diffupdate() + diff_need_update = false; + } else { + // Also need to redraw the other buffers. + diff_redraw(false); + apply_autocmds(EVENT_DIFFUPDATED, NULL, NULL, false, curbuf); + } +} + +/// Apply diffget/diffput to buffers and diffblocks +/// +/// @param idx_cur index of "curbuf" before aucmd_prepbuf() in the list of diff buffers +/// @param idx_from index of the buffer to read from in the list of diff buffers +/// @param idx_to index of the buffer to modify in the list of diff buffers +static void diffgetput(const int addr_count, const int idx_cur, const int idx_from, + const int idx_to, const linenr_T line1, const linenr_T line2) +{ + linenr_T off = 0; diff_T *dprev = NULL; - for (dp = curtab->tp_first_diff; dp != NULL;) { - if (dp->df_lnum[idx_cur] > eap->line2 + off) { + for (diff_T *dp = curtab->tp_first_diff; dp != NULL;) { + if (!addr_count) { + // handle the case with adjacent diff blocks + while (dp->is_linematched + && dp->df_next + && dp->df_next->df_lnum[idx_cur] == dp->df_lnum[idx_cur] + dp->df_count[idx_cur] + && dp->df_next->df_lnum[idx_cur] == line1 + off + 1) { + dprev = dp; + dp = dp->df_next; + } + } + + if (dp->df_lnum[idx_cur] > line2 + off) { // past the range that was specified break; } - dfree = NULL; - lnum = dp->df_lnum[idx_to]; - count = dp->df_count[idx_to]; + diff_T dfree = { 0 }; + bool did_free = false; + linenr_T lnum = dp->df_lnum[idx_to]; + linenr_T count = dp->df_count[idx_to]; - if ((dp->df_lnum[idx_cur] + dp->df_count[idx_cur] > eap->line1 + off) + if ((dp->df_lnum[idx_cur] + dp->df_count[idx_cur] > line1 + off) && (u_save(lnum - 1, lnum + count) != FAIL)) { // Inside the specified range and saving for undo worked. - start_skip = 0; - end_skip = 0; + linenr_T start_skip = 0; + linenr_T end_skip = 0; - if (eap->addr_count > 0) { + if (addr_count > 0) { // A range was specified: check if lines need to be skipped. - start_skip = eap->line1 + off - dp->df_lnum[idx_cur]; + start_skip = line1 + off - dp->df_lnum[idx_cur]; if (start_skip > 0) { // range starts below start of current diff block if (start_skip > count) { @@ -2679,13 +3055,13 @@ void ex_diffgetput(exarg_T *eap) } end_skip = dp->df_lnum[idx_cur] + dp->df_count[idx_cur] - 1 - - (eap->line2 + off); + - (line2 + off); if (end_skip > 0) { // range ends above end of current/from diff block if (idx_cur == idx_from) { // :diffput - i = dp->df_count[idx_cur] - start_skip - end_skip; + int i = dp->df_count[idx_cur] - start_skip - end_skip; if (count > i) { count = i; @@ -2704,10 +3080,10 @@ void ex_diffgetput(exarg_T *eap) } } - buf_empty = buf_is_empty(curbuf); - added = 0; + bool buf_empty = buf_is_empty(curbuf); + int added = 0; - for (i = 0; i < count; i++) { + for (int i = 0; i < count; i++) { // remember deleting the last line of the buffer buf_empty = curbuf->b_ml.ml_line_count == 1; if (ml_delete(lnum, false) == OK) { @@ -2715,13 +3091,13 @@ void ex_diffgetput(exarg_T *eap) } } - for (i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; i++) { + for (int i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; i++) { linenr_T nr = dp->df_lnum[idx_from] + start_skip + i; if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count) { break; } - p = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx_from], nr, false)); - ml_append(lnum + i - 1, (char *)p, 0, false); + char *p = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx_from], nr, false)); + ml_append(lnum + i - 1, p, 0, false); xfree(p); added++; if (buf_empty && (curbuf->b_ml.ml_line_count == 2)) { @@ -2731,12 +3107,13 @@ void ex_diffgetput(exarg_T *eap) ml_delete((linenr_T)2, false); } } - new_count = dp->df_count[idx_to] + added; + linenr_T new_count = dp->df_count[idx_to] + added; dp->df_count[idx_to] = new_count; if ((start_skip == 0) && (end_skip == 0)) { // Check if there are any other buffers and if the diff is // equal in them. + int i; for (i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != NULL) && (i != idx_from) @@ -2748,14 +3125,9 @@ void ex_diffgetput(exarg_T *eap) if (i == DB_COUNT) { // delete the diff entry, the buffers are now equal here - dfree = dp; - dp = dp->df_next; - - if (dprev == NULL) { - curtab->tp_first_diff = dp; - } else { - dprev->df_next = dp; - } + dfree = *dp; + did_free = true; + dp = diff_free(curtab, dprev, dp); } } @@ -2774,10 +3146,9 @@ void ex_diffgetput(exarg_T *eap) } changed_lines(lnum, 0, lnum + count, added, true); - if (dfree != NULL) { + if (did_free) { // Diff is deleted, update folds in other windows. - diff_fold_update(dfree, idx_to); - xfree(dfree); + diff_fold_update(&dfree, idx_to); } // mark_adjust() may have made "dp" invalid. We don't know where @@ -2786,7 +3157,7 @@ void ex_diffgetput(exarg_T *eap) break; } - if (dfree == NULL) { + if (!did_free) { // mark_adjust() may have changed the count in a wrong way dp->df_count[idx_to] = new_count; } @@ -2798,43 +3169,11 @@ void ex_diffgetput(exarg_T *eap) } // If before the range or not deleted, go to next diff. - if (dfree == NULL) { + if (!did_free) { dprev = dp; dp = dp->df_next; } } - - // restore curwin/curbuf and a few other things - if (eap->cmdidx != CMD_diffget) { - // Syncing undo only works for the current buffer, but we change - // another buffer. Sync undo if the command was typed. This isn't - // 100% right when ":diffput" is used in a function or mapping. - if (KeyTyped) { - u_sync(false); - } - aucmd_restbuf(&aco); - } - -theend: - diff_busy = false; - if (diff_need_update) { - ex_diffupdate(NULL); - } - - // Check that the cursor is on a valid character and update its - // position. When there were filler lines the topline has become - // invalid. - check_cursor(); - changed_line_abv_curs(); - - if (diff_need_update) { - // redraw already done by ex_diffupdate() - diff_need_update = false; - } else { - // Also need to redraw the other buffers. - diff_redraw(false); - apply_autocmds(EVENT_DIFFUPDATED, NULL, NULL, false, curbuf); - } } /// Update folds for all diff buffers for entry "dp". @@ -3058,7 +3397,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp) /// Handle an ED style diff line. /// Return FAIL if the line does not contain diff info. /// -static int parse_diff_ed(char_u *line, diffhunk_T *hunk) +static int parse_diff_ed(char *line, diffhunk_T *hunk) { long l1, l2; @@ -3066,7 +3405,7 @@ static int parse_diff_ed(char_u *line, diffhunk_T *hunk) // change: {first}[,{last}]c{first}[,{last}] // append: {first}a{first}[,{last}] // delete: {first}[,{last}]d{first} - char *p = (char *)line; + char *p = line; linenr_T f1 = getdigits_int32(&p, true, 0); if (*p == ',') { p++; @@ -3110,11 +3449,11 @@ static int parse_diff_ed(char_u *line, diffhunk_T *hunk) /// Parses unified diff with zero(!) context lines. /// Return FAIL if there is no diff information in "line". /// -static int parse_diff_unified(char_u *line, diffhunk_T *hunk) +static int parse_diff_unified(char *line, diffhunk_T *hunk) { // Parse unified diff hunk header: // @@ -oldline,oldcount +newline,newcount @@ - char *p = (char *)line; + char *p = line; if (*p++ == '@' && *p++ == '@' && *p++ == ' ' && *p++ == '-') { long oldcount; long newline; @@ -3166,13 +3505,11 @@ static int parse_diff_unified(char_u *line, diffhunk_T *hunk) static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv) { diffout_T *dout = (diffout_T *)priv; - diffhunk_T *p = xmalloc(sizeof(*p)); - - ga_grow(&dout->dout_ga, 1); - p->lnum_orig = (linenr_T)start_a + 1; - p->count_orig = count_a; - p->lnum_new = (linenr_T)start_b + 1; - p->count_new = count_b; - ((diffhunk_T **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p; + GA_APPEND(diffhunk_T, &(dout->dout_ga), ((diffhunk_T){ + .lnum_orig = (linenr_T)start_a + 1, + .count_orig = count_a, + .lnum_new = (linenr_T)start_b + 1, + .count_new = count_b, + })); return 0; } diff --git a/src/nvim/diff.h b/src/nvim/diff.h index 53fc5aa077..1f64465336 100644 --- a/src/nvim/diff.h +++ b/src/nvim/diff.h @@ -1,7 +1,10 @@ #ifndef NVIM_DIFF_H #define NVIM_DIFF_H +#include <stdbool.h> + #include "nvim/ex_cmds_defs.h" +#include "nvim/macros.h" #include "nvim/pos.h" // Value set from 'diffopt'. diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 0f511bd37c..a057978a5e 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -8,24 +8,34 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <string.h> #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/digraph.h" #include "nvim/drawscreen.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" +#include "nvim/keycodes.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/normal.h" +#include "nvim/option_defs.h" #include "nvim/os/input.h" #include "nvim/runtime.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" typedef int result_T; @@ -52,7 +62,6 @@ static garray_T user_digraphs = { 0, 0, (int)sizeof(digr_T), 10, NULL }; /// Note: Characters marked with XX are not included literally, because some /// compilers cannot handle them (Amiga SAS/C is the most picky one). static digr_T digraphdefault[] = - // digraphs for Unicode from RFC1345 // (also work for ISO-8859-1 aka latin1) { @@ -1480,7 +1489,7 @@ int do_digraph(int c) /// Find a digraph for "val". If found return the string to display it. /// If not found return NULL. -char_u *get_digraph_for_char(int val_arg) +char *get_digraph_for_char(int val_arg) { const int val = val_arg; const digr_T *dp; @@ -1497,7 +1506,7 @@ char_u *get_digraph_for_char(int val_arg) r[0] = dp->char1; r[1] = dp->char2; r[2] = NUL; - return r; + return (char *)r; } dp++; } @@ -1519,30 +1528,31 @@ int get_digraph(bool cmdline) no_mapping--; allow_keys--; - if (c != ESC) { - // ESC cancels CTRL-K - if (IS_SPECIAL(c)) { - // insert special key code - return c; - } + if (c == ESC) { // ESC cancels CTRL-K + return NUL; + } - if (cmdline) { - if ((char2cells(c) == 1) && c < 128 && (cmdline_star == 0)) { - putcmdline((char)c, true); - } - } else { - add_to_showcmd(c); - } - no_mapping++; - allow_keys++; - int cc = plain_vgetc(); - no_mapping--; - allow_keys--; - - if (cc != ESC) { - // ESC cancels CTRL-K - return digraph_get(c, cc, true); + if (IS_SPECIAL(c)) { + // insert special key code + return c; + } + + if (cmdline) { + if ((char2cells(c) == 1) && c < 128 && (cmdline_star == 0)) { + putcmdline((char)c, true); } + } else { + add_to_showcmd(c); + } + no_mapping++; + allow_keys++; + int cc = plain_vgetc(); + no_mapping--; + allow_keys--; + + if (cc != ESC) { + // ESC cancels CTRL-K + return digraph_get(c, cc, true); } return NUL; } @@ -1578,7 +1588,7 @@ static int getexactdigraph(int char1, int char2, bool meta_char) if (retval == 0) { dp = digraphdefault; - for (int i = 0; dp->char1 != 0; i++) { + while (dp->char1 != 0) { if (((int)dp->char1 == char1) && ((int)dp->char2 == char2)) { retval = dp->result; break; @@ -1646,8 +1656,8 @@ static void registerdigraph(int char1, int char2, int n) bool check_digraph_chars_valid(int char1, int char2) { if (char2 == 0) { - char_u msg[MB_MAXBYTES + 1]; - msg[utf_char2bytes(char1, (char *)msg)] = NUL; + char msg[MB_MAXBYTES + 1]; + msg[utf_char2bytes(char1, msg)] = NUL; semsg(_(e_digraph_must_be_just_two_characters_str), msg); return false; } @@ -1695,7 +1705,7 @@ static void digraph_header(const char *msg) if (msg_col > 0) { msg_putchar('\n'); } - msg_outtrans_attr((const char_u *)msg, HL_ATTR(HLF_CM)); + msg_outtrans_attr(msg, HL_ATTR(HLF_CM)); msg_putchar('\n'); } @@ -1707,7 +1717,7 @@ void listdigraphs(bool use_headers) const digr_T *dp = digraphdefault; - for (int i = 0; dp->char1 != NUL && !got_int; i++) { + while (dp->char1 != NUL && !got_int) { digr_T tmp; // May need to convert the result to 'encoding'. @@ -1739,16 +1749,16 @@ static void digraph_getlist_appendpair(const digr_T *dp, list_T *l) list_T *l2 = tv_list_alloc(2); tv_list_append_list(l, l2); - char_u buf[30]; - buf[0] = dp->char1; - buf[1] = dp->char2; + char buf[30]; + buf[0] = (char)dp->char1; + buf[1] = (char)dp->char2; buf[2] = NUL; - tv_list_append_string(l2, (char *)buf, -1); + tv_list_append_string(l2, buf, -1); - char_u *p = buf; - p += utf_char2bytes(dp->result, (char *)p); + char *p = buf; + p += utf_char2bytes(dp->result, p); *p = NUL; - tv_list_append_string(l2, (char *)buf, -1); + tv_list_append_string(l2, buf, -1); } void digraph_getlist_common(bool list_all, typval_T *rettv) @@ -1759,7 +1769,7 @@ void digraph_getlist_common(bool list_all, typval_T *rettv) if (list_all) { dp = digraphdefault; - for (int i = 0; dp->char1 != NUL && !got_int; i++) { + while (dp->char1 != NUL && !got_int) { digr_T tmp; tmp.char1 = dp->char1; tmp.char2 = dp->char2; @@ -1814,57 +1824,59 @@ struct dg_header_entry { static void printdigraph(const digr_T *dp, result_T *previous) FUNC_ATTR_NONNULL_ARG(1) { - char_u buf[30]; + char buf[30]; int list_width = 13; - if (dp->result != 0) { - if (previous != NULL) { - for (int i = 0; header_table[i].dg_header != NULL; i++) { - if (*previous < header_table[i].dg_start - && dp->result >= header_table[i].dg_start - && dp->result < header_table[i + 1].dg_start) { - digraph_header(_(header_table[i].dg_header)); - break; - } + if (dp->result == 0) { + return; + } + + if (previous != NULL) { + for (int i = 0; header_table[i].dg_header != NULL; i++) { + if (*previous < header_table[i].dg_start + && dp->result >= header_table[i].dg_start + && dp->result < header_table[i + 1].dg_start) { + digraph_header(_(header_table[i].dg_header)); + break; } - *previous = dp->result; - } - if (msg_col > Columns - list_width) { - msg_putchar('\n'); } + *previous = dp->result; + } + if (msg_col > Columns - list_width) { + msg_putchar('\n'); + } - // Make msg_col a multiple of list_width by using spaces. - if (msg_col % list_width != 0) { - int spaces = (msg_col / list_width + 1) * list_width - msg_col; - while (spaces--) { - msg_putchar(' '); - } + // Make msg_col a multiple of list_width by using spaces. + if (msg_col % list_width != 0) { + int spaces = (msg_col / list_width + 1) * list_width - msg_col; + while (spaces--) { + msg_putchar(' '); } + } - char_u *p = &buf[0]; - *p++ = dp->char1; - *p++ = dp->char2; - *p++ = ' '; - *p = NUL; - msg_outtrans((char *)buf); - p = buf; + char *p = &buf[0]; + *p++ = (char)dp->char1; + *p++ = (char)dp->char2; + *p++ = ' '; + *p = NUL; + msg_outtrans(buf); + p = buf; - // add a space to draw a composing char on - if (utf_iscomposing(dp->result)) { - *p++ = ' '; - } - p += utf_char2bytes(dp->result, (char *)p); + // add a space to draw a composing char on + if (utf_iscomposing(dp->result)) { + *p++ = ' '; + } + p += utf_char2bytes(dp->result, p); - *p = NUL; - msg_outtrans_attr(buf, HL_ATTR(HLF_8)); - p = buf; - if (char2cells(dp->result) == 1) { - *p++ = ' '; - } - assert(p >= buf); - vim_snprintf((char *)p, sizeof(buf) - (size_t)(p - buf), " %3d", dp->result); - msg_outtrans((char *)buf); + *p = NUL; + msg_outtrans_attr(buf, HL_ATTR(HLF_8)); + p = buf; + if (char2cells(dp->result) == 1) { + *p++ = ' '; } + assert(p >= buf); + vim_snprintf(p, sizeof(buf) - (size_t)(p - buf), " %3d", dp->result); + msg_outtrans(buf); } /// Get the two digraph characters from a typval. @@ -1873,7 +1885,7 @@ static int get_digraph_chars(const typval_T *arg, int *char1, int *char2) { char buf_chars[NUMBUFLEN]; const char *chars = tv_get_string_buf_chk(arg, buf_chars); - const char_u *p = (const char_u *)chars; + const char *p = chars; if (p != NULL) { if (*p != NUL) { @@ -1905,7 +1917,7 @@ static bool digraph_set_common(const typval_T *argchars, const typval_T *argdigr if (digraph == NULL) { return false; } - const char_u *p = (const char_u *)digraph; + const char *p = digraph; int n = mb_cptr2char_adv(&p); if (*p != NUL) { semsg(_(e_digraph_argument_must_be_one_character_str), digraph); @@ -1917,7 +1929,7 @@ static bool digraph_set_common(const typval_T *argchars, const typval_T *argdigr } /// "digraph_get()" function -void f_digraph_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_digraph_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; // Return empty string for failure @@ -1926,19 +1938,19 @@ void f_digraph_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (digraphs == NULL) { return; } - if (STRLEN(digraphs) != 2) { + if (strlen(digraphs) != 2) { semsg(_(e_digraph_must_be_just_two_characters_str), digraphs); return; } int code = digraph_get(digraphs[0], digraphs[1], false); - char_u buf[NUMBUFLEN]; - buf[utf_char2bytes(code, (char *)buf)] = NUL; - rettv->vval.v_string = (char *)vim_strsave(buf); + char buf[NUMBUFLEN]; + buf[utf_char2bytes(code, buf)] = NUL; + rettv->vval.v_string = xstrdup(buf); } /// "digraph_getlist()" function -void f_digraph_getlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_digraph_getlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool flag_list_all; @@ -1957,7 +1969,7 @@ void f_digraph_getlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "digraph_set()" function -void f_digraph_set(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_digraph_set(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_BOOL; rettv->vval.v_bool = kBoolVarFalse; @@ -1970,7 +1982,7 @@ void f_digraph_set(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "digraph_setlist()" function -void f_digraph_setlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_digraph_setlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_BOOL; rettv->vval.v_bool = kBoolVarFalse; @@ -2010,8 +2022,8 @@ void f_digraph_setlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// structure used for b_kmap_ga.ga_data typedef struct { - char_u *from; - char_u *to; + char *from; + char *to; } kmap_T; #define KMAP_MAXLEN 20 // maximum length of "from" or "to" @@ -2036,7 +2048,7 @@ char *keymap_init(void) // Source the keymap file. It will contain a ":loadkeymap" command // which will call ex_loadkeymap() below. - buflen = STRLEN(curbuf->b_p_keymap) + STRLEN(p_enc) + 14; + buflen = strlen(curbuf->b_p_keymap) + strlen(p_enc) + 14; buf = xmalloc(buflen); // try finding "keymap/'keymap'_'encoding'.vim" in 'runtimepath' @@ -2064,10 +2076,10 @@ char *keymap_init(void) /// @param eap void ex_loadkeymap(exarg_T *eap) { - char_u *s; + char *s; #define KMAP_LLEN 200 // max length of "to" and "from" together - char_u buf[KMAP_LLEN + 11]; + char buf[KMAP_LLEN + 11]; char *save_cpo = p_cpo; if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { @@ -2092,17 +2104,17 @@ void ex_loadkeymap(exarg_T *eap) break; } - char_u *p = (char_u *)skipwhite(line); + char *p = skipwhite(line); if ((*p != '"') && (*p != NUL)) { kmap_T *kp = GA_APPEND_VIA_PTR(kmap_T, &curbuf->b_kmap_ga); s = skiptowhite(p); - kp->from = vim_strnsave(p, (size_t)(s - p)); - p = (char_u *)skipwhite((char *)s); + kp->from = xstrnsave(p, (size_t)(s - p)); + p = skipwhite(s); s = skiptowhite(p); - kp->to = vim_strnsave(p, (size_t)(s - p)); + kp->to = xstrnsave(p, (size_t)(s - p)); - if ((STRLEN(kp->from) + STRLEN(kp->to) >= KMAP_LLEN) + if ((strlen(kp->from) + strlen(kp->to) >= KMAP_LLEN) || (*kp->from == NUL) || (*kp->to == NUL)) { if (*kp->to == NUL) { @@ -2118,7 +2130,7 @@ void ex_loadkeymap(exarg_T *eap) // setup ":lmap" to map the keys for (int i = 0; i < curbuf->b_kmap_ga.ga_len; i++) { - vim_snprintf((char *)buf, sizeof(buf), "<buffer> %s %s", + vim_snprintf(buf, sizeof(buf), "<buffer> %s %s", ((kmap_T *)curbuf->b_kmap_ga.ga_data)[i].from, ((kmap_T *)curbuf->b_kmap_ga.ga_data)[i].to); (void)do_map(MAPTYPE_MAP, buf, MODE_LANGMAP, false); @@ -2158,7 +2170,7 @@ static void keymap_unload(void) for (int i = 0; i < curbuf->b_kmap_ga.ga_len; i++) { vim_snprintf(buf, sizeof(buf), "<buffer> %s", kp[i].from); - (void)do_map(MAPTYPE_UNMAP, (char_u *)buf, MODE_LANGMAP, false); + (void)do_map(MAPTYPE_UNMAP, buf, MODE_LANGMAP, false); } keymap_ga_clear(&curbuf->b_kmap_ga); diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index 2b1b2607fb..e24d86b353 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -5,38 +5,69 @@ // This is the middle level, drawscreen.c is the top and grid.c/screen.c the lower level. #include <assert.h> -#include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/arabic.h" +#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/cursor_shape.h" +#include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/drawline.h" +#include "nvim/eval.h" +#include "nvim/extmark_defs.h" #include "nvim/fold.h" +#include "nvim/garray.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" +#include "nvim/mark.h" #include "nvim/match.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/memory.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" -#include "nvim/search.h" +#include "nvim/screen.h" #include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/state.h" +#include "nvim/statusline.h" +#include "nvim/strings.h" #include "nvim/syntax.h" -#include "nvim/undo.h" -#include "nvim/window.h" +#include "nvim/terminal.h" +#include "nvim/types.h" +#include "nvim/ui.h" +#include "nvim/vim.h" #define MB_FILLER_CHAR '<' // character used when a double-width character // doesn't fit. +/// possible draw states in win_line(), drawn in sequence. +typedef enum { + WL_START = 0, // nothing done yet + WL_CMDLINE, // cmdline window column + WL_FOLD, // 'foldcolumn' + WL_SIGN, // column for signs + WL_NR, // line number + WL_STC, // 'statuscolumn' + WL_BRI, // 'breakindent' + WL_SBR, // 'showbreak' or 'diff' + WL_LINE, // text in the line +} LineDrawState; + /// for line_putchar. Contains the state that needs to be remembered from /// putting one character to the next. typedef struct { @@ -109,9 +140,9 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col) /// Handles composing chars and arabic shaping state. static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, bool rl, int vcol) { - const char_u *p = (char_u *)s->p; - int cells = utf_ptr2cells((char *)p); - int c_len = utfc_ptr2len((char *)p); + const char *p = s->p; + int cells = utf_ptr2cells(p); + int c_len = utfc_ptr2len(p); int u8c, u8cc[MAX_MCO]; if (cells > maxcells) { return -1; @@ -123,22 +154,22 @@ static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, b schar_from_ascii(dest[c], ' '); } goto done; - } else if (*p < 0x80 && u8cc[0] == 0) { - schar_from_ascii(dest[0], (char)(*p)); + } else if ((uint8_t)(*p) < 0x80 && u8cc[0] == 0) { + schar_from_ascii(dest[0], *p); s->prev_c = u8c; } else { if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) { // Do Arabic shaping. int pc, pc1, nc; int pcc[MAX_MCO]; - int firstbyte = *p; + int firstbyte = (uint8_t)(*p); // The idea of what is the previous and next // character depends on 'rightleft'. if (rl) { pc = s->prev_c; pc1 = s->prev_c1; - nc = utf_ptr2char((char *)p + c_len); + nc = utf_ptr2char(p + c_len); s->prev_c1 = u8cc[0]; } else { pc = utfc_ptr2char(p + c_len, pcc); @@ -277,7 +308,7 @@ static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) } // Get information needed to display the sign in line 'lnum' in window 'wp'. -// If 'nrcol' is TRUE, the sign is going to be displayed in the number column. +// If 'nrcol' is true, the sign is going to be displayed in the number column. // Otherwise the sign is going to be displayed in the sign column. // // @param count max number of signs @@ -285,8 +316,8 @@ static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) // @param sign_idxp Index of the displayed sign static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, SignTextAttrs sattrs[], int row, int startrow, int filler_lines, int filler_todo, - int *c_extrap, int *c_finalp, char_u *extra, size_t extra_size, - char_u **pp_extra, int *n_extrap, int *char_attrp, int sign_idx, + int *c_extrap, int *c_finalp, char *extra, size_t extra_size, + char **pp_extra, int *n_extrap, int *char_attrp, int sign_idx, int cul_attr) { // Draw cells with the sign value or blank. @@ -320,16 +351,16 @@ static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, SignText STRCAT(extra, *pp_extra); STRCAT(extra, " "); *pp_extra = extra; - *n_extrap = (int)STRLEN(*pp_extra); + *n_extrap = (int)strlen(*pp_extra); } else { - size_t symbol_blen = STRLEN(*pp_extra); + size_t symbol_blen = strlen(*pp_extra); // TODO(oni-link): Is sign text already extended to // full cell width? assert((size_t)win_signcol_width(wp) >= mb_string2cells((char *)(*pp_extra))); // symbol(s) bytes + (filling spaces) (one byte each) *n_extrap = (int)symbol_blen + win_signcol_width(wp) - - (int)mb_string2cells((char *)(*pp_extra)); + (int)mb_string2cells(*pp_extra); assert(extra_size > symbol_blen); memset(extra, ' ', extra_size); @@ -367,6 +398,102 @@ static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int return num_signs; } +/// Prepare and build the 'statuscolumn' string for line "lnum" in window "wp". +/// Fill "stcp" with the built status column string and attributes. +/// This can be called three times per win_line(), once for virt_lines, once for +/// the start of the buffer line "lnum" and once for the wrapped lines. +/// +/// @param[out] stcp Status column attributes +static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines, + int cul_attr, int sign_num_attr, int sign_cul_attr, statuscol_T *stcp, + foldinfo_T foldinfo, SignTextAttrs *sattrs) +{ + long relnum = -1; + bool use_cul = use_cursor_line_sign(wp, lnum); + int virtnum = row - startrow - filler_lines; + + set_vim_var_nr(VV_VIRTNUM, virtnum); + // When called the first time for line "lnum" set num_attr + if (stcp->num_attr == 0) { + stcp->num_attr = sign_num_attr ? sign_num_attr + : get_line_number_attr(wp, lnum, row, startrow, filler_lines); + } + // When called for the first non-filler row of line "lnum" set num v:vars and fold column + if (virtnum == 0) { + relnum = labs(get_cursor_rel_lnum(wp, lnum)); + if (compute_foldcolumn(wp, 0)) { + size_t n = fill_foldcolumn(stcp->fold_text, wp, foldinfo, lnum); + stcp->fold_text[n] = NUL; + stcp->fold_attr = win_hl_attr(wp, use_cul ? HLF_CLF : HLF_FC); + } + } + // Make sure to clear->set->clear sign column for filler->first->wrapped lines + int i = 0; + for (; i < wp->w_scwidth; i++) { + SignTextAttrs *sattr = virtnum ? NULL : sign_get_attr(i, sattrs, wp->w_scwidth); + stcp->sign_text[i] = sattr && sattr->text ? sattr->text : " "; + stcp->sign_attr[i] = sattr ? (use_cul && sign_cul_attr ? sign_cul_attr : sattr->hl_attr_id) + : win_hl_attr(wp, use_cul ? HLF_CLS : HLF_SC); + } + stcp->sign_text[i] = NULL; + + int width = build_statuscol_str(wp, lnum, relnum, stcp->width, + ' ', stcp->text, &stcp->hlrec, stcp); + // Force a redraw in case of error or when truncated + if (*wp->w_p_stc == NUL || (stcp->truncate > 0 && wp->w_nrwidth < MAX_NUMBERWIDTH)) { + if (stcp->truncate) { // Avoid truncating 'statuscolumn' + wp->w_nrwidth = MIN(MAX_NUMBERWIDTH, wp->w_nrwidth + stcp->truncate); + wp->w_nrwidth_width = wp->w_nrwidth; + } else { // 'statuscolumn' reset due to error + wp->w_nrwidth_line_count = 0; + wp->w_nrwidth = (wp->w_p_nu || wp->w_p_rnu) * number_width(wp); + } + wp->w_redr_statuscol = true; + return; + } + + // Reset text/highlight pointer and current attr for new line + stcp->textp = stcp->text; + stcp->hlrecp = stcp->hlrec; + stcp->cur_attr = stcp->num_attr; + stcp->text_end = stcp->text + strlen(stcp->text); + + int fill = stcp->width - width; + if (fill > 0) { + // Fill up with ' ' + memset(stcp->text_end, ' ', (size_t)fill); + *(stcp->text_end += fill) = NUL; + } +} + +/// Get information needed to display the next segment in the 'statuscolumn'. +/// If not yet at the end, prepare for next segment and decrement "draw_state". +/// +/// @param stcp Status column attributes +/// @param[out] draw_state Current draw state in win_line() +static void get_statuscol_display_info(statuscol_T *stcp, LineDrawState *draw_state, int *char_attr, + int *n_extrap, int *c_extrap, int *c_finalp, char **pp_extra) +{ + *c_extrap = NUL; + *c_finalp = NUL; + do { + *draw_state = WL_STC; + *char_attr = stcp->cur_attr; + *pp_extra = stcp->textp; + *n_extrap = (int)((stcp->hlrecp->start ? stcp->hlrecp->start : stcp->text_end) - stcp->textp); + // Prepare for next highlight section if not yet at the end + if (stcp->textp + *n_extrap < stcp->text_end) { + int hl = stcp->hlrecp->userhl; + stcp->textp = stcp->hlrecp->start; + stcp->cur_attr = hl < 0 ? syn_id2attr(-stcp->hlrecp->userhl) + : hl > 0 ? hl : stcp->num_attr; + stcp->hlrecp++; + *draw_state = WL_STC - 1; + } + // Skip over empty highlight sections + } while (*n_extrap == 0 && stcp->textp < stcp->text_end); +} + /// Return true if CursorLineNr highlight is to be used for the number column. /// /// - 'cursorline' must be set @@ -386,7 +513,7 @@ static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, && (wp->w_p_culopt_flags & CULOPT_LINE))); } -static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len) +static inline void get_line_number_str(win_T *wp, linenr_T lnum, char *buf, size_t buf_len) { long num; char *fmt = "%*ld "; @@ -404,7 +531,7 @@ static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, si } } - snprintf((char *)buf, buf_len, fmt, number_width(wp), num); + snprintf(buf, buf_len, fmt, number_width(wp), num); } static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines) @@ -480,29 +607,28 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, long vcol = 0; // virtual column (for tabs) long vcol_sbr = -1; // virtual column after showbreak long vcol_prev = -1; // "vcol" of previous character - char_u *line; // current line - char_u *ptr; // current position in "line" + char *line; // current line + char *ptr; // current position in "line" int row; // row in the window, excl w_winrow ScreenGrid *grid = &wp->w_grid; // grid specific to the window - char_u extra[57]; // sign, line number and 'fdc' must + char extra[57]; // sign, line number and 'fdc' must // fit in here int n_extra = 0; // number of extra chars - char_u *p_extra = NULL; // string of extra chars, plus NUL - char_u *p_extra_free = NULL; // p_extra needs to be freed + char *p_extra = NULL; // string of extra chars, plus NUL + char *p_extra_free = NULL; // p_extra needs to be freed int c_extra = NUL; // extra chars, all the same int c_final = NUL; // final char, mandatory if set int extra_attr = 0; // attributes when n_extra != 0 - static char_u *at_end_str = (char_u *)""; // used for p_extra when displaying - // curwin->w_p_lcs_chars.eol at - // end-of-line + static char *at_end_str = ""; // used for p_extra when displaying curwin->w_p_lcs_chars.eol + // at end-of-line int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0; // saved "extra" items for when draw_state becomes WL_LINE (again) int saved_n_extra = 0; - char_u *saved_p_extra = NULL; + char *saved_p_extra = NULL; int saved_c_extra = 0; int saved_c_final = 0; int saved_char_attr = 0; @@ -525,7 +651,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, int char_attr = 0; // attributes for next character bool attr_pri = false; // char_attr has priority bool area_highlighting = false; // Visual or incsearch highlighting in this line - int attr = 0; // attributes for area highlighting + int vi_attr = 0; // attributes for Visual and incsearch highlighting int area_attr = 0; // attributes desired by highlighting int search_attr = 0; // attributes desired by 'hlsearch' int vcol_save_attr = 0; // saved attr for 'cursorcolumn' @@ -537,7 +663,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, int *color_cols = NULL; // pointer to according columns array bool has_spell = false; // this buffer has spell checking #define SPWORDLEN 150 - char_u nextline[SPWORDLEN * 2]; // text with start of the next line + char nextline[SPWORDLEN * 2]; // text with start of the next line int nextlinecol = 0; // column where nextline[] starts int nextline_idx = 0; // index in nextline[] where next line // starts @@ -576,7 +702,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, bool has_decor = false; // this buffer has decoration int win_col_offset = 0; // offset for window columns - char_u buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext + char buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext bool area_active = false; @@ -588,17 +714,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, int left_curline_col = 0; int right_curline_col = 0; - // draw_state: items that are drawn in sequence: -#define WL_START 0 // nothing done yet -#define WL_CMDLINE (WL_START + 1) // cmdline window column -#define WL_FOLD (WL_CMDLINE + 1) // 'foldcolumn' -#define WL_SIGN (WL_FOLD + 1) // column for signs -#define WL_NR (WL_SIGN + 1) // line number -#define WL_BRI (WL_NR + 1) // 'breakindent' -#define WL_SBR (WL_BRI + 1) // 'showbreak' or 'diff' -#define WL_LINE (WL_SBR + 1) // text in the line - int draw_state = WL_START; // what to draw next + LineDrawState draw_state = WL_START; // what to draw next + int match_conc = 0; ///< cchar for match functions + bool on_last_col = false; int syntax_flags = 0; int syntax_seqnr = 0; int prev_syntax_id = 0; @@ -608,7 +727,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, ///< force wrapping int vcol_off = 0; ///< offset for concealed characters int did_wcol = false; - int match_conc = 0; ///< cchar for match functions int old_boguscols = 0; #define VCOL_HLC (vcol - vcol_off) #define FIX_FOR_BOGUSCOLS \ @@ -654,7 +772,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, has_decor = decor_redraw_line(buf, lnum - 1, &decor_state); - providers_invoke_line(wp, providers, lnum - 1, &has_decor, provider_err); + decor_providers_invoke_line(wp, providers, lnum - 1, &has_decor, provider_err); if (*provider_err) { provider_err_virt_text(lnum, *provider_err); @@ -772,7 +890,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // if inverting in this line set area_highlighting if (fromcol >= 0) { area_highlighting = true; - attr = win_hl_attr(wp, HLF_V); + vi_attr = win_hl_attr(wp, HLF_V); } // handle 'incsearch' and ":s///c" highlighting } else if (highlight_match @@ -796,15 +914,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, tocol = fromcol + 1; } area_highlighting = true; - attr = win_hl_attr(wp, HLF_I); + vi_attr = win_hl_attr(wp, HLF_I); } } int bg_attr = win_bg_attr(wp); - filler_lines = diff_check(wp, lnum); - if (filler_lines < 0) { - if (filler_lines == -1) { + int linestatus = 0; + filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus); + if (filler_lines < 0 || linestatus < 0) { + if (filler_lines == -1 || linestatus == -1) { if (diff_find_change(wp, lnum, &change_start, &change_end)) { diff_hlf = HLF_ADD; // added line } else if (change_start == 0) { @@ -815,11 +934,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } else { diff_hlf = HLF_ADD; // added line } - filler_lines = 0; + if (linestatus == 0) { + filler_lines = 0; + } area_highlighting = true; } VirtLines virt_lines = KV_INITIAL_VALUE; - int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines); + int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines, has_fold); filler_lines += n_virt_lines; if (lnum == wp->w_topline) { filler_lines = wp->w_topfill; @@ -864,7 +985,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, line_attr_lowprio_save = line_attr_lowprio; } - line = end_fill ? (char_u *)"" : ml_get_buf(wp->w_buffer, lnum, false); + line = end_fill ? "" : ml_get_buf(wp->w_buffer, lnum, false); ptr = line; if (has_spell && !number_only) { @@ -881,7 +1002,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, nextlinecol = MAXCOL; nextline_idx = 0; } else { - v = (long)STRLEN(line); + v = (long)strlen(line); if (v < SPWORDLEN) { // Short line, use it completely and append the start of the // next line. @@ -892,7 +1013,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } else { // Long line, use only the last SPWORDLEN bytes. nextlinecol = (int)v - SPWORDLEN; - memmove(nextline, line + nextlinecol, SPWORDLEN); // -V512 + memmove(nextline, line + nextlinecol, SPWORDLEN); // -V1086 nextline_idx = SPWORDLEN + 1; } } @@ -909,7 +1030,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } // find start of trailing whitespace if (wp->w_p_lcs_chars.trail) { - trailcol = (colnr_T)STRLEN(ptr); + trailcol = (colnr_T)strlen(ptr); while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) { trailcol--; } @@ -939,13 +1060,20 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, v = wp->w_leftcol; } if (v > 0 && !number_only) { - char_u *prev_ptr = ptr; - while (vcol < v && *ptr != NUL) { - c = win_lbr_chartabsize(wp, line, ptr, (colnr_T)vcol, NULL); - vcol += c; - prev_ptr = ptr; - MB_PTR_ADV(ptr); + char *prev_ptr = ptr; + chartabsize_T cts; + int charsize; + + init_chartabsize_arg(&cts, wp, lnum, (colnr_T)vcol, line, ptr); + while (cts.cts_vcol < v && *cts.cts_ptr != NUL) { + charsize = win_lbr_chartabsize(&cts, NULL); + cts.cts_vcol += charsize; + prev_ptr = cts.cts_ptr; + MB_PTR_ADV(cts.cts_ptr); } + vcol = cts.cts_vcol; + ptr = cts.cts_ptr; + clear_chartabsize_arg(&cts); // When: // - 'cuc' is set, or @@ -963,11 +1091,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // Handle a character that's not completely on the screen: Put ptr at // that character but skip the first few screen characters. if (vcol > v) { - vcol -= c; + vcol -= charsize; ptr = prev_ptr; // If the character fits on the screen, don't need to skip it. // Except for a TAB. - if (utf_ptr2cells((char *)ptr) >= c || *ptr == TAB) { + if (utf_ptr2cells(ptr) >= charsize || *ptr == TAB) { n_skip = (int)(v - vcol); } } @@ -1068,7 +1196,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, extra_check = true; } + statuscol_T statuscol = { 0 }; + if (*wp->w_p_stc != NUL) { + // Draw the 'statuscolumn' if option is set. + statuscol.draw = true; + statuscol.width = win_col_off(wp); + } + int sign_idx = 0; + int virt_line_index; + int virt_line_offset = -1; // Repeat for the whole displayed line. for (;;) { int has_match_conc = 0; ///< match wants to conceal @@ -1096,8 +1233,25 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } if (draw_state == WL_FOLD - 1 && n_extra == 0) { - int fdc = compute_foldcolumn(wp, 0); + if (filler_todo > 0) { + int index = filler_todo - (filler_lines - n_virt_lines); + if (index > 0) { + virt_line_index = (int)kv_size(virt_lines) - index; + assert(virt_line_index >= 0); + virt_line_offset = kv_A(virt_lines, virt_line_index).left_col ? 0 : win_col_off(wp); + } + } + if (!virt_line_offset) { + // Skip the column states if there is a "virt_left_col" line. + draw_state = WL_BRI - 1; + } else if (statuscol.draw) { + // Skip fold, sign and number states if 'statuscolumn' is set. + draw_state = WL_STC - 1; + } + } + if (draw_state == WL_FOLD - 1 && n_extra == 0) { + int fdc = compute_foldcolumn(wp, 0); draw_state = WL_FOLD; if (fdc > 0) { // Draw the 'foldcolumn'. Allocate a buffer, "extra" may @@ -1155,7 +1309,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } else { // Draw the line number (empty space after wrapping). if (row == startrow + filler_lines) { - get_line_number_str(wp, lnum, (char_u *)extra, sizeof(extra)); + get_line_number_str(wp, lnum, extra, sizeof(extra)); if (wp->w_skipcol > 0) { for (p_extra = extra; *p_extra == ' '; p_extra++) { *p_extra = '-'; @@ -1163,10 +1317,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } if (wp->w_p_rl) { // reverse line numbers // like rl_mirror(), but keep the space at the end - char_u *p2 = (char_u *)skipwhite((char *)extra); + char *p2 = skipwhite(extra); p2 = skiptowhite(p2) - 1; - for (char_u *p1 = (char_u *)skipwhite((char *)extra); p1 < p2; p1++, p2--) { - const char_u t = *p1; + for (char *p1 = skipwhite(extra); p1 < p2; p1++, p2--) { + const char t = *p1; *p1 = *p2; *p2 = t; } @@ -1187,7 +1341,23 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } } - if (draw_state == WL_NR && n_extra == 0) { + if (draw_state == WL_STC - 1 && n_extra == 0) { + draw_state = WL_STC; + // Draw the 'statuscolumn' if option is set. + if (statuscol.draw) { + if (statuscol.textp == NULL) { + get_statuscol_str(wp, lnum, row, startrow, filler_lines, cul_attr, + sign_num_attr, sign_cul_attr, &statuscol, foldinfo, sattrs); + if (wp->w_redr_statuscol) { + break; + } + } + get_statuscol_display_info(&statuscol, &draw_state, &char_attr, + &n_extra, &c_extra, &c_final, &p_extra); + } + } + + if (draw_state == WL_STC && n_extra == 0) { win_col_offset = off; } @@ -1247,7 +1417,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } char_attr = 0; } else if (filler_todo > 0) { - // draw "deleted" diff line(s) + // Draw "deleted" diff line(s) if (char2cells(wp->w_p_fcs_chars.diff) > 1) { c_extra = '-'; c_final = NUL; @@ -1262,13 +1432,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } char_attr = win_hl_attr(wp, HLF_DED); } - char_u *const sbr = get_showbreak_value(wp); + char *const sbr = get_showbreak_value(wp); if (*sbr != NUL && need_showbreak) { // Draw 'showbreak' at the start of each broken line. p_extra = sbr; c_extra = NUL; c_final = NUL; - n_extra = (int)STRLEN(sbr); + n_extra = (int)strlen(sbr); char_attr = win_hl_attr(wp, HLF_AT); if (wp->w_skipcol == 0 || !wp->w_p_wrap) { need_showbreak = false; @@ -1319,7 +1489,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && wp == curwin && lnum == wp->w_cursor.lnum && vcol >= (long)wp->w_virtcol) - || (number_only && draw_state > WL_NR)) + || (number_only && draw_state > WL_STC)) && filler_todo <= 0) { draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row); grid_put_linebuf(grid, row, 0, col, -grid->cols, wp->w_p_rl, wp, bg_attr, false); @@ -1337,13 +1507,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && has_fold && col == win_col_offset && n_extra == 0 - && row == startrow) { + && row == startrow + filler_lines) { char_attr = win_hl_attr(wp, HLF_FL); linenr_T lnume = lnum + foldinfo.fi_lines - 1; memset(buf_fold, ' ', FOLD_TEXT_LEN); p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold); - n_extra = (int)STRLEN(p_extra); + n_extra = (int)strlen(p_extra); if (p_extra != buf_fold) { xfree(p_extra_free); @@ -1358,7 +1528,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && has_fold && col < grid->cols && n_extra == 0 - && row == startrow) { + && row == startrow + filler_lines) { // fill rest of line with 'fold' c_extra = wp->w_p_fcs_chars.fold; c_final = NUL; @@ -1370,7 +1540,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && has_fold && col >= grid->cols && n_extra != 0 - && row == startrow) { + && row == startrow + filler_lines) { // Truncate the folding. n_extra = 0; } @@ -1379,11 +1549,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // handle Visual or match highlighting in this line if (vcol == fromcol || (vcol + 1 == fromcol && n_extra == 0 - && utf_ptr2cells((char *)ptr) > 1) + && utf_ptr2cells(ptr) > 1) || ((int)vcol_prev == fromcol_prev && vcol_prev < vcol // not at margin && vcol < tocol)) { - area_attr = attr; // start highlighting + area_attr = vi_attr; // start highlighting if (area_highlighting) { area_active = true; } @@ -1400,8 +1570,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // When another match, have to check for start again. v = (ptr - line); search_attr = update_search_hl(wp, lnum, (colnr_T)v, &line, &screen_search_hl, - &has_match_conc, - &match_conc, lcs_eol_one, &search_attr_from_match); + &has_match_conc, &match_conc, lcs_eol_one, + &on_last_col, &search_attr_from_match); ptr = line + v; // "line" may have been changed // Do not allow a conceal over EOL otherwise EOL will be missed @@ -1473,11 +1643,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, mb_utf8 = check_mb_utf8(&c, u8cc); } else { assert(p_extra != NULL); - c = *p_extra; + c = (uint8_t)(*p_extra); mb_c = c; // If the UTF-8 character is more than one byte: // Decode it into "mb_c". - mb_l = utfc_ptr2len((char *)p_extra); + mb_l = utfc_ptr2len(p_extra); mb_utf8 = false; if (mb_l > n_extra) { mb_l = 1; @@ -1526,11 +1696,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, XFREE_CLEAR(p_extra_free); // Get a character from the line itself. - c0 = c = *ptr; + c0 = c = (uint8_t)(*ptr); mb_c = c; // If the UTF-8 character is more than one byte: Decode it // into "mb_c". - mb_l = utfc_ptr2len((char *)ptr); + mb_l = utfc_ptr2len(ptr); mb_utf8 = false; if (mb_l > 1) { mb_c = utfc_ptr2char(ptr, u8cc); @@ -1559,16 +1729,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, || (mb_l > 1 && (!vim_isprintc(mb_c)))) { // Illegal UTF-8 byte: display as <xx>. // Non-BMP character : display as ? or fullwidth ?. - transchar_hex((char *)extra, mb_c); + transchar_hex(extra, mb_c); if (wp->w_p_rl) { // reverse rl_mirror(extra); } p_extra = extra; - c = *p_extra; - mb_c = mb_ptr2char_adv((const char_u **)&p_extra); + c = (uint8_t)(*p_extra); + mb_c = mb_ptr2char_adv((const char **)&p_extra); mb_utf8 = (c >= 0x80); - n_extra = (int)STRLEN(p_extra); + n_extra = (int)strlen(p_extra); c_extra = NUL; c_final = NUL; if (area_attr == 0 && search_attr == 0) { @@ -1588,7 +1758,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (wp->w_p_rl) { pc = prev_c; pc1 = prev_c1; - nc = utf_ptr2char((char *)ptr + mb_l); + nc = utf_ptr2char(ptr + mb_l); prev_c1 = u8cc[0]; } else { pc = utfc_ptr2char(ptr + mb_l, pcc); @@ -1639,7 +1809,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, ptr++; if (extra_check) { - bool can_spell = true; + bool no_plain_buffer = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) != 0; + bool can_spell = !no_plain_buffer; // Get syntax attribute, unless still at the start of the line // (double-wide char that doesn't fit). @@ -1653,7 +1824,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, syntax_attr = get_syntax_attr((colnr_T)v - 1, has_spell ? &can_spell : NULL, false); - if (did_emsg) { + if (did_emsg) { // -V547 wp->w_s->b_syn_error = true; has_syntax = false; } else { @@ -1691,6 +1862,27 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, char_attr = 0; } + if (has_decor && v > 0) { + bool selected = (area_active || (area_highlighting && noinvcur + && (colnr_T)vcol == wp->w_virtcol)); + int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v - 1, off, + selected, &decor_state); + if (extmark_attr != 0) { + if (!attr_pri) { + char_attr = hl_combine_attr(char_attr, extmark_attr); + } else { + char_attr = hl_combine_attr(extmark_attr, char_attr); + } + } + + decor_conceal = decor_state.conceal; + if (decor_conceal && decor_state.conceal_char) { + decor_conceal = 2; // really?? + } + + can_spell = TRISTATE_TO_BOOL(decor_state.spell, can_spell); + } + // Check spelling (unless at the end of the line). // Only do this when there is no syntax highlighting, the // @Spell cluster is not used or the current syntax item @@ -1699,11 +1891,11 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (has_spell && v >= word_end && v > cur_checked_col) { spell_attr = 0; if (!attr_pri) { - char_attr = syntax_attr; + char_attr = hl_combine_attr(char_attr, syntax_attr); } - if (c != 0 && (!has_syntax || can_spell)) { - char_u *prev_ptr; - char_u *p; + if (c != 0 && ((!has_syntax && !no_plain_buffer) || can_spell)) { + char *prev_ptr; + char *p; int len; hlf_T spell_hlf = HLF_COUNT; prev_ptr = ptr - mb_l; @@ -1774,32 +1966,15 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, char_attr = hl_combine_attr(term_attrs[vcol], char_attr); } - if (has_decor && v > 0) { - bool selected = (area_active || (area_highlighting && noinvcur - && (colnr_T)vcol == wp->w_virtcol)); - int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v - 1, off, - selected, &decor_state); - if (extmark_attr != 0) { - if (!attr_pri) { - char_attr = hl_combine_attr(char_attr, extmark_attr); - } else { - char_attr = hl_combine_attr(extmark_attr, char_attr); - } - } - - decor_conceal = decor_state.conceal; - if (decor_conceal && decor_state.conceal_char) { - decor_conceal = 2; // really?? - } - } - // Found last space before word: check for line break. if (wp->w_p_lbr && c0 == c && vim_isbreak(c) && !vim_isbreak((int)(*ptr))) { int mb_off = utf_head_off(line, ptr - 1); - char_u *p = ptr - (mb_off + 1); - // TODO(neovim): is passing p for start of the line OK? - n_extra = win_lbr_chartabsize(wp, line, p, (colnr_T)vcol, NULL) - 1; + char *p = ptr - (mb_off + 1); + chartabsize_T cts; + + init_chartabsize_arg(&cts, wp, lnum, (colnr_T)vcol, line, p); + n_extra = win_lbr_chartabsize(&cts, NULL) - 1; // We have just drawn the showbreak value, no need to add // space for it again. @@ -1809,6 +1984,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, n_extra = 0; } } + if (on_last_col && c != TAB) { + // Do not continue search/match highlighting over the + // line break, but for TABs the highlighting should + // include the complete width of the character + search_attr = 0; + } if (c == TAB && n_extra + col > grid->cols) { n_extra = tabstop_padding((colnr_T)vcol, wp->w_buffer->b_p_ts, @@ -1825,6 +2006,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, c = ' '; } } + clear_chartabsize_arg(&cts); } in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' '); @@ -1893,7 +2075,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { int tab_len = 0; long vcol_adjusted = vcol; // removed showbreak length - char_u *const sbr = get_showbreak_value(wp); + char *const sbr = get_showbreak_value(wp); // Only adjust the tab_len, when at the first column after the // showbreak value was drawn. @@ -1908,7 +2090,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (!wp->w_p_lbr || !wp->w_p_list) { n_extra = tab_len; } else { - char_u *p; + char *p; int i; int saved_nextra = n_extra; @@ -1949,7 +2131,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) { lcs = wp->w_p_lcs_chars.tab3; } - p += utf_char2bytes(lcs, (char *)p); + p += utf_char2bytes(lcs, p); n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0); } p_extra = p_extra_free; @@ -2041,7 +2223,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, mb_c = c; mb_utf8 = check_mb_utf8(&c, u8cc); } else if (c != NUL) { - p_extra = transchar_buf(wp->w_buffer, c); + p_extra = (char *)transchar_buf(wp->w_buffer, c); if (n_extra == 0) { n_extra = byte2cells(c) - 1; } @@ -2051,18 +2233,20 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, c_extra = NUL; c_final = NUL; if (wp->w_p_lbr) { - char_u *p; + char *p; - c = *p_extra; + c = (uint8_t)(*p_extra); p = xmalloc((size_t)n_extra + 1); memset(p, ' ', (size_t)n_extra); - STRNCPY(p, p_extra + 1, STRLEN(p_extra) - 1); // NOLINT(runtime/printf) + strncpy(p, // NOLINT(runtime/printf) + p_extra + 1, + (size_t)strlen(p_extra) - 1); p[n_extra] = NUL; xfree(p_extra_free); p_extra_free = p_extra = p; } else { n_extra = byte2cells(c) - 1; - c = *p_extra++; + c = (uint8_t)(*p_extra++); } n_attr = n_extra + 1; extra_attr = win_hl_attr(wp, HLF_8); @@ -2082,7 +2266,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (wp->w_p_cole > 0 && (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp)) && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0) - && !(lnum_in_visual_area && vim_strchr((char *)wp->w_p_cocu, 'v') == NULL)) { + && !(lnum_in_visual_area && vim_strchr(wp->w_p_cocu, 'v') == NULL)) { char_attr = conceal_attr; if (((prev_syntax_id != syntax_seqnr && (syntax_flags & HL_CONCEAL) != 0) || has_match_conc > 1 || decor_conceal > 1) @@ -2170,7 +2354,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && wp->w_p_list && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0) && filler_todo <= 0 - && draw_state > WL_NR + && draw_state > WL_STC && c != NUL) { c = wp->w_p_lcs_chars.prec; lcs_prec_todo = NUL; @@ -2195,7 +2379,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // flag to indicate whether prevcol equals startcol of search_hl or // one of the matches bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl, - (long)(ptr - line) - 1); + (long)(ptr - line) - 1); // NOLINT(google-readability-casting) // Invert at least one char, used for Visual and empty line or // highlight match at end of line. If it's beyond the last @@ -2231,7 +2415,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (area_attr == 0 && !has_fold) { // Use attributes from match with highest priority among // 'search_hl' and the match list. - get_search_match_hl(wp, &screen_search_hl, (long)(ptr - line), &char_attr); + get_search_match_hl(wp, + &screen_search_hl, + (long)(ptr - line), // NOLINT(google-readability-casting) + &char_attr); } int eol_attr = char_attr; @@ -2461,7 +2648,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, col++; // UTF-8: Put a 0 in the second screen char. linebuf_char[off][0] = 0; - if (draw_state > WL_NR && filler_todo <= 0) { + if (draw_state > WL_STC && filler_todo <= 0) { vcol++; } // When "tocol" is halfway through a character, set it to the end of @@ -2544,7 +2731,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // Only advance the "vcol" when after the 'number' or 'relativenumber' // column. - if (draw_state > WL_NR + if (draw_state > WL_STC && filler_todo <= 0) { vcol++; } @@ -2554,7 +2741,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } // restore attributes after "predeces" in 'listchars' - if (draw_state > WL_NR && n_attr3 > 0 && --n_attr3 == 0) { + if (draw_state > WL_STC && n_attr3 > 0 && --n_attr3 == 0) { char_attr = saved_attr3; } @@ -2566,7 +2753,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // At end of screen line and there is more to come: Display the line // so far. If there is no more to display it is caught above. if ((wp->w_p_rl ? (col < 0) : (col >= grid->cols)) - && foldinfo.fi_lines == 0 + && (!has_fold || virt_line_offset >= 0) && (draw_state != WL_LINE || *ptr != NUL || filler_todo > 0 @@ -2583,15 +2770,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, && !wp->w_p_rl; // Not right-to-left. int draw_col = col - boguscols; - if (filler_todo > 0) { - int index = filler_todo - (filler_lines - n_virt_lines); - if (index > 0) { - int i = (int)kv_size(virt_lines) - index; - assert(i >= 0); - int offset = kv_A(virt_lines, i).left_col ? 0 : win_col_offset; - draw_virt_text_item(buf, offset, kv_A(virt_lines, i).line, - kHlModeReplace, grid->cols, offset); - } + if (virt_line_offset >= 0) { + draw_virt_text_item(buf, virt_line_offset, kv_A(virt_lines, virt_line_index).line, + kHlModeReplace, grid->cols, 0); } else { draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, row); } @@ -2621,6 +2802,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, // When the window is too narrow draw all "@" lines. if (draw_state != WL_LINE && filler_todo <= 0) { win_draw_end(wp, '@', ' ', true, row, wp->w_grid.rows, HLF_AT); + set_empty_rows(wp, row); row = endrow; } @@ -2649,7 +2831,19 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, if (filler_todo <= 0) { need_showbreak = true; } + if (statuscol.draw) { + if (row == startrow + filler_lines + 1 || row == startrow + filler_lines) { + // Re-evaluate 'statuscolumn' for the first wrapped row and non filler line + statuscol.textp = NULL; + } else if (statuscol.textp) { + // Draw the already built 'statuscolumn' on the next wrapped or filler line + statuscol.textp = statuscol.text; + statuscol.hlrecp = statuscol.hlrec; + } // Fall back to default columns if the 'n' flag isn't in 'cpo' + statuscol.draw = vim_strchr(p_cpo, CPO_NUMCOL) == NULL; + } filler_todo--; + virt_line_offset = -1; // When the filler lines are actually below the last line of the // file, don't draw the line itself, break here. if (filler_todo == 0 && (wp->w_botfill || end_fill)) { @@ -2659,7 +2853,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, } // for every character in the line // After an empty line check first word for capital. - if (*skipwhite((char *)line) == NUL) { + if (*skipwhite(line) == NUL) { capcol_lnum = lnum + 1; cap_col = 0; } diff --git a/src/nvim/drawline.h b/src/nvim/drawline.h index e50969983e..9f60b46e1b 100644 --- a/src/nvim/drawline.h +++ b/src/nvim/drawline.h @@ -1,9 +1,15 @@ #ifndef NVIM_DRAWLINE_H #define NVIM_DRAWLINE_H +#include <stdbool.h> +#include <stdint.h> + +#include "klib/kvec.h" #include "nvim/decoration_provider.h" #include "nvim/fold.h" +#include "nvim/macros.h" #include "nvim/screen.h" +#include "nvim/types.h" // Maximum columns for terminal highlight attributes #define TERM_ATTRS_MAX 1024 diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 2abff6c894..04c342e068 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -20,7 +20,7 @@ // // Commands that scroll a window change w_topline and must call // check_cursor() to move the cursor into the visible part of the window, and -// call redraw_later(wp, VALID) to have the window displayed by update_screen() +// call redraw_later(wp, UPD_VALID) to have the window displayed by update_screen() // later. // // Commands that change text in the buffer must call changed_bytes() or @@ -32,23 +32,23 @@ // // Commands that change how a window is displayed (e.g., setting 'list') or // invalidate the contents of a window in another way (e.g., change fold -// settings), must call redraw_later(wp, NOT_VALID) to have the whole window +// settings), must call redraw_later(wp, UPD_NOT_VALID) to have the whole window // redisplayed by update_screen() later. // // Commands that change how a buffer is displayed (e.g., setting 'tabstop') -// must call redraw_curbuf_later(NOT_VALID) to have all the windows for the +// must call redraw_curbuf_later(UPD_NOT_VALID) to have all the windows for the // buffer redisplayed by update_screen() later. // // Commands that change highlighting and possibly cause a scroll too must call -// redraw_later(wp, SOME_VALID) to update the whole window but still use +// redraw_later(wp, UPD_SOME_VALID) to update the whole window but still use // scrolling to avoid redrawing everything. But the length of displayed lines -// must not change, use NOT_VALID then. +// must not change, use UPD_NOT_VALID then. // -// Commands that move the window position must call redraw_later(wp, NOT_VALID). +// Commands that move the window position must call redraw_later(wp, UPD_NOT_VALID). // TODO(neovim): should minimize redrawing by scrolling when possible. // // Commands that change everything (e.g., resizing the screen) must call -// redraw_all_later(NOT_VALID) or redraw_all_later(CLEAR). +// redraw_all_later(UPD_NOT_VALID) or redraw_all_later(UPD_CLEAR). // // Things that are handled indirectly: // - When messages scroll the screen up, msg_scrolled will be set and @@ -59,28 +59,47 @@ #include <stdbool.h> #include <string.h> +#include "klib/kvec.h" +#include "nvim/api/private/defs.h" +#include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" +#include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/diff.h" +#include "nvim/drawline.h" #include "nvim/drawscreen.h" #include "nvim/ex_getln.h" +#include "nvim/extmark_defs.h" +#include "nvim/fold.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/insexpand.h" #include "nvim/match.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/message.h" #include "nvim/move.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/regexp.h" +#include "nvim/screen.h" #include "nvim/statusline.h" #include "nvim/syntax.h" +#include "nvim/terminal.h" +#include "nvim/types.h" +#include "nvim/ui.h" #include "nvim/ui_compositor.h" -#include "nvim/undo.h" #include "nvim/version.h" +#include "nvim/vim.h" #include "nvim/window.h" /// corner value flags for hsep_connected and vsep_connected @@ -97,7 +116,7 @@ typedef enum { static bool redraw_popupmenu = false; static bool msg_grid_invalid = false; -static bool resizing = false; +static bool resizing_autocmd = false; static char *provider_err = NULL; @@ -107,15 +126,17 @@ static char *provider_err = NULL; void conceal_check_cursor_line(void) { bool should_conceal = conceal_cursor_line(curwin); - if (curwin->w_p_cole > 0 && (conceal_cursor_used != should_conceal)) { - redrawWinline(curwin, curwin->w_cursor.lnum); - // Need to recompute cursor column, e.g., when starting Visual mode - // without concealing. - curs_columns(curwin, true); + if (curwin->w_p_cole <= 0 || conceal_cursor_used == should_conceal) { + return; } + + redrawWinline(curwin, curwin->w_cursor.lnum); + // Need to recompute cursor column, e.g., when starting Visual mode + // without concealing. + curs_columns(curwin, true); } -/// Resize the screen to Rows and Columns. +/// Resize default_grid to Rows and Columns. /// /// Allocate default_grid.chars[] and other grid arrays. /// @@ -125,19 +146,20 @@ void conceal_check_cursor_line(void) /// default_grid.Columns to access items in default_grid.chars[]. Use Rows /// and Columns for positioning text etc. where the final size of the screen is /// needed. -void screenalloc(void) +/// +/// @return whether resizing has been done +bool default_grid_alloc(void) { + static bool resizing = false; + // It's possible that we produce an out-of-memory message below, which // will cause this function to be called again. To break the loop, just // return here. if (resizing) { - return; + return false; } resizing = true; - int retry_count = 0; - -retry: // Allocation of the screen buffers is done only when the size changes and // when Rows and Columns have been set and we have started doing full // screen stuff. @@ -148,24 +170,9 @@ retry: || Columns == 0 || (!full_screen && default_grid.chars == NULL)) { resizing = false; - return; - } - - // Note that the window sizes are updated before reallocating the arrays, - // thus we must not redraw here! - RedrawingDisabled++; - - // win_new_screensize will recompute floats position, but tell the - // compositor to not redraw them yet - ui_comp_set_screen_valid(false); - if (msg_grid.chars) { - msg_grid_invalid = true; + return false; } - win_new_screensize(); // fit the windows in the new sized screen - - comp_col(); // recompute columns for shown command and ruler - // We're changing the size of the screen. // - Allocate new arrays for default_grid // - Move lines from the old arrays into the new arrays, clear extra @@ -177,14 +184,10 @@ retry: // size is wrong. grid_alloc(&default_grid, Rows, Columns, true, true); - StlClickDefinition *new_tab_page_click_defs = - xcalloc((size_t)Columns, sizeof(*new_tab_page_click_defs)); stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size); - xfree(tab_page_click_defs); - - tab_page_click_defs = new_tab_page_click_defs; - tab_page_click_defs_size = Columns; + tab_page_click_defs = stl_alloc_click_defs(tab_page_click_defs, Columns, + &tab_page_click_defs_size); default_grid.comp_height = Rows; default_grid.comp_width = Columns; @@ -193,35 +196,20 @@ retry: default_grid.col_offset = 0; default_grid.handle = DEFAULT_GRID_HANDLE; - must_redraw = CLEAR; // need to clear the screen later - - RedrawingDisabled--; - - // Do not apply autocommands more than 3 times to avoid an endless loop - // in case applying autocommands always changes Rows or Columns. - if (starting == 0 && ++retry_count <= 3) { - apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf); - // In rare cases, autocommands may have altered Rows or Columns, - // jump back to check if we need to allocate the screen again. - goto retry; - } - resizing = false; + return true; } void screenclear(void) { check_for_delay(false); - screenalloc(); // allocate screen buffers if size changed - - int i; if (starting == NO_SCREEN || default_grid.chars == NULL) { return; } // blank out the default grid - for (i = 0; i < default_grid.rows; i++) { + for (int i = 0; i < default_grid.rows; i++) { grid_clear_line(&default_grid, default_grid.line_offset[i], default_grid.cols, true); default_grid.line_wraps[i] = false; @@ -235,18 +223,18 @@ void screenclear(void) clear_cmdline = false; mode_displayed = false; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); redraw_cmdline = true; redraw_tabline = true; redraw_popupmenu = true; pum_invalidate(); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_floating) { - wp->w_redr_type = CLEAR; + wp->w_redr_type = UPD_CLEAR; } } - if (must_redraw == CLEAR) { - must_redraw = NOT_VALID; // no need to clear again + if (must_redraw == UPD_CLEAR) { + must_redraw = UPD_NOT_VALID; // no need to clear again } compute_cmdrow(); msg_row = cmdline_row; // put cursor on last line for messages @@ -281,13 +269,6 @@ void screen_resize(int width, int height) return; } - // curwin->w_buffer can be NULL when we are closing a window and the - // buffer has already been closed and removing a scrollbar causes a resize - // event. Don't resize then, it will happen after entering another buffer. - if (curwin->w_buffer == NULL) { - return; - } - resizing_screen = true; Rows = height; @@ -301,16 +282,42 @@ void screen_resize(int width, int height) width = Columns; p_lines = Rows; p_columns = Columns; + ui_call_grid_resize(1, width, height); - /// The window layout used to be adjusted here, but it now happens in - /// screenalloc() (also invoked from screenclear()). That is because the - /// recursize "resizing_screen" check above may skip this, but not screenalloc(). + int retry_count = 0; + resizing_autocmd = true; + + // In rare cases, autocommands may have altered Rows or Columns, + // so retry to check if we need to allocate the screen again. + while (default_grid_alloc()) { + // win_new_screensize will recompute floats position, but tell the + // compositor to not redraw them yet + ui_comp_set_screen_valid(false); + if (msg_grid.chars) { + msg_grid_invalid = true; + } + + RedrawingDisabled++; + + win_new_screensize(); // fit the windows in the new sized screen + + comp_col(); // recompute columns for shown command and ruler + + RedrawingDisabled--; + + // Do not apply autocommands more than 3 times to avoid an endless loop + // in case applying autocommands always changes Rows or Columns. + if (++retry_count > 3) { + break; + } - if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) { - screenclear(); + apply_autocmds(EVENT_VIMRESIZED, NULL, NULL, false, curbuf); } + resizing_autocmd = false; + redraw_all_later(UPD_CLEAR); + if (starting != NO_SCREEN) { maketitle(); @@ -320,14 +327,11 @@ void screen_resize(int width, int height) // We only redraw when it's needed: // - While at the more prompt or executing an external command, don't // redraw, but position the cursor. - // - While editing the command line, only redraw that. + // - While editing the command line, only redraw that. TODO: lies // - in Ex mode, don't redraw anything. // - Otherwise, redraw right now, and position the cursor. - // Always need to call update_screen() or screenalloc(), to make - // sure Rows/Columns and the size of the screen is correct! if (State == MODE_ASKMORE || State == MODE_EXTERNCMD || State == MODE_CONFIRM || exmode_active) { - screenalloc(); if (msg_grid.chars) { msg_grid_validate(); } @@ -341,7 +345,7 @@ void screen_resize(int width, int height) } if (State & MODE_CMDLINE) { redraw_popupmenu = false; - update_screen(NOT_VALID); + update_screen(); redrawcmdline(); if (pum_drawn()) { cmdline_pum_display(false); @@ -350,12 +354,12 @@ void screen_resize(int width, int height) update_topline(curwin); if (pum_drawn()) { // TODO(bfredl): ins_compl_show_pum wants to redraw the screen first. - // For now make sure the nested update_screen(0) won't redraw the + // For now make sure the nested update_screen() won't redraw the // pum at the old position. Try to untangle this later. redraw_popupmenu = false; ins_compl_show_pum(); } - update_screen(NOT_VALID); + update_screen(); if (redrawing()) { setcursor(); } @@ -370,9 +374,7 @@ void screen_resize(int width, int height) /// /// Most code shouldn't call this directly, rather use redraw_later() and /// and redraw_all_later() to mark parts of the screen as needing a redraw. -/// -/// @param type set to a NOT_VALID to force redraw of entire screen -int update_screen(int type) +int update_screen(void) { static bool did_intro = false; bool is_stl_global = global_stl_height() > 0; @@ -380,7 +382,7 @@ int update_screen(int type) // Don't do anything if the screen structures are (not yet) valid. // A VimResized autocmd can invoke redrawing in the middle of a resize, // which would bypass the checks in screen_resize for popupmenu etc. - if (!default_grid.chars || resizing) { + if (resizing_autocmd || !default_grid.chars) { return FAIL; } @@ -389,37 +391,25 @@ int update_screen(int type) diff_redraw(true); } - if (must_redraw) { - if (type < must_redraw) { // use maximal type - type = must_redraw; - } - - // must_redraw is reset here, so that when we run into some weird - // reason to redraw while busy redrawing (e.g., asynchronous - // scrolling), or update_topline() in win_update() will cause a - // scroll, or a decoration provider requires a redraw, the screen - // will be redrawn later or in win_update(). - must_redraw = 0; - } - - // Need to update w_lines[]. - if (curwin->w_lines_valid == 0 && type < NOT_VALID) { - type = NOT_VALID; - } - // Postpone the redrawing when it's not needed and when being called // recursively. if (!redrawing() || updating_screen) { - must_redraw = type; - if (type > INVERTED_ALL) { - curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now - } return FAIL; } + + int type = must_redraw; + + // must_redraw is reset here, so that when we run into some weird + // reason to redraw while busy redrawing (e.g., asynchronous + // scrolling), or update_topline() in win_update() will cause a + // scroll, or a decoration provider requires a redraw, the screen + // will be redrawn later or in win_update(). + must_redraw = 0; + updating_screen = 1; - display_tick++; // let syntax code know we're in a next round of - // display updating + display_tick++; // let syntax code know we're in a next round of + // display updating // Tricky: vim code can reset msg_scrolled behind our back, so need // separate bookkeeping for now. @@ -428,7 +418,7 @@ int update_screen(int type) msg_scrolled_at_flush = 0; } - if (type >= CLEAR || !default_grid.valid) { + if (type >= UPD_CLEAR || !default_grid.valid) { ui_comp_set_screen_valid(false); } @@ -443,68 +433,48 @@ int update_screen(int type) msg_grid.cols, false); } } - if (msg_use_msgsep()) { - msg_grid.throttled = false; - // CLEAR is already handled - if (type == NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) { - ui_comp_set_screen_valid(false); - for (int i = valid; i < Rows - p_ch; i++) { - grid_clear_line(&default_grid, default_grid.line_offset[i], - Columns, false); - } - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_floating) { - continue; - } - if (W_ENDROW(wp) > valid) { - wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID); - } - if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height > valid) { - wp->w_redr_status = true; - } - } - if (is_stl_global && Rows - p_ch - 1 > valid) { - curwin->w_redr_status = true; - } + msg_grid.throttled = false; + bool was_invalidated = false; + + // UPD_CLEAR is already handled + if (type == UPD_NOT_VALID && !ui_has(kUIMultigrid) && msg_scrolled) { + was_invalidated = ui_comp_set_screen_valid(false); + for (int i = valid; i < Rows - p_ch; i++) { + grid_clear_line(&default_grid, default_grid.line_offset[i], + Columns, false); } - msg_grid_set_pos(Rows - (int)p_ch, false); - msg_grid_invalid = false; - } else if (msg_scrolled > Rows - 5) { // clearing is faster - type = CLEAR; - } else if (type != CLEAR) { - check_for_delay(false); - grid_ins_lines(&default_grid, 0, msg_scrolled, Rows, 0, Columns); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_floating) { continue; } - if (wp->w_winrow < msg_scrolled) { - if (W_ENDROW(wp) > msg_scrolled - && wp->w_redr_type < REDRAW_TOP - && wp->w_lines_valid > 0 - && wp->w_topline == wp->w_lines[0].wl_lnum) { - wp->w_upd_rows = msg_scrolled - wp->w_winrow; - wp->w_redr_type = REDRAW_TOP; - } else { - wp->w_redr_type = NOT_VALID; - if (wp->w_winrow + wp->w_winbar_height <= msg_scrolled) { - wp->w_redr_status = true; - } - } + if (W_ENDROW(wp) > valid) { + // TODO(bfredl): too pessimistic. type could be UPD_NOT_VALID + // only because windows that are above the separator. + wp->w_redr_type = MAX(wp->w_redr_type, UPD_NOT_VALID); + } + if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height > valid) { + wp->w_redr_status = true; } } - if (is_stl_global && Rows - p_ch - 1 <= msg_scrolled) { + if (is_stl_global && Rows - p_ch - 1 > valid) { curwin->w_redr_status = true; } - redraw_cmdline = true; - redraw_tabline = true; + } + msg_grid_set_pos(Rows - (int)p_ch, false); + msg_grid_invalid = false; + if (was_invalidated) { + // screen was only invalid for the msgarea part. + // @TODO(bfredl): using the same "valid" flag + // for both messages and floats moving is bit of a mess. + ui_comp_set_screen_valid(true); } msg_scrolled = 0; msg_scrolled_at_flush = 0; + msg_grid_scroll_discount = 0; need_wait_return = false; } - win_ui_flush(); + win_ui_flush(true); msg_ext_check_clear(); // reset cmdline_row now (may have been changed temporarily) @@ -517,10 +487,11 @@ int update_screen(int type) hl_changed = true; } - if (type == CLEAR) { // first clear screen - screenclear(); // will reset clear_cmdline + if (type == UPD_CLEAR) { // first clear screen + screenclear(); // will reset clear_cmdline + // and set UPD_NOT_VALID for each window cmdline_screen_cleared(); // clear external cmdline state - type = NOT_VALID; + type = UPD_NOT_VALID; // must_redraw may be set indirectly, avoid another redraw later must_redraw = 0; } else if (!default_grid.valid) { @@ -528,16 +499,15 @@ int update_screen(int type) default_grid.valid = true; } - // After disabling msgsep the grid might not have been deallocated yet, - // hence we also need to check msg_grid.chars - if (type == NOT_VALID && (msg_use_grid() || msg_grid.chars)) { + // might need to clear space on default_grid for the message area. + if (type == UPD_NOT_VALID && clear_cmdline && !ui_has(kUIMessages)) { grid_fill(&default_grid, Rows - (int)p_ch, Rows, 0, Columns, ' ', ' ', 0); } ui_comp_set_screen_valid(true); DecorProviders providers; - decor_providers_start(&providers, type, &provider_err); + decor_providers_start(&providers, &provider_err); // "start" callback could have changed highlights for global elements if (win_check_ns_hl(NULL)) { @@ -551,37 +521,20 @@ int update_screen(int type) // Force redraw when width of 'number' or 'relativenumber' column // changes. - if (curwin->w_redr_type < NOT_VALID - && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu) + // TODO(bfredl): special casing curwin here is SÅ JÄVLA BULL. + // Either this should be done for all windows or not at all. + if (curwin->w_redr_type < UPD_NOT_VALID + && curwin->w_nrwidth != ((curwin->w_p_nu || curwin->w_p_rnu || *curwin->w_p_stc) ? number_width(curwin) : 0)) { - curwin->w_redr_type = NOT_VALID; - } - - // Only start redrawing if there is really something to do. - if (type == INVERTED) { - update_curswant(); - } - if (curwin->w_redr_type < type - && !((type == VALID - && curwin->w_lines[0].wl_valid - && curwin->w_topfill == curwin->w_old_topfill - && curwin->w_botfill == curwin->w_old_botfill - && curwin->w_topline == curwin->w_lines[0].wl_lnum) - || (type == INVERTED - && VIsual_active - && curwin->w_old_cursor_lnum == curwin->w_cursor.lnum - && curwin->w_old_visual_mode == VIsual_mode - && (curwin->w_valid & VALID_VIRTCOL) - && curwin->w_old_curswant == curwin->w_curswant))) { - curwin->w_redr_type = type; + curwin->w_redr_type = UPD_NOT_VALID; } // Redraw the tab pages line if needed. - if (redraw_tabline || type >= NOT_VALID) { - update_window_hl(curwin, type >= NOT_VALID); + if (redraw_tabline || type >= UPD_NOT_VALID) { + update_window_hl(curwin, type >= UPD_NOT_VALID); FOR_ALL_TABS(tp) { if (tp != curtab) { - update_window_hl(tp->tp_curwin, type >= NOT_VALID); + update_window_hl(tp->tp_curwin, type >= UPD_NOT_VALID); } } draw_tabline(); @@ -590,7 +543,7 @@ int update_screen(int type) // Correct stored syntax highlighting info for changes in each displayed // buffer. Each buffer must only be done once. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - update_window_hl(wp, type >= NOT_VALID || hl_changed); + update_window_hl(wp, type >= UPD_NOT_VALID || hl_changed); buf_T *buf = wp->w_buffer; if (buf->b_mod_set) { @@ -612,9 +565,9 @@ int update_screen(int type) screen_search_hl.rm.regprog = NULL; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_redr_type == CLEAR && wp->w_floating && wp->w_grid_alloc.chars) { + if (wp->w_redr_type == UPD_CLEAR && wp->w_floating && wp->w_grid_alloc.chars) { grid_invalidate(&wp->w_grid_alloc); - wp->w_redr_type = NOT_VALID; + wp->w_redr_type = UPD_NOT_VALID; } win_check_ns_hl(wp); @@ -622,7 +575,7 @@ int update_screen(int type) // reallocate grid if needed. win_grid_alloc(wp); - if (wp->w_redr_border || wp->w_redr_type >= NOT_VALID) { + if (wp->w_redr_border || wp->w_redr_type >= UPD_NOT_VALID) { win_redr_border(wp); } @@ -679,6 +632,20 @@ int update_screen(int type) return OK; } +static void win_border_redr_title(win_T *wp, ScreenGrid *grid, int col) +{ + VirtText title_chunks = wp->w_float_config.title_chunks; + + for (size_t i = 0; i < title_chunks.size; i++) { + char *text = title_chunks.items[i].text; + int cell = (int)mb_string2cells(text); + int hl_id = title_chunks.items[i].hl_id; + int attr = hl_id ? syn_id2attr(hl_id) : 0; + grid_puts(grid, text, 0, col, attr); + col += cell; + } +} + static void win_redr_border(win_T *wp) { wp->w_redr_border = false; @@ -699,9 +666,24 @@ static void win_redr_border(win_T *wp) if (adj[3]) { grid_put_schar(grid, 0, 0, chars[0], attrs[0]); } + for (int i = 0; i < icol; i++) { grid_put_schar(grid, 0, i + adj[3], chars[1], attrs[1]); } + + if (wp->w_float_config.title) { + int title_col = 0; + int title_width = wp->w_float_config.title_width; + AlignTextPos title_pos = wp->w_float_config.title_pos; + + if (title_pos == kAlignCenter) { + title_col = (icol - title_width) / 2 + 1; + } else { + title_col = title_pos == kAlignLeft ? 1 : icol - title_width + 1; + } + + win_border_redr_title(wp, grid, title_col); + } if (adj[1]) { grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]); } @@ -746,6 +728,8 @@ void show_cursor_info(bool always) if (!always && !redrawing()) { return; } + + win_check_ns_hl(curwin); if ((*p_stl != NUL || *curwin->w_p_stl != NUL) && (curwin->w_status_height || global_stl_height())) { redraw_custom_statusline(curwin); @@ -762,6 +746,7 @@ void show_cursor_info(bool always) maketitle(); } + win_check_ns_hl(NULL); // Redraw the tab pages line if needed. if (redraw_tabline) { draw_tabline(); @@ -854,29 +839,29 @@ static bool vsep_connected(win_T *wp, WindowCorner corner) /// Draw the vertical separator right of window "wp" static void draw_vsep_win(win_T *wp) { - int hl; - int c; - - if (wp->w_vsep_width) { - // draw the vertical separator right of this window - c = fillchar_vsep(wp, &hl); - grid_fill(&default_grid, wp->w_winrow, W_ENDROW(wp), - W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl); + if (!wp->w_vsep_width) { + return; } + + // draw the vertical separator right of this window + int hl; + int c = fillchar_vsep(wp, &hl); + grid_fill(&default_grid, wp->w_winrow, W_ENDROW(wp), + W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl); } /// Draw the horizontal separator below window "wp" static void draw_hsep_win(win_T *wp) { - int hl; - int c; - - if (wp->w_hsep_height) { - // draw the horizontal separator below this window - c = fillchar_hsep(wp, &hl); - grid_fill(&default_grid, W_ENDROW(wp), W_ENDROW(wp) + 1, - wp->w_wincol, W_ENDCOL(wp), c, c, hl); + if (!wp->w_hsep_height) { + return; } + + // draw the horizontal separator below this window + int hl; + int c = fillchar_hsep(wp, &hl); + grid_fill(&default_grid, W_ENDROW(wp), W_ENDROW(wp) + 1, + wp->w_wincol, W_ENDCOL(wp), c, c, hl); } /// Get the separator connector for specified window corner of window "wp" @@ -957,31 +942,26 @@ static void draw_sep_connectors_win(win_T *wp) /// /// How the window is redrawn depends on wp->w_redr_type. Each type also /// implies the one below it. -/// NOT_VALID redraw the whole window -/// SOME_VALID redraw the whole window but do scroll when possible -/// REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like VALID -/// INVERTED redraw the changed part of the Visual area -/// INVERTED_ALL redraw the whole Visual area -/// VALID 1. scroll up/down to adjust for a changed w_topline -/// 2. update lines at the top when scrolled down -/// 3. redraw changed text: -/// - if wp->w_buffer->b_mod_set set, update lines between -/// b_mod_top and b_mod_bot. -/// - if wp->w_redraw_top non-zero, redraw lines between -/// wp->w_redraw_top and wp->w_redr_bot. -/// - continue redrawing when syntax status is invalid. -/// 4. if scrolled up, update lines at the bottom. +/// UPD_NOT_VALID redraw the whole window +/// UPD_SOME_VALID redraw the whole window but do scroll when possible +/// UPD_REDRAW_TOP redraw the top w_upd_rows window lines, otherwise like UPD_VALID +/// UPD_INVERTED redraw the changed part of the Visual area +/// UPD_INVERTED_ALL redraw the whole Visual area +/// UPD_VALID 1. scroll up/down to adjust for a changed w_topline +/// 2. update lines at the top when scrolled down +/// 3. redraw changed text: +/// - if wp->w_buffer->b_mod_set set, update lines between +/// b_mod_top and b_mod_bot. +/// - if wp->w_redraw_top non-zero, redraw lines between +/// wp->w_redraw_top and wp->w_redr_bot. +/// - continue redrawing when syntax status is invalid. +/// 4. if scrolled up, update lines at the bottom. /// This results in three areas that may need updating: /// top: from first row to top_end (when scrolled down) /// mid: from mid_start to mid_end (update inversion or changed text) /// bot: from bot_start to last row (when scrolled up) static void win_update(win_T *wp, DecorProviders *providers) { - bool called_decor_providers = false; -win_update_start: - ; - buf_T *buf = wp->w_buffer; - int type; int top_end = 0; // Below last row of the top area that needs // updating. 0 when no top area updating. int mid_start = 999; // first row of the mid area that needs @@ -993,31 +973,28 @@ win_update_start: bool scrolled_down = false; // true when scrolled down when w_topline got smaller a bit bool top_to_mod = false; // redraw above mod_top - int row; // current window row to display - linenr_T lnum; // current buffer lnum to display - int idx; // current index in w_lines[] - int srow; // starting row of the current line + int bot_scroll_start = 999; // first line that needs to be redrawn due to + // scrolling. only used for EOB - bool eof = false; // if true, we hit the end of the file - bool didline = false; // if true, we finished the last line - int i; - long j; static bool recursive = false; // being called recursively - const linenr_T old_botline = wp->w_botline; + // Remember what happened to the previous line. -#define DID_NONE 1 // didn't update a line -#define DID_LINE 2 // updated a normal line -#define DID_FOLD 3 // updated a folded line - int did_update = DID_NONE; + enum { + DID_NONE = 1, // didn't update a line + DID_LINE = 2, // updated a normal line + DID_FOLD = 3, // updated a folded line + } did_update = DID_NONE; + linenr_T syntax_last_parsed = 0; // last parsed text line linenr_T mod_top = 0; linenr_T mod_bot = 0; - int save_got_int; - type = wp->w_redr_type; + int type = wp->w_redr_type; - if (type >= NOT_VALID) { + if (type >= UPD_NOT_VALID) { + // TODO(bfredl): should only be implied for CLEAR, not NOT_VALID! wp->w_redr_status = true; + wp->w_lines_valid = 0; } @@ -1039,16 +1016,32 @@ win_update_start: return; } + buf_T *buf = wp->w_buffer; + + // reset got_int, otherwise regexp won't work + int save_got_int = got_int; + got_int = 0; + // Set the time limit to 'redrawtime'. + proftime_T syntax_tm = profile_setlimit(p_rdt); + syn_set_timeout(&syntax_tm); + + win_extmark_arr.size = 0; + + decor_redraw_reset(buf, &decor_state); + + DecorProviders line_providers; + decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); + redraw_win_signcol(wp); init_search_hl(wp, &screen_search_hl); // Force redraw when width of 'number' or 'relativenumber' column // changes. - i = (wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) : 0; - if (wp->w_nrwidth != i) { - type = NOT_VALID; - wp->w_nrwidth = i; + int nrwidth = (wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc) ? number_width(wp) : 0; + if (wp->w_nrwidth != nrwidth) { + type = UPD_NOT_VALID; + wp->w_nrwidth = nrwidth; if (buf->terminal) { terminal_check_size(buf->terminal); @@ -1059,7 +1052,7 @@ win_update_start: // When there are both inserted/deleted lines and specific lines to be // redrawn, w_redraw_top and w_redraw_bot may be invalid, just redraw // everything (only happens when redrawing is off for while). - type = NOT_VALID; + type = UPD_NOT_VALID; } else { // Set mod_top to the first line that needs displaying because of // changes. Set mod_bot to the first line after the changes. @@ -1096,12 +1089,12 @@ win_update_start: } else { const matchitem_T *cur = wp->w_match_head; while (cur != NULL) { - if (cur->match.regprog != NULL - && re_multiline(cur->match.regprog)) { + if (cur->mit_match.regprog != NULL + && re_multiline(cur->mit_match.regprog)) { top_to_mod = true; break; } - cur = cur->next; + cur = cur->mit_next; } } } @@ -1120,7 +1113,7 @@ win_update_start: // to this line. If there is no valid entry, use MAXLNUM. lnumt = wp->w_topline; lnumb = MAXLNUM; - for (i = 0; i < wp->w_lines_valid; i++) { + for (int i = 0; i < wp->w_lines_valid; i++) { if (wp->w_lines[i].wl_valid) { if (wp->w_lines[i].wl_lastlnum < mod_top) { lnumt = wp->w_lines[i].wl_lastlnum + 1; @@ -1168,14 +1161,15 @@ win_update_start: mod_bot = MAXLNUM; } } + wp->w_redraw_top = 0; // reset for next time wp->w_redraw_bot = 0; // When only displaying the lines at the top, set top_end. Used when // window has scrolled down for msg_scrolled. - if (type == REDRAW_TOP) { - j = 0; - for (i = 0; i < wp->w_lines_valid; i++) { + if (type == UPD_REDRAW_TOP) { + long j = 0; + for (int i = 0; i < wp->w_lines_valid; i++) { j += wp->w_lines[i].wl_size; if (j >= wp->w_upd_rows) { top_end = (int)j; @@ -1184,10 +1178,10 @@ win_update_start: } if (top_end == 0) { // not found (cannot happen?): redraw everything - type = NOT_VALID; + type = UPD_NOT_VALID; } else { - // top area defined, the rest is VALID - type = VALID; + // top area defined, the rest is UPD_VALID + type = UPD_VALID; } } @@ -1197,8 +1191,8 @@ win_update_start: // 2: wp->w_topline is below wp->w_lines[0].wl_lnum: may scroll up // 3: wp->w_topline is wp->w_lines[0].wl_lnum: find first entry in // w_lines[] that needs updating. - if ((type == VALID || type == SOME_VALID - || type == INVERTED || type == INVERTED_ALL) + if ((type == UPD_VALID || type == UPD_SOME_VALID + || type == UPD_INVERTED || type == UPD_INVERTED_ALL) && !wp->w_botfill && !wp->w_old_botfill) { if (mod_top != 0 && wp->w_topline == mod_top @@ -1211,6 +1205,7 @@ win_update_start: || (wp->w_topline == wp->w_lines[0].wl_lnum && wp->w_topfill > wp->w_old_topfill))) { // New topline is above old topline: May scroll down. + long j; if (hasAnyFolding(wp)) { linenr_T ln; @@ -1228,7 +1223,7 @@ win_update_start: j = wp->w_lines[0].wl_lnum - wp->w_topline; } if (j < wp->w_grid.rows - 2) { // not too far off - i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1); + int i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1); // insert extra lines for previously invisible filler lines if (wp->w_lines[0].wl_lnum != wp->w_topline) { i += win_get_fill(wp, wp->w_lines[0].wl_lnum) - wp->w_old_topfill; @@ -1238,6 +1233,7 @@ win_update_start: // If not the last window, delete the lines at the bottom. // win_ins_lines may fail when the terminal can't do it. win_scroll_lines(wp, 0, i); + bot_scroll_start = 0; if (wp->w_lines_valid != 0) { // Need to update rows that are new, stop at the // first one that scrolled down. @@ -1249,6 +1245,7 @@ win_update_start: if ((wp->w_lines_valid += (linenr_T)j) > wp->w_grid.rows) { wp->w_lines_valid = wp->w_grid.rows; } + int idx; for (idx = wp->w_lines_valid; idx - j >= 0; idx--) { wp->w_lines[idx] = wp->w_lines[idx - j]; } @@ -1268,9 +1265,9 @@ win_update_start: // needs updating. // try to find wp->w_topline in wp->w_lines[].wl_lnum - j = -1; - row = 0; - for (i = 0; i < wp->w_lines_valid; i++) { + long j = -1; + int row = 0; + for (int i = 0; i < wp->w_lines_valid; i++) { if (wp->w_lines[i].wl_valid && wp->w_lines[i].wl_lnum == wp->w_topline) { j = i; @@ -1298,6 +1295,7 @@ win_update_start: if (row > 0) { win_scroll_lines(wp, 0, -row); bot_start = wp->w_grid.rows - row; + bot_scroll_start = bot_start; } if ((row == 0 || bot_start < 999) && wp->w_lines_valid != 0) { // Skip the lines (below the deleted lines) that are still @@ -1305,7 +1303,7 @@ win_update_start: // upwards, to compensate for the deleted lines. Set // bot_start to the first row that needs redrawing. bot_start = 0; - idx = 0; + int idx = 0; for (;;) { wp->w_lines[idx] = wp->w_lines[j]; // stop at line that didn't fit, unless it is still @@ -1339,25 +1337,25 @@ win_update_start: mid_end = wp->w_grid.rows; } } else { - // Not VALID or INVERTED: redraw all lines. + // Not UPD_VALID or UPD_INVERTED: redraw all lines. mid_start = 0; mid_end = wp->w_grid.rows; } - if (type == SOME_VALID) { - // SOME_VALID: redraw all lines. + if (type == UPD_SOME_VALID) { + // UPD_SOME_VALID: redraw all lines. mid_start = 0; mid_end = wp->w_grid.rows; - type = NOT_VALID; + type = UPD_NOT_VALID; } // check if we are updating or removing the inverted part if ((VIsual_active && buf == curwin->w_buffer) - || (wp->w_old_cursor_lnum != 0 && type != NOT_VALID)) { + || (wp->w_old_cursor_lnum != 0 && type != UPD_NOT_VALID)) { linenr_T from, to; if (VIsual_active) { - if (VIsual_mode != wp->w_old_visual_mode || type == INVERTED_ALL) { + if (VIsual_mode != wp->w_old_visual_mode || type == UPD_INVERTED_ALL) { // If the type of Visual selection changed, redraw the whole // selection. Also when the ownership of the X selection is // gained or lost. @@ -1443,7 +1441,7 @@ win_update_start: pos.lnum += cursor_above ? 1 : -1) { colnr_T t; - pos.col = (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false)); + pos.col = (colnr_T)strlen(ml_get_buf(wp->w_buffer, pos.lnum, false)); getvvcol(wp, &pos, NULL, NULL, &t); if (toc < t) { toc = t; @@ -1502,9 +1500,9 @@ win_update_start: // above the Visual area and reset wl_valid, do count these for // mid_end (in srow). if (mid_start > 0) { - lnum = wp->w_topline; - idx = 0; - srow = 0; + linenr_T lnum = wp->w_topline; + int idx = 0; + int srow = 0; if (scrolled_down) { mid_start = top_end; } else { @@ -1550,38 +1548,18 @@ win_update_start: wp->w_old_visual_col = 0; } - // reset got_int, otherwise regexp won't work - save_got_int = got_int; - got_int = 0; - // Set the time limit to 'redrawtime'. - proftime_T syntax_tm = profile_setlimit(p_rdt); - syn_set_timeout(&syntax_tm); - - // Update all the window rows. - idx = 0; // first entry in w_lines[].wl_size - row = 0; - srow = 0; - lnum = wp->w_topline; // first line shown in window - - win_extmark_arr.size = 0; - - decor_redraw_reset(buf, &decor_state); - - DecorProviders line_providers; - decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); - (void)win_signcol_count(wp); // check if provider changed signcol width - if (must_redraw != 0) { - must_redraw = 0; - if (!called_decor_providers) { - called_decor_providers = true; - goto win_update_start; - } - } - bool cursorline_standout = win_cursorline_standout(wp); win_check_ns_hl(wp); + // Update all the window rows. + int idx = 0; // first entry in w_lines[].wl_size + int row = 0; // current window row to display + int srow = 0; // starting row of the current line + linenr_T lnum = wp->w_topline; // first line shown in window + + bool eof = false; // if true, we hit the end of the file + bool didline = false; // if true, we finished the last line for (;;) { // stop updating when reached the end of the window (check for _past_ // the end of the window is at the end of the loop) @@ -1645,6 +1623,7 @@ win_update_start: int new_rows = 0; int xtra_rows; linenr_T l; + int i; // Count the old number of window rows, using w_lines[], which // should still contain the sizes for the lines as they are @@ -1675,10 +1654,11 @@ win_update_start: // need to redraw until the end of the window. // Inserting/deleting lines has no use. bot_start = 0; + bot_scroll_start = 0; } else { // Able to count old number of rows: Count new window // rows, and may insert/delete lines - j = idx; + long j = idx; for (l = lnum; l < mod_bot; l++) { if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) { new_rows++; @@ -1705,6 +1685,7 @@ win_update_start: } else { win_scroll_lines(wp, row, xtra_rows); bot_start = wp->w_grid.rows + xtra_rows; + bot_scroll_start = bot_start; } } else if (xtra_rows > 0) { // May scroll text down. If there is not enough @@ -1714,6 +1695,7 @@ win_update_start: mod_bot = MAXLNUM; } else { win_scroll_lines(wp, row + old_rows, xtra_rows); + bot_scroll_start = 0; if (top_end > row + old_rows) { // Scrolled the part at the top that requires // updating down. @@ -1792,7 +1774,7 @@ win_update_start: // Let the syntax stuff know we skipped a few lines. if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum && syntax_present(wp)) { - syntax_end_parsing(syntax_last_parsed + 1); + syntax_end_parsing(wp, syntax_last_parsed + 1); } // Display one line @@ -1847,6 +1829,18 @@ win_update_start: did_update = DID_NONE; } + // 'statuscolumn' width has changed or errored, start from the top. + if (wp->w_redr_statuscol) { + wp->w_redr_statuscol = false; + idx = 0; + row = 0; + lnum = wp->w_topline; + wp->w_lines_valid = 0; + wp->w_valid &= ~VALID_WCOL; + decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); + continue; + } + if (lnum > buf->b_ml.ml_line_count) { eof = true; break; @@ -1866,9 +1860,11 @@ win_update_start: // Let the syntax stuff know we stop parsing here. if (syntax_last_parsed != 0 && syntax_present(wp)) { - syntax_end_parsing(syntax_last_parsed + 1); + syntax_end_parsing(wp, syntax_last_parsed + 1); } + const linenr_T old_botline = wp->w_botline; + // If we didn't hit the end of the file, and we didn't finish the last // line we were working on, then the line didn't fit. wp->w_empty_rows = 0; @@ -1885,34 +1881,38 @@ win_update_start: wp->w_filler_rows = wp->w_grid.rows - srow; } else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate" int scr_row = wp->w_grid.rows - 1; + int symbol = wp->w_p_fcs_chars.lastline; + char fillbuf[12]; // 2 characters of 6 bytes + int charlen = utf_char2bytes(symbol, &fillbuf[0]); + utf_char2bytes(symbol, &fillbuf[charlen]); // Last line isn't finished: Display "@@@" in the last screen line. - grid_puts_len(&wp->w_grid, (char_u *)"@@", MIN(wp->w_grid.cols, 2), scr_row, 0, at_attr); - - grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, - '@', ' ', at_attr); + grid_puts_len(&wp->w_grid, fillbuf, MIN(wp->w_grid.cols, 2) * charlen, scr_row, 0, at_attr); + grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, symbol, ' ', at_attr); set_empty_rows(wp, srow); wp->w_botline = lnum; } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline" int start_col = wp->w_grid.cols - 3; + int symbol = wp->w_p_fcs_chars.lastline; // Last line isn't finished: Display "@@@" at the end. grid_fill(&wp->w_grid, wp->w_grid.rows - 1, wp->w_grid.rows, - MAX(start_col, 0), wp->w_grid.cols, '@', '@', at_attr); + MAX(start_col, 0), wp->w_grid.cols, symbol, symbol, at_attr); set_empty_rows(wp, srow); wp->w_botline = lnum; } else { - win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.rows, HLF_AT); + win_draw_end(wp, wp->w_p_fcs_chars.lastline, ' ', true, srow, wp->w_grid.rows, HLF_AT); + set_empty_rows(wp, srow); wp->w_botline = lnum; } } else { if (eof) { // we hit the end of the file wp->w_botline = buf->b_ml.ml_line_count + 1; - j = win_get_fill(wp, wp->w_botline); + long j = win_get_fill(wp, wp->w_botline); if (j > 0 && !wp->w_botfill && row < wp->w_grid.rows) { // Display filler text below last line. win_line() will check // for ml_line_count+1 and only draw filler lines - foldinfo_T info = FOLDINFO_INIT; + foldinfo_T info = { 0 }; row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, false, false, info, &line_providers, &provider_err); } @@ -1920,15 +1920,27 @@ win_update_start: wp->w_botline = lnum; } - // make sure the rest of the screen is blank - // write the 'eob' character to rows that aren't part of the file. - win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', false, row, wp->w_grid.rows, + // Make sure the rest of the screen is blank. + // write the "eob" character from 'fillchars' to rows that aren't part + // of the file. + // TODO(bfredl): just keep track of the valid EOB area from last redraw? + int lastline = bot_scroll_start; + if (mid_end >= row) { + lastline = MIN(lastline, mid_start); + } + // if (mod_bot > buf->b_ml.ml_line_count + 1) { + if (mod_bot > buf->b_ml.ml_line_count) { + lastline = 0; + } + + win_draw_end(wp, wp->w_p_fcs_chars.eob, ' ', false, MAX(lastline, row), wp->w_grid.rows, HLF_EOB); + set_empty_rows(wp, row); } kvi_destroy(line_providers); - if (wp->w_redr_type >= REDRAW_TOP) { + if (wp->w_redr_type >= UPD_REDRAW_TOP) { draw_vsep_win(wp); draw_hsep_win(wp); draw_sep_connectors_win(wp); @@ -1966,11 +1978,11 @@ win_update_start: update_topline(curwin); // may invalidate w_botline again if (must_redraw != 0) { // Don't update for changes in buffer again. - i = curbuf->b_mod_set; + int mod_set = curbuf->b_mod_set; curbuf->b_mod_set = false; win_update(curwin, providers); must_redraw = 0; - curbuf->b_mod_set = i; + curbuf->b_mod_set = mod_set; } recursive = false; } @@ -1982,16 +1994,16 @@ win_update_start: } } -/// Redraw a window later, with update_screen(type). +/// Redraw a window later, with wp->w_redr_type >= type. /// /// Set must_redraw only if not already set to a higher value. -/// e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing. +/// e.g. if must_redraw is UPD_CLEAR, type UPD_NOT_VALID will do nothing. void redraw_later(win_T *wp, int type) FUNC_ATTR_NONNULL_ALL { if (!exiting && wp->w_redr_type < type) { wp->w_redr_type = type; - if (type >= NOT_VALID) { + if (type >= UPD_NOT_VALID) { wp->w_lines_valid = 0; } if (must_redraw < type) { // must_redraw is the maximum of all windows @@ -2015,7 +2027,7 @@ void redraw_all_later(int type) void screen_invalidate_highlights(void) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); wp->w_grid_alloc.valid = false; } } @@ -2035,12 +2047,14 @@ void redraw_buf_later(buf_T *buf, int type) } } -void redraw_buf_line_later(buf_T *buf, linenr_T line) +void redraw_buf_line_later(buf_T *buf, linenr_T line, bool force) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf - && line >= wp->w_topline && line < wp->w_botline) { - redrawWinline(wp, line); + if (wp->w_buffer == buf) { + redrawWinline(wp, MIN(line, buf->b_ml.ml_line_count)); + if (force && line > buf->b_ml.ml_line_count) { + wp->w_redraw_bot = line; + } } } } @@ -2056,7 +2070,7 @@ void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline) if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) { wp->w_redraw_bot = lastline; } - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } } } @@ -2070,8 +2084,8 @@ void redraw_buf_status_later(buf_T *buf) || (wp == curwin && global_stl_height()) || wp->w_winbar_height)) { wp->w_redr_status = true; - if (must_redraw < VALID) { - must_redraw = VALID; + if (must_redraw < UPD_VALID) { + must_redraw = UPD_VALID; } } } @@ -2086,7 +2100,7 @@ void status_redraw_all(void) if ((!is_stl_global && wp->w_status_height) || (is_stl_global && wp == curwin) || wp->w_winbar_height) { wp->w_redr_status = true; - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } } } @@ -2106,7 +2120,7 @@ void status_redraw_buf(buf_T *buf) if (wp->w_buffer == buf && ((!is_stl_global && wp->w_status_height) || (is_stl_global && wp == curwin) || wp->w_winbar_height)) { wp->w_redr_status = true; - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } } } @@ -2116,10 +2130,13 @@ void redraw_statuslines(void) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_redr_status) { + win_check_ns_hl(wp); win_redr_winbar(wp); win_redr_status(wp); } } + + win_check_ns_hl(NULL); if (redraw_tabline) { draw_tabline(); } @@ -2162,6 +2179,6 @@ void redrawWinline(win_T *wp, linenr_T lnum) if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) { wp->w_redraw_bot = lnum; } - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } } diff --git a/src/nvim/drawscreen.h b/src/nvim/drawscreen.h index 3eac1caaa1..c14703dfa9 100644 --- a/src/nvim/drawscreen.h +++ b/src/nvim/drawscreen.h @@ -1,18 +1,21 @@ #ifndef NVIM_DRAWSCREEN_H #define NVIM_DRAWSCREEN_H +#include <stdbool.h> + #include "nvim/drawline.h" +#include "nvim/macros.h" /// flags for update_screen() /// The higher the value, the higher the priority enum { - VALID = 10, ///< buffer not changed, or changes marked with b_mod_* - INVERTED = 20, ///< redisplay inverted part that changed - INVERTED_ALL = 25, ///< redisplay whole inverted part - REDRAW_TOP = 30, ///< display first w_upd_rows screen lines - SOME_VALID = 35, ///< like NOT_VALID but may scroll - NOT_VALID = 40, ///< buffer needs complete redraw - CLEAR = 50, ///< screen messed up, clear it + UPD_VALID = 10, ///< buffer not changed, or changes marked with b_mod_* + UPD_INVERTED = 20, ///< redisplay inverted part that changed + UPD_INVERTED_ALL = 25, ///< redisplay whole inverted part + UPD_REDRAW_TOP = 30, ///< display first w_upd_rows screen lines + UPD_SOME_VALID = 35, ///< like UPD_NOT_VALID but may scroll + UPD_NOT_VALID = 40, ///< buffer needs complete redraw + UPD_CLEAR = 50, ///< screen messed up, clear it }; /// While redrawing the screen this flag is set. It means the screen size diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 6583ac8584..095d73f53f 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1,16 +1,17 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * edit.c: functions for Insert mode - */ +// edit.c: functions for Insert mode #include <assert.h> +#include <ctype.h> #include <inttypes.h> #include <stdbool.h> #include <string.h> +#include <sys/types.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" @@ -19,20 +20,23 @@ #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/event/loop.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" -#include "nvim/main.h" +#include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -45,17 +49,18 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/input.h" -#include "nvim/os/time.h" -#include "nvim/path.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" -#include "nvim/quickfix.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/search.h" -#include "nvim/spell.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/terminal.h" +#include "nvim/textformat.h" +#include "nvim/textobject.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -81,31 +86,34 @@ typedef struct insert_state { int did_restart_edit; // remember if insert mode was restarted // after a ctrl+o bool nomove; - char_u *ptr; + char *ptr; } InsertState; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "edit.c.generated.h" #endif -#define BACKSPACE_CHAR 1 -#define BACKSPACE_WORD 2 -#define BACKSPACE_WORD_NOT_SPACE 3 -#define BACKSPACE_LINE 4 +enum { + BACKSPACE_CHAR = 1, + BACKSPACE_WORD = 2, + BACKSPACE_WORD_NOT_SPACE = 3, + BACKSPACE_LINE = 4, +}; + +/// Set when doing something for completion that may call edit() recursively, +/// which is not allowed. +static bool compl_busy = false; static colnr_T Insstart_textlen; // length of line when insert started static colnr_T Insstart_blank_vcol; // vcol for first inserted blank static bool update_Insstart_orig = true; // set Insstart_orig to Insstart -static char_u *last_insert = NULL; // the text of the previous insert, - // K_SPECIAL is escaped -static int last_insert_skip; // nr of chars in front of previous insert -static int new_insert_skip; // nr of chars in front of current insert +static char *last_insert = NULL; // the text of the previous insert, K_SPECIAL is escaped +static int last_insert_skip; // nr of chars in front of previous insert +static int new_insert_skip; // nr of chars in front of current insert static int did_restart_edit; // "restart_edit" when calling edit() static bool can_cindent; // may do cindenting on this line -static int old_indent = 0; // for ^^D command in insert mode - static int revins_on; // reverse insert mode on static int revins_chars; // how much to skip after edit static int revins_legal; // was the last char 'legal'? @@ -115,8 +123,6 @@ static bool ins_need_undo; // call u_save() before inserting a // char. Set when edit() is called. // after that arrow_used is used. -static bool did_add_space = false; // auto_format() added an extra space - // under the cursor static TriState dont_sync_undo = kFalse; // CTRL-G U prevents syncing undo // for the next left/right cursor key @@ -143,14 +149,14 @@ static void insert_enter(InsertState *s) pos_T save_cursor = curwin->w_cursor; if (s->cmdchar == 'R') { - s->ptr = (char_u *)"r"; + s->ptr = "r"; } else if (s->cmdchar == 'V') { - s->ptr = (char_u *)"v"; + s->ptr = "v"; } else { - s->ptr = (char_u *)"i"; + s->ptr = "i"; } - set_vim_var_string(VV_INSERTMODE, (char *)s->ptr, 1); + set_vim_var_string(VV_INSERTMODE, s->ptr, 1); set_vim_var_string(VV_CHAR, NULL, -1); ins_apply_autocmds(EVENT_INSERTENTER); @@ -276,7 +282,7 @@ static void insert_enter(InsertState *s) if (s->ptr[1] == NUL) { curwin->w_cursor.col++; } else { - s->i = utfc_ptr2len((char *)s->ptr); + s->i = utfc_ptr2len(s->ptr); if (s->ptr[s->i] == NUL) { curwin->w_cursor.col += s->i; } @@ -321,7 +327,7 @@ static void insert_enter(InsertState *s) if (s->ptr == NULL) { new_insert_skip = 0; } else { - new_insert_skip = (int)STRLEN(s->ptr); + new_insert_skip = (int)strlen(s->ptr); xfree(s->ptr); } @@ -439,8 +445,7 @@ static int insert_check(VimState *state) s->mincol = curwin->w_wcol; validate_cursor_col(); - if ( - curwin->w_wcol < s->mincol - tabstop_at(get_nolist_virtcol(), + if (curwin->w_wcol < s->mincol - tabstop_at(get_nolist_virtcol(), curbuf->b_p_ts, curbuf->b_p_vts_array) && curwin->w_wrow == curwin->w_winrow @@ -448,7 +453,7 @@ static int insert_check(VimState *state) && (curwin->w_cursor.lnum != curwin->w_topline || curwin->w_topfill > 0)) { if (curwin->w_topfill > 0) { - --curwin->w_topfill; + curwin->w_topfill--; } else if (hasFolding(curwin->w_topline, NULL, &s->old_topline)) { set_topline(curwin, s->old_topline + 1); } else { @@ -552,12 +557,12 @@ static int insert_execute(VimState *state, int key) // completion: Add to "compl_leader". if (ins_compl_accept_char(s->c)) { // Trigger InsertCharPre. - char_u *str = do_insert_char_pre(s->c); - char_u *p; + char *str = do_insert_char_pre(s->c); + char *p; if (str != NULL) { for (p = str; *p != NUL; MB_PTR_ADV(p)) { - ins_compl_addleader(utf_ptr2char((char *)p)); + ins_compl_addleader(utf_ptr2char(p)); } xfree(str); } else { @@ -624,16 +629,17 @@ static int insert_execute(VimState *state, int key) } if (cindent_on() && ctrl_x_mode_none()) { + s->line_is_white = inindent(0); // A key name preceded by a bang means this key is not to be // inserted. Skip ahead to the re-indenting below. - // A key name preceded by a star means that indenting has to be - // done before inserting the key. - s->line_is_white = inindent(0); - if (in_cinkeys(s->c, '!', s->line_is_white)) { - insert_do_cindent(s); + if (in_cinkeys(s->c, '!', s->line_is_white) + && stop_arrow() == OK) { + do_c_expr_indent(); return 1; // continue } + // A key name preceded by a star means that indenting has to be + // done before inserting the key. if (can_cindent && in_cinkeys(s->c, '*', s->line_is_white) && stop_arrow() == OK) { do_c_expr_indent(); @@ -901,6 +907,12 @@ check_pum: } pum_want.active = false; } + + if (curbuf->b_u_synced) { + // The K_EVENT, K_COMMAND, or K_LUA caused undo to be synced. + // Need to save the line for undo before inserting the next char. + ins_need_undo = true; + } break; case K_HOME: // <Home> @@ -1090,7 +1102,7 @@ check_pum: // but it is under other ^X modes if (*curbuf->b_p_cpt == NUL && (ctrl_x_mode_normal() || ctrl_x_mode_whole_line()) - && !(compl_cont_status & CONT_LOCAL)) { + && !compl_status_local()) { goto normalchar; } @@ -1109,7 +1121,7 @@ normalchar: if (!p_paste) { // Trigger InsertCharPre. - char *str = (char *)do_insert_char_pre(s->c); + char *str = do_insert_char_pre(s->c); char *p; if (str != NULL) { @@ -1176,9 +1188,11 @@ normalchar: static void insert_do_complete(InsertState *s) { compl_busy = true; + disable_fold_update++; // don't redraw folds here if (ins_complete(s->c, true) == FAIL) { - compl_cont_status = 0; + compl_status_clear(); } + disable_fold_update--; compl_busy = false; can_si = may_do_si(); // allow smartindenting } @@ -1225,9 +1239,8 @@ bool edit(int cmdchar, bool startln, long count) restart_edit = 'i'; force_restart_edit = true; return false; - } else { - return terminal_enter(); } + return terminal_enter(); } // Don't allow inserting in the sandbox. @@ -1283,7 +1296,7 @@ void ins_redraw(bool ready) // a "(". The autocommand may also require a redraw, so it's done // again below, unfortunately. if (syntax_present(curwin) && must_redraw) { - update_screen(0); + update_screen(); } // Make sure curswant is correct, an autocommand may call // getcurpos() @@ -1331,8 +1344,7 @@ void ins_redraw(bool ready) } if (ready) { - // Trigger Scroll if viewport changed. - may_trigger_winscrolled(); + may_trigger_win_scrolled_resized(); } // Trigger BufModified if b_changed_invalid is set. @@ -1345,7 +1357,7 @@ void ins_redraw(bool ready) pum_check_clear(); if (must_redraw) { - update_screen(0); + update_screen(); } else if (clear_cmdline || redraw_cmdline) { showmode(); // clear cmdline and show mode } @@ -1354,9 +1366,7 @@ void ins_redraw(bool ready) emsg_on_display = false; // may remove error message now } -/* - * Handle a CTRL-V or CTRL-Q typed in Insert mode. - */ +// Handle a CTRL-V or CTRL-Q typed in Insert mode. static void ins_ctrl_v(void) { int c; @@ -1386,10 +1396,8 @@ static void ins_ctrl_v(void) revins_legal++; } -/* - * Put a character directly onto the screen. It's not stored in a buffer. - * Used while handling CTRL-K, CTRL-V, etc. in Insert mode. - */ +// Put a character directly onto the screen. It's not stored in a buffer. +// Used while handling CTRL-K, CTRL-V, etc. in Insert mode. static int pc_status; #define PC_STATUS_UNSET 0 // pc_bytes was not set #define PC_STATUS_RIGHT 1 // right half of double-wide char @@ -1433,25 +1441,25 @@ void edit_putchar(int c, bool highlight) // save the character to be able to put it back if (pc_status == PC_STATUS_UNSET) { - grid_getbytes(&curwin->w_grid, pc_row, pc_col, pc_bytes, &pc_attr); + grid_getbytes(&curwin->w_grid, pc_row, pc_col, (char *)pc_bytes, &pc_attr); pc_status = PC_STATUS_SET; } grid_putchar(&curwin->w_grid, c, pc_row, pc_col, attr); } } -/// Return the effective prompt for the specified buffer. -char_u *buf_prompt_text(const buf_T *const buf) +/// @return the effective prompt for the specified buffer. +char *buf_prompt_text(const buf_T *const buf) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { if (buf->b_prompt_text == NULL) { - return (char_u *)"% "; + return "% "; } - return (char_u *)buf->b_prompt_text; + return buf->b_prompt_text; } -// Return the effective prompt for the current buffer. -char_u *prompt_text(void) +/// @return the effective prompt for the current buffer. +char *prompt_text(void) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { return buf_prompt_text(curbuf); @@ -1461,17 +1469,17 @@ char_u *prompt_text(void) // Move the cursor to this line. static void init_prompt(int cmdchar_todo) { - char_u *prompt = prompt_text(); - char_u *text; + char *prompt = prompt_text(); + char *text; curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; text = get_cursor_line_ptr(); - if (STRNCMP(text, prompt, STRLEN(prompt)) != 0) { + if (strncmp(text, prompt, strlen(prompt)) != 0) { // prompt is missing, insert it or append a line with it if (*text == NUL) { - ml_replace(curbuf->b_ml.ml_line_count, (char *)prompt, true); + ml_replace(curbuf->b_ml.ml_line_count, prompt, true); } else { - ml_append(curbuf->b_ml.ml_line_count, (char *)prompt, 0, false); + ml_append(curbuf->b_ml.ml_line_count, prompt, 0, false); } curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; coladvance(MAXCOL); @@ -1479,9 +1487,9 @@ static void init_prompt(int cmdchar_todo) } // Insert always starts after the prompt, allow editing text after it. - if (Insstart_orig.lnum != curwin->w_cursor.lnum || Insstart_orig.col != (colnr_T)STRLEN(prompt)) { + if (Insstart_orig.lnum != curwin->w_cursor.lnum || Insstart_orig.col != (colnr_T)strlen(prompt)) { Insstart.lnum = curwin->w_cursor.lnum; - Insstart.col = (colnr_T)STRLEN(prompt); + Insstart.col = (colnr_T)strlen(prompt); Insstart_orig = Insstart; Insstart_textlen = Insstart.col; Insstart_blank_vcol = MAXCOL; @@ -1491,8 +1499,8 @@ static void init_prompt(int cmdchar_todo) if (cmdchar_todo == 'A') { coladvance(MAXCOL); } - if (curwin->w_cursor.col < (colnr_T)STRLEN(prompt)) { - curwin->w_cursor.col = (colnr_T)STRLEN(prompt); + if (curwin->w_cursor.col < (colnr_T)strlen(prompt)) { + curwin->w_cursor.col = (colnr_T)strlen(prompt); } // Make sure the cursor is in a valid position. check_cursor(); @@ -1503,12 +1511,10 @@ bool prompt_curpos_editable(void) FUNC_ATTR_PURE { return curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count - && curwin->w_cursor.col >= (int)STRLEN(prompt_text()); + && curwin->w_cursor.col >= (int)strlen(prompt_text()); } -/* - * Undo the previous edit_putchar(). - */ +// Undo the previous edit_putchar(). void edit_unputchar(void) { if (pc_status != PC_STATUS_UNSET && pc_row >= msg_scrolled) { @@ -1518,16 +1524,13 @@ void edit_unputchar(void) if (pc_status == PC_STATUS_RIGHT || pc_status == PC_STATUS_LEFT) { redrawWinline(curwin, curwin->w_cursor.lnum); } else { - grid_puts(&curwin->w_grid, pc_bytes, pc_row - msg_scrolled, pc_col, - pc_attr); + grid_puts(&curwin->w_grid, (char *)pc_bytes, pc_row - msg_scrolled, pc_col, pc_attr); } } } -/* - * Called when p_dollar is set: display a '$' at the end of the changed text - * Only works when cursor is in the line that changes. - */ +// Called when p_dollar is set: display a '$' at the end of the changed text +// Only works when cursor is in the line that changes. void display_dollar(colnr_T col) { colnr_T save_col; @@ -1540,7 +1543,7 @@ void display_dollar(colnr_T col) curwin->w_cursor.col = col; // If on the last byte of a multi-byte move to the first byte. - char_u *p = get_cursor_line_ptr(); + char *p = get_cursor_line_ptr(); curwin->w_cursor.col -= utf_head_off(p, p + col); curs_columns(curwin, false); // Recompute w_wrow and w_wcol if (curwin->w_wcol < curwin->w_grid.cols) { @@ -1550,11 +1553,9 @@ void display_dollar(colnr_T col) curwin->w_cursor.col = save_col; } -/* - * Call this function before moving the cursor from the normal insert position - * in insert mode. - */ -static void undisplay_dollar(void) +// Call this function before moving the cursor from the normal insert position +// in insert mode. +void undisplay_dollar(void) { if (dollar_vcol >= 0) { dollar_vcol = -1; @@ -1568,7 +1569,7 @@ static void undisplay_dollar(void) /// type == INDENT_DEC decrease indent (for CTRL-D) /// type == INDENT_SET set indent to "amount" /// -/// @param round if TRUE, round the indent to 'shiftwidth' (only with _INC and _Dec). +/// @param round if true, round the indent to 'shiftwidth' (only with _INC and _Dec). /// @param replaced replaced character, put on replace stack /// @param call_changed_bytes call changed_bytes() void change_indent(int type, int amount, int round, int replaced, int call_changed_bytes) @@ -1577,30 +1578,28 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang int last_vcol; int insstart_less; // reduction for Insstart.col int new_cursor_col; - char_u *ptr; + char *ptr; int save_p_list; int start_col; colnr_T vc; colnr_T orig_col = 0; // init for GCC - char_u *new_line, *orig_line = NULL; // init for GCC + char *new_line, *orig_line = NULL; // init for GCC // MODE_VREPLACE state needs to know what the line was like before changing if (State & VREPLACE_FLAG) { - orig_line = vim_strsave(get_cursor_line_ptr()); // Deal with NULL below + orig_line = xstrdup(get_cursor_line_ptr()); // Deal with NULL below orig_col = curwin->w_cursor.col; } // for the following tricks we don't want list mode save_p_list = curwin->w_p_list; - curwin->w_p_list = FALSE; + curwin->w_p_list = false; vc = getvcol_nolist(&curwin->w_cursor); vcol = vc; - /* - * For Replace mode we need to fix the replace stack later, which is only - * possible when the cursor is in the indent. Remember the number of - * characters before the cursor if it's possible. - */ + // For Replace mode we need to fix the replace stack later, which is only + // possible when the cursor is in the indent. Remember the number of + // characters before the cursor if it's possible. start_col = curwin->w_cursor.col; // determine offset from first non-blank @@ -1610,10 +1609,8 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang insstart_less = curwin->w_cursor.col; - /* - * If the cursor is in the indent, compute how many screen columns the - * cursor is to the left of the first non-blank. - */ + // If the cursor is in the indent, compute how many screen columns the + // cursor is to the left of the first non-blank. if (new_cursor_col < 0) { vcol = get_indent() - vcol; } @@ -1622,9 +1619,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang start_col = -1; } - /* - * Set the new indent. The cursor will be put on the first non-blank. - */ + // Set the new indent. The cursor will be put on the first non-blank. if (type == INDENT_SET) { (void)set_indent(amount, call_changed_bytes ? SIN_CHANGED : 0); } else { @@ -1639,20 +1634,16 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang } insstart_less -= curwin->w_cursor.col; - /* - * Try to put cursor on same character. - * If the cursor is at or after the first non-blank in the line, - * compute the cursor column relative to the column of the first - * non-blank character. - * If we are not in insert mode, leave the cursor on the first non-blank. - * If the cursor is before the first non-blank, position it relative - * to the first non-blank, counted in screen columns. - */ + // Try to put cursor on same character. + // If the cursor is at or after the first non-blank in the line, + // compute the cursor column relative to the column of the first + // non-blank character. + // If we are not in insert mode, leave the cursor on the first non-blank. + // If the cursor is before the first non-blank, position it relative + // to the first non-blank, counted in screen columns. if (new_cursor_col >= 0) { - /* - * When changing the indent while the cursor is touching it, reset - * Insstart_col to 0. - */ + // When changing the indent while the cursor is touching it, reset + // Insstart_col to 0. if (new_cursor_col == 0) { insstart_less = MAXCOL; } @@ -1660,33 +1651,31 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang } else if (!(State & MODE_INSERT)) { new_cursor_col = curwin->w_cursor.col; } else { - /* - * Compute the screen column where the cursor should be. - */ + // Compute the screen column where the cursor should be. vcol = get_indent() - vcol; curwin->w_virtcol = (colnr_T)((vcol < 0) ? 0 : vcol); - /* - * Advance the cursor until we reach the right screen column. - */ - vcol = last_vcol = 0; - new_cursor_col = -1; + // Advance the cursor until we reach the right screen column. + last_vcol = 0; ptr = get_cursor_line_ptr(); - while (vcol <= (int)curwin->w_virtcol) { - last_vcol = vcol; - if (new_cursor_col >= 0) { - new_cursor_col += utfc_ptr2len((char *)ptr + new_cursor_col); - } else { - new_cursor_col++; + 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; } - vcol += lbr_chartabsize(ptr, ptr + new_cursor_col, (colnr_T)vcol); + cts.cts_vcol += lbr_chartabsize(&cts); } 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. - */ + // 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); @@ -1697,10 +1686,8 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang xfree(ptr); } - /* - * When changing the indent while the cursor is in it, reset - * Insstart_col to 0. - */ + // When changing the indent while the cursor is in it, reset + // Insstart_col to 0. insstart_less = MAXCOL; } @@ -1711,12 +1698,10 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang } else { curwin->w_cursor.col = (colnr_T)new_cursor_col; } - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; changed_cline_bef_curs(); - /* - * May have to adjust the start of the insert. - */ + // May have to adjust the start of the insert. if (State & MODE_INSERT) { if (curwin->w_cursor.lnum == Insstart.lnum && Insstart.col != 0) { if ((int)Insstart.col <= insstart_less) { @@ -1757,14 +1742,14 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang // then put it back again the way we wanted it. if (State & VREPLACE_FLAG) { // Save new line - new_line = vim_strsave(get_cursor_line_ptr()); + new_line = xstrdup(get_cursor_line_ptr()); // We only put back the new line up to the cursor new_line[curwin->w_cursor.col] = NUL; int new_col = curwin->w_cursor.col; // Put back original line - ml_replace(curwin->w_cursor.lnum, (char *)orig_line, false); + ml_replace(curwin->w_cursor.lnum, orig_line, false); curwin->w_cursor.col = orig_col; curbuf_splice_pending++; @@ -1792,12 +1777,12 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang /// Truncate the space at the end of a line. This is to be used only in an /// insert mode. It handles fixing the replace stack for MODE_REPLACE and /// MODE_VREPLACE modes. -void truncate_spaces(char_u *line) +void truncate_spaces(char *line) { int i; // find start of trailing white space - for (i = (int)STRLEN(line) - 1; i >= 0 && ascii_iswhite(line[i]); i--) { + for (i = (int)strlen(line) - 1; i >= 0 && ascii_iswhite(line[i]); i--) { if (State & REPLACE_FLAG) { replace_join(0); // remove a NUL from the replace stack } @@ -1837,7 +1822,7 @@ static bool del_char_after_col(int limit_col) // composing character. mb_adjust_cursor(); while (curwin->w_cursor.col < (colnr_T)limit_col) { - int l = utf_ptr2len((char *)get_cursor_pos_ptr()); + int l = utf_ptr2len(get_cursor_pos_ptr()); if (l == 0) { // end of line break; @@ -1964,28 +1949,28 @@ int get_literal(bool no_simplify) /// @param ctrlv `c` was typed after CTRL-V static void insert_special(int c, int allow_modmask, int ctrlv) { - char_u *p; + char *p; int len; // Special function key, translate into "<Key>". Up to the last '>' is // inserted with ins_str(), so as not to replace characters in replace // mode. // Only use mod_mask for special keys, to avoid things like <S-Space>, - // unless 'allow_modmask' is TRUE. + // unless 'allow_modmask' is true. if (mod_mask & MOD_MASK_CMD) { // Command-key never produces a normal key. allow_modmask = true; } if (IS_SPECIAL(c) || (mod_mask && allow_modmask)) { - p = get_special_key_name(c, mod_mask); - len = (int)STRLEN(p); - c = p[len - 1]; + p = (char *)get_special_key_name(c, mod_mask); + len = (int)strlen(p); + c = (uint8_t)p[len - 1]; if (len > 2) { if (stop_arrow() == FAIL) { return; } p[len - 1] = NUL; ins_str(p); - AppendToRedobuffLit((char *)p, -1); + AppendToRedobuffLit(p, -1); ctrlv = false; } } @@ -1994,20 +1979,15 @@ static void insert_special(int c, int allow_modmask, int ctrlv) } } -/* - * Special characters in this context are those that need processing other - * than the simple insertion that can be performed here. This includes ESC - * which terminates the insert, and CR/NL which need special processing to - * open up a new line. This routine tries to optimize insertions performed by - * the "redo", "undo" or "put" commands, so it needs to know when it should - * stop and defer processing to the "normal" mechanism. - * '0' and '^' are special, because they can be followed by CTRL-D. - */ +// Special characters in this context are those that need processing other +// than the simple insertion that can be performed here. This includes ESC +// which terminates the insert, and CR/NL which need special processing to +// open up a new line. This routine tries to optimize insertions performed by +// the "redo", "undo" or "put" commands, so it needs to know when it should +// stop and defer processing to the "normal" mechanism. +// '0' and '^' are special, because they can be followed by CTRL-D. #define ISSPECIAL(c) ((c) < ' ' || (c) >= DEL || (c) == '0' || (c) == '^') -#define WHITECHAR(cc) (ascii_iswhite(cc) \ - && !utf_iscomposing(utf_ptr2char((char *)get_cursor_pos_ptr() + 1))) - /// /// "flags": INSCHAR_FORMAT - force formatting /// INSCHAR_CTRLV - char typed just after CTRL-V @@ -2081,8 +2061,8 @@ void insertchar(int c, int flags, int second_indent) // Need to remove existing (middle) comment leader and insert end // comment leader. First, check what comment leader we can find. - char_u *line = get_cursor_line_ptr(); - int i = get_leader_len((char *)line, &p, false, true); + char *line = get_cursor_line_ptr(); + int i = get_leader_len(line, &p, false, true); if (i > 0 && vim_strchr(p, COM_MIDDLE) != NULL) { // Just checking // Skip middle-comment string while (*p && p[-1] != ':') { // find end of middle flags @@ -2115,7 +2095,7 @@ void insertchar(int c, int flags, int second_indent) // Insert the end-comment string, except for the last // character, which will get inserted as normal later. - ins_bytes_len(lead_end, (size_t)(end_len - 1)); + ins_bytes_len((char *)lead_end, (size_t)(end_len - 1)); } } } @@ -2143,11 +2123,11 @@ void insertchar(int c, int flags, int second_indent) && !cindent_on() && !p_ri) { #define INPUT_BUFLEN 100 - char_u buf[INPUT_BUFLEN + 1]; + char buf[INPUT_BUFLEN + 1]; int i; colnr_T virtcol = 0; - buf[0] = (char_u)c; + buf[0] = (char)c; i = 1; if (textwidth > 0) { virtcol = get_nolist_virtcol(); @@ -2163,27 +2143,27 @@ void insertchar(int c, int flags, int second_indent) && MB_BYTE2LEN(c) == 1 && i < INPUT_BUFLEN && (textwidth == 0 - || (virtcol += byte2cells(buf[i - 1])) < (colnr_T)textwidth) - && !(!no_abbr && !vim_iswordc(c) && vim_iswordc(buf[i - 1]))) { + || (virtcol += byte2cells((uint8_t)buf[i - 1])) < (colnr_T)textwidth) + && !(!no_abbr && !vim_iswordc(c) && vim_iswordc((uint8_t)buf[i - 1]))) { c = vgetc(); if (p_hkmap && KeyTyped) { c = hkmap(c); // Hebrew mode mapping } - buf[i++] = (char_u)c; + buf[i++] = (char)c; } do_digraph(-1); // clear digraphs - do_digraph(buf[i - 1]); // may be the start of a digraph + do_digraph((uint8_t)buf[i - 1]); // may be the start of a digraph buf[i] = NUL; ins_str(buf); if (flags & INSCHAR_CTRLV) { - redo_literal(*buf); + redo_literal((uint8_t)(*buf)); i = 1; } else { i = 0; } if (buf[i] != NUL) { - AppendToRedobuffLit((char *)buf + i, -1); + AppendToRedobuffLit(buf + i, -1); } } else { int cc; @@ -2191,9 +2171,9 @@ void insertchar(int c, int flags, int second_indent) if ((cc = utf_char2len(c)) > 1) { char buf[MB_MAXBYTES + 1]; - utf_char2bytes(c, (char *)buf); + utf_char2bytes(c, buf); buf[cc] = NUL; - ins_char_bytes((char_u *)buf, (size_t)cc); + ins_char_bytes(buf, (size_t)cc); AppendCharToRedobuff(c); } else { ins_char(c); @@ -2206,600 +2186,7 @@ void insertchar(int c, int flags, int second_indent) } } -/// Format text at the current insert position. -/// -/// If the INSCHAR_COM_LIST flag is present, then the value of second_indent -/// will be the comment leader length sent to open_line(). -/// -/// @param c character to be inserted (can be NUL) -static void internal_format(int textwidth, int second_indent, int flags, int format_only, int c) -{ - int cc; - int save_char = NUL; - bool haveto_redraw = false; - const bool fo_ins_blank = has_format_option(FO_INS_BLANK); - const bool fo_multibyte = has_format_option(FO_MBYTE_BREAK); - const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW); - const bool fo_white_par = has_format_option(FO_WHITE_PAR); - bool first_line = true; - colnr_T leader_len; - bool no_leader = false; - int do_comments = (flags & INSCHAR_DO_COM); - int has_lbr = curwin->w_p_lbr; - - // make sure win_lbr_chartabsize() counts correctly - curwin->w_p_lbr = false; - - /* - * When 'ai' is off we don't want a space under the cursor to be - * deleted. Replace it with an 'x' temporarily. - */ - if (!curbuf->b_p_ai - && !(State & VREPLACE_FLAG)) { - cc = gchar_cursor(); - if (ascii_iswhite(cc)) { - save_char = cc; - pchar_cursor('x'); - } - } - - /* - * Repeat breaking lines, until the current line is not too long. - */ - while (!got_int) { - int startcol; // Cursor column at entry - int wantcol; // column at textwidth border - int foundcol; // column for start of spaces - int end_foundcol = 0; // column for start of word - colnr_T len; - colnr_T virtcol; - int orig_col = 0; - char_u *saved_text = NULL; - colnr_T col; - colnr_T end_col; - bool did_do_comment = false; - - virtcol = get_nolist_virtcol() - + char2cells(c != NUL ? c : gchar_cursor()); - if (virtcol <= (colnr_T)textwidth) { - break; - } - - if (no_leader) { - do_comments = false; - } else if (!(flags & INSCHAR_FORMAT) - && has_format_option(FO_WRAP_COMS)) { - do_comments = true; - } - - // Don't break until after the comment leader - if (do_comments) { - char_u *line = get_cursor_line_ptr(); - leader_len = get_leader_len((char *)line, NULL, false, true); - if (leader_len == 0 && curbuf->b_p_cin) { - // Check for a line comment after code. - int comment_start = check_linecomment(line); - if (comment_start != MAXCOL) { - leader_len = get_leader_len((char *)line + comment_start, NULL, false, true); - if (leader_len != 0) { - leader_len += comment_start; - } - } - } - } else { - leader_len = 0; - } - - // If the line doesn't start with a comment leader, then don't - // start one in a following broken line. Avoids that a %word - // moved to the start of the next line causes all following lines - // to start with %. - if (leader_len == 0) { - no_leader = true; - } - if (!(flags & INSCHAR_FORMAT) - && leader_len == 0 - && !has_format_option(FO_WRAP)) { - break; - } - if ((startcol = curwin->w_cursor.col) == 0) { - break; - } - - // find column of textwidth border - coladvance((colnr_T)textwidth); - wantcol = curwin->w_cursor.col; - - curwin->w_cursor.col = startcol; - foundcol = 0; - int skip_pos = 0; - - /* - * Find position to break at. - * Stop at first entered white when 'formatoptions' has 'v' - */ - while ((!fo_ins_blank && !has_format_option(FO_INS_VI)) - || (flags & INSCHAR_FORMAT) - || curwin->w_cursor.lnum != Insstart.lnum - || curwin->w_cursor.col >= Insstart.col) { - if (curwin->w_cursor.col == startcol && c != NUL) { - cc = c; - } else { - cc = gchar_cursor(); - } - if (WHITECHAR(cc)) { - // remember position of blank just before text - end_col = curwin->w_cursor.col; - - // find start of sequence of blanks - int wcc = 0; // counter for whitespace chars - while (curwin->w_cursor.col > 0 && WHITECHAR(cc)) { - dec_cursor(); - cc = gchar_cursor(); - - // Increment count of how many whitespace chars in this - // group; we only need to know if it's more than one. - if (wcc < 2) { - wcc++; - } - } - if (curwin->w_cursor.col == 0 && WHITECHAR(cc)) { - break; // only spaces in front of text - } - - // Don't break after a period when 'formatoptions' has 'p' and - // there are less than two spaces. - if (has_format_option(FO_PERIOD_ABBR) && cc == '.' && wcc < 2) { - continue; - } - - // Don't break until after the comment leader - if (curwin->w_cursor.col < leader_len) { - break; - } - - if (has_format_option(FO_ONE_LETTER)) { - // do not break after one-letter words - if (curwin->w_cursor.col == 0) { - break; // one-letter word at begin - } - // do not break "#a b" when 'tw' is 2 - if (curwin->w_cursor.col <= leader_len) { - break; - } - col = curwin->w_cursor.col; - dec_cursor(); - cc = gchar_cursor(); - - if (WHITECHAR(cc)) { - continue; // one-letter, continue - } - curwin->w_cursor.col = col; - } - - inc_cursor(); - - end_foundcol = end_col + 1; - foundcol = curwin->w_cursor.col; - if (curwin->w_cursor.col <= (colnr_T)wantcol) { - break; - } - } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) && fo_multibyte) { - int ncc; - bool allow_break; - - // Break after or before a multi-byte character. - if (curwin->w_cursor.col != startcol) { - // Don't break until after the comment leader - if (curwin->w_cursor.col < leader_len) { - break; - } - col = curwin->w_cursor.col; - inc_cursor(); - ncc = gchar_cursor(); - allow_break = utf_allow_break(cc, ncc); - - // If we have already checked this position, skip! - if (curwin->w_cursor.col != skip_pos && allow_break) { - foundcol = curwin->w_cursor.col; - end_foundcol = foundcol; - if (curwin->w_cursor.col <= (colnr_T)wantcol) { - break; - } - } - curwin->w_cursor.col = col; - } - - if (curwin->w_cursor.col == 0) { - break; - } - - ncc = cc; - col = curwin->w_cursor.col; - - dec_cursor(); - cc = gchar_cursor(); - - if (WHITECHAR(cc)) { - continue; // break with space - } - // Don't break until after the comment leader. - if (curwin->w_cursor.col < leader_len) { - break; - } - - curwin->w_cursor.col = col; - skip_pos = curwin->w_cursor.col; - - allow_break = utf_allow_break(cc, ncc); - - // Must handle this to respect line break prohibition. - if (allow_break) { - foundcol = curwin->w_cursor.col; - end_foundcol = foundcol; - } - if (curwin->w_cursor.col <= (colnr_T)wantcol) { - const bool ncc_allow_break = utf_allow_break_before(ncc); - - if (allow_break) { - break; - } - if (!ncc_allow_break && !fo_rigor_tw) { - // Enable at most 1 punct hang outside of textwidth. - if (curwin->w_cursor.col == startcol) { - // We are inserting a non-breakable char, postpone - // line break check to next insert. - end_foundcol = foundcol = 0; - break; - } - - // Neither cc nor ncc is NUL if we are here, so - // it's safe to inc_cursor. - col = curwin->w_cursor.col; - - inc_cursor(); - cc = ncc; - ncc = gchar_cursor(); - // handle insert - ncc = (ncc != NUL) ? ncc : c; - - allow_break = utf_allow_break(cc, ncc); - - if (allow_break) { - // Break only when we are not at end of line. - end_foundcol = foundcol = ncc == NUL? 0 : curwin->w_cursor.col; - break; - } - curwin->w_cursor.col = col; - } - } - } - if (curwin->w_cursor.col == 0) { - break; - } - dec_cursor(); - } - - if (foundcol == 0) { // no spaces, cannot break line - curwin->w_cursor.col = startcol; - break; - } - - // Going to break the line, remove any "$" now. - undisplay_dollar(); - - // Offset between cursor position and line break is used by replace - // stack functions. MODE_VREPLACE does not use this, and backspaces - // over the text instead. - if (State & VREPLACE_FLAG) { - orig_col = startcol; // Will start backspacing from here - } else { - replace_offset = startcol - end_foundcol; - } - - /* - * adjust startcol for spaces that will be deleted and - * characters that will remain on top line - */ - curwin->w_cursor.col = foundcol; - while ((cc = gchar_cursor(), WHITECHAR(cc)) - && (!fo_white_par || curwin->w_cursor.col < startcol)) { - inc_cursor(); - } - startcol -= curwin->w_cursor.col; - if (startcol < 0) { - startcol = 0; - } - - if (State & VREPLACE_FLAG) { - // In MODE_VREPLACE state, we will backspace over the text to be - // wrapped, so save a copy now to put on the next line. - saved_text = vim_strsave(get_cursor_pos_ptr()); - curwin->w_cursor.col = orig_col; - saved_text[startcol] = NUL; - - // Backspace over characters that will move to the next line - if (!fo_white_par) { - backspace_until_column(foundcol); - } - } else { - // put cursor after pos. to break line - if (!fo_white_par) { - curwin->w_cursor.col = foundcol; - } - } - - /* - * Split the line just before the margin. - * Only insert/delete lines, but don't really redraw the window. - */ - open_line(FORWARD, OPENLINE_DELSPACES + OPENLINE_MARKFIX - + (fo_white_par ? OPENLINE_KEEPTRAIL : 0) - + (do_comments ? OPENLINE_DO_COM : 0) - + OPENLINE_FORMAT - + ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0), - ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent), - &did_do_comment); - if (!(flags & INSCHAR_COM_LIST)) { - old_indent = 0; - } - - // If a comment leader was inserted, may also do this on a following - // line. - if (did_do_comment) { - no_leader = false; - } - - replace_offset = 0; - if (first_line) { - if (!(flags & INSCHAR_COM_LIST)) { - // This section is for auto-wrap of numeric lists. When not - // in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST - // flag will be set and open_line() will handle it (as seen - // above). The code here (and in get_number_indent()) will - // recognize comments if needed... - if (second_indent < 0 && has_format_option(FO_Q_NUMBER)) { - second_indent = get_number_indent(curwin->w_cursor.lnum - 1); - } - if (second_indent >= 0) { - if (State & VREPLACE_FLAG) { - change_indent(INDENT_SET, second_indent, false, NUL, true); - } else if (leader_len > 0 && second_indent - leader_len > 0) { - int padding = second_indent - leader_len; - - // We started at the first_line of a numbered list - // that has a comment. the open_line() function has - // inserted the proper comment leader and positioned - // the cursor at the end of the split line. Now we - // add the additional whitespace needed after the - // comment leader for the numbered list. - for (int i = 0; i < padding; i++) { - ins_str((char_u *)" "); - } - changed_bytes(curwin->w_cursor.lnum, leader_len); - } else { - (void)set_indent(second_indent, SIN_CHANGED); - } - } - } - first_line = false; - } - - if (State & VREPLACE_FLAG) { - // In MODE_VREPLACE state we have backspaced over the text to be - // moved, now we re-insert it into the new line. - ins_bytes(saved_text); - xfree(saved_text); - } else { - /* - * Check if cursor is not past the NUL off the line, cindent - * may have added or removed indent. - */ - curwin->w_cursor.col += startcol; - len = (colnr_T)STRLEN(get_cursor_line_ptr()); - if (curwin->w_cursor.col > len) { - curwin->w_cursor.col = len; - } - } - - haveto_redraw = true; - can_cindent = true; - // moved the cursor, don't autoindent or cindent now - did_ai = false; - did_si = false; - can_si = false; - can_si_back = false; - line_breakcheck(); - } - - if (save_char != NUL) { // put back space after cursor - pchar_cursor((char_u)save_char); - } - - curwin->w_p_lbr = has_lbr; - - if (!format_only && haveto_redraw) { - update_topline(curwin); - redraw_curbuf_later(VALID); - } -} - -/// Called after inserting or deleting text: When 'formatoptions' includes the -/// 'a' flag format from the current line until the end of the paragraph. -/// Keep the cursor at the same position relative to the text. -/// The caller must have saved the cursor line for undo, following ones will be -/// saved here. -/// -/// @param trailblank when true also format with trailing blank -/// @param prev_line may start in previous line -void auto_format(bool trailblank, bool prev_line) -{ - pos_T pos; - colnr_T len; - char_u *old; - char_u *new, *pnew; - int wasatend; - int cc; - - if (!has_format_option(FO_AUTO)) { - return; - } - - pos = curwin->w_cursor; - old = get_cursor_line_ptr(); - - // may remove added space - check_auto_format(false); - - // Don't format in Insert mode when the cursor is on a trailing blank, the - // user might insert normal text next. Also skip formatting when "1" is - // in 'formatoptions' and there is a single character before the cursor. - // Otherwise the line would be broken and when typing another non-white - // next they are not joined back together. - wasatend = (pos.col == (colnr_T)STRLEN(old)); - if (*old != NUL && !trailblank && wasatend) { - dec_cursor(); - cc = gchar_cursor(); - if (!WHITECHAR(cc) && curwin->w_cursor.col > 0 - && has_format_option(FO_ONE_LETTER)) { - dec_cursor(); - } - cc = gchar_cursor(); - if (WHITECHAR(cc)) { - curwin->w_cursor = pos; - return; - } - curwin->w_cursor = pos; - } - - // With the 'c' flag in 'formatoptions' and 't' missing: only format - // comments. - if (has_format_option(FO_WRAP_COMS) && !has_format_option(FO_WRAP) - && get_leader_len((char *)old, NULL, false, true) == 0) { - return; - } - - /* - * May start formatting in a previous line, so that after "x" a word is - * moved to the previous line if it fits there now. Only when this is not - * the start of a paragraph. - */ - if (prev_line && !paragraph_start(curwin->w_cursor.lnum)) { - --curwin->w_cursor.lnum; - if (u_save_cursor() == FAIL) { - return; - } - } - - /* - * Do the formatting and restore the cursor position. "saved_cursor" will - * be adjusted for the text formatting. - */ - saved_cursor = pos; - format_lines((linenr_T) - 1, false); - curwin->w_cursor = saved_cursor; - saved_cursor.lnum = 0; - - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - // "cannot happen" - curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance(MAXCOL); - } else { - check_cursor_col(); - } - - // Insert mode: If the cursor is now after the end of the line while it - // previously wasn't, the line was broken. Because of the rule above we - // need to add a space when 'w' is in 'formatoptions' to keep a paragraph - // formatted. - if (!wasatend && has_format_option(FO_WHITE_PAR)) { - new = get_cursor_line_ptr(); - len = (colnr_T)STRLEN(new); - if (curwin->w_cursor.col == len) { - pnew = vim_strnsave(new, (size_t)len + 2); - pnew[len] = ' '; - pnew[len + 1] = NUL; - ml_replace(curwin->w_cursor.lnum, (char *)pnew, false); - // remove the space later - did_add_space = true; - } else { - // may remove added space - check_auto_format(false); - } - } - - check_cursor(); -} - -/// When an extra space was added to continue a paragraph for auto-formatting, -/// delete it now. The space must be under the cursor, just after the insert -/// position. -/// -/// @param end_insert true when ending Insert mode -static void check_auto_format(bool end_insert) -{ - int c = ' '; - int cc; - - if (did_add_space) { - cc = gchar_cursor(); - if (!WHITECHAR(cc)) { - // Somehow the space was removed already. - did_add_space = false; - } else { - if (!end_insert) { - inc_cursor(); - c = gchar_cursor(); - dec_cursor(); - } - if (c != NUL) { - // The space is no longer at the end of the line, delete it. - del_char(false); - did_add_space = false; - } - } - } -} - -/// Find out textwidth to be used for formatting: -/// if 'textwidth' option is set, use it -/// else if 'wrapmargin' option is set, use curwin->w_width_inner-'wrapmargin' -/// if invalid value, use 0. -/// Set default to window width (maximum 79) for "gq" operator. -/// -/// @param ff force formatting (for "gq" command) -int comp_textwidth(bool ff) -{ - int textwidth = (int)curbuf->b_p_tw; - if (textwidth == 0 && curbuf->b_p_wm) { - // The width is the window width minus 'wrapmargin' minus all the - // things that add to the margin. - textwidth = curwin->w_width_inner - (int)curbuf->b_p_wm; - if (cmdwin_type != 0) { - textwidth -= 1; - } - textwidth -= win_fdccol_count(curwin); - textwidth -= win_signcol_count(curwin); - - if (curwin->w_p_nu || curwin->w_p_rnu) { - textwidth -= 8; - } - } - if (textwidth < 0) { - textwidth = 0; - } - if (ff && textwidth == 0) { - textwidth = curwin->w_width_inner - 1; - if (textwidth > 79) { - textwidth = 79; - } - } - return textwidth; -} - -/* - * Put a character in the redo buffer, for when just after a CTRL-V. - */ +// Put a character in the redo buffer, for when just after a CTRL-V. static void redo_literal(int c) { char buf[10]; @@ -2849,10 +2236,8 @@ static void start_arrow_common(pos_T *end_insert_pos, bool end_change) check_spell_redraw(); } -/* - * If we skipped highlighting word at cursor, do it now. - * It may be skipped again, thus reset spell_redraw_lnum first. - */ +// If we skipped highlighting word at cursor, do it now. +// It may be skipped again, thus reset spell_redraw_lnum first. static void check_spell_redraw(void) { if (spell_redraw_lnum != 0) { @@ -2863,11 +2248,9 @@ static void check_spell_redraw(void) } } -/* - * stop_arrow() is called before a change is made in insert mode. - * If an arrow key has been used, start a new insertion. - * Returns FAIL if undo is impossible, shouldn't insert then. - */ +// stop_arrow() is called before a change is made in insert mode. +// If an arrow key has been used, start a new insertion. +// Returns FAIL if undo is impossible, shouldn't insert then. int stop_arrow(void) { if (arrow_used) { @@ -2912,19 +2295,17 @@ int stop_arrow(void) static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) { int cc; - char_u *ptr; + char *ptr; stop_redo_ins(); replace_flush(); // abandon replace stack - /* - * Save the inserted text for later redo with ^@ and CTRL-A. - * Don't do it when "restart_edit" was set and nothing was inserted, - * otherwise CTRL-O w and then <Left> will clear "last_insert". - */ + // Save the inserted text for later redo with ^@ and CTRL-A. + // Don't do it when "restart_edit" was set and nothing was inserted, + // otherwise CTRL-O w and then <Left> will clear "last_insert". ptr = get_inserted(); if (did_restart_edit == 0 || (ptr != NULL - && (int)STRLEN(ptr) > new_insert_skip)) { + && (int)strlen(ptr) > new_insert_skip)) { xfree(last_insert); last_insert = ptr; last_insert_skip = new_insert_skip; @@ -2958,8 +2339,8 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) if (gchar_cursor() != NUL) { inc_cursor(); } - /* If the cursor is still at the same character, also keep - * the "coladd". */ + // If the cursor is still at the same character, also keep + // the "coladd". if (gchar_cursor() == NUL && curwin->w_cursor.lnum == tpos.lnum && curwin->w_cursor.col == tpos.col) { @@ -2986,7 +2367,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) check_cursor_col(); // make sure it is not past the line for (;;) { if (gchar_cursor() == NUL && curwin->w_cursor.col > 0) { - --curwin->w_cursor.col; + curwin->w_cursor.col--; } cc = gchar_cursor(); if (!ascii_iswhite(cc)) { @@ -3003,7 +2384,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) tpos = curwin->w_cursor; tpos.col++; if (cc != NUL && gchar_pos(&tpos) == NUL) { - ++curwin->w_cursor.col; // put cursor back on the NUL + curwin->w_cursor.col++; // put cursor back on the NUL } } @@ -3028,13 +2409,11 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) } } -/* - * Set the last inserted text to a single character. - * Used for the replace command. - */ +// Set the last inserted text to a single character. +// Used for the replace command. void set_last_insert(int c) { - char_u *s; + char *s; xfree(last_insert); last_insert = xmalloc(MB_MAXBYTES * 3 + 5); @@ -3043,7 +2422,7 @@ void set_last_insert(int c) if (c < ' ' || c == DEL) { *s++ = Ctrl_V; } - s = add_char2buf(c, s); + s = (char *)add_char2buf(c, (char_u *)s); *s++ = ESC; *s++ = NUL; last_insert_skip = 0; @@ -3056,13 +2435,11 @@ void free_last_insert(void) } #endif -/* - * move cursor to start of line - * if flags & BL_WHITE move to first non-white - * if flags & BL_SOL move to first non-white if startofline is set, - * otherwise keep "curswant" column - * if flags & BL_FIX don't leave the cursor on a NUL. - */ +// move cursor to start of line +// if flags & BL_WHITE move to first non-white +// if flags & BL_SOL move to first non-white if startofline is set, +// otherwise keep "curswant" column +// if flags & BL_FIX don't leave the cursor on a NUL. void beginline(int flags) { if ((flags & BL_SOL) && !p_sol) { @@ -3074,22 +2451,20 @@ void beginline(int flags) if (flags & (BL_WHITE | BL_SOL)) { char_u *ptr; - for (ptr = get_cursor_line_ptr(); ascii_iswhite(*ptr) - && !((flags & BL_FIX) && ptr[1] == NUL); ++ptr) { - ++curwin->w_cursor.col; + for (ptr = (char_u *)get_cursor_line_ptr(); ascii_iswhite(*ptr) + && !((flags & BL_FIX) && ptr[1] == NUL); ptr++) { + curwin->w_cursor.col++; } } - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } } -/* - * oneright oneleft cursor_down cursor_up - * - * Move one char {right,left,down,up}. - * Doesn't move onto the NUL past the end of the line, unless it is allowed. - * Return OK when successful, FAIL when we hit a line of file boundary. - */ +// oneright oneleft cursor_down cursor_up +// +// Move one char {right,left,down,up}. +// Doesn't move onto the NUL past the end of the line, unless it is allowed. +// Return OK when successful, FAIL when we hit a line of file boundary. int oneright(void) { @@ -3100,7 +2475,7 @@ int oneright(void) pos_T prevpos = curwin->w_cursor; // Adjust for multi-wide char (excluding TAB) - ptr = (char *)get_cursor_pos_ptr(); + ptr = get_cursor_pos_ptr(); coladvance(getviscol() + ((*ptr != TAB && vim_isprintc(utf_ptr2char(ptr))) ? ptr2cells(ptr) : 1)); curwin->w_set_curswant = true; @@ -3109,7 +2484,7 @@ int oneright(void) || prevpos.coladd != curwin->w_cursor.coladd) ? OK : FAIL; } - ptr = (char *)get_cursor_pos_ptr(); + ptr = get_cursor_pos_ptr(); if (*ptr == NUL) { return FAIL; // already at the very end } @@ -3123,7 +2498,7 @@ int oneright(void) } curwin->w_cursor.col += l; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; return OK; } @@ -3152,13 +2527,13 @@ int oneleft(void) if (curwin->w_cursor.coladd == 1) { // Adjust for multi-wide char (not a TAB) - char *ptr = (char *)get_cursor_pos_ptr(); + char *ptr = get_cursor_pos_ptr(); if (*ptr != TAB && vim_isprintc(utf_ptr2char(ptr)) && ptr2cells(ptr) > 1) { curwin->w_cursor.coladd = 0; } } - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; return OK; } @@ -3166,8 +2541,8 @@ int oneleft(void) return FAIL; } - curwin->w_set_curswant = TRUE; - --curwin->w_cursor.col; + curwin->w_set_curswant = true; + curwin->w_cursor.col--; // if the character on the left of the current cursor is a multi-byte // character, move to its first byte @@ -3175,98 +2550,112 @@ int oneleft(void) return OK; } -/// @oaram upd_topline When TRUE: update topline -int cursor_up(long n, int upd_topline) +/// Move the cursor up "n" lines in window "wp". +/// Takes care of closed folds. +/// Returns the new cursor line or zero for failure. +linenr_T cursor_up_inner(win_T *wp, long n) { - linenr_T lnum; + linenr_T lnum = wp->w_cursor.lnum; - if (n > 0) { - lnum = curwin->w_cursor.lnum; + // This fails if the cursor is already in the first line. + if (lnum <= 1) { + return 0; + } + if (n >= lnum) { + lnum = 1; + } else if (hasAnyFolding(wp)) { + // Count each sequence of folded lines as one logical line. - // This fails if the cursor is already in the first line. - if (lnum <= 1) { - return FAIL; - } - if (n >= lnum) { - lnum = 1; - } else if (hasAnyFolding(curwin)) { - /* - * Count each sequence of folded lines as one logical line. - */ - // go to the start of the current fold - (void)hasFolding(lnum, &lnum, NULL); - - while (n--) { - // move up one line - lnum--; - if (lnum <= 1) { - break; - } - // If we entered a fold, move to the beginning, unless in - // Insert mode or when 'foldopen' contains "all": it will open - // in a moment. - if (n > 0 || !((State & MODE_INSERT) || (fdo_flags & FDO_ALL))) { - (void)hasFolding(lnum, &lnum, NULL); - } + // go to the start of the current fold + (void)hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL); + + while (n--) { + // move up one line + lnum--; + if (lnum <= 1) { + break; } - if (lnum < 1) { - lnum = 1; + // If we entered a fold, move to the beginning, unless in + // Insert mode or when 'foldopen' contains "all": it will open + // in a moment. + if (n > 0 || !((State & MODE_INSERT) || (fdo_flags & FDO_ALL))) { + (void)hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL); } - } else { - lnum -= (linenr_T)n; } - curwin->w_cursor.lnum = lnum; + if (lnum < 1) { + lnum = 1; + } + } else { + lnum -= (linenr_T)n; + } + + wp->w_cursor.lnum = lnum; + return lnum; +} + +/// @param upd_topline When true: update topline +int cursor_up(long n, int upd_topline) +{ + if (n > 0 && cursor_up_inner(curwin, n) == 0) { + return FAIL; } // try to advance to the column we want to be at coladvance(curwin->w_curswant); if (upd_topline) { - update_topline(curwin); // make sure curwin->w_topline is valid + update_topline(curwin); // make sure curwin->w_topline is valid } return OK; } -/// Cursor down a number of logical lines. -/// -/// @param upd_topline When TRUE: update topline -int cursor_down(long n, int upd_topline) +/// Move the cursor down "n" lines in window "wp". +/// Takes care of closed folds. +/// Returns the new cursor line or zero for failure. +linenr_T cursor_down_inner(win_T *wp, long n) { - linenr_T lnum; - - if (n > 0) { - lnum = curwin->w_cursor.lnum; - // Move to last line of fold, will fail if it's the end-of-file. - (void)hasFolding(lnum, NULL, &lnum); + linenr_T lnum = wp->w_cursor.lnum; + linenr_T line_count = wp->w_buffer->b_ml.ml_line_count; - // This fails if the cursor is already in the last line. - if (lnum >= curbuf->b_ml.ml_line_count) { - return FAIL; - } - if (lnum + n >= curbuf->b_ml.ml_line_count) { - lnum = curbuf->b_ml.ml_line_count; - } else if (hasAnyFolding(curwin)) { - linenr_T last; + // Move to last line of fold, will fail if it's the end-of-file. + (void)hasFoldingWin(wp, lnum, NULL, &lnum, true, NULL); + // This fails if the cursor is already in the last line. + if (lnum >= line_count) { + return FAIL; + } + if (lnum + n >= line_count) { + lnum = line_count; + } else if (hasAnyFolding(wp)) { + linenr_T last; - // count each sequence of folded lines as one logical line - while (n--) { - if (hasFolding(lnum, NULL, &last)) { - lnum = last + 1; - } else { - lnum++; - } - if (lnum >= curbuf->b_ml.ml_line_count) { - break; - } + // count each sequence of folded lines as one logical line + while (n--) { + if (hasFoldingWin(wp, lnum, NULL, &last, true, NULL)) { + lnum = last + 1; + } else { + lnum++; } - if (lnum > curbuf->b_ml.ml_line_count) { - lnum = curbuf->b_ml.ml_line_count; + if (lnum >= line_count) { + break; } - } else { - lnum += (linenr_T)n; } - curwin->w_cursor.lnum = lnum; + if (lnum > line_count) { + lnum = line_count; + } + } else { + lnum += (linenr_T)n; + } + + wp->w_cursor.lnum = lnum; + return lnum; +} + +/// @param upd_topline When true: update topline +int cursor_down(long n, int upd_topline) +{ + if (n > 0 && cursor_down_inner(curwin, n) == 0) { + return FAIL; } // try to advance to the column we want to be at @@ -3288,12 +2677,12 @@ int cursor_down(long n, int upd_topline) /// @param no_esc Don't add an ESC at the end int stuff_inserted(int c, long count, int no_esc) { - char_u *esc_ptr; - char_u *ptr; - char_u *last_ptr; - char_u last = NUL; + char *esc_ptr; + char *ptr; + char *last_ptr; + char last = NUL; - ptr = get_last_insert(); + ptr = (char *)get_last_insert(); if (ptr == NULL) { emsg(_(e_noinstext)); return FAIL; @@ -3303,7 +2692,7 @@ int stuff_inserted(int c, long count, int no_esc) if (c != NUL) { stuffcharReadbuff(c); } - if ((esc_ptr = STRRCHR(ptr, ESC)) != NULL) { + if ((esc_ptr = strrchr(ptr, ESC)) != NULL) { // remove the ESC. *esc_ptr = NUL; } @@ -3311,7 +2700,7 @@ int stuff_inserted(int c, long count, int no_esc) // when the last char is either "0" or "^" it will be quoted if no ESC // comes after it OR if it will inserted more than once and "ptr" // starts with ^D. -- Acevedo - last_ptr = (esc_ptr ? esc_ptr : ptr + STRLEN(ptr)) - 1; + last_ptr = (esc_ptr ? esc_ptr : ptr + strlen(ptr)) - 1; if (last_ptr >= ptr && (*last_ptr == '0' || *last_ptr == '^') && (no_esc || (*ptr == Ctrl_D && count > 1))) { last = *last_ptr; @@ -3348,23 +2737,21 @@ char_u *get_last_insert(void) if (last_insert == NULL) { return NULL; } - return last_insert + last_insert_skip; + return (char_u *)last_insert + last_insert_skip; } -/* - * Get last inserted string, and remove trailing <Esc>. - * Returns pointer to allocated memory (must be freed) or NULL. - */ -char_u *get_last_insert_save(void) +// Get last inserted string, and remove trailing <Esc>. +// Returns pointer to allocated memory (must be freed) or NULL. +char *get_last_insert_save(void) { - char_u *s; + char *s; int len; if (last_insert == NULL) { return NULL; } - s = vim_strsave(last_insert + last_insert_skip); - len = (int)STRLEN(s); + s = xstrdup(last_insert + last_insert_skip); + len = (int)strlen(s); if (len > 0 && s[len - 1] == ESC) { // remove trailing ESC s[len - 1] = NUL; } @@ -3393,20 +2780,18 @@ static bool echeck_abbr(int c) curwin->w_cursor.lnum == Insstart.lnum ? Insstart.col : 0); } -/* - * replace-stack functions - * - * When replacing characters, the replaced characters are remembered for each - * new character. This is used to re-insert the old text when backspacing. - * - * There is a NUL headed list of characters for each character that is - * currently in the file after the insertion point. When BS is used, one NUL - * headed list is put back for the deleted character. - * - * For a newline, there are two NUL headed lists. One contains the characters - * that the NL replaced. The extra one stores the characters after the cursor - * that were deleted (always white space). - */ +// replace-stack functions +// +// When replacing characters, the replaced characters are remembered for each +// new character. This is used to re-insert the old text when backspacing. +// +// There is a NUL headed list of characters for each character that is +// currently in the file after the insertion point. When BS is used, one NUL +// headed list is put back for the deleted character. +// +// For a newline, there are two NUL headed lists. One contains the characters +// that the NL replaced. The extra one stores the characters after the cursor +// that were deleted (always white space). static char_u *replace_stack = NULL; static ssize_t replace_stack_nr = 0; // next entry in replace stack @@ -3437,17 +2822,16 @@ void replace_push(int c) replace_stack_nr++; } -/* - * Push a character onto the replace stack. Handles a multi-byte character in - * reverse byte order, so that the first byte is popped off first. - * Return the number of bytes done (includes composing characters). - */ -int replace_push_mb(char_u *p) +/// Push a character onto the replace stack. Handles a multi-byte character in +/// reverse byte order, so that the first byte is popped off first. +/// +/// @return the number of bytes done (includes composing characters). +int replace_push_mb(char *p) { - int l = utfc_ptr2len((char *)p); + int l = utfc_ptr2len(p); int j; - for (j = l - 1; j >= 0; --j) { + for (j = l - 1; j >= 0; j--) { replace_push(p[j]); } return l; @@ -3492,10 +2876,8 @@ static void replace_pop_ins(void) State = oldState; } -/* - * Insert bytes popped from the replace stack. "cc" is the first byte. If it - * indicates a multi-byte char, pop the other bytes too. - */ +// Insert bytes popped from the replace stack. "cc" is the first byte. If it +// indicates a multi-byte char, pop the other bytes too. static void mb_replace_pop_ins(int cc) { int n; @@ -3508,7 +2890,7 @@ static void mb_replace_pop_ins(int cc) for (i = 1; i < n; i++) { buf[i] = (char_u)replace_pop(); } - ins_bytes_len(buf, (size_t)n); + ins_bytes_len((char *)buf, (size_t)n); } else { ins_char(cc); } @@ -3531,7 +2913,7 @@ static void mb_replace_pop_ins(int cc) buf[i] = (char_u)replace_pop(); } if (utf_iscomposing(utf_ptr2char((char *)buf))) { - ins_bytes_len(buf, (size_t)n); + ins_bytes_len((char *)buf, (size_t)n); } else { // Not a composing char, put it back. for (i = n - 1; i >= 0; i--) { @@ -3542,10 +2924,8 @@ static void mb_replace_pop_ins(int cc) } } -/* - * make the replace stack empty - * (called when exiting replace mode) - */ +// make the replace stack empty +// (called when exiting replace mode) static void replace_flush(void) { XFREE_CLEAR(replace_stack); @@ -3553,15 +2933,13 @@ static void replace_flush(void) replace_stack_nr = 0; } -/* - * Handle doing a BS for one character. - * cc < 0: replace stack empty, just move cursor - * cc == 0: character was inserted, delete it - * cc > 0: character was replaced, put cc (first byte of original char) back - * and check for more characters to be put back - * When "limit_col" is >= 0, don't delete before this column. Matters when - * using composing characters, use del_char_after_col() instead of del_char(). - */ +// Handle doing a BS for one character. +// cc < 0: replace stack empty, just move cursor +// cc == 0: character was inserted, delete it +// cc > 0: character was replaced, put cc (first byte of original char) back +// and check for more characters to be put back +// When "limit_col" is >= 0, don't delete before this column. Matters when +// using composing characters, use del_char_after_col() instead of del_char(). static void replace_do_bs(int limit_col) { int cc; @@ -3569,7 +2947,7 @@ static void replace_do_bs(int limit_col) int ins_len; int orig_vcols = 0; colnr_T start_vcol; - char_u *p; + char *p; int i; int vcol; const int l_State = State; @@ -3584,7 +2962,7 @@ static void replace_do_bs(int limit_col) } (void)del_char_after_col(limit_col); if (l_State & VREPLACE_FLAG) { - orig_len = (int)STRLEN(get_cursor_pos_ptr()); + orig_len = (int)strlen(get_cursor_pos_ptr()); } replace_push(cc); replace_pop_ins(); @@ -3592,11 +2970,11 @@ static void replace_do_bs(int limit_col) if (l_State & VREPLACE_FLAG) { // Get the number of screen cells used by the inserted characters p = get_cursor_pos_ptr(); - ins_len = (int)STRLEN(p) - orig_len; + ins_len = (int)strlen(p) - orig_len; vcol = start_vcol; for (i = 0; i < ins_len; i++) { vcol += win_chartabsize(curwin, p + i, vcol); - i += utfc_ptr2len((char *)p) - 1; + i += utfc_ptr2len(p) - 1; } vcol -= start_vcol; @@ -3624,40 +3002,10 @@ bool cindent_on(void) return !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL); } -/* - * Re-indent the current line, based on the current contents of it and the - * surrounding lines. Fixing the cursor position seems really easy -- I'm very - * confused what all the part that handles Control-T is doing that I'm not. - * "get_the_indent" should be get_c_indent, get_expr_indent or get_lisp_indent. - */ -void fixthisline(IndentGetter get_the_indent) -{ - int amount = get_the_indent(); - - if (amount >= 0) { - change_indent(INDENT_SET, amount, false, 0, true); - if (linewhite(curwin->w_cursor.lnum)) { - did_ai = true; // delete the indent if the line stays empty - } - } -} - -void fix_indent(void) -{ - if (p_paste) { - return; - } - if (curbuf->b_p_lisp && curbuf->b_p_ai) { - fixthisline(get_lisp_indent); - } else if (cindent_on()) { - do_c_expr_indent(); - } -} - /// Check that "cinkeys" contains the key "keytyped", /// when == '*': Only if key is preceded with '*' (indent before insert) /// when == '!': Only if key is preceded with '!' (don't insert) -/// when == ' ': Only if key is not preceded with '*' (indent afterwards) +/// when == ' ': Only if key is not preceded with '*' or '!' (indent afterwards) /// /// "keytyped" can have a few special values: /// KEY_OPEN_FORW : @@ -3669,11 +3017,11 @@ void fix_indent(void) /// @param line_is_empty when true, accept keys with '0' before them. bool in_cinkeys(int keytyped, int when, bool line_is_empty) { - char_u *look; + char *look; int try_match; int try_match_word; - char_u *p; - char_u *line; + char *p; + char *line; bool icase; if (keytyped == NUL) { @@ -3687,17 +3035,15 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) look = curbuf->b_p_cink; // 'indentexpr' empty: use 'cinkeys' } while (*look) { - /* - * Find out if we want to try a match with this key, depending on - * 'when' and a '*' or '!' before the key. - */ + // Find out if we want to try a match with this key, depending on + // 'when' and a '*' or '!' before the key. switch (when) { case '*': try_match = (*look == '*'); break; case '!': try_match = (*look == '!'); break; default: - try_match = (*look != '*'); break; + try_match = (*look != '*') && (*look != '!'); break; } if (*look == '*' || *look == '!') { look++; @@ -3740,8 +3086,8 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) } else if (*look == 'e') { if (try_match && keytyped == 'e' && curwin->w_cursor.col >= 4) { p = get_cursor_line_ptr(); - if ((char_u *)skipwhite((char *)p) == p + curwin->w_cursor.col - 4 - && STRNCMP(p + curwin->w_cursor.col - 4, "else", 4) == 0) { + if (skipwhite(p) == p + curwin->w_cursor.col - 4 + && strncmp(p + curwin->w_cursor.col - 4, "else", 4) == 0) { return true; } } @@ -3780,12 +3126,12 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) // make up some named keys <o>, <O>, <e>, <0>, <>>, <<>, <*>, // <:> and <!> so that people can re-indent on o, O, e, 0, <, // >, *, : and ! keys if they really really want to. - if (vim_strchr("<>!*oOe0:", look[1]) != NULL + if (vim_strchr("<>!*oOe0:", (uint8_t)look[1]) != NULL && keytyped == look[1]) { return true; } - if (keytyped == get_special_key_code(look + 1)) { + if (keytyped == get_special_key_code((char_u *)look + 1)) { return true; } } @@ -3804,19 +3150,19 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) } else { icase = false; } - p = (char_u *)vim_strchr((char *)look, ','); + p = vim_strchr(look, ','); if (p == NULL) { - p = look + STRLEN(look); + p = look + strlen(look); } if ((try_match || try_match_word) && curwin->w_cursor.col >= (colnr_T)(p - look)) { bool match = false; if (keytyped == KEY_COMPLETE) { - char_u *n, *s; + char *n, *s; - /* Just completed a word, check if it starts with "look". - * search back for the start of a word. */ + // Just completed a word, check if it starts with "look". + // search back for the start of a word. line = get_cursor_line_ptr(); for (s = line + curwin->w_cursor.col; s > line; s = n) { n = mb_prevptr(line, s); @@ -3828,28 +3174,28 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) if (s + (p - look) <= line + curwin->w_cursor.col && (icase ? mb_strnicmp(s, look, (size_t)(p - look)) - : STRNCMP(s, look, p - look)) == 0) { + : strncmp(s, look, (size_t)(p - look))) == 0) { match = true; } } else { // TODO(@brammool): multi-byte - if (keytyped == (int)p[-1] + if (keytyped == (int)(uint8_t)p[-1] || (icase && keytyped < 256 - && TOLOWER_LOC(keytyped) == TOLOWER_LOC((int)p[-1]))) { + && TOLOWER_LOC(keytyped) == TOLOWER_LOC((uint8_t)p[-1]))) { line = get_cursor_pos_ptr(); assert(p >= look && (uintmax_t)(p - look) <= SIZE_MAX); if ((curwin->w_cursor.col == (colnr_T)(p - look) - || !vim_iswordc(line[-(p - look) - 1])) + || !vim_iswordc((uint8_t)line[-(p - look) - 1])) && (icase ? mb_strnicmp(line - (p - look), look, (size_t)(p - look)) - : STRNCMP(line - (p - look), look, p - look)) == 0) { + : strncmp(line - (p - look), look, (size_t)(p - look))) == 0) { match = true; } } } if (match && try_match_word && !try_match) { - /* "0=word": Check if there are only blanks before the - * word. */ + // "0=word": Check if there are only blanks before the + // word. if (getwhitecols_curline() != (int)(curwin->w_cursor.col - (p - look))) { match = false; @@ -3863,7 +3209,7 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) // Ok, it's a boring generic character. } else { - if (try_match && *look == keytyped) { + if (try_match && (uint8_t)(*look) == keytyped) { return true; } if (*look != NUL) { @@ -3871,17 +3217,13 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) } } - /* - * Skip over ", ". - */ + // Skip over ", ". look = skip_to_option_part(look); } return false; } -/* - * Map Hebrew keyboard when in hkmap mode. - */ +// Map Hebrew keyboard when in hkmap mode. int hkmap(int c) FUNC_ATTR_PURE { @@ -3983,9 +3325,7 @@ static void ins_reg(void) int literally = 0; int vis_active = VIsual_active; - /* - * If we are going to wait for a character, show a '"'. - */ + // If we are going to wait for a character, show a '"'. pc_status = PC_STATUS_UNSET; if (redrawing() && !char_avail()) { // may need to redraw when no more chars available now @@ -4000,13 +3340,13 @@ static void ins_reg(void) no_mapping++; allow_keys++; regname = plain_vgetc(); - LANGMAP_ADJUST(regname, TRUE); + LANGMAP_ADJUST(regname, true); if (regname == Ctrl_R || regname == Ctrl_O || regname == Ctrl_P) { // Get a third key for literal register insertion literally = regname; add_to_showcmd_c(literally); regname = plain_vgetc(); - LANGMAP_ADJUST(regname, TRUE); + LANGMAP_ADJUST(regname, true); } no_mapping--; allow_keys--; @@ -4044,7 +3384,7 @@ static void ins_reg(void) need_redraw = true; // remove the '"' } else if (stop_insert_mode) { // When the '=' register was used and a function was invoked that - // did ":stopinsert" then stuff_empty() returns FALSE but we won't + // did ":stopinsert" then stuff_empty() returns false but we won't // insert anything, need to remove the '"' need_redraw = true; } @@ -4067,9 +3407,7 @@ static void ins_reg(void) } } -/* - * CTRL-G commands in Insert mode. - */ +// CTRL-G commands in Insert mode. static void ins_ctrl_g(void) { int c; @@ -4123,9 +3461,7 @@ static void ins_ctrl_g(void) } } -/* - * CTRL-^ in Insert mode. - */ +// CTRL-^ in Insert mode. static void ins_ctrl_hat(void) { if (map_to_exists_mode("", MODE_LANGMAP, false)) { @@ -4138,7 +3474,7 @@ static void ins_ctrl_hat(void) State |= MODE_LANGMAP; } } - set_iminsert_global(); + set_iminsert_global(curbuf); showmode(); // Show/unshow value of 'keymap' in status lines. status_redraw_curbuf(); @@ -4169,10 +3505,8 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) AppendToRedobuff(ESC_STR); } - /* - * Repeating insert may take a long time. Check for - * interrupt now and then. - */ + // Repeating insert may take a long time. Check for + // interrupt now and then. if (*count > 0) { line_breakcheck(); if (got_int) { @@ -4195,7 +3529,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) // Repeat the insert return false; } - stop_insert(&curwin->w_cursor, TRUE, nomove); + stop_insert(&curwin->w_cursor, true, nomove); undisplay_dollar(); } @@ -4215,10 +3549,8 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) RESET_FMARK(&curbuf->b_last_insert, curwin->w_cursor, curbuf->b_fnum, view); } - /* - * The cursor should end up on the last inserted character. - * Don't do it for CTRL-O, unless past the end of the line. - */ + // The cursor should end up on the last inserted character. + // Don't do it for CTRL-O, unless past the end of the line. if (!nomove && (curwin->w_cursor.col != 0 || curwin->w_cursor.coladd > 0) && (restart_edit == NUL || (gchar_cursor() == NUL && !VIsual_active)) @@ -4256,15 +3588,13 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) return true; } -/* - * Toggle language: hkmap and revins_on. - * Move to end of reverse inserted text. - */ +// Toggle language: hkmap and revins_on. +// Move to end of reverse inserted text. static void ins_ctrl_(void) { if (revins_on && revins_chars && revins_scol >= 0) { while (gchar_cursor() != NUL && revins_chars--) { - ++curwin->w_cursor.col; + curwin->w_cursor.col++; } } p_ri = !p_ri; @@ -4326,14 +3656,11 @@ static bool ins_start_select(int c) return false; } -/* - * <Insert> key in Insert mode: toggle insert/replace mode. - */ +// <Insert> key in Insert mode: toggle insert/replace mode. static void ins_insert(int replaceState) { set_vim_var_string(VV_INSERTMODE, ((State & REPLACE_FLAG) ? "i" : - replaceState == MODE_VREPLACE ? "v" : - "r"), 1); + replaceState == MODE_VREPLACE ? "v" : "r"), 1); ins_apply_autocmds(EVENT_INSERTCHANGE); if (State & REPLACE_FLAG) { State = MODE_INSERT | (State & MODE_LANGMAP); @@ -4346,9 +3673,7 @@ static void ins_insert(int replaceState) ui_cursor_shape(); // may show different cursor shape } -/* - * Pressed CTRL-O in Insert mode. - */ +// Pressed CTRL-O in Insert mode. static void ins_ctrl_o(void) { if (State & VREPLACE_FLAG) { @@ -4365,13 +3690,11 @@ static void ins_ctrl_o(void) } } -/* - * If the cursor is on an indent, ^T/^D insert/delete one - * shiftwidth. Otherwise ^T/^D behave like a "<<" or ">>". - * Always round the indent to 'shiftwidth', this is compatible - * with vi. But vi only supports ^T and ^D after an - * autoindent, we support it everywhere. - */ +// If the cursor is on an indent, ^T/^D insert/delete one +// shiftwidth. Otherwise ^T/^D behave like a "<<" or ">>". +// Always round the indent to 'shiftwidth', this is compatible +// with vi. But vi only supports ^T and ^D after an +// autoindent, we support it everywhere. static void ins_shift(int c, int lastc) { if (stop_arrow() == FAIL) { @@ -4379,9 +3702,7 @@ static void ins_shift(int c, int lastc) } AppendCharToRedobuff(c); - /* - * 0^D and ^^D: remove all indent. - */ + // 0^D and ^^D: remove all indent. if (c == Ctrl_D && (lastc == '0' || lastc == '^') && curwin->w_cursor.col > 0) { curwin->w_cursor.col--; @@ -4393,12 +3714,12 @@ static void ins_shift(int c, int lastc) if (lastc == '^') { old_indent = get_indent(); // remember curr. indent } - change_indent(INDENT_SET, 0, TRUE, 0, TRUE); + change_indent(INDENT_SET, 0, true, 0, true); } else { - change_indent(c == Ctrl_D ? INDENT_DEC : INDENT_INC, 0, TRUE, 0, TRUE); + change_indent(c == Ctrl_D ? INDENT_DEC : INDENT_INC, 0, true, 0, true); } - if (did_ai && *skipwhite((char *)get_cursor_line_ptr()) != NUL) { + if (did_ai && *skipwhite(get_cursor_line_ptr()) != NUL) { did_ai = false; } did_si = false; @@ -4437,9 +3758,7 @@ static void ins_del(void) AppendCharToRedobuff(K_DEL); } -/* - * Delete one character for ins_bs(). - */ +// Delete one character for ins_bs(). static void ins_bs_one(colnr_T *vcolp) { dec_cursor(); @@ -4532,13 +3851,11 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) return false; } Insstart.lnum--; - Insstart.col = (colnr_T)STRLEN(ml_get(Insstart.lnum)); + Insstart.col = (colnr_T)strlen(ml_get(Insstart.lnum)); } - /* - * In replace mode: - * cc < 0: NL was inserted, delete it - * cc >= 0: NL was replaced, put original characters back - */ + // In replace mode: + // cc < 0: NL was inserted, delete it + // cc >= 0: NL was replaced, put original characters back cc = -1; if (State & REPLACE_FLAG) { cc = replace_pop(); // returns -1 if NL was inserted @@ -4558,16 +3875,16 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) // again when auto-formatting. if (has_format_option(FO_AUTO) && has_format_option(FO_WHITE_PAR)) { - char_u *ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum, true); + char *ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum, true); int len; - len = (int)STRLEN(ptr); + len = (int)strlen(ptr); if (len > 0 && ptr[len - 1] == ' ') { ptr[len - 1] = NUL; } } - do_join(2, FALSE, FALSE, FALSE, false); + do_join(2, false, false, false, false); if (temp == NUL && gchar_cursor() != NUL) { inc_cursor(); } @@ -4618,9 +3935,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) curwin->w_cursor.col = save_col; } - /* - * Handle deleting one 'shiftwidth' or 'softtabstop'. - */ + // Handle deleting one 'shiftwidth' or 'softtabstop'. if (mode == BACKSPACE_CHAR && ((p_sta && in_indent) || ((get_sts_value() != 0 @@ -4654,7 +3969,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) // delete characters until we are at or before want_vcol while (vcol > want_vcol && curwin->w_cursor.col > 0 - && (cc = *(get_cursor_pos_ptr() - 1), ascii_iswhite(cc))) { + && (cc = (uint8_t)(*(get_cursor_pos_ptr() - 1)), ascii_iswhite(cc))) { ins_bs_one(&vcol); } @@ -4669,7 +3984,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) if (State & VREPLACE_FLAG) { ins_char(' '); } else { - ins_str((char_u *)" "); + ins_str(" "); if ((State & REPLACE_FLAG)) { replace_push(NUL); } @@ -4841,7 +4156,7 @@ static void ins_mousescroll(int dir) if (dir == MSCR_DOWN || dir == MSCR_UP) { if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { scroll_redraw(dir, (long)(curwin->w_botline - curwin->w_topline)); - } else { + } else if (p_mousescroll_vert > 0) { scroll_redraw(dir, p_mousescroll_vert); } } else { @@ -4849,7 +4164,7 @@ static void ins_mousescroll(int dir) } } - curwin->w_redr_status = TRUE; + curwin->w_redr_status = true; curwin = old_curwin; curbuf = curwin->w_buffer; @@ -4880,7 +4195,7 @@ static void ins_left(void) revins_legal++; } revins_chars++; - } else if (vim_strchr((char *)p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) { + } else if (vim_strchr(p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) { // if 'whichwrap' set for cursor in insert mode may go to previous line. // always break undo when moving upwards/downwards, else undo may break start_arrow(&tpos); @@ -4966,14 +4281,14 @@ static void ins_right(void) if (virtual_active()) { oneright(); } else { - curwin->w_cursor.col += utfc_ptr2len((char *)get_cursor_pos_ptr()); + curwin->w_cursor.col += utfc_ptr2len(get_cursor_pos_ptr()); } revins_legal++; if (revins_chars) { revins_chars--; } - } else if (vim_strchr((char *)p_ww, ']') != NULL + } else if (vim_strchr(p_ww, ']') != NULL && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { // if 'whichwrap' set for cursor in insert mode, may move the // cursor to the next line @@ -5017,13 +4332,13 @@ static void ins_up(bool startcol) undisplay_dollar(); tpos = curwin->w_cursor; - if (cursor_up(1L, TRUE) == OK) { + if (cursor_up(1L, true) == OK) { if (startcol) { coladvance(getvcol_nolist(&Insstart)); } if (old_topline != curwin->w_topline || old_topfill != curwin->w_topfill) { - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } start_arrow(&tpos); can_cindent = true; @@ -5065,13 +4380,13 @@ static void ins_down(bool startcol) undisplay_dollar(); tpos = curwin->w_cursor; - if (cursor_down(1L, TRUE) == OK) { + if (cursor_down(1L, true) == OK) { if (startcol) { coladvance(getvcol_nolist(&Insstart)); } if (old_topline != curwin->w_topline || old_topfill != curwin->w_topfill) { - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } start_arrow(&tpos); can_cindent = true; @@ -5175,21 +4490,19 @@ static bool ins_tab(void) if (State & VREPLACE_FLAG) { ins_char(' '); } else { - ins_str((char_u *)" "); + ins_str(" "); if (State & REPLACE_FLAG) { // no char replaced replace_push(NUL); } } } - /* - * When 'expandtab' not set: Replace spaces by TABs where possible. - */ + // When 'expandtab' not set: Replace spaces by TABs where possible. if (!curbuf->b_p_et && (tabstop_count(curbuf->b_p_vsts_array) > 0 || get_sts_value() > 0 || (p_sta && ind))) { - char_u *ptr; - char_u *saved_line = NULL; // init for GCC + char *ptr; + char *saved_line = NULL; // init for GCC pos_T pos; pos_T fpos; pos_T *cursor; @@ -5202,7 +4515,7 @@ static bool ins_tab(void) if (State & VREPLACE_FLAG) { pos = curwin->w_cursor; cursor = &pos; - saved_line = vim_strsave(get_cursor_line_ptr()); + saved_line = xstrdup(get_cursor_line_ptr()); ptr = saved_line + pos.col; } else { ptr = get_cursor_pos_ptr(); @@ -5233,11 +4546,15 @@ static bool ins_tab(void) getvcol(curwin, &fpos, &vcol, NULL, NULL); getvcol(curwin, cursor, &want_vcol, NULL, NULL); + char *tab = "\t"; + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, 0, vcol, tab, tab); + // Use as many TABs as possible. Beware of 'breakindent', 'showbreak' // and 'linebreak' adding extra virtual columns. while (ascii_iswhite(*ptr)) { - i = lbr_chartabsize(NULL, (char_u *)"\t", vcol); - if (vcol + i > want_vcol) { + i = lbr_chartabsize(&cts); + if (cts.cts_vcol + i > want_vcol) { break; } if (*ptr != TAB) { @@ -5252,19 +4569,24 @@ static bool ins_tab(void) } fpos.col++; ptr++; - vcol += i; + cts.cts_vcol += i; } + vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); if (change_col >= 0) { int repl_off = 0; - char_u *line = ptr; - // Skip over the spaces we need. - while (vcol < want_vcol && *ptr == ' ') { - vcol += lbr_chartabsize(line, ptr, vcol); - ptr++; + 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++; 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. ptr--; @@ -5275,7 +4597,7 @@ static bool ins_tab(void) // Delete following spaces. i = cursor->col - fpos.col; if (i > 0) { - STRMOVE(ptr, ptr + i); + STRMOVE(ptr, (char *)ptr + i); // correct replace stack. if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG)) { @@ -5326,11 +4648,9 @@ bool ins_eol(int c) } undisplay_dollar(); - /* - * Strange Vi behaviour: In Replace mode, typing a NL will not delete the - * character under the cursor. Only push a NUL on the replace stack, - * nothing to put back when the NL is deleted. - */ + // Strange Vi behaviour: In Replace mode, typing a NL will not delete the + // character under the cursor. Only push a NUL on the replace stack, + // nothing to put back when the NL is deleted. if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG)) { replace_push(NUL); @@ -5349,7 +4669,7 @@ bool ins_eol(int c) // NL in reverse insert will always start in the end of current line. if (revins_on) { - curwin->w_cursor.col += (colnr_T)STRLEN(get_cursor_pos_ptr()); + curwin->w_cursor.col += (colnr_T)strlen(get_cursor_pos_ptr()); } AppendToRedobuff(NL_STR); @@ -5364,11 +4684,9 @@ bool ins_eol(int c) return i; } -/* - * Handle digraph in insert mode. - * Returns character still to be inserted, or NUL when nothing remaining to be - * done. - */ +// Handle digraph in insert mode. +// Returns character still to be inserted, or NUL when nothing remaining to be +// done. static int ins_digraph(void) { int c; @@ -5400,7 +4718,7 @@ static int ins_digraph(void) if (IS_SPECIAL(c) || mod_mask) { // special key clear_showcmd(); - insert_special(c, TRUE, FALSE); + insert_special(c, true, false); return NUL; } if (c != ESC) { @@ -5437,16 +4755,13 @@ static int ins_digraph(void) return NUL; } -/* - * Handle CTRL-E and CTRL-Y in Insert mode: copy char from other line. - * Returns the char to be inserted, or NUL if none found. - */ +// Handle CTRL-E and CTRL-Y in Insert mode: copy char from other line. +// Returns the char to be inserted, or NUL if none found. int ins_copychar(linenr_T lnum) { int c; - int temp; - char_u *ptr, *prev_ptr; - char_u *line; + char *ptr, *prev_ptr; + char *line; if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { vim_beep(BO_COPY); @@ -5454,28 +4769,32 @@ int ins_copychar(linenr_T lnum) } // try to advance to the cursor column - temp = 0; - line = ptr = ml_get(lnum); - prev_ptr = ptr; + line = ml_get(lnum); + prev_ptr = line; validate_virtcol(); - while ((colnr_T)temp < curwin->w_virtcol && *ptr != NUL) { - prev_ptr = ptr; - temp += lbr_chartabsize_adv(line, &ptr, (colnr_T)temp); + + 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 ((colnr_T)temp > curwin->w_virtcol) { + + if (cts.cts_vcol > curwin->w_virtcol) { ptr = prev_ptr; + } else { + ptr = cts.cts_ptr; } + clear_chartabsize_arg(&cts); - c = utf_ptr2char((char *)ptr); + c = utf_ptr2char(ptr); if (c == NUL) { vim_beep(BO_COPY); } return c; } -/* - * CTRL-Y or CTRL-E typed in Insert mode. - */ +// CTRL-Y or CTRL-E typed in Insert mode. static int ins_ctrl_ey(int tc) { int c = tc; @@ -5486,7 +4805,7 @@ static int ins_ctrl_ey(int tc) } else { scrollup_clamp(); } - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } else { c = ins_copychar(curwin->w_cursor.lnum + (c == Ctrl_Y ? -1 : 1)); if (c != NUL) { @@ -5501,7 +4820,7 @@ static int ins_ctrl_ey(int tc) } tw_save = curbuf->b_p_tw; curbuf->b_p_tw = -1; - insert_special(c, TRUE, FALSE); + insert_special(c, true, false); curbuf->b_p_tw = tw_save; revins_chars++; revins_legal++; @@ -5512,31 +4831,25 @@ static int ins_ctrl_ey(int tc) return c; } -/* - * Try to do some very smart auto-indenting. - * Used when inserting a "normal" character. - */ +// Try to do some very smart auto-indenting. +// Used when inserting a "normal" character. static void ins_try_si(int c) { pos_T *pos, old_pos; - char_u *ptr; + char *ptr; int i; bool temp; - /* - * do some very smart indenting when entering '{' or '}' - */ + // do some very smart indenting when entering '{' or '}' if (((did_si || can_si_back) && c == '{') || (can_si && c == '}' && inindent(0))) { // for '}' set indent equal to indent of line containing matching '{' if (c == '}' && (pos = findmatch(NULL, '{')) != NULL) { old_pos = curwin->w_cursor; - /* - * If the matching '{' has a ')' immediately before it (ignoring - * white-space), then line up with the start of the line - * containing the matching '(' if there is one. This handles the - * case where an "if (..\n..) {" statement continues over multiple - * lines -- webb - */ + // If the matching '{' has a ')' immediately before it (ignoring + // white-space), then line up with the start of the line + // containing the matching '(' if there is one. This handles the + // case where an "if (..\n..) {" statement continues over multiple + // lines -- webb ptr = ml_get(pos->lnum); i = pos->col; if (i > 0) { // skip blanks before '{' @@ -5550,7 +4863,7 @@ static void ins_try_si(int c) i = get_indent(); curwin->w_cursor = old_pos; if (State & VREPLACE_FLAG) { - change_indent(INDENT_SET, i, FALSE, NUL, TRUE); + change_indent(INDENT_SET, i, false, NUL, true); } else { (void)set_indent(i, SIN_CHANGED); } @@ -5562,7 +4875,7 @@ static void ins_try_si(int c) old_pos = curwin->w_cursor; i = get_indent(); while (curwin->w_cursor.lnum > 1) { - ptr = (char_u *)skipwhite((char *)ml_get(--(curwin->w_cursor.lnum))); + ptr = skipwhite(ml_get(--(curwin->w_cursor.lnum))); // ignore empty lines and lines starting with '#'. if (*ptr != '#' && *ptr != NUL) { @@ -5575,14 +4888,12 @@ static void ins_try_si(int c) curwin->w_cursor = old_pos; } if (temp) { - shift_line(TRUE, FALSE, 1, TRUE); + shift_line(true, false, 1, true); } } } - /* - * set indent of '#' always to 0 - */ + // set indent of '#' always to 0 if (curwin->w_cursor.col > 0 && can_si && c == '#' && inindent(0)) { // remember current indent for next line old_indent = get_indent(); @@ -5595,10 +4906,8 @@ static void ins_try_si(int c) } } -/* - * Get the value that w_virtcol would have when 'list' is off. - * Unless 'cpo' contains the 'L' flag. - */ +// Get the value that w_virtcol would have when 'list' is off. +// Unless 'cpo' contains the 'L' flag. colnr_T get_nolist_virtcol(void) { // check validity of cursor in current buffer @@ -5613,13 +4922,11 @@ colnr_T get_nolist_virtcol(void) return curwin->w_virtcol; } -/* - * Handle the InsertCharPre autocommand. - * "c" is the character that was typed. - * Return a pointer to allocated memory with the replacement string. - * Return NULL to continue inserting "c". - */ -static char_u *do_insert_char_pre(int c) +// Handle the InsertCharPre autocommand. +// "c" is the character that was typed. +// Return a pointer to allocated memory with the replacement string. +// Return NULL to continue inserting "c". +static char *do_insert_char_pre(int c) { char buf[MB_MAXBYTES + 1]; const int save_State = State; @@ -5634,13 +4941,13 @@ static char_u *do_insert_char_pre(int c) textlock++; set_vim_var_string(VV_CHAR, buf, -1); - char_u *res = NULL; + char *res = NULL; if (ins_apply_autocmds(EVENT_INSERTCHARPRE)) { // Get the value of v:char. It may be empty or more than one // character. Only use it when changed, otherwise continue with the // original character to avoid breaking autoindent. - if (STRCMP(buf, get_vim_var_str(VV_CHAR)) != 0) { - res = vim_strsave((char_u *)get_vim_var_str(VV_CHAR)); + if (strcmp(buf, get_vim_var_str(VV_CHAR)) != 0) { + res = xstrdup(get_vim_var_str(VV_CHAR)); } } @@ -5653,11 +4960,16 @@ static char_u *do_insert_char_pre(int c) return res; } -bool can_cindent_get(void) +bool get_can_cindent(void) { return can_cindent; } +void set_can_cindent(bool val) +{ + can_cindent = val; +} + /// Trigger "event" and take care of fixing undo. int ins_apply_autocmds(event_T event) { diff --git a/src/nvim/edit.h b/src/nvim/edit.h index eda6d8c9db..91c519f015 100644 --- a/src/nvim/edit.h +++ b/src/nvim/edit.h @@ -4,8 +4,6 @@ #include "nvim/autocmd.h" #include "nvim/vim.h" -typedef int (*IndentGetter)(void); - // Values for in_cinkeys() #define KEY_OPEN_FORW 0x101 #define KEY_OPEN_BACK 0x102 diff --git a/src/nvim/eval.c b/src/nvim/eval.c index cb46e26f82..4392ea306f 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -3,19 +3,19 @@ // eval.c: Expression evaluation. +#include <assert.h> +#include <ctype.h> +#include <inttypes.h> #include <math.h> +#include <stdio.h> #include <stdlib.h> +#include <string.h> #include "auto/config.h" - -#ifdef HAVE_LOCALE_H -# include <locale.h> -#endif - +#include "nvim/api/private/defs.h" #include "nvim/ascii.h" -#include "nvim/autocmd.h" #include "nvim/buffer.h" -#include "nvim/change.h" +#include "nvim/buffer_defs.h" #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cmdhist.h" @@ -28,36 +28,58 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" +#include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" +#include "nvim/event/process.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" +#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/highlight_group.h" +#include "nvim/insexpand.h" +#include "nvim/keycodes.h" +#include "nvim/lib/queue.h" +#include "nvim/locale.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" +#include "nvim/main.h" +#include "nvim/map.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/optionstr.h" -#include "nvim/os/input.h" +#include "nvim/os/fileio.h" +#include "nvim/os/fs_defs.h" +#include "nvim/os/os.h" #include "nvim/os/shell.h" +#include "nvim/os/stdpaths_defs.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/runtime.h" -#include "nvim/screen.h" #include "nvim/search.h" -#include "nvim/sign.h" -#include "nvim/syntax.h" +#include "nvim/strings.h" +#include "nvim/tag.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" -#include "nvim/undo.h" +#include "nvim/usercmd.h" #include "nvim/version.h" +#include "nvim/vim.h" #include "nvim/window.h" // TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead @@ -65,11 +87,15 @@ #define DICT_MAXNEST 100 // maximum nesting of lists and dicts static char *e_missbrac = N_("E111: Missing ']'"); +static char *e_list_end = N_("E697: Missing end of List ']': %s"); static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); static char *e_nowhitespace = N_("E274: No white space allowed before parenthesis"); static char *e_write2 = N_("E80: Error while writing: %s"); static char *e_string_list_or_blob_required = N_("E1098: String, List or Blob required"); +static char e_expression_too_recursive_str[] = N_("E1169: Expression too recursive: %s"); +static char e_dot_can_only_be_used_on_dictionary_str[] + = N_("E1203: Dot can only be used on a dictionary: %s"); static char * const namespace_char = "abglstvw"; @@ -101,7 +127,7 @@ static garray_T ga_loaded = { 0, 0, sizeof(char *), 4, NULL }; /// Info used by a ":for" loop. typedef struct { - int fi_semicolon; // TRUE if ending in '; var]' + int fi_semicolon; // true if ending in '; var]' int fi_varcount; // nr of variables in the list listwatch_T fi_lw; // keep an eye on the item used. list_T *fi_list; // list being used @@ -137,8 +163,7 @@ static struct vimvar { char *vv_name; ///< Name of the variable, without v:. TV_DICTITEM_STRUCT(VIMVAR_KEY_LEN + 1) vv_di; ///< Value and name for key (max 16 chars). char vv_flags; ///< Flags: #VV_COMPAT, #VV_RO, #VV_RO_SBX. -} vimvars[] = -{ +} vimvars[] = { // VV_ tails differing from upcased string literals: // VV_CC_FROM "charconvert_from" // VV_CC_TO "charconvert_to" @@ -242,6 +267,8 @@ static struct vimvar { VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), VV(VV__NULL_BLOB, "_null_blob", VAR_BLOB, VV_RO), VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), + VV(VV_RELNUM, "relnum", VAR_NUMBER, VV_RO), + VV(VV_VIRTNUM, "virtnum", VAR_NUMBER, VV_RO), }; #undef VV @@ -335,6 +362,10 @@ varnumber_T num_divide(varnumber_T n1, varnumber_T n2) } else { result = VARNUMBER_MAX; } + } else if (n1 == VARNUMBER_MIN && n2 == -1) { + // specific case: trying to do VARNUMBAR_MIN / -1 results in a positive + // number that doesn't fit in varnumber_T and causes an FPE + result = VARNUMBER_MAX; } else { result = n1 / n2; } @@ -363,7 +394,7 @@ void eval_init(void) for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { struct vimvar *p = &vimvars[i]; - assert(STRLEN(p->vv_name) <= VIMVAR_KEY_LEN); + assert(strlen(p->vv_name) <= VIMVAR_KEY_LEN); STRCPY(p->vv_di.di_key, p->vv_name); if (p->vv_flags & VV_RO) { p->vv_di.di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; @@ -375,11 +406,11 @@ void eval_init(void) // add to v: scope dict, unless the value is not always available if (p->vv_type != VAR_UNKNOWN) { - hash_add(&vimvarht, p->vv_di.di_key); + hash_add(&vimvarht, (char *)p->vv_di.di_key); } if (p->vv_flags & VV_COMPAT) { // add to compat scope dict - hash_add(&compat_hashtab, p->vv_di.di_key); + hash_add(&compat_hashtab, (char *)p->vv_di.di_key); } } vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; @@ -490,7 +521,7 @@ void eval_clear(void) /// Set an internal variable to a string value. Creates the variable if it does /// not already exist. -void set_internal_string_var(const char *name, char *value) +void set_internal_string_var(const char *name, char *value) // NOLINT(readability-non-const-parameter) FUNC_ATTR_NONNULL_ARG(1) { typval_T tv = { @@ -546,7 +577,7 @@ int var_redir_start(char *name, int append) // check if we can write to the variable: set it to or append an empty // string - int save_emsg = did_emsg; + const int called_emsg_before = called_emsg; did_emsg = false; typval_T tv; tv.v_type = VAR_STRING; @@ -557,9 +588,7 @@ int var_redir_start(char *name, int append) set_var_lval(redir_lval, redir_endp, &tv, true, false, "="); } clear_lval(redir_lval); - int err = did_emsg; - did_emsg |= save_emsg; - if (err) { + if (called_emsg > called_emsg_before) { redir_endp = NULL; // don't store a value, only cleanup var_redir_stop(); return FAIL; @@ -583,7 +612,7 @@ void var_redir_str(char *value, int value_len) int len; if (value_len == -1) { - len = (int)STRLEN(value); // Append the entire string + len = (int)strlen(value); // Append the entire string } else { len = value_len; // Append only "value_len" characters } @@ -631,7 +660,7 @@ int eval_charconvert(const char *const enc_from, const char *const enc_to, set_vim_var_string(VV_CC_TO, enc_to, -1); set_vim_var_string(VV_FNAME_IN, fname_from, -1); set_vim_var_string(VV_FNAME_OUT, fname_to, -1); - if (eval_to_bool((char *)p_ccv, &err, NULL, false)) { + if (eval_to_bool(p_ccv, &err, NULL, false)) { err = true; } set_vim_var_string(VV_CC_FROM, NULL, -1); @@ -645,25 +674,6 @@ int eval_charconvert(const char *const enc_from, const char *const enc_to, return OK; } -int eval_printexpr(const char *const fname, const char *const args) -{ - bool err = false; - - set_vim_var_string(VV_FNAME_IN, fname, -1); - set_vim_var_string(VV_CMDARG, args, -1); - if (eval_to_bool((char *)p_pexpr, &err, NULL, false)) { - err = true; - } - set_vim_var_string(VV_FNAME_IN, NULL, -1); - set_vim_var_string(VV_CMDARG, NULL, -1); - - if (err) { - os_remove(fname); - return FAIL; - } - return OK; -} - void eval_diff(const char *const origfile, const char *const newfile, const char *const outfile) { bool err = false; @@ -671,7 +681,7 @@ void eval_diff(const char *const origfile, const char *const newfile, const char set_vim_var_string(VV_FNAME_IN, origfile, -1); set_vim_var_string(VV_FNAME_NEW, newfile, -1); set_vim_var_string(VV_FNAME_OUT, outfile, -1); - (void)eval_to_bool((char *)p_dex, &err, NULL, false); + (void)eval_to_bool(p_dex, &err, NULL, false); set_vim_var_string(VV_FNAME_IN, NULL, -1); set_vim_var_string(VV_FNAME_NEW, NULL, -1); set_vim_var_string(VV_FNAME_OUT, NULL, -1); @@ -684,18 +694,18 @@ void eval_patch(const char *const origfile, const char *const difffile, const ch set_vim_var_string(VV_FNAME_IN, origfile, -1); set_vim_var_string(VV_FNAME_DIFF, difffile, -1); set_vim_var_string(VV_FNAME_OUT, outfile, -1); - (void)eval_to_bool((char *)p_pex, &err, NULL, false); + (void)eval_to_bool(p_pex, &err, NULL, false); set_vim_var_string(VV_FNAME_IN, NULL, -1); set_vim_var_string(VV_FNAME_DIFF, NULL, -1); set_vim_var_string(VV_FNAME_OUT, NULL, -1); } /// Top level evaluation function, returning a boolean. -/// Sets "error" to TRUE if there was an error. +/// Sets "error" to true if there was an error. /// /// @param skip only parse, don't execute /// -/// @return TRUE or FALSE. +/// @return true or false. int eval_to_bool(char *arg, bool *error, char **nextcmd, int skip) { typval_T tv; @@ -762,7 +772,7 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv, int argc, typval_T *r if (s == NULL || *s == NUL) { return FAIL; } - funcexe.evaluate = true; + funcexe.fe_evaluate = true; if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) { return FAIL; } @@ -772,8 +782,8 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv, int argc, typval_T *r if (s == NULL || *s == NUL) { return FAIL; } - funcexe.evaluate = true; - funcexe.partial = partial; + funcexe.fe_evaluate = true; + funcexe.fe_partial = partial; if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) { return FAIL; } @@ -895,7 +905,7 @@ char *eval_to_string(char *arg, char **nextcmd, bool convert) /// Call eval_to_string() without using current local variables and using /// textlock. /// -/// @param use_sandbox when TRUE, use the sandbox. +/// @param use_sandbox when true, use the sandbox. char *eval_to_string_safe(char *arg, char **nextcmd, int use_sandbox) { char *retval; @@ -982,7 +992,7 @@ void prepare_vimvar(int idx, typval_T *save_tv) { *save_tv = vimvars[idx].vv_tv; if (vimvars[idx].vv_type == VAR_UNKNOWN) { - hash_add(&vimvarht, vimvars[idx].vv_di.di_key); + hash_add(&vimvarht, (char *)vimvars[idx].vv_di.di_key); } } @@ -991,24 +1001,15 @@ void prepare_vimvar(int idx, typval_T *save_tv) void restore_vimvar(int idx, typval_T *save_tv) { vimvars[idx].vv_tv = *save_tv; - if (vimvars[idx].vv_type == VAR_UNKNOWN) { - hashitem_T *hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key); - if (HASHITEM_EMPTY(hi)) { - internal_error("restore_vimvar()"); - } else { - hash_remove(&vimvarht, hi); - } + if (vimvars[idx].vv_type != VAR_UNKNOWN) { + return; } -} -/// If there is a window for "curbuf", make it the current window. -void find_win_for_curbuf(void) -{ - for (wininfo_T *wip = curbuf->b_wininfo; wip != NULL; wip = wip->wi_next) { - if (wip->wi_win != NULL) { - curwin = wip->wi_win; - break; - } + hashitem_T *hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key); + if (HASHITEM_EMPTY(hi)) { + internal_error("restore_vimvar()"); + } else { + hash_remove(&vimvarht, hi); } } @@ -1074,16 +1075,16 @@ int get_spellword(list_T *const list, const char **ret_word) return (int)tv_list_find_nr(list, -1, NULL); } -// Call some vim script function and return the result in "*rettv". -// Uses argv[0] to argv[argc-1] for the function arguments. argv[argc] -// should have type VAR_UNKNOWN. -// -// @return OK or FAIL. +/// Call some Vim script function and return the result in "*rettv". +/// Uses argv[0] to argv[argc - 1] for the function arguments. argv[argc] +/// should have type VAR_UNKNOWN. +/// +/// @return OK or FAIL. int call_vim_function(const char *func, int argc, typval_T *argv, typval_T *rettv) FUNC_ATTR_NONNULL_ALL { int ret; - int len = (int)STRLEN(func); + int len = (int)strlen(func); partial_T *pt = NULL; if (len >= 6 && !memcmp(func, "v:lua.", 6)) { @@ -1098,10 +1099,10 @@ int call_vim_function(const char *func, int argc, typval_T *argv, typval_T *rett rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this. funcexe_T funcexe = FUNCEXE_INIT; - funcexe.firstline = curwin->w_cursor.lnum; - funcexe.lastline = curwin->w_cursor.lnum; - funcexe.evaluate = true; - funcexe.partial = pt; + funcexe.fe_firstline = curwin->w_cursor.lnum; + funcexe.fe_lastline = curwin->w_cursor.lnum; + funcexe.fe_evaluate = true; + funcexe.fe_partial = pt; ret = call_func(func, len, rettv, argc, argv, &funcexe); fail: @@ -1111,26 +1112,10 @@ fail: return ret; } -/// Call Vim script function and return the result as a number -/// -/// @param[in] func Function name. -/// @param[in] argc Number of arguments. -/// @param[in] argv Array with typval_T arguments. -/// -/// @return -1 when calling function fails, result of function otherwise. -varnumber_T call_func_retnr(const char *func, int argc, typval_T *argv) - FUNC_ATTR_NONNULL_ALL -{ - typval_T rettv; - if (call_vim_function((char *)func, argc, argv, &rettv) == FAIL) { - return -1; - } - varnumber_T retval = tv_get_number_chk(&rettv, NULL); - tv_clear(&rettv); - return retval; -} -/// Call Vim script function and return the result as a string +/// Call Vim script function and return the result as a string. +/// Uses "argv[0]" to "argv[argc - 1]" for the function arguments. "argv[argc]" +/// should have type VAR_UNKNOWN. /// /// @param[in] func Function name. /// @param[in] argc Number of arguments. @@ -1152,7 +1137,9 @@ char *call_func_retstr(const char *const func, int argc, typval_T *argv) tv_clear(&rettv); return retval; } -/// Call Vim script function and return the result as a List + +/// Call Vim script function and return the result as a List. +/// Uses "argv" and "argc" as call_func_retstr(). /// /// @param[in] func Function name. /// @param[in] argc Number of arguments. @@ -1220,8 +1207,6 @@ int eval_foldexpr(char *arg, int *cp) return (int)retval; } -// TODO(ZyX-I): move to eval/executor - /// Get an lvalue /// /// Lvalue may be @@ -1316,13 +1301,26 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const return NULL; } - // Loop until no more [idx] or .key is following. lp->ll_tv = &v->di_tv; + + if (tv_is_luafunc(lp->ll_tv)) { + // For v:lua just return a pointer to the "." after the "v:lua". + // If the caller is trans_function_name() it will check for a Lua function name. + return p; + } + + // Loop until no more [idx] or .key is following. typval_T var1; var1.v_type = VAR_UNKNOWN; typval_T var2; var2.v_type = VAR_UNKNOWN; - while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) { + while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.')) { + if (*p == '.' && lp->ll_tv->v_type != VAR_DICT) { + if (!quiet) { + semsg(_(e_dot_can_only_be_used_on_dictionary_str), name); + } + return NULL; + } if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL) && !(lp->ll_tv->v_type == VAR_DICT && lp->ll_tv->vval.v_dict != NULL) && !(lp->ll_tv->v_type == VAR_BLOB && lp->ll_tv->vval.v_blob != NULL)) { @@ -1444,12 +1442,13 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const } wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE && tv_is_func(*rettv) - && !var_check_func_name((const char *)key, lp->ll_di == NULL)) + && var_wrong_func_name((const char *)key, lp->ll_di == NULL)) || !valid_varname((const char *)key)); if (len != -1) { key[len] = prevval; } if (wrong) { + tv_clear(&var1); return NULL; } } @@ -1486,9 +1485,9 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const tv_clear(&var1); break; // existing variable, need to check if it can be changed - } else if (!(flags & GLV_READ_ONLY) && var_check_ro(lp->ll_di->di_flags, - (const char *)name, - (size_t)(p - name))) { + } else if (!(flags & GLV_READ_ONLY) + && (var_check_ro(lp->ll_di->di_flags, name, (size_t)(p - name)) + || var_check_lock(lp->ll_di->di_flags, name, (size_t)(p - name)))) { tv_clear(&var1); return NULL; } @@ -1592,8 +1591,6 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const return p; } -// TODO(ZyX-I): move to eval/executor - /// Clear lval "lp" that was filled by get_lval(). void clear_lval(lval_T *lp) { @@ -1601,8 +1598,6 @@ void clear_lval(lval_T *lp) xfree(lp->ll_newkey); } -// TODO(ZyX-I): move to eval/executor - /// Set a variable that was parsed by get_lval() to "rettv". /// /// @param endp points to just after the parsed name. @@ -1623,7 +1618,7 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool semsg(_(e_letwrong), op); return; } - if (var_check_lock(lp->ll_blob->bv_lock, lp->ll_name, TV_CSTRING)) { + if (value_check_lock(lp->ll_blob->bv_lock, lp->ll_name, TV_CSTRING)) { return; } @@ -1653,7 +1648,7 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool // the end is an error otherwise. if (lp->ll_n1 < gap->ga_len || lp->ll_n1 == gap->ga_len) { ga_grow(&lp->ll_blob->bv_ga, 1); - tv_blob_set(lp->ll_blob, (int)lp->ll_n1, (char_u)val); + tv_blob_set(lp->ll_blob, (int)lp->ll_n1, (uint8_t)val); if (lp->ll_n1 == gap->ga_len) { gap->ga_len++; } @@ -1672,7 +1667,7 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool // handle +=, -=, *=, /=, %= and .= di = NULL; - if (get_var_tv(lp->ll_name, (int)STRLEN(lp->ll_name), + if (get_var_tv(lp->ll_name, (int)strlen(lp->ll_name), &tv, &di, true, false) == OK) { if ((di == NULL || (!var_check_ro(di->di_flags, lp->ll_name, TV_CSTRING) @@ -1686,10 +1681,10 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool set_var_const(lp->ll_name, lp->ll_name_len, rettv, copy, is_const); } *endp = (char)cc; - } else if (var_check_lock(lp->ll_newkey == NULL - ? lp->ll_tv->v_lock - : lp->ll_tv->vval.v_dict->dv_lock, - lp->ll_name, TV_CSTRING)) { + } else if (value_check_lock(lp->ll_newkey == NULL + ? lp->ll_tv->v_lock + : lp->ll_tv->vval.v_dict->dv_lock, + lp->ll_name, TV_CSTRING)) { // Skip } else if (lp->ll_range) { listitem_T *ll_li = lp->ll_li; @@ -1703,8 +1698,8 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool // Check whether any of the list items is locked for (ri = tv_list_first(rettv->vval.v_list); ri != NULL && ll_li != NULL;) { - if (var_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, lp->ll_name, - TV_CSTRING)) { + if (value_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, lp->ll_name, + TV_CSTRING)) { return; } ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri); @@ -1759,7 +1754,10 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool // Assign to a List or Dictionary item. if (lp->ll_newkey != NULL) { if (op != NULL && *op != '=') { - semsg(_(e_letwrong), op); + semsg(_(e_dictkey), lp->ll_newkey); + return; + } + if (tv_dict_wrong_func_name(lp->ll_tv->vval.v_dict, rettv, lp->ll_newkey)) { return; } @@ -1807,12 +1805,10 @@ notify: } } -// TODO(ZyX-I): move to eval/ex_cmds - /// Evaluate the expression used in a ":for var in expr" command. /// "arg" points to "var". /// -/// @param[out] *errp set to TRUE for an error, FALSE otherwise; +/// @param[out] *errp set to true for an error, false otherwise; /// /// @return a pointer that holds the info. Null when there is an error. void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) @@ -1884,8 +1880,6 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip) return fi; } -// TODO(ZyX-I): move to eval/ex_cmds - /// Use the first item in a ":for" list. Advance to the next. /// Assign the values to the variable (list). "arg" points to the first one. /// @@ -1926,15 +1920,12 @@ bool next_for_item(void *fi_void, char *arg) listitem_T *item = fi->fi_lw.lw_item; if (item == NULL) { return false; - } else { - fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item); - return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, - fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK); } + fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item); + return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, + fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK); } -// TODO(ZyX-I): move to eval/ex_cmds - /// Free the structure used to store info used by ":for". void free_for_info(void *fi_void) { @@ -1965,7 +1956,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) xp->xp_context = EXPAND_USER_VARS; if (strpbrk(arg, "\"'+-*/%.=!?~|&$([<>,#") == NULL) { // ":let var1 var2 ...": find last space. - for (p = arg + STRLEN(arg); p >= arg;) { + for (p = arg + strlen(arg); p >= arg;) { xp->xp_pattern = p; MB_PTR_BACK(arg, p); if (ascii_iswhite(*p)) { @@ -2047,7 +2038,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx) || cmdidx == CMD_echomsg) && xp->xp_context == EXPAND_EXPRESSION) { for (;;) { - char *const n = (char *)skiptowhite((char_u *)arg); + char *const n = skiptowhite(arg); if (n == arg || ascii_iswhite_or_nul(*skipwhite(n))) { break; @@ -2064,7 +2055,7 @@ void del_menutrans_vars(void) { hash_lock(&globvarht); HASHTAB_ITER(&globvarht, hi, { - if (STRNCMP(hi->hi_key, "menutrans_", 10) == 0) { + if (strncmp(hi->hi_key, "menutrans_", 10) == 0) { delete_var(&globvarht, hi); } }); @@ -2082,7 +2073,7 @@ static size_t varnamebuflen = 0; char *cat_prefix_varname(int prefix, const char *name) FUNC_ATTR_NONNULL_ALL { - size_t len = STRLEN(name) + 3; + size_t len = strlen(name) + 3; if (len > varnamebuflen) { xfree(varnamebuf); @@ -2122,10 +2113,10 @@ char *get_user_var_name(expand_T *xp, int idx) while (HASHITEM_EMPTY(hi)) { hi++; } - if (STRNCMP("g:", xp->xp_pattern, 2) == 0) { - return cat_prefix_varname('g', (char *)hi->hi_key); + if (strncmp("g:", xp->xp_pattern, 2) == 0) { + return cat_prefix_varname('g', hi->hi_key); } - return (char *)hi->hi_key; + return hi->hi_key; } // b: variables @@ -2139,7 +2130,7 @@ char *get_user_var_name(expand_T *xp, int idx) while (HASHITEM_EMPTY(hi)) { hi++; } - return cat_prefix_varname('b', (char *)hi->hi_key); + return cat_prefix_varname('b', hi->hi_key); } // w: variables @@ -2153,7 +2144,7 @@ char *get_user_var_name(expand_T *xp, int idx) while (HASHITEM_EMPTY(hi)) { hi++; } - return cat_prefix_varname('w', (char *)hi->hi_key); + return cat_prefix_varname('w', hi->hi_key); } // t: variables @@ -2167,7 +2158,7 @@ char *get_user_var_name(expand_T *xp, int idx) while (HASHITEM_EMPTY(hi)) { hi++; } - return cat_prefix_varname('t', (char *)hi->hi_key); + return cat_prefix_varname('t', hi->hi_key); } // v: variables @@ -2180,23 +2171,21 @@ char *get_user_var_name(expand_T *xp, int idx) return NULL; } -// TODO(ZyX-I): move to eval/expressions - /// Does not use 'cpo' and always uses 'magic'. /// -/// @return TRUE if "pat" matches "text". -int pattern_match(char *pat, char *text, bool ic) +/// @return true if "pat" matches "text". +int pattern_match(const char *pat, const char *text, bool ic) { int matches = 0; regmatch_T regmatch; // avoid 'l' flag in 'cpoptions' char *save_cpo = p_cpo; - p_cpo = ""; - regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + p_cpo = empty_option; + regmatch.regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { regmatch.rm_ic = ic; - matches = vim_regexec_nl(®match, (char_u *)text, (colnr_T)0); + matches = vim_regexec_nl(®match, (char *)text, (colnr_T)0); vim_regfree(regmatch.regprog); } p_cpo = save_cpo; @@ -2224,7 +2213,7 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ // If "s" is the name of a variable of type VAR_FUNC // use its contents. partial_T *partial; - s = (char *)deref_func_name((const char *)s, &len, &partial, !evaluate); + s = deref_func_name((const char *)s, &len, &partial, !evaluate); // Need to make a copy, in case evaluating the arguments makes // the name invalid. @@ -2232,12 +2221,12 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ // Invoke the function. funcexe_T funcexe = FUNCEXE_INIT; - funcexe.firstline = curwin->w_cursor.lnum; - funcexe.lastline = curwin->w_cursor.lnum; - funcexe.evaluate = evaluate; - funcexe.partial = partial; - funcexe.basetv = basetv; - int ret = get_func_tv((char_u *)s, len, rettv, arg, &funcexe); + funcexe.fe_firstline = curwin->w_cursor.lnum; + funcexe.fe_lastline = curwin->w_cursor.lnum; + funcexe.fe_evaluate = evaluate; + funcexe.fe_partial = partial; + funcexe.fe_basetv = basetv; + int ret = get_func_tv(s, len, rettv, arg, &funcexe); xfree(s); @@ -2261,15 +2250,13 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ return ret; } -// TODO(ZyX-I): move to eval/expressions - -/// The "evaluate" argument: When FALSE, the argument is only parsed but not +/// The "evaluate" argument: When false, the argument is only parsed but not /// executed. The function may return OK, but the rettv will be of type /// VAR_UNKNOWN. The function still returns FAIL for a syntax error. /// Handle zero level expression. /// This calls eval1() and handles error message and nextcmd. -/// Put the result in "rettv" when returning OK and "evaluate" is TRUE. +/// Put the result in "rettv" when returning OK and "evaluate" is true. /// Note: "rettv.v_lock" is not set. /// /// @return OK or FAIL. @@ -2279,10 +2266,15 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) char *p; const int did_emsg_before = did_emsg; const int called_emsg_before = called_emsg; + bool end_error = false; p = skipwhite(arg); ret = eval1(&p, rettv, evaluate); - if (ret == FAIL || !ends_excmd(*p)) { + + if (ret != FAIL) { + end_error = !ends_excmd(*p); + } + if (ret == FAIL || end_error) { if (ret != FAIL) { tv_clear(rettv); } @@ -2292,19 +2284,21 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate) // Also check called_emsg for when using assert_fails(). if (!aborting() && did_emsg == did_emsg_before && called_emsg == called_emsg_before) { - semsg(_(e_invexpr2), arg); + if (end_error) { + semsg(_(e_trailing_arg), p); + } else { + semsg(_(e_invexpr2), arg); + } } ret = FAIL; } if (nextcmd != NULL) { - *nextcmd = (char *)check_nextcmd((char_u *)p); + *nextcmd = check_nextcmd(p); } return ret; } -// TODO(ZyX-I): move to eval/expressions - /// Handle top level expression: /// expr2 ? expr1 : expr1 /// @@ -2369,8 +2363,6 @@ int eval1(char **arg, typval_T *rettv, int evaluate) return OK; } -// TODO(ZyX-I): move to eval/expressions - /// Handle first level expression: /// expr2 || expr2 || expr2 logical OR /// @@ -2428,8 +2420,6 @@ static int eval2(char **arg, typval_T *rettv, int evaluate) return OK; } -// TODO(ZyX-I): move to eval/expressions - /// Handle second level expression: /// expr3 && expr3 && expr3 logical AND /// @@ -2487,8 +2477,6 @@ static int eval3(char **arg, typval_T *rettv, int evaluate) return OK; } -// TODO(ZyX-I): move to eval/expressions - /// Handle third level expression: /// var1 == var2 /// var1 =~ var2 @@ -2555,7 +2543,7 @@ static int eval4(char **arg, typval_T *rettv, int evaluate) if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') { len = 5; } - if (!isalnum(p[len]) && p[len] != '_') { + if (!isalnum((uint8_t)p[len]) && p[len] != '_') { type = len == 2 ? EXPR_IS : EXPR_ISNOT; } } @@ -2592,8 +2580,6 @@ static int eval4(char **arg, typval_T *rettv, int evaluate) return OK; } -// TODO(ZyX-I): move to eval/expressions - /// Handle fourth level expression: /// + number addition /// - number subtraction @@ -2663,7 +2649,7 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) tv_clear(&var2); return FAIL; } - p = (char *)concat_str((const char_u *)s1, (const char_u *)s2); + p = concat_str(s1, s2); tv_clear(rettv); rettv->v_type = VAR_STRING; rettv->vval.v_string = p; @@ -2674,10 +2660,10 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) blob_T *const b = tv_blob_alloc(); for (int i = 0; i < tv_blob_len(b1); i++) { - ga_append(&b->bv_ga, (char)tv_blob_get(b1, i)); + ga_append(&b->bv_ga, tv_blob_get(b1, i)); } for (int i = 0; i < tv_blob_len(b2); i++) { - ga_append(&b->bv_ga, (char)tv_blob_get(b2, i)); + ga_append(&b->bv_ga, tv_blob_get(b2, i)); } tv_clear(rettv); @@ -2755,8 +2741,6 @@ static int eval5(char **arg, typval_T *rettv, int evaluate) return OK; } -// TODO(ZyX-I): move to eval/expressions - /// Handle fifth level expression: /// - * number multiplication /// - / number division @@ -2872,8 +2856,6 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) return OK; } -// TODO(ZyX-I): move to eval/expressions - /// Handle sixth level expression: /// number number constant /// 0zFFFFFFFF Blob constant @@ -2905,23 +2887,33 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string) /// @return OK or FAIL. static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) { - varnumber_T n; - int len; - char *s; - const char *start_leader, *end_leader; int ret = OK; - char *alias; + static int recurse = 0; // Initialise variable so that tv_clear() can't mistake this for a // string and free a string that isn't there. rettv->v_type = VAR_UNKNOWN; // Skip '!', '-' and '+' characters. They are handled later. - start_leader = *arg; + const char *start_leader = *arg; while (**arg == '!' || **arg == '-' || **arg == '+') { *arg = skipwhite(*arg + 1); } - end_leader = *arg; + const char *end_leader = *arg; + + // Limit recursion to 1000 levels. At least at 10000 we run out of stack + // and crash. With MSVC the stack is smaller. + if (recurse == +#ifdef _MSC_VER + 300 +#else + 1000 +#endif + ) { + semsg(_(e_expression_too_recursive_str), *arg); + return FAIL; + } + recurse++; switch (**arg) { // Number constant. @@ -2934,86 +2926,9 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) case '6': case '7': case '8': - case '9': { - char *p = skipdigits(*arg + 1); - int get_float = false; - - // We accept a float when the format matches - // "[0-9]\+\.[0-9]\+\([eE][+-]\?[0-9]\+\)\?". This is very - // strict to avoid backwards compatibility problems. - // Don't look for a float after the "." operator, so that - // ":let vers = 1.2.3" doesn't fail. - if (!want_string && p[0] == '.' && ascii_isdigit(p[1])) { - get_float = true; - p = skipdigits(p + 2); - if (*p == 'e' || *p == 'E') { - p++; - if (*p == '-' || *p == '+') { - p++; - } - if (!ascii_isdigit(*p)) { - get_float = false; - } else { - p = skipdigits(p + 1); - } - } - if (ASCII_ISALPHA(*p) || *p == '.') { - get_float = false; - } - } - if (get_float) { - float_T f; - - *arg += string2float(*arg, &f); - if (evaluate) { - rettv->v_type = VAR_FLOAT; - rettv->vval.v_float = f; - } - } else if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z')) { - blob_T *blob = NULL; - // Blob constant: 0z0123456789abcdef - if (evaluate) { - blob = tv_blob_alloc(); - } - char *bp; - for (bp = *arg + 2; ascii_isxdigit(bp[0]); bp += 2) { - if (!ascii_isxdigit(bp[1])) { - if (blob != NULL) { - emsg(_("E973: Blob literal should have an even number of hex " - "characters")); - ga_clear(&blob->bv_ga); - XFREE_CLEAR(blob); - } - ret = FAIL; - break; - } - if (blob != NULL) { - ga_append(&blob->bv_ga, (char)((hex2nr(*bp) << 4) + hex2nr(*(bp + 1)))); - } - if (bp[2] == '.' && ascii_isxdigit(bp[3])) { - bp++; - } - } - if (blob != NULL) { - tv_blob_set_ret(rettv, blob); - } - *arg = bp; - } else { - // decimal, hex or octal number - vim_str2nr((char_u *)(*arg), NULL, &len, STR2NR_ALL, &n, NULL, 0, true); - if (len == 0) { - semsg(_(e_invexpr2), *arg); - ret = FAIL; - break; - } - *arg += len; - if (evaluate) { - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = n; - } - } + case '9': + ret = get_number_tv(arg, rettv, evaluate, want_string); break; - } // String constant: "string". case '"': @@ -3034,7 +2949,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) case '#': if ((*arg)[1] == '{') { (*arg)++; - ret = dict_get_tv(arg, rettv, evaluate, true); + ret = eval_dict(arg, rettv, evaluate, true); } else { ret = NOTDONE; } @@ -3045,7 +2960,7 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) case '{': ret = get_lambda_tv(arg, rettv, evaluate); if (ret == NOTDONE) { - ret = dict_get_tv(arg, rettv, evaluate, false); + ret = eval_dict(arg, rettv, evaluate, false); } break; @@ -3091,8 +3006,9 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) if (ret == NOTDONE) { // Must be a variable or function name. // Can also be a curly-braces kind of name: {expr}. - s = *arg; - len = get_name_len((const char **)arg, &alias, evaluate, true); + char *s = *arg; + char *alias; + int len = get_name_len((const char **)arg, &alias, evaluate, true); if (alias != NULL) { s = alias; } @@ -3125,6 +3041,8 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string) if (ret == OK && evaluate && end_leader > start_leader) { ret = eval7_leader(rettv, (char *)start_leader, &end_leader); } + + recurse--; return ret; } @@ -3214,13 +3132,13 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e } funcexe_T funcexe = FUNCEXE_INIT; - funcexe.firstline = curwin->w_cursor.lnum; - funcexe.lastline = curwin->w_cursor.lnum; - funcexe.evaluate = evaluate; - funcexe.partial = pt; - funcexe.selfdict = selfdict; - funcexe.basetv = basetv; - const int ret = get_func_tv((char_u *)funcname, is_lua ? (int)(*arg - funcname) : -1, rettv, + funcexe.fe_firstline = curwin->w_cursor.lnum; + funcexe.fe_lastline = curwin->w_cursor.lnum; + funcexe.fe_evaluate = evaluate; + funcexe.fe_partial = pt; + funcexe.fe_selfdict = selfdict; + funcexe.fe_basetv = basetv; + const int ret = get_func_tv(funcname, is_lua ? (int)(*arg - funcname) : -1, rettv, arg, &funcexe); // Clear the funcref afterwards, so that deleting it while @@ -3293,7 +3211,7 @@ static int eval_method(char **const arg, typval_T *const rettv, const bool evalu int len; char *name = *arg; char *lua_funcname = NULL; - if (STRNCMP(name, "v:lua.", 6) == 0) { + if (strncmp(name, "v:lua.", 6) == 0) { lua_funcname = name + 6; *arg = (char *)skip_luafunc_name((const char *)lua_funcname); *arg = skipwhite(*arg); // to detect trailing whitespace later @@ -3348,8 +3266,6 @@ static int eval_method(char **const arg, typval_T *const rettv, const bool evalu return ret; } -// TODO(ZyX-I): move to eval/expressions - /// Evaluate an "[expr]" or "[expr:expr]" index. Also "dict.key". /// "*arg" points to the '[' or '.'. /// @@ -3401,7 +3317,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) if (**arg == '.') { // dict.name key = *arg + 1; - for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; len++) {} + for (len = 0; eval_isdictc(key[len]); len++) {} if (len == 0) { return FAIL; } @@ -3649,8 +3565,6 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) return OK; } -// TODO(ZyX-I): move to eval/executor - /// Get an option value /// /// @param[in,out] arg Points to the '&' or '+' before the option name. Is @@ -3662,11 +3576,11 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose) int get_option_tv(const char **const arg, typval_T *const rettv, const bool evaluate) FUNC_ATTR_NONNULL_ARG(1) { - bool working = (**arg == '+'); // has("+option") - int opt_flags; + const bool working = (**arg == '+'); // has("+option") + int scope; // Isolate the option name and find its value. - char *option_end = (char *)find_option_end(arg, &opt_flags); + char *option_end = (char *)find_option_end(arg, &scope); if (option_end == NULL) { if (rettv != NULL) { semsg(_("E112: Option name missing: %s"), *arg); @@ -3686,7 +3600,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval char c = *option_end; *option_end = NUL; getoption_T opt_type = get_option_value(*arg, &numval, - rettv == NULL ? NULL : &stringval, opt_flags); + rettv == NULL ? NULL : &stringval, NULL, scope); if (opt_type == gov_unknown) { if (rettv != NULL) { @@ -3719,6 +3633,91 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval return ret; } +/// Allocate a variable for a number constant. Also deals with "0z" for blob. +/// +/// @return OK or FAIL. +static int get_number_tv(char **arg, typval_T *rettv, bool evaluate, bool want_string) +{ + char *p = skipdigits(*arg + 1); + bool get_float = false; + + // We accept a float when the format matches + // "[0-9]\+\.[0-9]\+\([eE][+-]\?[0-9]\+\)\?". This is very + // strict to avoid backwards compatibility problems. + // Don't look for a float after the "." operator, so that + // ":let vers = 1.2.3" doesn't fail. + if (!want_string && p[0] == '.' && ascii_isdigit(p[1])) { + get_float = true; + p = skipdigits(p + 2); + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '-' || *p == '+') { + p++; + } + if (!ascii_isdigit(*p)) { + get_float = false; + } else { + p = skipdigits(p + 1); + } + } + if (ASCII_ISALPHA(*p) || *p == '.') { + get_float = false; + } + } + if (get_float) { + float_T f; + *arg += string2float(*arg, &f); + if (evaluate) { + rettv->v_type = VAR_FLOAT; + rettv->vval.v_float = f; + } + } else if (**arg == '0' && ((*arg)[1] == 'z' || (*arg)[1] == 'Z')) { + // Blob constant: 0z0123456789abcdef + blob_T *blob = NULL; + if (evaluate) { + blob = tv_blob_alloc(); + } + char *bp; + for (bp = *arg + 2; ascii_isxdigit(bp[0]); bp += 2) { + if (!ascii_isxdigit(bp[1])) { + if (blob != NULL) { + emsg(_("E973: Blob literal should have an even number of hex characters")); + ga_clear(&blob->bv_ga); + XFREE_CLEAR(blob); + } + return FAIL; + } + if (blob != NULL) { + ga_append(&blob->bv_ga, (uint8_t)((hex2nr(*bp) << 4) + hex2nr(*(bp + 1)))); + } + if (bp[2] == '.' && ascii_isxdigit(bp[3])) { + bp++; + } + } + if (blob != NULL) { + tv_blob_set_ret(rettv, blob); + } + *arg = bp; + } else { + // decimal, hex or octal number + int len; + varnumber_T n; + vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0, true); + if (len == 0) { + if (evaluate) { + semsg(_(e_invexpr2), *arg); + } + return FAIL; + } + *arg += len; + if (evaluate) { + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = n; + } + } + return OK; +} + /// Allocate a variable for a string constant. /// /// @return OK or FAIL. @@ -3780,7 +3779,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) case 'U': if (ascii_isxdigit(p[1])) { int n, nr; - int c = toupper(*p); + int c = toupper((uint8_t)(*p)); if (c == 'X') { n = 2; @@ -3831,7 +3830,7 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate) if (p[1] != '*') { flags |= FSK_SIMPLIFY; } - extra = trans_special((const char_u **)&p, STRLEN(p), (char_u *)name, flags, false, NULL); + extra = trans_special((const char **)&p, strlen(p), name, flags, false, NULL); if (extra != 0) { name += extra; if (name >= rettv->vval.v_string + len) { @@ -3914,13 +3913,11 @@ char *partial_name(partial_T *pt) FUNC_ATTR_PURE { if (pt->pt_name != NULL) { - return (char *)pt->pt_name; + return pt->pt_name; } return (char *)pt->pt_func->uf_name; } -// TODO(ZyX-I): Move to eval/typval.h - static void partial_free(partial_T *pt) { for (int i = 0; i < pt->pt_argc; i++) { @@ -3937,8 +3934,6 @@ static void partial_free(partial_T *pt) xfree(pt); } -// TODO(ZyX-I): Move to eval/typval.h - /// Unreference a closure: decrement the reference count and free it when it /// becomes zero. void partial_unref(partial_T *pt) @@ -3981,7 +3976,7 @@ static int get_list_tv(char **arg, typval_T *rettv, int evaluate) } if (**arg != ']') { - semsg(_("E697: Missing end of List ']': %s"), *arg); + semsg(_(e_list_end), *arg); failret: if (evaluate) { tv_list_free(l); @@ -4001,13 +3996,11 @@ failret: bool func_equal(typval_T *tv1, typval_T *tv2, bool ic) { // empty and NULL function name considered the same - char_u *s1 = - (char_u *)(tv1->v_type == VAR_FUNC ? tv1->vval.v_string : partial_name(tv1->vval.v_partial)); + char *s1 = tv1->v_type == VAR_FUNC ? tv1->vval.v_string : partial_name(tv1->vval.v_partial); if (s1 != NULL && *s1 == NUL) { s1 = NULL; } - char_u *s2 = - (char_u *)(tv2->v_type == VAR_FUNC ? tv2->vval.v_string : partial_name(tv2->vval.v_partial)); + char *s2 = tv2->v_type == VAR_FUNC ? tv2->vval.v_string : partial_name(tv2->vval.v_partial); if (s2 != NULL && *s2 == NUL) { s2 = NULL; } @@ -4015,7 +4008,7 @@ bool func_equal(typval_T *tv1, typval_T *tv2, bool ic) if (s1 != s2) { return false; } - } else if (STRCMP(s1, s2) != 0) { + } else if (strcmp(s1, s2) != 0) { return false; } @@ -4148,10 +4141,23 @@ bool garbage_collect(bool testing) ABORTING(set_ref_dict)(buf->additional_data, copyID); // buffer callback functions - set_ref_in_callback(&buf->b_prompt_callback, copyID, NULL, NULL); - set_ref_in_callback(&buf->b_prompt_interrupt, copyID, NULL, NULL); + ABORTING(set_ref_in_callback)(&buf->b_prompt_callback, copyID, NULL, NULL); + ABORTING(set_ref_in_callback)(&buf->b_prompt_interrupt, copyID, NULL, NULL); + ABORTING(set_ref_in_callback)(&buf->b_cfu_cb, copyID, NULL, NULL); + ABORTING(set_ref_in_callback)(&buf->b_ofu_cb, copyID, NULL, NULL); + ABORTING(set_ref_in_callback)(&buf->b_tsrfu_cb, copyID, NULL, NULL); + ABORTING(set_ref_in_callback)(&buf->b_tfu_cb, copyID, NULL, NULL); } + // 'completefunc', 'omnifunc' and 'thesaurusfunc' callbacks + ABORTING(set_ref_in_insexpand_funcs)(copyID); + + // 'operatorfunc' callback + ABORTING(set_ref_in_opfunc)(copyID); + + // 'tagfunc' callback + ABORTING(set_ref_in_tagfunc)(copyID); + FOR_ALL_TAB_WINDOWS(tp, wp) { // window-local variables ABORTING(set_ref_in_item)(&wp->w_winvar.di_tv, copyID, NULL, NULL); @@ -4160,8 +4166,11 @@ bool garbage_collect(bool testing) ABORTING(set_ref_in_fmark)(wp->w_jumplist[i].fmark, copyID); } } - if (aucmd_win != NULL) { - ABORTING(set_ref_in_item)(&aucmd_win->w_winvar.di_tv, copyID, NULL, NULL); + // window-local variables in autocmd windows + for (int i = 0; i < AUCMD_WIN_COUNT; i++) { + if (aucmd_win[i].auc_win != NULL) { + ABORTING(set_ref_in_item)(&aucmd_win[i].auc_win->w_winvar.di_tv, copyID, NULL, NULL); + } } // registers (ShaDa additional data) @@ -4231,11 +4240,11 @@ bool garbage_collect(bool testing) // history items (ShaDa additional elements) if (p_hi) { - for (uint8_t i = 0; i < HIST_COUNT; i++) { + for (HistoryType i = 0; i < HIST_COUNT; i++) { const void *iter = NULL; do { histentry_T hist; - iter = hist_iter(iter, i, false, &hist); + iter = hist_iter(iter, (uint8_t)i, false, &hist); if (hist.hisstr != NULL) { ABORTING(set_ref_list)(hist.additional_elements, copyID); } @@ -4500,7 +4509,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack break; } case VAR_FUNC: - abort = set_ref_in_func((char_u *)tv->vval.v_string, NULL, copyID); + abort = set_ref_in_func(tv->vval.v_string, NULL, copyID); break; case VAR_UNKNOWN: case VAR_BOOL: @@ -4583,25 +4592,24 @@ static int get_literal_key(char **arg, typval_T *tv) /// "literal" is true for #{key: val} /// /// @return OK or FAIL. Returns NOTDONE for {expr}. -static int dict_get_tv(char **arg, typval_T *rettv, int evaluate, bool literal) +static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal) { typval_T tv; char *key = NULL; - char *start = skipwhite(*arg + 1); + char *curly_expr = skipwhite(*arg + 1); char buf[NUMBUFLEN]; - // First check if it's not a curly-braces thing: {expr}. + // First check if it's not a curly-braces expression: {expr}. // Must do this without evaluating, otherwise a function may be called // twice. Unfortunately this means we need to call eval1() twice for the // first item. - // But {} is an empty Dictionary. - if (*start != '}') { - if (eval1(&start, &tv, false) == FAIL) { // recursive! - return FAIL; - } - if (*skipwhite(start) == '}') { - return NOTDONE; - } + // "{}" is an empty Dictionary. + // "#{abc}" is never a curly-braces expression. + if (*curly_expr != '}' + && !literal + && eval1(&curly_expr, &tv, false) == OK + && *skipwhite(curly_expr) == '}') { + return NOTDONE; } dict_T *d = NULL; @@ -4765,19 +4773,6 @@ void assert_error(garray_T *gap) (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); } -/// Find a window: When using a Window ID in any tab page, when using a number -/// in the current tab page. -win_T *find_win_by_nr_or_id(typval_T *vp) -{ - int nr = (int)tv_get_number_chk(vp, NULL); - - if (nr >= LOWEST_WIN_ID) { - return win_id2wp((int)tv_get_number(vp)); - } - - return find_win_by_nr(vp, NULL); -} - /// Implementation of map() and filter(). void filter_map(typval_T *argvars, typval_T *rettv, int map) { @@ -4792,22 +4787,22 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) int save_did_emsg; int idx = 0; + // Always return the first argument, also on failure. + tv_copy(&argvars[0], rettv); + if (argvars[0].v_type == VAR_BLOB) { - tv_copy(&argvars[0], rettv); if ((b = argvars[0].vval.v_blob) == NULL) { return; } } else if (argvars[0].v_type == VAR_LIST) { - tv_copy(&argvars[0], rettv); if ((l = argvars[0].vval.v_list) == NULL || (!map - && var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) { + && value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) { return; } } else if (argvars[0].v_type == VAR_DICT) { - tv_copy(&argvars[0], rettv); if ((d = argvars[0].vval.v_dict) == NULL - || (!map && var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { + || (!map && value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { return; } } else { @@ -4846,12 +4841,12 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) dictitem_T *di = TV_DICT_HI2DI(hi); if (map - && (var_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) + && (value_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE) || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) { break; } - vimvars[VV_KEY].vv_str = (char *)vim_strsave(di->di_key); + vimvars[VV_KEY].vv_str = xstrdup((char *)di->di_key); int r = filter_map_one(&di->di_tv, expr, map, &rem); tv_clear(&vimvars[VV_KEY].vv_tv); if (r == FAIL || did_emsg) { @@ -4886,7 +4881,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) } if (map) { if (tv.vval.v_number != val) { - tv_blob_set(b, i, (char_u)tv.vval.v_number); + tv_blob_set(b, i, (uint8_t)tv.vval.v_number); } } else if (rem) { char *const p = argvars[0].vval.v_blob->bv_ga.ga_data; @@ -4906,8 +4901,8 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) } for (listitem_T *li = tv_list_first(l); li != NULL;) { if (map - && var_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, - TV_TRANSLATE)) { + && value_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, + TV_TRANSLATE)) { break; } vimvars[VV_KEY].vv_nr = idx; @@ -4968,7 +4963,7 @@ theend: return retval; } -void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr fptr) +void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref) { char *s; char *name; @@ -4993,9 +4988,8 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) { name = s; - trans_name = (char *)trans_function_name(&name, false, - TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD - | TFN_NO_DEREF, NULL, NULL); + trans_name = save_function_name(&name, false, + TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL); if (*name != NUL) { s = NULL; } @@ -5008,26 +5002,19 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr // Don't check an autoload name for existence here. } else if (trans_name != NULL && (is_funcref - ? find_func((char_u *)trans_name) == NULL + ? find_func(trans_name) == NULL : !translated_function_exists((const char *)trans_name))) { semsg(_("E700: Unknown function: %s"), s); } else { int dict_idx = 0; int arg_idx = 0; list_T *list = NULL; - if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "<SID>", 5) == 0) { - char sid_buf[25]; - int off = *s == 's' ? 2 : 5; - + if (strncmp(s, "s:", 2) == 0 || strncmp(s, "<SID>", 5) == 0) { // Expand s: and <SID> into <SNR>nr_, so that the function can // also be called from another script. Using trans_function_name() // would also work, but some plugins depend on the name being // printable text. - snprintf(sid_buf, sizeof(sid_buf), "<SNR>%" PRId64 "_", - (int64_t)current_sctx.sc_sid); - name = xmalloc(STRLEN(sid_buf) + STRLEN(s + off) + 1); - STRCPY(name, sid_buf); - STRCAT(name, s + off); + name = get_scriptlocal_funcname(s); } else { name = xstrdup(s); } @@ -5065,7 +5052,7 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr if (tv_list_len(list) == 0) { arg_idx = 0; } else if (tv_list_len(list) > MAX_FUNC_ARGS) { - emsg_funcname((char *)e_toomanyarg, (char_u *)s); + emsg_funcname((char *)e_toomanyarg, s); xfree(name); goto theend; } @@ -5114,12 +5101,12 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr func_ptr_ref(pt->pt_func); xfree(name); } else if (is_funcref) { - pt->pt_func = find_func((char_u *)trans_name); + pt->pt_func = find_func(trans_name); func_ptr_ref(pt->pt_func); xfree(name); } else { - pt->pt_name = (char_u *)name; - func_ref((char_u *)name); + pt->pt_name = name; + func_ref(name); } rettv->v_type = VAR_PARTIAL; @@ -5128,53 +5115,13 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr // result is a VAR_FUNC rettv->v_type = VAR_FUNC; rettv->vval.v_string = name; - func_ref((char_u *)name); + func_ref(name); } } theend: xfree(trans_name); } -/// @return buffer options, variables and other attributes in a dictionary. -dict_T *get_buffer_info(buf_T *buf) -{ - dict_T *const dict = tv_dict_alloc(); - - tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum); - tv_dict_add_str(dict, S_LEN("name"), - buf->b_ffname != NULL ? (const char *)buf->b_ffname : ""); - tv_dict_add_nr(dict, S_LEN("lnum"), - buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf)); - tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count); - tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL); - tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl); - tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf)); - tv_dict_add_nr(dict, S_LEN("changedtick"), buf_get_changedtick(buf)); - tv_dict_add_nr(dict, S_LEN("hidden"), - buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0); - - // Get a reference to buffer variables - tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); - - // List of windows displaying this buffer - list_T *const windows = tv_list_alloc(kListLenMayKnow); - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - tv_list_append_number(windows, (varnumber_T)wp->handle); - } - } - tv_dict_add_list(dict, S_LEN("windows"), windows); - - if (buf->b_signlist != NULL) { - // List of signs placed in this buffer - tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); - } - - tv_dict_add_nr(dict, S_LEN("lastused"), buf->b_last_used); - - return dict; -} - /// Get the line number from VimL object /// /// @note Unlike tv_get_lnum(), this one supports only "$" special string. @@ -5191,121 +5138,13 @@ linenr_T tv_get_lnum_buf(const typval_T *const tv, const buf_T *const buf) if (tv->v_type == VAR_STRING && tv->vval.v_string != NULL && tv->vval.v_string[0] == '$' + && tv->vval.v_string[1] == NUL && buf != NULL) { return buf->b_ml.ml_line_count; } return (linenr_T)tv_get_number_chk(tv, NULL); } -/// @return information (variables, options, etc.) about a tab page -/// as a dictionary. -dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) -{ - dict_T *const dict = tv_dict_alloc(); - - tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); - - list_T *const l = tv_list_alloc(kListLenMayKnow); - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - tv_list_append_number(l, (varnumber_T)wp->handle); - } - tv_dict_add_list(dict, S_LEN("windows"), l); - - // Make a reference to tabpage variables - tv_dict_add_dict(dict, S_LEN("variables"), tp->tp_vars); - - return dict; -} - -/// @return information about a window as a dictionary. -dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) -{ - dict_T *const dict = tv_dict_alloc(); - - // make sure w_botline is valid - validate_botline(wp); - - tv_dict_add_nr(dict, S_LEN("tabnr"), tpnr); - tv_dict_add_nr(dict, S_LEN("winnr"), winnr); - tv_dict_add_nr(dict, S_LEN("winid"), wp->handle); - tv_dict_add_nr(dict, S_LEN("height"), wp->w_height); - tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1); - tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); - tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); - tv_dict_add_nr(dict, S_LEN("winbar"), wp->w_winbar_height); - tv_dict_add_nr(dict, S_LEN("width"), wp->w_width); - tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); - tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1); - tv_dict_add_nr(dict, S_LEN("textoff"), win_col_off(wp)); - tv_dict_add_nr(dict, S_LEN("terminal"), bt_terminal(wp->w_buffer)); - tv_dict_add_nr(dict, S_LEN("quickfix"), bt_quickfix(wp->w_buffer)); - tv_dict_add_nr(dict, S_LEN("loclist"), - (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)); - - // Add a reference to window variables - tv_dict_add_dict(dict, S_LEN("variables"), wp->w_vars); - - return dict; -} - -/// Find window specified by "vp" in tabpage "tp". -/// -/// @param tp NULL for current tab page -win_T *find_win_by_nr(typval_T *vp, tabpage_T *tp) -{ - int nr = (int)tv_get_number_chk(vp, NULL); - - if (nr < 0) { - return NULL; - } - - if (nr == 0) { - return curwin; - } - - // This method accepts NULL as an alias for curtab. - if (tp == NULL) { - tp = curtab; - } - - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - if (nr >= LOWEST_WIN_ID) { - if (wp->handle == nr) { - return wp; - } - } else if (--nr <= 0) { - return wp; - } - } - return NULL; -} - -/// Find window specified by "wvp" in tabpage "tvp". -win_T *find_tabwin(typval_T *wvp, typval_T *tvp) -{ - win_T *wp = NULL; - tabpage_T *tp = NULL; - - if (wvp->v_type != VAR_UNKNOWN) { - if (tvp->v_type != VAR_UNKNOWN) { - long n = tv_get_number(tvp); - if (n >= 0) { - tp = find_tabpage((int)n); - } - } else { - tp = curtab; - } - - if (tp != NULL) { - wp = find_win_by_nr(wvp, tp); - } - } else { - wp = curwin; - } - - return wp; -} - /// This function is used by f_input() and f_inputdialog() functions. The third /// argument to f_input() specifies the type of completion to use at the /// prompt. The third argument to f_inputdialog() specifies the value to return @@ -5527,126 +5366,6 @@ void screenchar_adjust(ScreenGrid **grid, int *row, int *col) *col -= (*grid)->comp_col; } -/// Set line or list of lines in buffer "buf". -void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, const typval_T *lines, - typval_T *rettv) - FUNC_ATTR_NONNULL_ARG(4, 5) -{ - linenr_T lnum = lnum_arg + (append ? 1 : 0); - long added = 0; - buf_T *curbuf_save = NULL; - win_T *curwin_save = NULL; - const bool is_curbuf = buf == curbuf; - const bool save_VIsual_active = VIsual_active; - - // When using the current buffer ml_mfp will be set if needed. Useful when - // setline() is used on startup. For other buffers the buffer must be - // loaded. - if (buf == NULL || (!is_curbuf && buf->b_ml.ml_mfp == NULL) || lnum < 1) { - rettv->vval.v_number = 1; // FAIL - return; - } - - if (!is_curbuf) { - VIsual_active = false; - curbuf_save = curbuf; - curwin_save = curwin; - curbuf = buf; - find_win_for_curbuf(); - } - - linenr_T append_lnum; - if (append) { - // appendbufline() uses the line number below which we insert - append_lnum = lnum - 1; - } else { - // setbufline() uses the line number above which we insert, we only - // append if it's below the last line - append_lnum = curbuf->b_ml.ml_line_count; - } - - list_T *l = NULL; - listitem_T *li = NULL; - const char *line = NULL; - if (lines->v_type == VAR_LIST) { - l = lines->vval.v_list; - li = tv_list_first(l); - } else { - line = tv_get_string_chk(lines); - } - - // Default result is zero == OK. - for (;;) { - if (lines->v_type == VAR_LIST) { - // List argument, get next string. - if (li == NULL) { - break; - } - line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); - li = TV_LIST_ITEM_NEXT(l, li); - } - - rettv->vval.v_number = 1; // FAIL - if (line == NULL || lnum > curbuf->b_ml.ml_line_count + 1) { - break; - } - - // When coming here from Insert mode, sync undo, so that this can be - // undone separately from what was previously inserted. - if (u_sync_once == 2) { - u_sync_once = 1; // notify that u_sync() was called - u_sync(true); - } - - if (!append && lnum <= curbuf->b_ml.ml_line_count) { - // Existing line, replace it. - int old_len = (int)STRLEN(ml_get(lnum)); - if (u_savesub(lnum) == OK - && ml_replace(lnum, (char *)line, true) == OK) { - inserted_bytes(lnum, 0, old_len, (int)STRLEN(line)); - if (is_curbuf && lnum == curwin->w_cursor.lnum) { - check_cursor_col(); - } - rettv->vval.v_number = 0; // OK - } - } else if (added > 0 || u_save(lnum - 1, lnum) == OK) { - // append the line. - added++; - if (ml_append(lnum - 1, (char *)line, 0, false) == OK) { - rettv->vval.v_number = 0; // OK - } - } - - if (l == NULL) { // only one string argument - break; - } - lnum++; - } - - if (added > 0) { - appended_lines_mark(append_lnum, added); - - // Only adjust the cursor for buffers other than the current, unless it - // is the current window. For curbuf and other windows it has been done - // in mark_adjust_internal(). - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf - && (wp->w_buffer != curbuf || wp == curwin) - && wp->w_cursor.lnum > append_lnum) { - wp->w_cursor.lnum += (linenr_T)added; - } - } - check_cursor_col(); - update_topline(curwin); - } - - if (!is_curbuf) { - curbuf = curbuf_save; - curwin = curwin_save; - VIsual_active = save_VIsual_active; - } -} - /// "stdpath()" helper for list results void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) FUNC_ATTR_NONNULL_ALL @@ -5699,7 +5418,7 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist // get input to the shell command (if any), and its length ptrdiff_t input_len; - char *input = save_tv_as_string(&argvars[1], &input_len, false); + char *input = save_tv_as_string(&argvars[1], &input_len, false, false); if (input_len < 0) { assert(input == NULL); return; @@ -5783,7 +5502,8 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist } } -bool callback_from_typval(Callback *const callback, typval_T *const arg) +/// Get a callback from "arg". It can be a Funcref or a function name. +bool callback_from_typval(Callback *const callback, const typval_T *const arg) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { int r = OK; @@ -5804,13 +5524,19 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) callback->type = kCallbackNone; callback->data.funcref = NULL; } else { - func_ref((char_u *)name); - callback->data.funcref = xstrdup(name); + callback->data.funcref = NULL; + if (arg->v_type == VAR_STRING) { + callback->data.funcref = get_scriptlocal_funcname(name); + } + if (callback->data.funcref == NULL) { + callback->data.funcref = xstrdup(name); + } + func_ref(callback->data.funcref); callback->type = kCallbackFuncref; } } else if (nlua_is_table_from_lua(arg)) { // TODO(tjdvries): UnifiedCallback - char *name = (char *)nlua_register_table_as_callable(arg); + char *name = nlua_register_table_as_callable(arg); if (name != NULL) { callback->data.funcref = xstrdup(name); @@ -5833,6 +5559,7 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) return true; } +/// @return whether the callback could be called. bool callback_call(Callback *const callback, const int argcount_in, typval_T *const argvars_in, typval_T *const rettv) FUNC_ATTR_NONNULL_ALL @@ -5844,7 +5571,7 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co switch (callback->type) { case kCallbackFuncref: name = callback->data.funcref; - int len = (int)STRLEN(name); + int len = (int)strlen(name); if (len >= 6 && !memcmp(name, "v:lua.", 6)) { name += 6; len = check_luafunc_name(name, false); @@ -5863,13 +5590,8 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co break; case kCallbackLua: - rv = nlua_call_ref(callback->data.luaref, NULL, args, true, NULL); - switch (rv.type) { - case kObjectTypeBoolean: - return rv.data.boolean; - default: - return false; - } + rv = nlua_call_ref(callback->data.luaref, NULL, args, false, NULL); + return (rv.type == kObjectTypeBoolean && rv.data.boolean == true); case kCallbackNone: return false; @@ -5880,15 +5602,15 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co } funcexe_T funcexe = FUNCEXE_INIT; - funcexe.firstline = curwin->w_cursor.lnum; - funcexe.lastline = curwin->w_cursor.lnum; - funcexe.evaluate = true; - funcexe.partial = partial; + funcexe.fe_firstline = curwin->w_cursor.lnum; + funcexe.fe_lastline = curwin->w_cursor.lnum; + funcexe.fe_evaluate = true; + funcexe.fe_partial = partial; return call_func(name, -1, rettv, argcount_in, argvars_in, &funcexe); } -static bool set_ref_in_callback(Callback *callback, int copyID, ht_stack_T **ht_stack, - list_stack_T **list_stack) +bool set_ref_in_callback(Callback *callback, int copyID, ht_stack_T **ht_stack, + list_stack_T **list_stack) { typval_T tv; switch (callback->type) { @@ -5990,7 +5712,7 @@ void timer_due_cb(TimeWatcher *tw, void *data) // Handle error message if (called_emsg > called_emsg_before && did_emsg) { timer->emsg_count++; - if (current_exception != NULL) { + if (did_throw) { discard_current_exception(); } } @@ -6191,9 +5913,10 @@ bool read_blob(FILE *const fd, blob_T *const blob) /// @param[in] tv Value to store as a string. /// @param[out] len Length of the resulting string or -1 on error. /// @param[in] endnl If true, the output will end in a newline (if a list). +/// @param[in] crlf If true, list items will be joined with CRLF (if a list). /// @returns an allocated string if `tv` represents a VimL string, list, or /// number; NULL otherwise. -char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) +char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl, bool crlf) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { *len = 0; @@ -6218,7 +5941,7 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) buf_T *buf = buflist_findnr((int)tv->vval.v_number); if (buf) { for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - for (char *p = (char *)ml_get_buf(buf, lnum, false); *p != NUL; p++) { + for (char *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) { *len += 1; } *len += 1; @@ -6236,7 +5959,7 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) char *ret = xmalloc((size_t)(*len) + 1); char *end = ret; for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - for (char *p = (char *)ml_get_buf(buf, lnum, false); *p != NUL; p++) { + for (char *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) { *end++ = (*p == '\n') ? NUL : *p; } *end++ = '\n'; @@ -6250,20 +5973,23 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) // Pre-calculate the resulting length. list_T *list = tv->vval.v_list; TV_LIST_ITER_CONST(list, li, { - *len += (ptrdiff_t)strlen(tv_get_string(TV_LIST_ITEM_TV(li))) + 1; + *len += (ptrdiff_t)strlen(tv_get_string(TV_LIST_ITEM_TV(li))) + (crlf ? 2 : 1); }); if (*len == 0) { return NULL; } - char *ret = xmalloc((size_t)(*len) + endnl); + char *ret = xmalloc((size_t)(*len) + (endnl ? (crlf ? 2 : 1) : 0)); char *end = ret; TV_LIST_ITER_CONST(list, li, { for (const char *s = tv_get_string(TV_LIST_ITEM_TV(li)); *s != NUL; s++) { *end++ = (*s == '\n') ? NUL : *s; } if (endnl || TV_LIST_ITEM_NEXT(list, li) != NULL) { + if (crlf) { + *end++ = '\r'; + } *end++ = '\n'; } }); @@ -6285,7 +6011,7 @@ int buf_byteidx_to_charidx(buf_T *buf, linenr_T lnum, int byteidx) lnum = buf->b_ml.ml_line_count; } - char *str = (char *)ml_get_buf(buf, lnum, false); + char *str = ml_get_buf(buf, lnum, false); if (*str == NUL) { return 0; @@ -6323,7 +6049,7 @@ int buf_charidx_to_byteidx(buf_T *buf, linenr_T lnum, int charidx) lnum = buf->b_ml.ml_line_count; } - char *str = (char *)ml_get_buf(buf, lnum, false); + char *str = ml_get_buf(buf, lnum, false); // Convert the character offset to a byte offset char *t = str; @@ -6376,14 +6102,14 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (charcol) { len = mb_charlen(ml_get(pos.lnum)); } else { - len = (int)STRLEN(ml_get(pos.lnum)); + len = (int)strlen(ml_get(pos.lnum)); } // We accept "$" for the column number: last column. listitem_T *li = tv_list_find(l, 1L); if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING && TV_LIST_ITEM_TV(li)->vval.v_string != NULL - && STRCMP(TV_LIST_ITEM_TV(li)->vval.v_string, "$") == 0) { + && strcmp(TV_LIST_ITEM_TV(li)->vval.v_string, "$") == 0) { pos.col = len + 1; } @@ -6462,7 +6188,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (charcol) { pos.col = (colnr_T)mb_charlen(get_cursor_line_ptr()); } else { - pos.col = (colnr_T)STRLEN(get_cursor_line_ptr()); + pos.col = (colnr_T)strlen(get_cursor_line_ptr()); } } return &pos; @@ -6470,11 +6196,14 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret return NULL; } -/// Convert list in "arg" into a position and optional file number. -/// When "fnump" is NULL there is no file number, only 3 items. +/// Convert list in "arg" into position "posp" and optional file number "fnump". +/// When "fnump" is NULL there is no file number, only 3 items: [lnum, col, off] /// Note that the column is passed on as-is, the caller may want to decrement /// it to use 1 for the first column. /// +/// @param charcol if true, use the column as the character index instead of the +/// byte index. +/// /// @return FAIL when conversion is not possible, doesn't check the position for /// validity. int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool charcol) @@ -6514,13 +6243,16 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c return FAIL; } // If character position is specified, then convert to byte position + // If the line number is zero use the cursor line. if (charcol) { // Get the text for the specified line in a loaded buffer buf_T *buf = buflist_findnr(fnump == NULL ? curbuf->b_fnum : *fnump); if (buf == NULL || buf->b_ml.ml_mfp == NULL) { return FAIL; } - n = buf_charidx_to_byteidx(buf, posp->lnum, (int)n) + 1; + n = buf_charidx_to_byteidx(buf, + posp->lnum == 0 ? curwin->w_cursor.lnum : posp->lnum, + (int)n) + 1; } posp->col = (colnr_T)n; @@ -6545,7 +6277,7 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c int get_env_len(const char **arg) { const char *p; - for (p = *arg; vim_isIDc(*p); p++) {} + for (p = *arg; vim_isIDc((uint8_t)(*p)); p++) {} if (p == *arg) { // No name found. return 0; } @@ -6572,7 +6304,7 @@ int get_id_len(const char **const arg) // slice "[n:]". Also "xx:" is not a namespace. len = (int)(p - *arg); if (len > 1 - || (len == 1 && vim_strchr(namespace_char, **arg) == NULL)) { + || (len == 1 && vim_strchr(namespace_char, (uint8_t)(**arg)) == NULL)) { break; } } @@ -6632,7 +6364,7 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo } *alias = temp_string; *arg = (const char *)skipwhite(p); - return (int)STRLEN(temp_string); + return (int)strlen(temp_string); } len += get_id_len(arg); @@ -6675,7 +6407,8 @@ const char *find_name_end(const char *arg, const char **expr_start, const char * for (p = arg; *p != NUL && (eval_isnamec(*p) || *p == '{' - || ((flags & FNE_INCL_BR) && (*p == '[' || *p == '.')) + || ((flags & FNE_INCL_BR) && (*p == '[' + || (*p == '.' && eval_isdictc(p[1])))) || mb_nest != 0 || br_nest != 0); MB_PTR_ADV(p)) { if (*p == '\'') { @@ -6699,7 +6432,7 @@ const char *find_name_end(const char *arg, const char **expr_start, const char * // slice "[n:]". Also "xx:" is not a namespace. But {ns}: is. len = (int)(p - arg); if ((len > 1 && p[-1] != '}') - || (len == 1 && vim_strchr(namespace_char, *arg) == NULL)) { + || (len == 1 && vim_strchr(namespace_char, (uint8_t)(*arg)) == NULL)) { break; } } @@ -6758,7 +6491,7 @@ static char *make_expanded_name(const char *in_start, char *expr_start, char *ex char *temp_result = eval_to_string(expr_start + 1, &nextcmd, false); if (temp_result != NULL && nextcmd == NULL) { - retval = xmalloc(STRLEN(temp_result) + (size_t)(expr_start - in_start) + retval = xmalloc(strlen(temp_result) + (size_t)(expr_start - in_start) + (size_t)(in_end - expr_end) + 1); STRCPY(retval, in_start); STRCAT(retval, temp_result); @@ -6786,20 +6519,27 @@ static char *make_expanded_name(const char *in_start, char *expr_start, char *ex return retval; } -/// @return TRUE if character "c" can be used in a variable or function name. +/// @return true if character "c" can be used in a variable or function name. /// Does not include '{' or '}' for magic braces. -int eval_isnamec(int c) +bool eval_isnamec(int c) { return ASCII_ISALNUM(c) || c == '_' || c == ':' || c == AUTOLOAD_CHAR; } -/// @return TRUE if character "c" can be used as the first character in a +/// @return true if character "c" can be used as the first character in a /// variable or function name (excluding '{' and '}'). -int eval_isnamec1(int c) +bool eval_isnamec1(int c) { return ASCII_ISALPHA(c) || c == '_'; } +/// @return true if character "c" can be used as the first character of a +/// dictionary key. +bool eval_isdictc(int c) +{ + return ASCII_ISALNUM(c) || c == '_'; +} + /// Get typval_T v: variable value. typval_T *get_vim_var_tv(int idx) { @@ -6846,7 +6586,7 @@ void set_vim_var_char(int c) /// Set v:count to "count" and v:count1 to "count1". /// -/// @param set_prevcount if TRUE, first set v:prevcount from v:count. +/// @param set_prevcount if true, first set v:prevcount from v:count. void set_vcount(long count, long count1, int set_prevcount) { if (set_prevcount) { @@ -6932,12 +6672,13 @@ void set_vim_var_dict(const VimVarIndex idx, dict_T *const val) tv_clear(&vimvars[idx].vv_di.di_tv); vimvars[idx].vv_type = VAR_DICT; vimvars[idx].vv_dict = val; - - if (val != NULL) { - val->dv_refcount++; - // Set readonly - tv_dict_set_keys_readonly(val); + if (val == NULL) { + return; } + + val->dv_refcount++; + // Set readonly + tv_dict_set_keys_readonly(val); } /// Set the v:argv list. @@ -7026,11 +6767,14 @@ char *set_cmdarg(exarg_T *eap, char *oldarg) len += 10; // " ++ff=unix" } if (eap->force_enc != 0) { - len += STRLEN(eap->cmd + eap->force_enc) + 7; + len += strlen(eap->cmd + eap->force_enc) + 7; } if (eap->bad_char != 0) { len += 7 + 4; // " ++bad=" + "keep" or "drop" } + if (eap->mkdir_p != 0) { + len += 4; + } const size_t newval_len = len + 1; char *newval = xmalloc(newval_len); @@ -7048,22 +6792,27 @@ char *set_cmdarg(exarg_T *eap, char *oldarg) } if (eap->force_ff != 0) { - snprintf(newval + STRLEN(newval), newval_len, " ++ff=%s", + snprintf(newval + strlen(newval), newval_len, " ++ff=%s", eap->force_ff == 'u' ? "unix" : eap->force_ff == 'd' ? "dos" : "mac"); } if (eap->force_enc != 0) { - snprintf(newval + STRLEN(newval), newval_len, " ++enc=%s", + snprintf(newval + strlen(newval), newval_len, " ++enc=%s", eap->cmd + eap->force_enc); } if (eap->bad_char == BAD_KEEP) { - STRCPY(newval + STRLEN(newval), " ++bad=keep"); + STRCPY(newval + strlen(newval), " ++bad=keep"); } else if (eap->bad_char == BAD_DROP) { - STRCPY(newval + STRLEN(newval), " ++bad=drop"); + STRCPY(newval + strlen(newval), " ++bad=drop"); } else if (eap->bad_char != 0) { - snprintf(newval + STRLEN(newval), newval_len, " ++bad=%c", + snprintf(newval + strlen(newval), newval_len, " ++bad=%c", eap->bad_char); } + + if (eap->mkdir_p) { + snprintf(newval, newval_len, " ++p"); + } + vimvars[VV_CMDARG].vv_str = newval; return oldval; } @@ -7118,9 +6867,8 @@ int check_luafunc_name(const char *const str, const bool paren) const char *const p = skip_luafunc_name(str); if (*p != (paren ? '(' : NUL)) { return 0; - } else { - return (int)(p - str); } + return (int)(p - str); } /// Handle: @@ -7405,7 +7153,7 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char bool should_free; // should_free is ignored as script_sctx will be resolved to a fnmae // & new_script_item will consume it. - char *sc_name = (char *)get_scriptname(last_set, &should_free); + char *sc_name = get_scriptname(last_set, &should_free); new_script_item(sc_name, ¤t_sctx.sc_sid); } } @@ -7532,9 +7280,9 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c } else { to->v_type = VAR_STRING; to->v_lock = VAR_UNLOCKED; - if ((to->vval.v_string = (char *)string_convert((vimconv_T *)conv, - (char_u *)from->vval.v_string, - NULL)) + if ((to->vval.v_string = string_convert((vimconv_T *)conv, + from->vval.v_string, + NULL)) == NULL) { to->vval.v_string = xstrdup(from->vval.v_string); } @@ -7644,7 +7392,7 @@ void ex_echo(exarg_T *eap) tv_clear(&rettv); arg = skipwhite(arg); } - eap->nextcmd = (char *)check_nextcmd((char_u *)arg); + eap->nextcmd = check_nextcmd(arg); if (eap->skip) { emsg_skip--; @@ -7662,7 +7410,7 @@ void ex_echo(exarg_T *eap) /// ":echohl {name}". void ex_echohl(exarg_T *eap) { - echo_attr = syn_name2attr((char_u *)eap->arg); + echo_attr = syn_name2attr(eap->arg); } /// ":execute expr1 ..." execute the result of an expression. @@ -7741,7 +7489,7 @@ void ex_execute(exarg_T *eap) emsg_skip--; } - eap->nextcmd = (char *)check_nextcmd((char_u *)arg); + eap->nextcmd = check_nextcmd(arg); } /// Skip over the name of an option: "&option", "&g:option" or "&l:option". @@ -7750,19 +7498,19 @@ void ex_execute(exarg_T *eap) /// /// @return NULL when no option name found. Otherwise pointer to the char /// after the option name. -const char *find_option_end(const char **const arg, int *const opt_flags) +const char *find_option_end(const char **const arg, int *const scope) { const char *p = *arg; p++; if (*p == 'g' && p[1] == ':') { - *opt_flags = OPT_GLOBAL; + *scope = OPT_GLOBAL; p += 2; } else if (*p == 'l' && p[1] == ':') { - *opt_flags = OPT_LOCAL; + *scope = OPT_LOCAL; p += 2; } else { - *opt_flags = 0; + *scope = 0; } if (!ASCII_ISALPHA(*p)) { @@ -7831,7 +7579,7 @@ bool script_autoload(const char *const name, const size_t name_len, const bool r // "autoload/", it's always the same. int i = 0; for (; i < ga_loaded.ga_len; i++) { - if (STRCMP(((char **)ga_loaded.ga_data)[i] + 9, scriptname + 9) == 0) { + if (strcmp(((char **)ga_loaded.ga_data)[i] + 9, scriptname + 9) == 0) { break; } } @@ -7866,9 +7614,8 @@ static var_flavour_T var_flavour(char *varname) } } return VAR_FLAVOUR_SHADA; - } else { - return VAR_FLAVOUR_DEFAULT; } + return VAR_FLAVOUR_DEFAULT; } /// Iterate over global variables @@ -7894,7 +7641,7 @@ const void *var_shada_iter(const void *const iter, const char **const name, typv hi = globvarht.ht_array; while ((size_t)(hi - hifirst) < hinum && (HASHITEM_EMPTY(hi) - || !(var_flavour((char *)hi->hi_key) & flavour))) { + || !(var_flavour(hi->hi_key) & flavour))) { hi++; } if ((size_t)(hi - hifirst) == hinum) { @@ -7906,7 +7653,7 @@ const void *var_shada_iter(const void *const iter, const char **const name, typv *name = (char *)TV_DICT_HI2DI(hi)->di_key; tv_copy(&TV_DICT_HI2DI(hi)->di_tv, rettv); while ((size_t)(++hi - hifirst) < hinum) { - if (!HASHITEM_EMPTY(hi) && (var_flavour((char *)hi->hi_key) & flavour)) { + if (!HASHITEM_EMPTY(hi) && (var_flavour(hi->hi_key) & flavour)) { return hi; } } @@ -7930,8 +7677,7 @@ int store_session_globals(FILE *fd) && var_flavour((char *)this_var->di_key) == VAR_FLAVOUR_SESSION) { // Escape special characters with a backslash. Turn a LF and // CR into \n and \r. - char *const p = (char *)vim_strsave_escaped((const char_u *)tv_get_string(&this_var->di_tv), - (const char_u *)"\\\"\n\r"); + char *const p = (char *)vim_strsave_escaped(tv_get_string(&this_var->di_tv), "\\\"\n\r"); for (char *t = p; *t != NUL; t++) { if (*t == '\n') { *t = 'n'; @@ -7985,7 +7731,7 @@ void option_last_set_msg(LastSet last_set) { if (last_set.script_ctx.sc_sid != 0) { bool should_free; - char *p = (char *)get_scriptname(last_set, &should_free); + char *p = get_scriptname(last_set, &should_free); verbose_enter(); msg_puts(_("\n\tLast set from ")); msg_puts(p); @@ -8072,8 +7818,8 @@ repeat: } // FullName_save() is slow, don't use it when not needed. - if (*p != NUL || !vim_isAbsName((char_u *)(*fnamep))) { - *fnamep = FullName_save((*fnamep), *p != NUL); + if (*p != NUL || !vim_isAbsName(*fnamep)) { + *fnamep = FullName_save(*fnamep, *p != NUL); xfree(*bufp); // free any allocated file name *bufp = *fnamep; if (*fnamep == NULL) { @@ -8082,9 +7828,9 @@ repeat: } // Append a path separator to a directory. - if (os_isdir((char_u *)(*fnamep))) { + if (os_isdir(*fnamep)) { // Make room for one or two extra characters. - *fnamep = xstrnsave(*fnamep, STRLEN(*fnamep) + 2); + *fnamep = xstrnsave(*fnamep, strlen(*fnamep) + 2); xfree(*bufp); // free any allocated file name *bufp = *fnamep; add_pathsep(*fnamep); @@ -8118,17 +7864,17 @@ repeat: if (p != NULL) { if (c == '.') { - os_dirname((char_u *)dirname, MAXPATHL); + os_dirname(dirname, MAXPATHL); if (has_homerelative) { s = xstrdup(dirname); home_replace(NULL, s, dirname, MAXPATHL, true); xfree(s); } - size_t namelen = STRLEN(dirname); + size_t namelen = strlen(dirname); // Do not call shorten_fname() here since it removes the prefix // even though the path does not have a prefix. - if (FNAMENCMP(p, dirname, namelen) == 0) { + if (path_fnamencmp(p, dirname, namelen) == 0) { p += namelen; if (vim_ispathsep(*p)) { while (*p && vim_ispathsep(*p)) { @@ -8148,6 +7894,7 @@ repeat: // Only replace it when it starts with '~' if (*dirname == '~') { s = xstrdup(dirname); + assert(s != NULL); // suppress clang "Argument with 'nonnull' attribute passed null" *fnamep = s; xfree(*bufp); *bufp = s; @@ -8159,14 +7906,14 @@ repeat: } char *tail = path_tail(*fnamep); - *fnamelen = STRLEN(*fnamep); + *fnamelen = strlen(*fnamep); // ":h" - head, remove "/file_name", can be repeated // Don't remove the first "/" or "c:\" while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h') { valid |= VALID_HEAD; *usedlen += 2; - s = (char *)get_past_head((char_u *)(*fnamep)); + s = get_past_head(*fnamep); while (tail > s && after_pathsep(s, tail)) { MB_PTR_BACK(*fnamep, tail); } @@ -8261,7 +8008,7 @@ repeat: s++; } - int sep = (char_u)(*s++); + int sep = (uint8_t)(*s++); if (sep) { // find end of pattern p = vim_strchr(s, sep); @@ -8276,7 +8023,7 @@ repeat: *usedlen = (size_t)(p + 1 - src); s = do_string_sub(str, pat, sub, NULL, flags); *fnamep = s; - *fnamelen = STRLEN(s); + *fnamelen = strlen(s); xfree(*bufp); *bufp = s; didit = true; @@ -8294,17 +8041,17 @@ repeat: if (src[*usedlen] == ':' && src[*usedlen + 1] == 'S') { // vim_strsave_shellescape() needs a NUL terminated string. - c = (char_u)(*fnamep)[*fnamelen]; + c = (uint8_t)(*fnamep)[*fnamelen]; if (c != NUL) { (*fnamep)[*fnamelen] = NUL; } - p = (char *)vim_strsave_shellescape((char_u *)(*fnamep), false, false); + p = vim_strsave_shellescape(*fnamep, false, false); if (c != NUL) { (*fnamep)[*fnamelen] = (char)c; } xfree(*bufp); *bufp = *fnamep = p; - *fnamelen = STRLEN(p); + *fnamelen = strlen(p); *usedlen += 2; } @@ -8316,7 +8063,7 @@ repeat: /// "flags" can be "g" to do a global substitute. /// /// @return an allocated string, NULL for error. -char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags) +char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char *flags) { int sublen; regmatch_T regmatch; @@ -8325,7 +8072,7 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags // Make 'cpoptions' empty, so that the 'l' flag doesn't work here char *save_cpo = p_cpo; - p_cpo = (char *)empty_option; + p_cpo = empty_option; ga_init(&ga, 1, 200); @@ -8335,11 +8082,11 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { char *tail = str; - char *end = str + STRLEN(str); - while (vim_regexec_nl(®match, (char_u *)str, (colnr_T)(tail - str))) { + char *end = str + strlen(str); + while (vim_regexec_nl(®match, str, (colnr_T)(tail - str))) { // Skip empty match except for first match. if (regmatch.startp[0] == regmatch.endp[0]) { - if ((char_u *)zero_width == regmatch.startp[0]) { + if (zero_width == regmatch.startp[0]) { // avoid getting stuck on a match with an empty string int i = utfc_ptr2len(tail); memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i); @@ -8347,7 +8094,7 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags tail += i; continue; } - zero_width = (char *)regmatch.startp[0]; + zero_width = regmatch.startp[0]; } // Get some space for a temporary buffer to do the substitution @@ -8355,19 +8102,19 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags // - The text up to where the match is. // - The substituted text. // - The text after the match. - sublen = vim_regsub(®match, (char_u *)sub, expr, (char_u *)tail, 0, REGSUB_MAGIC); + sublen = vim_regsub(®match, sub, expr, tail, 0, REGSUB_MAGIC); ga_grow(&ga, (int)((end - tail) + sublen - (regmatch.endp[0] - regmatch.startp[0]))); // copy the text up to where the match is - int i = (int)(regmatch.startp[0] - (char_u *)tail); + int i = (int)(regmatch.startp[0] - tail); memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i); // add the substituted text - (void)vim_regsub(®match, (char_u *)sub, expr, - (char_u *)ga.ga_data + ga.ga_len + i, sublen, + (void)vim_regsub(®match, sub, expr, + (char *)ga.ga_data + ga.ga_len + i, sublen, REGSUB_COPY | REGSUB_MAGIC); ga.ga_len += i + sublen - 1; - tail = (char *)regmatch.endp[0]; + tail = regmatch.endp[0]; if (*tail == NUL) { break; } @@ -8385,11 +8132,16 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, char *flags char *ret = xstrdup(ga.ga_data == NULL ? str : ga.ga_data); ga_clear(&ga); - if ((char_u *)p_cpo == empty_option) { + if (p_cpo == empty_option) { p_cpo = save_cpo; } else { // Darn, evaluating {sub} expression or {expr} changed the value. - free_string_option((char_u *)save_cpo); + // If it's still empty it was changed and restored, need to restore in + // the complicated way. + if (*p_cpo == NUL) { + set_option_value_give_err("cpo", 0L, save_cpo, 0); + } + free_string_option(save_cpo); } return ret; @@ -8496,9 +8248,9 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo tv_list_ref(arguments); funcexe_T funcexe = FUNCEXE_INIT; - funcexe.firstline = curwin->w_cursor.lnum; - funcexe.lastline = curwin->w_cursor.lnum; - funcexe.evaluate = true; + funcexe.fe_firstline = curwin->w_cursor.lnum; + funcexe.fe_lastline = curwin->w_cursor.lnum; + funcexe.fe_evaluate = true; (void)call_func(func, name_len, &rettv, 2, argvars, &funcexe); tv_list_unref(arguments); @@ -8547,7 +8299,7 @@ bool eval_has_provider(const char *feat) if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) { // Show a hint if Call() is defined but g:loaded_xx_provider is missing. snprintf(buf, sizeof(buf), "provider#%s#Call", name); - if (!!find_func((char_u *)buf) && p_lpl) { + if (!!find_func(buf) && p_lpl) { semsg("provider: %s: missing required variable g:loaded_%s_provider", name, name); } @@ -8562,7 +8314,7 @@ bool eval_has_provider(const char *feat) if (ok) { // Call() must be defined if provider claims to be working. snprintf(buf, sizeof(buf), "provider#%s#Call", name); - if (!find_func((char_u *)buf)) { + if (!find_func(buf)) { semsg("provider: %s: g:loaded_%s_provider=2 but %s is not defined", name, name, buf); ok = false; @@ -8585,17 +8337,17 @@ void eval_fmt_source_name_line(char *buf, size_t bufsize) /// ":checkhealth [plugins]" void ex_checkhealth(exarg_T *eap) { - bool found = !!find_func((char_u *)"health#check"); + bool found = !!find_func("health#check"); if (!found && script_autoload("health#check", sizeof("health#check") - 1, false)) { - found = !!find_func((char_u *)"health#check"); + found = !!find_func("health#check"); } if (!found) { const char *vimruntime_env = os_getenv("VIMRUNTIME"); if (vimruntime_env == NULL) { emsg(_("E5009: $VIMRUNTIME is empty or unset")); } else { - bool rtp_ok = NULL != strstr((char *)p_rtp, vimruntime_env); + bool rtp_ok = NULL != strstr(p_rtp, vimruntime_env); if (rtp_ok) { semsg(_("E5009: Invalid $VIMRUNTIME: %s"), vimruntime_env); } else { @@ -8605,7 +8357,7 @@ void ex_checkhealth(exarg_T *eap) return; } - size_t bufsize = STRLEN(eap->arg) + sizeof("call health#check('')"); + size_t bufsize = strlen(eap->arg) + sizeof("call health#check('')"); char *buf = xmalloc(bufsize); snprintf(buf, bufsize, "call health#check('%s')", eap->arg); @@ -8629,10 +8381,10 @@ void invoke_prompt_callback(void) if (curbuf->b_prompt_callback.type == kCallbackNone) { return; } - char *text = (char *)ml_get(lnum); - char *prompt = (char *)prompt_text(); - if (STRLEN(text) >= STRLEN(prompt)) { - text += STRLEN(prompt); + char *text = ml_get(lnum); + char *prompt = prompt_text(); + if (strlen(text) >= strlen(prompt)) { + text += strlen(prompt); } argv[0].v_type = VAR_STRING; argv[0].vval.v_string = xstrdup(text); @@ -8655,9 +8407,9 @@ bool invoke_prompt_interrupt(void) argv[0].v_type = VAR_UNKNOWN; got_int = false; // don't skip executing commands - callback_call(&curbuf->b_prompt_interrupt, 0, argv, &rettv); + int ret = callback_call(&curbuf->b_prompt_interrupt, 0, argv, &rettv); tv_clear(&rettv); - return true; + return ret != FAIL; } /// Compare "typ1" and "typ2". Put the result in "typ1". @@ -8855,7 +8607,7 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic) case EXPR_MATCH: case EXPR_NOMATCH: - n1 = pattern_match((char *)s2, (char *)s1, ic); + n1 = pattern_match(s2, s1, ic); if (type == EXPR_NOMATCH) { n1 = !n1; } @@ -8870,10 +8622,16 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic) return OK; } -char *typval_tostring(typval_T *arg) +/// Convert any type to a string, never give an error. +/// When "quotes" is true add quotes to a string. +/// Returns an allocated string. +char *typval_tostring(typval_T *arg, bool quotes) { if (arg == NULL) { return xstrdup("(does not exist)"); } + if (!quotes && arg->v_type == VAR_STRING) { + return xstrdup(arg->vval.v_string == NULL ? "" : arg->vval.v_string); + } return encode_tv2string(arg, NULL); } diff --git a/src/nvim/eval.h b/src/nvim/eval.h index b0cb5fd8c1..86bc76e793 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -1,43 +1,46 @@ #ifndef NVIM_EVAL_H #define NVIM_EVAL_H +#include <stdbool.h> +#include <stddef.h> + #include "nvim/buffer_defs.h" #include "nvim/channel.h" -#include "nvim/event/time.h" // For TimeWatcher -#include "nvim/ex_cmds_defs.h" // For exarg_T -#include "nvim/os/fileio.h" // For FileDescriptor -#include "nvim/os/stdpaths_defs.h" // For XDGVarType +#include "nvim/eval/typval_defs.h" +#include "nvim/event/time.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/hashtab.h" +#include "nvim/os/fileio.h" +#include "nvim/os/stdpaths_defs.h" #define COPYID_INC 2 #define COPYID_MASK (~0x1) -/* - * Structure returned by get_lval() and used by set_var_lval(). - * For a plain name: - * "name" points to the variable name. - * "exp_name" is NULL. - * "tv" is NULL - * For a magic braces name: - * "name" points to the expanded variable name. - * "exp_name" is non-NULL, to be freed later. - * "tv" is NULL - * For an index in a list: - * "name" points to the (expanded) variable name. - * "exp_name" NULL or non-NULL, to be freed later. - * "tv" points to the (first) list item value - * "li" points to the (first) list item - * "range", "n1", "n2" and "empty2" indicate what items are used. - * For an existing Dict item: - * "name" points to the (expanded) variable name. - * "exp_name" NULL or non-NULL, to be freed later. - * "tv" points to the dict item value - * "newkey" is NULL - * For a non-existing Dict item: - * "name" points to the (expanded) variable name. - * "exp_name" NULL or non-NULL, to be freed later. - * "tv" points to the Dictionary typval_T - * "newkey" is the key for the new item. - */ +// Structure returned by get_lval() and used by set_var_lval(). +// For a plain name: +// "name" points to the variable name. +// "exp_name" is NULL. +// "tv" is NULL +// For a magic braces name: +// "name" points to the expanded variable name. +// "exp_name" is non-NULL, to be freed later. +// "tv" is NULL +// For an index in a list: +// "name" points to the (expanded) variable name. +// "exp_name" NULL or non-NULL, to be freed later. +// "tv" points to the (first) list item value +// "li" points to the (first) list item +// "range", "n1", "n2" and "empty2" indicate what items are used. +// For an existing Dict item: +// "name" points to the (expanded) variable name. +// "exp_name" NULL or non-NULL, to be freed later. +// "tv" points to the dict item value +// "newkey" is NULL +// For a non-existing Dict item: +// "name" points to the (expanded) variable name. +// "exp_name" NULL or non-NULL, to be freed later. +// "tv" points to the Dictionary typval_T +// "newkey" is the key for the new item. typedef struct lval_S { const char *ll_name; ///< Start of variable name (can be NULL). size_t ll_name_len; ///< Length of the .ll_name. @@ -162,6 +165,8 @@ typedef enum { VV__NULL_DICT, // Dictionary with NULL value. For test purposes only. VV__NULL_BLOB, // Blob with NULL value. For test purposes only. VV_LUA, + VV_RELNUM, + VV_VIRTNUM, } VimVarIndex; /// All recognized msgpack types diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index e4e9b34ec6..c17a44b990 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -23,7 +23,7 @@ end return { funcs={ abs={args=1, base=1}, - acos={args=1, base=1, func="float_op_wrapper", data="&acos"}, -- WJMc + acos={args=1, base=1, float_func="acos"}, -- WJMc add={args=2, base=1}, ['and']={args=2, base=1}, api_info={}, @@ -33,12 +33,12 @@ return { argidx={}, arglistid={args={0, 2}}, argv={args={0, 2}}, - asin={args=1, base=1, func="float_op_wrapper", data="&asin"}, -- WJMc + asin={args=1, base=1, float_func="asin"}, -- WJMc assert_beeps={args=1, base=1}, assert_equal={args={2, 3}, base=2}, assert_equalfile={args={2, 3}, base=1}, assert_exception={args={1, 2}}, - assert_fails={args={1, 3}, base=1}, + assert_fails={args={1, 5}, base=1}, assert_false={args={1, 2}, base=1}, assert_inrange={args={3, 4}, base=3}, assert_match={args={2, 3}, base=2}, @@ -47,7 +47,7 @@ return { assert_notmatch={args={2, 3}, base=2}, assert_report={args=1, base=1}, assert_true={args={1, 2}, base=1}, - atan={args=1, base=1, func="float_op_wrapper", data="&atan"}, + atan={args=1, base=1, float_func="atan"}, atan2={args=2, base=1}, browse={args=4}, browsedir={args=2}, @@ -67,28 +67,27 @@ return { byteidx={args=2, base=1}, byteidxcomp={args=2, base=1}, call={args={2, 3}, base=1}, - ceil={args=1, base=1, func="float_op_wrapper", data="&ceil"}, + ceil={args=1, base=1, float_func="ceil"}, changenr={}, chanclose={args={1, 2}}, chansend={args=2}, char2nr={args={1, 2}, base=1}, charclass={args=1, base=1}, - charcol={args=1, base=1}, + charcol={args={1, 2}, base=1}, charidx={args={2, 3}, base=1}, chdir={args=1, base=1}, cindent={args=1, base=1}, clearmatches={args={0, 1}, base=1}, - col={args=1, base=1}, + col={args={1, 2}, base=1}, complete={args=2, base=2}, complete_add={args=1, base=1}, complete_check={}, complete_info={args={0, 1}, base=1}, confirm={args={1, 4}, base=1}, copy={args=1, base=1}, - cos={args=1, base=1, func="float_op_wrapper", data="&cos"}, - cosh={args=1, base=1, func="float_op_wrapper", data="&cosh"}, + cos={args=1, base=1, float_func="cos"}, + cosh={args=1, base=1, float_func="cosh"}, count={args={2, 4}, base=1}, - cscope_connection={args={0, 3}}, ctxget={args={0, 1}}, ctxpop={}, ctxpush={args={0, 1}}, @@ -117,9 +116,9 @@ return { execute={args={1, 2}, base=1}, exepath={args=1, base=1}, exists={args=1, base=1}, - exp={args=1, base=1, func="float_op_wrapper", data="&exp"}, + exp={args=1, base=1, float_func="exp"}, expand={args={1, 3}, base=1}, - expandcmd={args=1, base=1}, + expandcmd={args={1, 2}, base=1}, extend={args={2, 3}, base=1}, feedkeys={args={1, 2}, base=1}, file_readable={args=1, base=1, func='f_filereadable'}, -- obsolete @@ -130,7 +129,7 @@ return { findfile={args={1, 3}, base=1}, flatten={args={1, 2}, base=1}, float2nr={args=1, base=1}, - floor={args=1, base=1, func="float_op_wrapper", data="&floor"}, + floor={args=1, base=1, float_func="floor"}, fmod={args=2, base=1}, fnameescape={args=1, base=1}, fnamemodify={args=2, base=1}, @@ -147,7 +146,9 @@ return { get={args={2, 3}, base=1}, getbufinfo={args={0, 1}, base=1}, getbufline={args={2, 3}, base=1}, + getbufoneline={args=2, base=1}, getbufvar={args={2, 3}, base=1}, + getcellwidths={}, getchangelist={args={0, 1}, base=1}, getchar={args={0, 1}}, getcharmod={}, @@ -186,6 +187,7 @@ return { gettabvar={args={2, 3}, base=1}, gettabwinvar={args={3, 4}, base=1}, gettagstack={args={0, 1}, base=1}, + gettext={args=1, base=1}, getwininfo={args={0, 1}, base=1}, getwinpos={args={0, 1}, base=1}, getwinposx={}, @@ -236,6 +238,7 @@ return { json_decode={args=1, base=1}, json_encode={args=1, base=1}, keys={args=1, base=1}, + keytrans={args=1, base=1}, last_buffer_nr={}, -- obsolete len={args=1, base=1}, libcall={args=3, base=3}, @@ -245,8 +248,8 @@ return { lispindent={args=1, base=1}, list2str={args={1, 2}, base=1}, localtime={}, - log={args=1, base=1, func="float_op_wrapper", data="&log"}, - log10={args=1, base=1, func="float_op_wrapper", data="&log10"}, + log={args=1, base=1, float_func="log"}, + log10={args=1, base=1, float_func="log10"}, luaeval={args={1, 2}, base=1}, map={args=2, base=1}, maparg={args={1, 4}, base=1}, @@ -290,6 +293,7 @@ return { perleval={args=1, base=1}, rand={args={0, 1}, base=1}, range={args={1, 3}, base=1}, + readblob={args=1, base=1}, readdir={args={1, 2}, base=1}, readfile={args={1, 3}, base=1}, reduce={args={2, 3}, base=1}, @@ -304,7 +308,7 @@ return { ['repeat']={args=2, base=1}, resolve={args=1, base=1}, reverse={args=1, base=1}, - round={args=1, base=1, func="float_op_wrapper", data="&round"}, + round={args=1, base=1, float_func="round"}, rpcnotify={args=varargs(2)}, rpcrequest={args=varargs(2)}, rpcstart={args={1, 2}}, @@ -332,6 +336,7 @@ return { setcharpos={args=2, base=2}, setcharsearch={args=1, base=1}, setcmdpos={args=1, base=1}, + setcmdline={args={1, 2}, base=1}, setcursorcharpos={args={1, 3}, base=1}, setenv={args=2, base=2}, setfperm={args=2, base=1}, @@ -358,8 +363,8 @@ return { sign_unplace={args={1, 2}, base=1}, sign_unplacelist={args=1, base=1}, simplify={args=1, base=1}, - sin={args=1, base=1, func="float_op_wrapper", data="&sin"}, - sinh={args=1, base=1, func="float_op_wrapper", data="&sinh"}, + sin={args=1, base=1, float_func="sin"}, + sinh={args=1, base=1, float_func="sinh"}, sockconnect={args={2,3}}, sort={args={1, 3}, base=1}, soundfold={args=1, base=1}, @@ -367,12 +372,13 @@ return { spellbadword={args={0, 1}, base=1}, spellsuggest={args={1, 3}, base=1}, split={args={1, 3}, base=1}, - sqrt={args=1, base=1, func="float_op_wrapper", data="&sqrt"}, + sqrt={args=1, base=1, float_func="sqrt"}, srand={args={0, 1}, base=1}, stdpath={args=1}, str2float={args=1, base=1}, str2list={args={1, 2}, base=1}, str2nr={args={1, 3}, base=1}, + strcharlen={args=1, base=1}, strcharpart={args={2, 3}, base=1}, strchars={args={1, 2}, base=1}, strdisplaywidth={args={1, 2}, base=1}, @@ -402,8 +408,8 @@ return { tabpagewinnr={args={1, 2}, base=1}, tagfiles={}, taglist={args={1, 2}, base=1}, - tan={args=1, base=1, func="float_op_wrapper", data="&tan"}, - tanh={args=1, base=1, func="float_op_wrapper", data="&tanh"}, + tan={args=1, base=1, float_func="tan"}, + tanh={args=1, base=1, float_func="tanh"}, tempname={}, termopen={args={1, 2}}, test_garbagecollect_now={}, @@ -417,13 +423,14 @@ return { toupper={args=1, base=1}, tr={args=3, base=1}, trim={args={1, 3}, base=1}, - trunc={args=1, base=1, func="float_op_wrapper", data="&trunc"}, + trunc={args=1, base=1, float_func="trunc"}, type={args=1, base=1}, undofile={args=1, base=1}, undotree={}, uniq={args={1, 3}, base=1}, values={args=1, base=1}, virtcol={args=1, base=1}, + virtcol2col={args=3, base=1}, visualmode={args={0, 1}}, wait={args={2,3}}, wildmenumode={}, diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c new file mode 100644 index 0000000000..2f37d1ba2e --- /dev/null +++ b/src/nvim/eval/buffer.c @@ -0,0 +1,734 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// eval/buffer.c: Buffer related builtin functions + +#include <stdbool.h> +#include <string.h> + +#include "nvim/ascii.h" +#include "nvim/autocmd.h" +#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" +#include "nvim/change.h" +#include "nvim/cursor.h" +#include "nvim/eval.h" +#include "nvim/eval/buffer.h" +#include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/move.h" +#include "nvim/path.h" +#include "nvim/pos.h" +#include "nvim/sign.h" +#include "nvim/types.h" +#include "nvim/undo.h" +#include "nvim/vim.h" + +typedef struct { + win_T *cob_curwin_save; + aco_save_T cob_aco; + int cob_using_aco; + int cob_save_VIsual_active; +} cob_T; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/buffer.c.generated.h" +#endif + +/// Find a buffer by number or exact name. +buf_T *find_buffer(typval_T *avar) +{ + buf_T *buf = NULL; + + if (avar->v_type == VAR_NUMBER) { + buf = buflist_findnr((int)avar->vval.v_number); + } else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) { + buf = buflist_findname_exp(avar->vval.v_string); + if (buf == NULL) { + // No full path name match, try a match with a URL or a "nofile" + // buffer, these don't use the full path. + FOR_ALL_BUFFERS(bp) { + if (bp->b_fname != NULL + && (path_with_url(bp->b_fname) || bt_nofilename(bp)) + && strcmp(bp->b_fname, avar->vval.v_string) == 0) { + buf = bp; + break; + } + } + } + } + return buf; +} + +/// If there is a window for "curbuf", make it the current window. +static void find_win_for_curbuf(void) +{ + wininfo_T *wip; + + // The b_wininfo list should have the windows that recently contained the + // buffer, going over this is faster than going over all the windows. + // Do check the buffer is still there. + FOR_ALL_BUF_WININFO(curbuf, wip) { + if (wip->wi_win != NULL && wip->wi_win->w_buffer == curbuf) { + curwin = wip->wi_win; + break; + } + } +} + +/// Used before making a change in "buf", which is not the current one: Make +/// "buf" the current buffer and find a window for this buffer, so that side +/// effects are done correctly (e.g., adjusting marks). +/// +/// Information is saved in "cob" and MUST be restored by calling +/// change_other_buffer_restore(). +static void change_other_buffer_prepare(cob_T *cob, buf_T *buf) +{ + CLEAR_POINTER(cob); + + // Set "curbuf" to the buffer being changed. Then make sure there is a + // window for it to handle any side effects. + cob->cob_save_VIsual_active = VIsual_active; + VIsual_active = false; + cob->cob_curwin_save = curwin; + curbuf = buf; + find_win_for_curbuf(); // simplest: find existing window for "buf" + + if (curwin->w_buffer != buf) { + // No existing window for this buffer. It is dangerous to have + // curwin->w_buffer differ from "curbuf", use the autocmd window. + curbuf = curwin->w_buffer; + aucmd_prepbuf(&cob->cob_aco, buf); + cob->cob_using_aco = true; + } +} + +static void change_other_buffer_restore(cob_T *cob) +{ + if (cob->cob_using_aco) { + aucmd_restbuf(&cob->cob_aco); + } else { + curwin = cob->cob_curwin_save; + curbuf = curwin->w_buffer; + } + VIsual_active = cob->cob_save_VIsual_active; +} + +/// Set line or list of lines in buffer "buf" to "lines". +/// Any type is allowed and converted to a string. +static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_T *lines, + typval_T *rettv) + FUNC_ATTR_NONNULL_ARG(4, 5) +{ + linenr_T lnum = lnum_arg + (append ? 1 : 0); + long added = 0; + + // When using the current buffer ml_mfp will be set if needed. Useful when + // setline() is used on startup. For other buffers the buffer must be + // loaded. + const bool is_curbuf = buf == curbuf; + if (buf == NULL || (!is_curbuf && buf->b_ml.ml_mfp == NULL) || lnum < 1) { + rettv->vval.v_number = 1; // FAIL + return; + } + + // After this don't use "return", goto "cleanup"! + cob_T cob; + if (!is_curbuf) { + // set "curbuf" to "buf" and find a window for this buffer + change_other_buffer_prepare(&cob, buf); + } + + linenr_T append_lnum; + if (append) { + // appendbufline() uses the line number below which we insert + append_lnum = lnum - 1; + } else { + // setbufline() uses the line number above which we insert, we only + // append if it's below the last line + append_lnum = curbuf->b_ml.ml_line_count; + } + + list_T *l = NULL; + listitem_T *li = NULL; + char *line = NULL; + if (lines->v_type == VAR_LIST) { + l = lines->vval.v_list; + if (l == NULL || tv_list_len(l) == 0) { + // set proper return code + if (lnum > curbuf->b_ml.ml_line_count) { + rettv->vval.v_number = 1; // FAIL + } + goto cleanup; + } + li = tv_list_first(l); + } else { + line = typval_tostring(lines, false); + } + + // Default result is zero == OK. + for (;;) { + if (lines->v_type == VAR_LIST) { + // List argument, get next string. + if (li == NULL) { + break; + } + xfree(line); + line = typval_tostring(TV_LIST_ITEM_TV(li), false); + li = TV_LIST_ITEM_NEXT(l, li); + } + + rettv->vval.v_number = 1; // FAIL + if (line == NULL || lnum > curbuf->b_ml.ml_line_count + 1) { + break; + } + + // When coming here from Insert mode, sync undo, so that this can be + // undone separately from what was previously inserted. + if (u_sync_once == 2) { + u_sync_once = 1; // notify that u_sync() was called + u_sync(true); + } + + if (!append && lnum <= curbuf->b_ml.ml_line_count) { + // Existing line, replace it. + int old_len = (int)strlen(ml_get(lnum)); + if (u_savesub(lnum) == OK + && ml_replace(lnum, line, true) == OK) { + inserted_bytes(lnum, 0, old_len, (int)strlen(line)); + if (is_curbuf && lnum == curwin->w_cursor.lnum) { + check_cursor_col(); + } + rettv->vval.v_number = 0; // OK + } + } else if (added > 0 || u_save(lnum - 1, lnum) == OK) { + // append the line. + added++; + if (ml_append(lnum - 1, line, 0, false) == OK) { + rettv->vval.v_number = 0; // OK + } + } + + if (l == NULL) { // only one string argument + break; + } + lnum++; + } + xfree(line); + + if (added > 0) { + appended_lines_mark(append_lnum, added); + + // Only adjust the cursor for buffers other than the current, unless it + // is the current window. For curbuf and other windows it has been done + // in mark_adjust_internal(). + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf + && (wp->w_buffer != curbuf || wp == curwin) + && wp->w_cursor.lnum > append_lnum) { + wp->w_cursor.lnum += (linenr_T)added; + } + } + check_cursor_col(); + update_topline(curwin); + } + +cleanup: + if (!is_curbuf) { + change_other_buffer_restore(&cob); + } +} + +/// "append(lnum, string/list)" function +void f_append(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const int did_emsg_before = did_emsg; + const linenr_T lnum = tv_get_lnum(&argvars[0]); + if (did_emsg == did_emsg_before) { + set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); + } +} + +/// Set or append lines to a buffer. +static void buf_set_append_line(typval_T *argvars, typval_T *rettv, bool append) +{ + const int did_emsg_before = did_emsg; + buf_T *const buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + rettv->vval.v_number = 1; // FAIL + } else { + const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); + if (did_emsg == did_emsg_before) { + set_buffer_lines(buf, lnum, append, &argvars[2], rettv); + } + } +} + +/// "appendbufline(buf, lnum, string/list)" function +void f_appendbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_set_append_line(argvars, rettv, true); +} + +/// "bufadd(expr)" function +void f_bufadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char *name = (char *)tv_get_string(&argvars[0]); + + rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0); +} + +/// "bufexists(expr)" function +void f_bufexists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); +} + +/// "buflisted(expr)" function +void f_buflisted(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_T *buf; + + buf = find_buffer(&argvars[0]); + rettv->vval.v_number = (buf != NULL && buf->b_p_bl); +} + +/// "bufload(expr)" function +void f_bufload(typval_T *argvars, typval_T *unused, EvalFuncData fptr) +{ + buf_T *buf = get_buf_arg(&argvars[0]); + + if (buf != NULL) { + buffer_ensure_loaded(buf); + } +} + +/// "bufloaded(expr)" function +void f_bufloaded(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_T *buf; + + buf = find_buffer(&argvars[0]); + rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); +} + +/// "bufname(expr)" function +void f_bufname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const buf_T *buf; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (argvars[0].v_type == VAR_UNKNOWN) { + buf = curbuf; + } else { + buf = tv_get_buf_from_arg(&argvars[0]); + } + if (buf != NULL && buf->b_fname != NULL) { + rettv->vval.v_string = xstrdup(buf->b_fname); + } +} + +/// "bufnr(expr)" function +void f_bufnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const buf_T *buf; + bool error = false; + + rettv->vval.v_number = -1; + + if (argvars[0].v_type == VAR_UNKNOWN) { + buf = curbuf; + } else { + // Don't use tv_get_buf_from_arg(); we continue if the buffer wasn't found + // and the second argument isn't zero, but we want to return early if the + // first argument isn't a string or number so only one error is shown. + if (!tv_check_str_or_nr(&argvars[0])) { + return; + } + emsg_off++; + buf = tv_get_buf(&argvars[0], false); + emsg_off--; + } + + // If the buffer isn't found and the second argument is not zero create a + // new buffer. + const char *name; + if (buf == NULL + && argvars[1].v_type != VAR_UNKNOWN + && tv_get_number_chk(&argvars[1], &error) != 0 + && !error + && (name = tv_get_string_chk(&argvars[0])) != NULL) { + buf = buflist_new((char *)name, NULL, 1, 0); + } + + if (buf != NULL) { + rettv->vval.v_number = buf->b_fnum; + } +} + +static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) +{ + const buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); + if (buf == NULL) { // no need to search if invalid arg or buffer not found + rettv->vval.v_number = -1; + return; + } + + int winnr = 0; + int winid; + bool found_buf = false; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + winnr++; + if (wp->w_buffer == buf) { + found_buf = true; + winid = wp->handle; + break; + } + } + rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1); +} + +/// "bufwinid(nr)" function +void f_bufwinid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_win_common(argvars, rettv, false); +} + +/// "bufwinnr(nr)" function +void f_bufwinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_win_common(argvars, rettv, true); +} + +/// "deletebufline()" function +void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const int did_emsg_before = did_emsg; + rettv->vval.v_number = 1; // FAIL by default + buf_T *const buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + return; + } + + linenr_T last; + const linenr_T first = tv_get_lnum_buf(&argvars[1], buf); + if (did_emsg > did_emsg_before) { + return; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + last = tv_get_lnum_buf(&argvars[2], buf); + } else { + last = first; + } + + if (buf->b_ml.ml_mfp == NULL || first < 1 + || first > buf->b_ml.ml_line_count || last < first) { + return; + } + + // After this don't use "return", goto "cleanup"! + const bool is_curbuf = buf == curbuf; + cob_T cob; + if (!is_curbuf) { + // set "curbuf" to "buf" and find a window for this buffer + change_other_buffer_prepare(&cob, buf); + } + + if (last > curbuf->b_ml.ml_line_count) { + last = curbuf->b_ml.ml_line_count; + } + const long count = last - first + 1; + + // When coming here from Insert mode, sync undo, so that this can be + // undone separately from what was previously inserted. + if (u_sync_once == 2) { + u_sync_once = 1; // notify that u_sync() was called + u_sync(true); + } + + if (u_save(first - 1, last + 1) == FAIL) { + goto cleanup; + } + + for (linenr_T lnum = first; lnum <= last; lnum++) { + ml_delete(first, true); + } + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + if (wp->w_cursor.lnum > last) { + wp->w_cursor.lnum -= (linenr_T)count; + } else if (wp->w_cursor.lnum > first) { + wp->w_cursor.lnum = first; + } + if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { + wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; + } + } + } + check_cursor_col(); + deleted_lines_mark(first, count); + rettv->vval.v_number = 0; // OK + +cleanup: + if (!is_curbuf) { + change_other_buffer_restore(&cob); + } +} + +/// @return buffer options, variables and other attributes in a dictionary. +static dict_T *get_buffer_info(buf_T *buf) +{ + dict_T *const dict = tv_dict_alloc(); + + tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum); + tv_dict_add_str(dict, S_LEN("name"), + buf->b_ffname != NULL ? (const char *)buf->b_ffname : ""); + tv_dict_add_nr(dict, S_LEN("lnum"), + buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf)); + tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count); + tv_dict_add_nr(dict, S_LEN("loaded"), buf->b_ml.ml_mfp != NULL); + tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl); + tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf)); + tv_dict_add_nr(dict, S_LEN("changedtick"), buf_get_changedtick(buf)); + tv_dict_add_nr(dict, S_LEN("hidden"), + buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0); + + // Get a reference to buffer variables + tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); + + // List of windows displaying this buffer + list_T *const windows = tv_list_alloc(kListLenMayKnow); + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + tv_list_append_number(windows, (varnumber_T)wp->handle); + } + } + tv_dict_add_list(dict, S_LEN("windows"), windows); + + if (buf->b_signlist != NULL) { + // List of signs placed in this buffer + tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); + } + + tv_dict_add_nr(dict, S_LEN("lastused"), buf->b_last_used); + + return dict; +} + +/// "getbufinfo()" function +void f_getbufinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_T *argbuf = NULL; + bool filtered = false; + bool sel_buflisted = false; + bool sel_bufloaded = false; + bool sel_bufmodified = false; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + // List of all the buffers or selected buffers + if (argvars[0].v_type == VAR_DICT) { + dict_T *sel_d = argvars[0].vval.v_dict; + + if (sel_d != NULL) { + dictitem_T *di; + + filtered = true; + + di = tv_dict_find(sel_d, S_LEN("buflisted")); + if (di != NULL && tv_get_number(&di->di_tv)) { + sel_buflisted = true; + } + + di = tv_dict_find(sel_d, S_LEN("bufloaded")); + if (di != NULL && tv_get_number(&di->di_tv)) { + sel_bufloaded = true; + } + di = tv_dict_find(sel_d, S_LEN("bufmodified")); + if (di != NULL && tv_get_number(&di->di_tv)) { + sel_bufmodified = true; + } + } + } else if (argvars[0].v_type != VAR_UNKNOWN) { + // Information about one buffer. Argument specifies the buffer + argbuf = tv_get_buf_from_arg(&argvars[0]); + if (argbuf == NULL) { + return; + } + } + + // Return information about all the buffers or a specified buffer + FOR_ALL_BUFFERS(buf) { + if (argbuf != NULL && argbuf != buf) { + continue; + } + if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL) + || (sel_buflisted && !buf->b_p_bl) + || (sel_bufmodified && !buf->b_changed))) { + continue; + } + + dict_T *const d = get_buffer_info(buf); + tv_list_append_dict(rettv->vval.v_list, d); + if (argbuf != NULL) { + return; + } + } +} + +/// Get line or list of lines from buffer "buf" into "rettv". +/// +/// @param retlist if true, then the lines are returned as a Vim List. +/// +/// @return range (from start to end) of lines in rettv from the specified +/// buffer. +static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv) +{ + rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); + rettv->vval.v_string = NULL; + + if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { + if (retlist) { + tv_list_alloc_ret(rettv, 0); + } + return; + } + + if (retlist) { + if (start < 1) { + start = 1; + } + if (end > buf->b_ml.ml_line_count) { + end = buf->b_ml.ml_line_count; + } + tv_list_alloc_ret(rettv, end - start + 1); + while (start <= end) { + tv_list_append_string(rettv->vval.v_list, + (const char *)ml_get_buf(buf, start++, false), -1); + } + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) + ? xstrdup(ml_get_buf(buf, start, false)) : NULL); + } +} + +/// @param retlist true: "getbufline()" function +/// false: "getbufoneline()" function +static void getbufline(typval_T *argvars, typval_T *rettv, bool retlist) +{ + const int did_emsg_before = did_emsg; + buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); + const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); + if (did_emsg > did_emsg_before) { + return; + } + const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN + ? lnum + : tv_get_lnum_buf(&argvars[2], buf)); + + get_buffer_lines(buf, lnum, end, retlist, rettv); +} + +/// "getbufline()" function +void f_getbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + getbufline(argvars, rettv, true); +} + +/// "getbufoneline()" function +void f_getbufoneline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + getbufline(argvars, rettv, false); +} + +/// "getline(lnum, [end])" function +void f_getline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + linenr_T end; + bool retlist; + + const linenr_T lnum = tv_get_lnum(argvars); + if (argvars[1].v_type == VAR_UNKNOWN) { + end = lnum; + retlist = false; + } else { + end = tv_get_lnum(&argvars[1]); + retlist = true; + } + + get_buffer_lines(curbuf, lnum, end, retlist, rettv); +} + +/// "setbufline()" function +void f_setbufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + buf_set_append_line(argvars, rettv, false); +} + +/// "setline()" function +void f_setline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const int did_emsg_before = did_emsg; + linenr_T lnum = tv_get_lnum(&argvars[0]); + if (did_emsg == did_emsg_before) { + set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv); + } +} + +/// Make "buf" the current buffer. +/// +/// restore_buffer() MUST be called to undo. +/// No autocommands will be executed. Use aucmd_prepbuf() if there are any. +void switch_buffer(bufref_T *save_curbuf, buf_T *buf) +{ + block_autocmds(); + set_bufref(save_curbuf, curbuf); + curbuf->b_nwindows--; + curbuf = buf; + curwin->w_buffer = buf; + curbuf->b_nwindows++; +} + +/// Restore the current buffer after using switch_buffer(). +void restore_buffer(bufref_T *save_curbuf) +{ + unblock_autocmds(); + // Check for valid buffer, just in case. + if (bufref_valid(save_curbuf)) { + curbuf->b_nwindows--; + curwin->w_buffer = save_curbuf->br_buf; + curbuf = save_curbuf->br_buf; + curbuf->b_nwindows++; + } +} + +/// Find a window for buffer "buf". +/// If found true is returned and "wp" and "tp" are set to +/// the window and tabpage. +/// If not found, false is returned. +/// +/// @param buf buffer to find a window for +/// @param[out] wp stores the found window +/// @param[out] tp stores the found tabpage +/// +/// @return true if a window was found for the buffer. +bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp) +{ + *wp = NULL; + *tp = NULL; + FOR_ALL_TAB_WINDOWS(tp2, wp2) { + if (wp2->w_buffer == buf) { + *tp = tp2; + *wp = wp2; + return true; + } + } + return false; +} diff --git a/src/nvim/eval/buffer.h b/src/nvim/eval/buffer.h new file mode 100644 index 0000000000..4a2f8f9e94 --- /dev/null +++ b/src/nvim/eval/buffer.h @@ -0,0 +1,10 @@ +#ifndef NVIM_EVAL_BUFFER_H +#define NVIM_EVAL_BUFFER_H + +#include "nvim/buffer_defs.h" +#include "nvim/eval/typval_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/buffer.h.generated.h" +#endif +#endif // NVIM_EVAL_BUFFER_H diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 7b975ce775..cd1479f150 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -1,20 +1,31 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <msgpack.h> +#include <assert.h> +#include <msgpack/object.h> +#include <stdbool.h> #include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" #include "nvim/ascii.h" -#include "nvim/charset.h" // vim_str2nr +#include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/eval/decode.h" #include "nvim/eval/encode.h" #include "nvim/eval/typval.h" -#include "nvim/globals.h" -#include "nvim/lib/kvec.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/hashtab.h" #include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/vim.h" // OK, FAIL +#include "nvim/types.h" +#include "nvim/vim.h" /// Helper structure for container_struct typedef struct { @@ -271,11 +282,9 @@ typval_T decode_string(const char *const s, const size_t len, const TriState has list_T *const list = tv_list_alloc(kListLenMayKnow); tv_list_ref(list); create_special_dict(&tv, kMPString, - ((typval_T){ - .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list }, - })); + (typval_T){ .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval = { .v_list = list } }); const int elw_ret = encode_list_write((void *)list, s, len); if (s_allocated) { xfree((void *)s); @@ -286,13 +295,12 @@ typval_T decode_string(const char *const s, const size_t len, const TriState has } } return tv; - } else { - return (typval_T) { - .v_type = VAR_STRING, - .v_lock = VAR_UNLOCKED, - .vval = { .v_string = ((s == NULL || s_allocated) ? (char *)s : xmemdupz(s, len)) }, - }; } + return (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval = { .v_string = ((s == NULL || s_allocated) ? (char *)s : xmemdupz(s, len)) }, + }; } /// Parse JSON double-quoted string @@ -368,7 +376,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len, goto parse_json_string_fail; } } else { - uint8_t p_byte = (uint8_t)*p; + uint8_t p_byte = (uint8_t)(*p); // unescaped = %x20-21 / %x23-5B / %x5D-10FFFF if (p_byte < 0x20) { semsg(_("E474: ASCII control characters cannot be present " @@ -430,7 +438,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len, const char ubuf[] = { t[1], t[2], t[3], t[4] }; t += 4; uvarnumber_T ch; - vim_str2nr((char_u *)ubuf, NULL, NULL, + vim_str2nr(ubuf, NULL, NULL, STR2NR_HEX | STR2NR_FORCE, NULL, &ch, 4, true); if (ch == 0) { hasnul = true; @@ -469,7 +477,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len, ['r'] = CAR, ['f'] = FF, }; - *str_end++ = escapes[(int)*t]; + *str_end++ = escapes[(int)(*t)]; break; } default: @@ -600,7 +608,7 @@ parse_json_number_check: // Convert integer varnumber_T nr; int num_len; - vim_str2nr((char_u *)s, NULL, &num_len, 0, &nr, NULL, (int)(p - s), true); + vim_str2nr(s, NULL, &num_len, 0, &nr, NULL, (int)(p - s), true); if ((int)exp_num_len != num_len) { semsg(_("E685: internal error: while converting number \"%.*s\" " "to integer vim_str2nr consumed %i bytes in place of %zu"), @@ -838,12 +846,10 @@ json_decode_string_cycle_start: .v_lock = VAR_UNLOCKED, .vval = { .v_list = list }, }; - kv_push(container_stack, ((ContainerStackItem) { - .stack_index = kv_size(stack), - .s = p, - .container = tv, - .special_val = NULL, - })); + kv_push(container_stack, ((ContainerStackItem) { .stack_index = kv_size(stack), + .s = p, + .container = tv, + .special_val = NULL })); kv_push(stack, OBJ(tv, false, didcomma, didcolon)); break; } @@ -862,12 +868,10 @@ json_decode_string_cycle_start: .vval = { .v_dict = dict }, }; } - kv_push(container_stack, ((ContainerStackItem) { - .stack_index = kv_size(stack), - .s = p, - .container = tv, - .special_val = val_list, - })); + kv_push(container_stack, ((ContainerStackItem) { .stack_index = kv_size(stack), + .s = p, + .container = tv, + .special_val = val_list })); kv_push(stack, OBJ(tv, false, didcomma, didcolon)); break; } @@ -1089,11 +1093,9 @@ msgpack_to_vim_generic_map: {} tv_list_append_number(list, mobj.via.ext.type); list_T *const ext_val_list = tv_list_alloc(kListLenMayKnow); tv_list_append_list(list, ext_val_list); - create_special_dict(rettv, kMPExt, ((typval_T) { - .v_type = VAR_LIST, - .v_lock = VAR_UNLOCKED, - .vval = { .v_list = list }, - })); + create_special_dict(rettv, kMPExt, ((typval_T) { .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval = { .v_list = list } })); if (encode_list_write((void *)ext_val_list, mobj.via.ext.ptr, mobj.via.ext.size) == -1) { return FAIL; diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index bb514fba47..c2f1eae8af 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -10,23 +10,28 @@ #include <assert.h> #include <inttypes.h> #include <math.h> -#include <msgpack.h> +#include <stdbool.h> #include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" +#include "msgpack/pack.h" #include "nvim/ascii.h" -#include "nvim/buffer_defs.h" -#include "nvim/charset.h" // vim_isprintc() #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/eval/typval.h" #include "nvim/eval/typval_encode.h" #include "nvim/garray.h" -#include "nvim/lib/kvec.h" +#include "nvim/gettext.h" +#include "nvim/hashtab.h" #include "nvim/macros.h" #include "nvim/math.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" // For _() const char *const encode_bool_var_names[] = { @@ -131,35 +136,35 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, typval_T key_tv = { .v_type = VAR_STRING, .vval = { .v_string = - (char *)(v.data.d.hi == - NULL ? v.data.d.dict->dv_hashtab.ht_array : (v.data.d.hi - - 1))->hi_key }, + (v.data.d.hi == + NULL ? v.data.d.dict->dv_hashtab.ht_array : (v.data.d.hi - + 1))->hi_key }, }; char *const key = encode_tv2string(&key_tv, NULL); - vim_snprintf((char *)IObuff, IOSIZE, key_msg, key); + vim_snprintf(IObuff, IOSIZE, key_msg, key); xfree(key); - ga_concat(&msg_ga, (char *)IObuff); + ga_concat(&msg_ga, IObuff); break; } case kMPConvPairs: case kMPConvList: { const int idx = (v.data.l.li == tv_list_first(v.data.l.list) - ? 0 - : (v.data.l.li == NULL - ? tv_list_len(v.data.l.list) - 1 - : (int)tv_list_idx_of_item(v.data.l.list, - TV_LIST_ITEM_PREV(v.data.l.list, - v.data.l.li)))); + ? 0 + : (v.data.l.li == NULL + ? tv_list_len(v.data.l.list) - 1 + : (int)tv_list_idx_of_item(v.data.l.list, + TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)))); const listitem_T *const li = (v.data.l.li == NULL - ? tv_list_last(v.data.l.list) - : TV_LIST_ITEM_PREV(v.data.l.list, - v.data.l.li)); + ? tv_list_last(v.data.l.list) + : TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)); if (v.type == kMPConvList || li == NULL || (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST && tv_list_len(TV_LIST_ITEM_TV(li)->vval.v_list) <= 0)) { - vim_snprintf((char *)IObuff, IOSIZE, idx_msg, idx); - ga_concat(&msg_ga, (char *)IObuff); + vim_snprintf(IObuff, IOSIZE, idx_msg, idx); + ga_concat(&msg_ga, IObuff); } else { assert(li != NULL); listitem_T *const first_item = @@ -167,9 +172,9 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, assert(first_item != NULL); typval_T key_tv = *TV_LIST_ITEM_TV(first_item); char *const key = encode_tv2echo(&key_tv, NULL); - vim_snprintf((char *)IObuff, IOSIZE, key_pair_msg, key, idx); + vim_snprintf(IObuff, IOSIZE, key_pair_msg, key, idx); xfree(key); - ga_concat(&msg_ga, (char *)IObuff); + ga_concat(&msg_ga, IObuff); } break; } @@ -188,8 +193,8 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, break; case kMPConvPartialList: { const int idx = (int)(v.data.a.arg - v.data.a.argv) - 1; - vim_snprintf((char *)IObuff, IOSIZE, partial_arg_i_msg, idx); - ga_concat(&msg_ga, (char *)IObuff); + vim_snprintf(IObuff, IOSIZE, partial_arg_i_msg, idx); + ga_concat(&msg_ga, IObuff); break; } } @@ -219,7 +224,7 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len, cha } len++; if (TV_LIST_ITEM_TV(li)->vval.v_string != NULL) { - len += STRLEN(TV_LIST_ITEM_TV(li)->vval.v_string); + len += strlen(TV_LIST_ITEM_TV(li)->vval.v_string); } }); if (len) { @@ -281,7 +286,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s state->offset = 0; state->li_length = (TV_LIST_ITEM_TV(state->li)->vval.v_string == NULL ? 0 - : STRLEN(TV_LIST_ITEM_TV(state->li)->vval.v_string)); + : strlen(TV_LIST_ITEM_TV(state->li)->vval.v_string)); } } *read_bytes = nbuf; @@ -304,7 +309,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s if (buf_[i_] == '\'') { \ ga_append(gap, '\''); \ } \ - ga_append(gap, buf_[i_]); \ + ga_append(gap, (uint8_t)buf_[i_]); \ } \ ga_append(gap, '\''); \ } \ diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h index 8755ff48ac..41e7614fc0 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -2,11 +2,15 @@ #define NVIM_EVAL_ENCODE_H #include <msgpack.h> +#include <msgpack/pack.h> #include <stddef.h> +#include <string.h> #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/garray.h" -#include "nvim/vim.h" // For STRLEN +#include "nvim/vim.h" /// Convert VimL value to msgpack string /// @@ -15,9 +19,7 @@ /// @param[in] objname Object name, used for error message. /// /// @return OK in case of success, FAIL otherwise. -int encode_vim_to_msgpack(msgpack_packer *const packer, - typval_T *const tv, - const char *const objname); +int encode_vim_to_msgpack(msgpack_packer *packer, typval_T *tv, const char *objname); /// Convert VimL value to :echo output /// @@ -26,9 +28,7 @@ int encode_vim_to_msgpack(msgpack_packer *const packer, /// @param[in] objname Object name, used for error message. /// /// @return OK in case of success, FAIL otherwise. -int encode_vim_to_echo(garray_T *const packer, - typval_T *const tv, - const char *const objname); +int encode_vim_to_echo(garray_T *packer, typval_T *tv, const char *objname); /// Structure defining state for read_from_list() typedef struct { @@ -48,7 +48,7 @@ static inline ListReaderState encode_init_lrstate(const list_T *const list) .offset = 0, .li_length = (TV_LIST_ITEM_TV(tv_list_first(list))->vval.v_string == NULL ? 0 - : STRLEN(TV_LIST_ITEM_TV(tv_list_first(list))->vval.v_string)), + : strlen(TV_LIST_ITEM_TV(tv_list_first(list))->vval.v_string)), }; } diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index b461456a3a..9caea2fef1 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -1,15 +1,23 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <inttypes.h> +#include <stdlib.h> + #include "nvim/eval.h" #include "nvim/eval/executor.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/message.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "eval/executor.c.generated.h" +# include "eval/executor.c.generated.h" // IWYU pragma: export #endif char *e_listidx = N_("E684: list index out of range: %" PRId64); @@ -43,7 +51,7 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *cons blob_T *const b1 = tv1->vval.v_blob; blob_T *const b2 = tv2->vval.v_blob; for (int i = 0; i < tv_blob_len(b2); i++) { - ga_append(&b1->bv_ga, (char)tv_blob_get(b2, i)); + ga_append(&b1->bv_ga, tv_blob_get(b2, i)); } } return OK; @@ -61,7 +69,7 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *cons if (tv2->v_type == VAR_LIST) { break; } - if (vim_strchr("+-*/%", *op) != NULL) { + if (vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { // nr += nr or nr -= nr, nr *= nr, nr /= nr, nr %= nr varnumber_T n = tv_get_number(tv1); if (tv2->v_type == VAR_FLOAT) { @@ -108,8 +116,7 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *cons const char *tvs = tv_get_string(tv1); char numbuf[NUMBUFLEN]; char *const s = - (char *)concat_str((const char_u *)tvs, (const char_u *)tv_get_string_buf(tv2, - numbuf)); + concat_str(tvs, tv_get_string_buf(tv2, numbuf)); tv_clear(tv1); tv1->v_type = VAR_STRING; tv1->vval.v_string = s; @@ -123,8 +130,8 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *cons break; } const float_T f = (tv2->v_type == VAR_FLOAT - ? tv2->vval.v_float - : (float_T)tv_get_number(tv2)); + ? tv2->vval.v_float + : (float_T)tv_get_number(tv2)); switch (*op) { case '+': tv1->vval.v_float += f; break; diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 9f851653eb..a6dc41d0f3 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1,27 +1,44 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> +#include <fcntl.h> #include <float.h> +#include <inttypes.h> +#include <limits.h> #include <math.h> - +#include <msgpack/object.h> +#include <msgpack/pack.h> +#include <msgpack/unpack.h> +#include <signal.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <time.h> +#include <uv.h> + +#include "auto/config.h" #include "nvim/api/private/converter.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" -#include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" -#include "nvim/change.h" +#include "nvim/buffer_defs.h" #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" -#include "nvim/cmdhist.h" #include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/diff.h" -#include "nvim/digraph.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/buffer.h" #include "nvim/eval/decode.h" #include "nvim/eval/encode.h" #include "nvim/eval/executor.h" @@ -29,55 +46,72 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" +#include "nvim/eval/window.h" +#include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" +#include "nvim/event/process.h" +#include "nvim/event/time.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/file_search.h" #include "nvim/fileio.h" -#include "nvim/fold.h" +#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/grid_defs.h" +#include "nvim/hashtab.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" -#include "nvim/if_cscope.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/input.h" -#include "nvim/insexpand.h" +#include "nvim/keycodes.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" -#include "nvim/mapping.h" +#include "nvim/main.h" #include "nvim/mark.h" -#include "nvim/match.h" #include "nvim/math.h" +#include "nvim/mbyte.h" +#include "nvim/memfile_defs.h" #include "nvim/memline.h" +#include "nvim/memory.h" #include "nvim/menu.h" +#include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/server.h" +#include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/optionstr.h" #include "nvim/os/dl.h" +#include "nvim/os/fileio.h" +#include "nvim/os/fs_defs.h" +#include "nvim/os/os.h" +#include "nvim/os/pty_process.h" #include "nvim/os/shell.h" +#include "nvim/os/stdpaths_defs.h" +#include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" -#include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/runtime.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/sha256.h" -#include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/spellsuggest.h" #include "nvim/state.h" +#include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" -#include "nvim/testing.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/version.h" @@ -106,6 +140,7 @@ typedef enum { PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH # include "funcs.generated.h" + PRAGMA_DIAG_POP PRAGMA_DIAG_POP #endif @@ -113,6 +148,8 @@ PRAGMA_DIAG_POP static char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob"); static char *e_invalwindow = N_("E957: Invalid window number"); static char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value"); +static char e_using_number_as_bool_nr[] + = N_("E1023: Using a Number as a Bool: %d"); /// Dummy va_list for passing to vim_snprintf /// @@ -132,13 +169,13 @@ char *get_function_name(expand_T *xp, int idx) intidx = -1; } if (intidx < 0) { - char_u *name = (char_u *)get_user_func_name(xp, idx); + char *name = get_user_func_name(xp, idx); if (name != NULL) { if (*name != NUL && *name != '<' - && STRNCMP("g:", xp->xp_pattern, 2) == 0) { - return cat_prefix_varname('g', (char *)name); + && strncmp("g:", xp->xp_pattern, 2) == 0) { + return cat_prefix_varname('g', name); } - return (char *)name; + return name; } } @@ -155,7 +192,7 @@ char *get_function_name(expand_T *xp, int idx) } else { IObuff[key_len + 1] = NUL; } - return (char *)IObuff; + return IObuff; } /// Function given to ExpandGeneric() to obtain the list of internal or @@ -168,9 +205,9 @@ char *get_expr_name(expand_T *xp, int idx) intidx = -1; } if (intidx < 0) { - char_u *name = (char_u *)get_function_name(xp, idx); + char *name = get_function_name(xp, idx); if (name != NULL) { - return (char *)name; + return name; } } return get_user_var_name(xp, ++intidx); @@ -189,37 +226,37 @@ const EvalFuncDef *find_internal_func(const char *const name) return index >= 0 ? &functions[index] : NULL; } -int call_internal_func(const char_u *const fname, const int argcount, typval_T *const argvars, +int call_internal_func(const char *const fname, const int argcount, typval_T *const argvars, typval_T *const rettv) FUNC_ATTR_NONNULL_ALL { - const EvalFuncDef *const fdef = find_internal_func((const char *)fname); + const EvalFuncDef *const fdef = find_internal_func(fname); if (fdef == NULL) { - return ERROR_UNKNOWN; + return FCERR_UNKNOWN; } else if (argcount < fdef->min_argc) { - return ERROR_TOOFEW; + return FCERR_TOOFEW; } else if (argcount > fdef->max_argc) { - return ERROR_TOOMANY; + return FCERR_TOOMANY; } argvars[argcount].v_type = VAR_UNKNOWN; fdef->func(argvars, rettv, fdef->data); - return ERROR_NONE; + return FCERR_NONE; } /// Invoke a method for base->method(). -int call_internal_method(const char_u *const fname, const int argcount, typval_T *const argvars, +int call_internal_method(const char *const fname, const int argcount, typval_T *const argvars, typval_T *const rettv, typval_T *const basetv) FUNC_ATTR_NONNULL_ALL { - const EvalFuncDef *const fdef = find_internal_func((const char *)fname); + const EvalFuncDef *const fdef = find_internal_func(fname); if (fdef == NULL) { - return ERROR_UNKNOWN; + return FCERR_UNKNOWN; } else if (fdef->base_arg == BASE_NONE) { - return ERROR_NOTMETHOD; + return FCERR_NOTMETHOD; } else if (argcount + 1 < fdef->min_argc) { - return ERROR_TOOFEW; + return FCERR_TOOFEW; } else if (argcount + 1 > fdef->max_argc) { - return ERROR_TOOMANY; + return FCERR_TOOMANY; } typval_T argv[MAX_FUNC_ARGS + 1]; @@ -231,10 +268,10 @@ int call_internal_method(const char_u *const fname, const int argcount, typval_T argv[argcount + 1].v_type = VAR_UNKNOWN; fdef->func(argv, rettv, fdef->data); - return ERROR_NONE; + return FCERR_NONE; } -/// @return TRUE for a non-zero Number and a non-empty String. +/// @return true for a non-zero Number and a non-empty String. static int non_zero_arg(typval_T *argvars) { return ((argvars[0].v_type == VAR_NUMBER @@ -251,26 +288,25 @@ static int non_zero_arg(typval_T *argvars) /// Some versions of glibc on i386 have an optimization that makes it harder to /// call math functions indirectly from inside an inlined function, causing /// compile-time errors. Avoid `inline` in that case. #3072 -static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void float_op_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T f; - float_T (*function)(float_T) = (float_T (*)(float_T)) fptr; rettv->v_type = VAR_FLOAT; if (tv_get_float_chk(argvars, &f)) { - rettv->vval.v_float = function(f); + rettv->vval.v_float = fptr.float_func(f); } else { rettv->vval.v_float = 0.0; } } -static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void api_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; } - ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr; + MsgpackRpcRequestHandler handler = *fptr.api_handler; Array args = ARRAY_DICT_INIT; @@ -279,7 +315,8 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) } Error err = ERROR_INIT; - Object result = fn(VIML_INTERNAL_CALL, args, &err); + Arena res_arena = ARENA_EMPTY; + Object result = handler.fn(VIML_INTERNAL_CALL, args, &res_arena, &err); if (ERROR_SET(&err)) { semsg_multiline((const char *)e_api_error, err.msg); @@ -292,15 +329,19 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) end: api_free_array(args); - api_free_object(result); + if (handler.arena_return) { + arena_mem_free(arena_finish(&res_arena)); + } else { + api_free_object(result); + } api_clear_error(&err); } /// "abs(expr)" function -static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_abs(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type == VAR_FLOAT) { - float_op_wrapper(argvars, rettv, (FunPtr)&fabs); + float_op_wrapper(argvars, rettv, (EvalFuncData){ .float_func = &fabs }); } else { bool error = false; @@ -316,25 +357,25 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "add(list, item)" function -static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_add(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = 1; // Default: failed. if (argvars[0].v_type == VAR_LIST) { list_T *const l = argvars[0].vval.v_list; - if (!var_check_lock(tv_list_locked(l), N_("add() argument"), - TV_TRANSLATE)) { + if (!value_check_lock(tv_list_locked(l), N_("add() argument"), + TV_TRANSLATE)) { tv_list_append_tv(l, &argvars[1]); tv_copy(&argvars[0], rettv); } } else if (argvars[0].v_type == VAR_BLOB) { blob_T *const b = argvars[0].vval.v_blob; if (b != NULL - && !var_check_lock(b->bv_lock, N_("add() argument"), TV_TRANSLATE)) { + && !value_check_lock(b->bv_lock, N_("add() argument"), TV_TRANSLATE)) { bool error = false; const varnumber_T n = tv_get_number_chk(&argvars[1], &error); if (!error) { - ga_append(&b->bv_ga, (char)n); + ga_append(&b->bv_ga, (uint8_t)n); tv_copy(&argvars[0], rettv); } } @@ -344,42 +385,21 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "and(expr, expr)" function -static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_and(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) & tv_get_number_chk(&argvars[1], NULL); } /// "api_info()" function -static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_api_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { Dictionary metadata = api_metadata(); (void)object_to_vim(DICTIONARY_OBJ(metadata), rettv, NULL); - api_free_dictionary(metadata); -} - -/// "append(lnum, string/list)" function -static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const linenr_T lnum = tv_get_lnum(&argvars[0]); - - set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); -} - -/// "appendbufline(buf, lnum, string/list)" function -static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *const buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - } else { - const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); - set_buffer_lines(buf, lnum, true, &argvars[2], rettv); - } } /// "atan2()" function -static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_atan2(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T fx; float_T fy; @@ -393,176 +413,16 @@ static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "browse(save, title, initdir, default)" function -static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_browse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_string = NULL; rettv->v_type = VAR_STRING; } /// "browsedir(title, initdir)" function -static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - f_browse(argvars, rettv, NULL); -} - -/// Find a buffer by number or exact name. -static buf_T *find_buffer(typval_T *avar) -{ - buf_T *buf = NULL; - - if (avar->v_type == VAR_NUMBER) { - buf = buflist_findnr((int)avar->vval.v_number); - } else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) { - buf = buflist_findname_exp(avar->vval.v_string); - if (buf == NULL) { - // No full path name match, try a match with a URL or a "nofile" - // buffer, these don't use the full path. - FOR_ALL_BUFFERS(bp) { - if (bp->b_fname != NULL - && (path_with_url(bp->b_fname) || bt_nofilename(bp)) - && STRCMP(bp->b_fname, avar->vval.v_string) == 0) { - buf = bp; - break; - } - } - } - } - return buf; -} - -/// "bufadd(expr)" function -static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u *name = (char_u *)tv_get_string(&argvars[0]); - - rettv->vval.v_number = buflist_add(*name == NUL ? NULL : (char *)name, 0); -} - -/// "bufexists(expr)" function -static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); -} - -/// "buflisted(expr)" function -static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf; - - buf = find_buffer(&argvars[0]); - rettv->vval.v_number = (buf != NULL && buf->b_p_bl); -} - -/// "bufload(expr)" function -static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) -{ - buf_T *buf = get_buf_arg(&argvars[0]); - - if (buf != NULL && buf->b_ml.ml_mfp == NULL) { - aco_save_T aco; - - aucmd_prepbuf(&aco, buf); - swap_exists_action = SEA_NONE; - open_buffer(false, NULL, 0); - aucmd_restbuf(&aco); - } -} - -/// "bufloaded(expr)" function -static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_browsedir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - buf_T *buf; - - buf = find_buffer(&argvars[0]); - rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); -} - -/// "bufname(expr)" function -static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const buf_T *buf; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (argvars[0].v_type == VAR_UNKNOWN) { - buf = curbuf; - } else { - buf = tv_get_buf_from_arg(&argvars[0]); - } - if (buf != NULL && buf->b_fname != NULL) { - rettv->vval.v_string = xstrdup(buf->b_fname); - } -} - -/// "bufnr(expr)" function -static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const buf_T *buf; - bool error = false; - - rettv->vval.v_number = -1; - - if (argvars[0].v_type == VAR_UNKNOWN) { - buf = curbuf; - } else { - // Don't use tv_get_buf_from_arg(); we continue if the buffer wasn't found - // and the second argument isn't zero, but we want to return early if the - // first argument isn't a string or number so only one error is shown. - if (!tv_check_str_or_nr(&argvars[0])) { - return; - } - emsg_off++; - buf = tv_get_buf(&argvars[0], false); - emsg_off--; - } - - // If the buffer isn't found and the second argument is not zero create a - // new buffer. - const char *name; - if (buf == NULL - && argvars[1].v_type != VAR_UNKNOWN - && tv_get_number_chk(&argvars[1], &error) != 0 - && !error - && (name = tv_get_string_chk(&argvars[0])) != NULL) { - buf = buflist_new((char *)name, NULL, 1, 0); - } - - if (buf != NULL) { - rettv->vval.v_number = buf->b_fnum; - } -} - -static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr) -{ - const buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); - if (buf == NULL) { // no need to search if invalid arg or buffer not found - rettv->vval.v_number = -1; - return; - } - - int winnr = 0; - int winid; - bool found_buf = false; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - winnr++; - if (wp->w_buffer == buf) { - found_buf = true; - winid = wp->handle; - break; - } - } - rettv->vval.v_number = (found_buf ? (get_nr ? winnr : winid) : -1); -} - -/// "bufwinid(nr)" function -static void f_bufwinid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_win_common(argvars, rettv, false); -} - -/// "bufwinnr(nr)" function -static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_win_common(argvars, rettv, true); + f_browse(argvars, rettv, fptr); } /// Get buffer by number or pattern. @@ -575,7 +435,7 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only) return NULL; } - char_u *name = (char_u *)tv->vval.v_string; + char *name = tv->vval.v_string; if (name == NULL || *name == NUL) { return curbuf; @@ -588,9 +448,9 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only) int save_magic = p_magic; p_magic = true; char *save_cpo = p_cpo; - p_cpo = ""; + p_cpo = empty_option; - buf_T *buf = buflist_findnr(buflist_findpat((char *)name, (char *)name + STRLEN(name), + buf_T *buf = buflist_findnr(buflist_findpat(name, name + strlen(name), true, false, curtab_only)); p_magic = save_magic; @@ -630,7 +490,7 @@ buf_T *get_buf_arg(typval_T *arg) } /// "byte2line(byte)" function -static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { long boff = tv_get_number(&argvars[0]) - 1; if (boff < 0) { @@ -665,19 +525,19 @@ static void byteidx(typval_T *argvars, typval_T *rettv, int comp) } /// "byteidx()" function -static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_byteidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { byteidx(argvars, rettv, false); } /// "byteidxcomp()" function -static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { byteidx(argvars, rettv, true); } /// "call(func, arglist [, dict])" function -static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[1].v_type != VAR_LIST) { emsg(_(e_listreq)); @@ -688,19 +548,19 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) } bool owned = false; - char_u *func; + char *func; partial_T *partial = NULL; if (argvars[0].v_type == VAR_FUNC) { - func = (char_u *)argvars[0].vval.v_string; + func = argvars[0].vval.v_string; } else if (argvars[0].v_type == VAR_PARTIAL) { partial = argvars[0].vval.v_partial; - func = (char_u *)partial_name(partial); + func = partial_name(partial); } else if (nlua_is_table_from_lua(&argvars[0])) { // TODO(tjdevries): UnifiedCallback func = nlua_register_table_as_callable(&argvars[0]); owned = true; } else { - func = (char_u *)tv_get_string(&argvars[0]); + func = (char *)tv_get_string(&argvars[0]); } if (*func == NUL) { @@ -726,13 +586,13 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "changenr()" function -static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_changenr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = curbuf->b_u_seq_cur; } /// "chanclose(id[, stream])" function -static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_chanclose(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -771,7 +631,7 @@ static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "chansend(id, data)" function -static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_chansend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -788,6 +648,14 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) ptrdiff_t input_len = 0; char *input = NULL; + uint64_t id = (uint64_t)argvars[0].vval.v_number; +#ifdef UNIX + bool crlf = false; +#else + Channel *chan = find_channel(id); + bool crlf = (chan != NULL && chan->term) ? true: false; +#endif + if (argvars[1].v_type == VAR_BLOB) { const blob_T *const b = argvars[1].vval.v_blob; input_len = tv_blob_len(b); @@ -795,7 +663,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) input = xmemdup(b->bv_ga.ga_data, (size_t)input_len); } } else { - input = save_tv_as_string(&argvars[1], &input_len, false); + input = save_tv_as_string(&argvars[1], &input_len, false, crlf); } if (!input) { @@ -803,7 +671,6 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) // or there is no input to send. return; } - uint64_t id = (uint64_t)argvars[0].vval.v_number; const char *error = NULL; rettv->vval.v_number = (varnumber_T)channel_send(id, input, (size_t)input_len, true, &error); if (error) { @@ -812,7 +679,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "char2nr(string)" function -static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_char2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[1].v_type != VAR_UNKNOWN) { if (!tv_check_num(&argvars[1])) { @@ -829,15 +696,38 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// otherwise the byte index of the column. static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) { + if (tv_check_for_string_or_list_arg(argvars, 0) == FAIL + || tv_check_for_opt_number_arg(argvars, 1) == FAIL) { + return; + } + + switchwin_T switchwin; + bool winchanged = false; + + if (argvars[1].v_type != VAR_UNKNOWN) { + // use the window specified in the second argument + tabpage_T *tp; + win_T *wp = win_id2wp_tp((int)tv_get_number(&argvars[1]), &tp); + if (wp == NULL || tp == NULL) { + return; + } + + if (switch_win_noblock(&switchwin, wp, tp, true) != OK) { + return; + } + + check_cursor(); + winchanged = true; + } + colnr_T col = 0; int fnum = curbuf->b_fnum; - pos_T *fp = var2fpos(&argvars[0], false, &fnum, charcol); if (fp != NULL && fnum == curbuf->b_fnum) { if (fp->col == MAXCOL) { // '> can be MAXCOL, get the length of the line then if (fp->lnum <= curbuf->b_ml.ml_line_count) { - col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; + col = (colnr_T)strlen(ml_get(fp->lnum)) + 1; } else { col = MAXCOL; } @@ -846,11 +736,12 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) // col(".") when the cursor is on the NUL at the end of the line // because of "coladd" can be seen as an extra column. if (virtual_active() && fp == &curwin->w_cursor) { - char_u *p = get_cursor_pos_ptr(); + char *p = get_cursor_pos_ptr(); if (curwin->w_cursor.coladd >= - (colnr_T)win_chartabsize(curwin, p, curwin->w_virtcol - curwin->w_cursor.coladd)) { + (colnr_T)win_chartabsize(curwin, p, + curwin->w_virtcol - curwin->w_cursor.coladd)) { int l; - if (*p != NUL && p[(l = utfc_ptr2len((char *)p))] == NUL) { + if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) { col += l; } } @@ -858,23 +749,28 @@ static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) } } rettv->vval.v_number = col; + + if (winchanged) { + restore_win_noblock(&switchwin, true); + } } /// "charcol()" function -static void f_charcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_col(argvars, rettv, true); } /// "charidx()" function -static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_NUMBER || (argvars[2].v_type != VAR_UNKNOWN - && argvars[2].v_type != VAR_NUMBER)) { + && argvars[2].v_type != VAR_NUMBER + && argvars[2].v_type != VAR_BOOL)) { emsg(_(e_invarg)); return; } @@ -889,7 +785,7 @@ static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) countcc = (int)tv_get_number(&argvars[2]); } if (countcc < 0 || countcc > 1) { - emsg(_(e_invarg)); + semsg(_(e_using_number_as_bool_nr), countcc); return; } @@ -913,7 +809,7 @@ static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "chdir(dir)" function -static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -925,12 +821,12 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // Return the current directory - char_u *cwd = xmalloc(MAXPATHL); + char *cwd = xmalloc(MAXPATHL); if (os_dirname(cwd, MAXPATHL) != FAIL) { #ifdef BACKSLASH_IN_FILENAME slash_adjust(cwd); #endif - rettv->vval.v_string = (char *)vim_strsave(cwd); + rettv->vval.v_string = xstrdup(cwd); } xfree(cwd); @@ -948,7 +844,7 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "cindent(lnum)" function -static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_cindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { pos_T pos = curwin->w_cursor; linenr_T lnum = tv_get_lnum(argvars); @@ -963,26 +859,26 @@ static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) win_T *get_optional_window(typval_T *argvars, int idx) { - win_T *win = curwin; + if (argvars[idx].v_type == VAR_UNKNOWN) { + return curwin; + } - if (argvars[idx].v_type != VAR_UNKNOWN) { - win = find_win_by_nr_or_id(&argvars[idx]); - if (win == NULL) { - emsg(_(e_invalwindow)); - return NULL; - } + win_T *win = find_win_by_nr_or_id(&argvars[idx]); + if (win == NULL) { + emsg(_(e_invalwindow)); + return NULL; } return win; } /// "col(string)" function -static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_col(argvars, rettv, false); } /// "confirm(message, buttons[, default [, type]])" function -static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_confirm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; char buf2[NUMBUFLEN]; @@ -1029,19 +925,19 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (!error) { - rettv->vval.v_number = do_dialog(type, NULL, (char_u *)message, (char_u *)buttons, def, NULL, + rettv->vval.v_number = do_dialog(type, NULL, (char *)message, (char *)buttons, def, NULL, false); } } /// "copy()" function -static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_copy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { var_item_copy(NULL, &argvars[0], rettv, false, 0); } /// "count()" function -static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { long n = 0; int ic = 0; @@ -1052,15 +948,15 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (argvars[0].v_type == VAR_STRING) { - const char_u *expr = (char_u *)tv_get_string_chk(&argvars[1]); - const char_u *p = (char_u *)argvars[0].vval.v_string; + const char *expr = tv_get_string_chk(&argvars[1]); + const char *p = argvars[0].vval.v_string; if (!error && expr != NULL && *expr != NUL && p != NULL) { if (ic) { - const size_t len = STRLEN(expr); + const size_t len = strlen(expr); while (*p != NUL) { - if (mb_strnicmp(p, expr, len) == 0) { + if (mb_strnicmp((char *)p, (char *)expr, len) == 0) { n++; p += len; } else { @@ -1068,10 +964,10 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } else { - char_u *next; - while ((next = (char_u *)strstr((char *)p, (char *)expr)) != NULL) { + char *next; + while ((next = strstr((char *)p, (char *)expr)) != NULL) { n++; - p = next + STRLEN(expr); + p = next + strlen(expr); } } } @@ -1127,31 +1023,8 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } -/// "cscope_connection([{num} , {dbpath} [, {prepend}]])" function -/// -/// Checks the existence of a cscope connection. -static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int num = 0; - const char *dbpath = NULL; - const char *prepend = NULL; - char buf[NUMBUFLEN]; - - if (argvars[0].v_type != VAR_UNKNOWN - && argvars[1].v_type != VAR_UNKNOWN) { - num = (int)tv_get_number(&argvars[0]); - dbpath = tv_get_string(&argvars[1]); - if (argvars[2].v_type != VAR_UNKNOWN) { - prepend = tv_get_string_buf(&argvars[2], buf); - } - } - - rettv->vval.v_number = cs_connection(num, (char_u *)dbpath, - (char_u *)prepend); -} - /// "ctxget([{index}])" function -static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { size_t index = 0; if (argvars[0].v_type == VAR_NUMBER) { @@ -1175,7 +1048,7 @@ static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxpop()" function -static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxpop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (!ctx_restore(NULL, kCtxAll)) { emsg(_("Context stack is empty")); @@ -1183,7 +1056,7 @@ static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxpush([{types}])" function -static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxpush(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int types = kCtxAll; if (argvars[0].v_type == VAR_LIST) { @@ -1214,7 +1087,7 @@ static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxset({context}[, {index}])" function -static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_DICT) { semsg(_(e_invarg2), "expected dictionary as first argument"); @@ -1254,7 +1127,7 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "ctxsize()" function -static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_ctxsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = (varnumber_T)ctx_size(); @@ -1265,7 +1138,7 @@ static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Otherwise use the column number as a byte offset. static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) { - long line, col; + long lnum, col; long coladd = 0; bool set_curswant = true; @@ -1279,7 +1152,7 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) return; } - line = pos.lnum; + lnum = pos.lnum; col = pos.col; coladd = pos.coladd; if (curswant >= 0) { @@ -1288,10 +1161,15 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) } } else if ((argvars[0].v_type == VAR_NUMBER || argvars[0].v_type == VAR_STRING) && (argvars[1].v_type == VAR_NUMBER || argvars[1].v_type == VAR_STRING)) { - line = tv_get_lnum(argvars); + lnum = tv_get_lnum(argvars); + if (lnum < 0) { + semsg(_(e_invarg2), tv_get_string(&argvars[0])); + } else if (lnum == 0) { + lnum = curwin->w_cursor.lnum; + } col = (long)tv_get_number_chk(&argvars[1], NULL); if (charcol) { - col = buf_charidx_to_byteidx(curbuf, (linenr_T)line, (int)col) + 1; + col = buf_charidx_to_byteidx(curbuf, (linenr_T)lnum, (int)col) + 1; } if (argvars[2].v_type != VAR_UNKNOWN) { coladd = (long)tv_get_number_chk(&argvars[2], NULL); @@ -1300,11 +1178,11 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) emsg(_(e_invarg)); return; } - if (line < 0 || col < 0 || coladd < 0) { + if (lnum < 0 || col < 0 || coladd < 0) { return; // type error; errmsg already given } - if (line > 0) { - curwin->w_cursor.lnum = (linenr_T)line; + if (lnum > 0) { + curwin->w_cursor.lnum = (linenr_T)lnum; } if (col > 0) { curwin->w_cursor.col = (colnr_T)col - 1; @@ -1326,43 +1204,45 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) /// Moves the cursor to the specified line and column. /// /// @return 0 when the position could be set, -1 otherwise. -static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_cursor(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { set_cursorpos(argvars, rettv, false); } /// "debugbreak()" function -static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_debugbreak(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = FAIL; int pid = (int)tv_get_number(&argvars[0]); if (pid == 0) { emsg(_(e_invarg)); - } else { -#ifdef WIN32 - HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); + return; + } - if (hProcess != NULL) { - DebugBreakProcess(hProcess); - CloseHandle(hProcess); - rettv->vval.v_number = OK; - } +#ifdef MSWIN + HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid); + if (hProcess == NULL) { + return; + } + + DebugBreakProcess(hProcess); + CloseHandle(hProcess); + rettv->vval.v_number = OK; #else - uv_kill(pid, SIGINT); + uv_kill(pid, SIGINT); #endif - } } /// "deepcopy()" function -static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int noref = 0; if (argvars[1].v_type != VAR_UNKNOWN) { - noref = (int)tv_get_number_chk(&argvars[1], NULL); + noref = (int)tv_get_bool_chk(&argvars[1], NULL); } if (noref < 0 || noref > 1) { - emsg(_(e_invarg)); + semsg(_(e_using_number_as_bool_nr), noref); } else { var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 ? get_copyID() @@ -1371,7 +1251,7 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "delete()" function -static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_delete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; if (check_secure()) { @@ -1407,7 +1287,7 @@ static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// dictwatcheradd(dict, key, funcref) function -static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -1445,7 +1325,7 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// dictwatcherdel(dict, key, funcref) function -static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -1479,96 +1359,20 @@ static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) callback_free(&callback); } -/// "deletebufline()" function -static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *const buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - return; - } - const bool is_curbuf = buf == curbuf; - const bool save_VIsual_active = VIsual_active; - - linenr_T last; - const linenr_T first = tv_get_lnum_buf(&argvars[1], buf); - if (argvars[2].v_type != VAR_UNKNOWN) { - last = tv_get_lnum_buf(&argvars[2], buf); - } else { - last = first; - } - - if (buf->b_ml.ml_mfp == NULL || first < 1 - || first > buf->b_ml.ml_line_count || last < first) { - rettv->vval.v_number = 1; // FAIL - return; - } - - buf_T *curbuf_save = NULL; - win_T *curwin_save = NULL; - if (!is_curbuf) { - VIsual_active = false; - curbuf_save = curbuf; - curwin_save = curwin; - curbuf = buf; - find_win_for_curbuf(); - } - if (last > curbuf->b_ml.ml_line_count) { - last = curbuf->b_ml.ml_line_count; - } - const long count = last - first + 1; - - // When coming here from Insert mode, sync undo, so that this can be - // undone separately from what was previously inserted. - if (u_sync_once == 2) { - u_sync_once = 1; // notify that u_sync() was called - u_sync(true); - } - - if (u_save(first - 1, last + 1) == FAIL) { - rettv->vval.v_number = 1; // FAIL - } else { - for (linenr_T lnum = first; lnum <= last; lnum++) { - ml_delete(first, true); - } - - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - if (wp->w_cursor.lnum > last) { - wp->w_cursor.lnum -= (linenr_T)count; - } else if (wp->w_cursor.lnum > first) { - wp->w_cursor.lnum = first; - } - if (wp->w_cursor.lnum > wp->w_buffer->b_ml.ml_line_count) { - wp->w_cursor.lnum = wp->w_buffer->b_ml.ml_line_count; - } - } - } - check_cursor_col(); - deleted_lines_mark(first, count); - } - - if (!is_curbuf) { - curbuf = curbuf_save; - curwin = curwin_save; - VIsual_active = save_VIsual_active; - } -} - /// "did_filetype()" function -static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_did_filetype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = did_filetype; } /// "diff_filler()" function -static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_diff_filler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = MAX(0, diff_check(curwin, tv_get_lnum(argvars))); } /// "diff_hlID()" function -static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_diff_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum = tv_get_lnum(argvars); static linenr_T prev_lnum = 0; @@ -1585,9 +1389,10 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) || changedtick != buf_get_changedtick(curbuf) || fnum != curbuf->b_fnum) { // New line, buffer, change: need to get the values. - int filler_lines = diff_check(curwin, lnum); - if (filler_lines < 0) { - if (filler_lines == -1) { + int linestatus = 0; + int filler_lines = diff_check_with_linestatus(curwin, lnum, &linestatus); + if (filler_lines < 0 || linestatus < 0) { + if (filler_lines == -1 || linestatus == -1) { change_start = MAXCOL; change_end = -1; if (diff_find_change(curwin, lnum, &change_start, &change_end)) { @@ -1614,11 +1419,11 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) hlID = HLF_CHD; // Changed line. } } - rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1); + rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (hlID + 1); } /// "empty({expr})" function -static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_empty(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool n = true; @@ -1668,7 +1473,7 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "environ()" function -static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_environ(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); @@ -1690,7 +1495,7 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) char c = env[i][len]; env[i][len] = NUL; -#ifdef WIN32 +#ifdef MSWIN // Upper-case all the keys for Windows so we can detect duplicates char *const key = strcase_save(str, true); #else @@ -1713,32 +1518,31 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "escape({string}, {chars})" function -static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_escape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; - rettv->vval.v_string = (char *)vim_strsave_escaped((const char_u *)tv_get_string(&argvars[0]), - (const char_u *)tv_get_string_buf(&argvars[1], - buf)); + rettv->vval.v_string = vim_strsave_escaped(tv_get_string(&argvars[0]), + tv_get_string_buf(&argvars[1], buf)); rettv->v_type = VAR_STRING; } /// "getenv()" function -static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0])); + char *p = vim_getenv(tv_get_string(&argvars[0])); if (p == NULL) { rettv->v_type = VAR_SPECIAL; rettv->vval.v_special = kSpecialVarNull; return; } - rettv->vval.v_string = (char *)p; + rettv->vval.v_string = p; rettv->v_type = VAR_STRING; } /// "eval()" function -static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *s = tv_get_string_chk(&argvars[0]); if (s != NULL) { @@ -1759,15 +1563,15 @@ static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "eventhandler()" function -static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_eventhandler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = vgetc_busy; } /// "executable()" function -static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_executable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (tv_check_for_string(&argvars[0]) == FAIL) { + if (tv_check_for_string_arg(argvars, 0) == FAIL) { return; } @@ -1794,7 +1598,7 @@ static char *get_list_line(int c, void *cookie, int indent, bool do_concat) return s == NULL ? NULL : xstrdup(s); } -static void execute_common(typval_T *argvars, typval_T *rettv, FunPtr fptr, int arg_off) +void execute_common(typval_T *argvars, typval_T *rettv, int arg_off) { const int save_msg_silent = msg_silent; const int save_emsg_silent = emsg_silent; @@ -1873,30 +1677,15 @@ static void execute_common(typval_T *argvars, typval_T *rettv, FunPtr fptr, int } /// "execute(command)" function -static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - execute_common(argvars, rettv, fptr, 0); -} - -/// "win_execute(win_id, command)" function -static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // Return an empty string if something fails. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - - int id = (int)tv_get_number(argvars); - tabpage_T *tp; - win_T *wp = win_id2wp_tp(id, &tp); - if (wp != NULL && tp != NULL) { - WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, fptr, 1)); - } + execute_common(argvars, rettv, 0); } /// "exepath()" function -static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (tv_check_for_nonempty_string(&argvars[0]) == FAIL) { + if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) { return; } @@ -1906,7 +1695,7 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) #ifdef BACKSLASH_IN_FILENAME if (path != NULL) { - slash_adjust((char_u *)path); + slash_adjust(path); } #endif @@ -1915,7 +1704,7 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "exists()" function -static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int n = false; @@ -1955,13 +1744,12 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "expand()" function -static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char *errormsg; int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND; bool error = false; #ifdef BACKSLASH_IN_FILENAME - char_u *p_csl_save = p_csl; + char *p_csl_save = p_csl; // avoid using 'completeslash' here p_csl = empty_option; @@ -1977,10 +1765,17 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *s = tv_get_string(&argvars[0]); if (*s == '%' || *s == '#' || *s == '<') { - emsg_off++; + if (p_verbose == 0) { + emsg_off++; + } size_t len; - char_u *result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); - emsg_off--; + char *errormsg = NULL; + char *result = eval_vars((char *)s, s, &len, NULL, &errormsg, NULL, false); + if (p_verbose == 0) { + emsg_off--; + } else if (errormsg != NULL) { + emsg(errormsg); + } if (rettv->v_type == VAR_LIST) { tv_list_alloc_ret(rettv, (result != NULL)); if (result != NULL) { @@ -1988,7 +1783,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } XFREE_CLEAR(result); } else { - rettv->vval.v_string = (char *)result; + rettv->vval.v_string = result; } } else { // When the optional second argument is non-zero, don't remove matches @@ -2005,10 +1800,9 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) options += WILD_ICASE; } if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = (char *)ExpandOne(&xpc, (char_u *)s, NULL, options, - WILD_ALL); + rettv->vval.v_string = ExpandOne(&xpc, (char *)s, NULL, options, WILD_ALL); } else { - ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP); + ExpandOne(&xpc, (char *)s, NULL, options, WILD_ALL_KEEP); tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, @@ -2026,7 +1820,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "menu_get(path [, modes])" function -static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_menu_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenMayKnow); int modes = MENU_ALL_MODES; @@ -2039,9 +1833,15 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "expandcmd()" function /// Expand all the special characters in a command string. -static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *errormsg = NULL; + bool emsgoff = true; + + if (argvars[1].v_type == VAR_DICT + && tv_dict_get_bool(argvars[1].vval.v_dict, "errmsg", kBoolVarFalse)) { + emsgoff = false; + } rettv->v_type = VAR_STRING; char *cmdstr = xstrdup(tv_get_string(&argvars[0])); @@ -2055,15 +1855,23 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) }; eap.argt |= EX_NOSPC; - emsg_off++; - expand_filename(&eap, &cmdstr, &errormsg); - emsg_off--; + if (emsgoff) { + emsg_off++; + } + if (expand_filename(&eap, &cmdstr, &errormsg) == FAIL) { + if (!emsgoff && errormsg != NULL && *errormsg != NUL) { + emsg(errormsg); + } + } + if (emsgoff) { + emsg_off--; + } rettv->vval.v_string = cmdstr; } /// "flatten(list[, {maxdepth}])" function -static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool error = false; @@ -2088,9 +1896,9 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *list = argvars[0].vval.v_list; if (list != NULL - && !var_check_lock(tv_list_locked(list), - N_("flatten() argument"), - TV_TRANSLATE) + && !value_check_lock(tv_list_locked(list), + N_("flatten() argument"), + TV_TRANSLATE) && tv_list_flatten(list, maxdepth) == OK) { tv_copy(&argvars[0], rettv); } @@ -2098,7 +1906,7 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "extend(list, list [, idx])" function /// "extend(dict, dict [, action])" function -static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const arg_errmsg = N_("extend() argument"); @@ -2107,7 +1915,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *const l1 = argvars[0].vval.v_list; list_T *const l2 = argvars[1].vval.v_list; - if (!var_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { + if (!value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { listitem_T *item; if (argvars[2].v_type != VAR_UNKNOWN) { long before = (long)tv_get_number_chk(&argvars[2], &error); @@ -2136,13 +1944,13 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) dict_T *const d1 = argvars[0].vval.v_dict; dict_T *const d2 = argvars[1].vval.v_dict; if (d1 == NULL) { - const bool locked = var_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); + const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); (void)locked; assert(locked == true); } else if (d2 == NULL) { // Do nothing tv_copy(&argvars[0], rettv); - } else if (!var_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { + } else if (!value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) { const char *action = "force"; // Check the third argument. if (argvars[2].v_type != VAR_UNKNOWN) { @@ -2174,7 +1982,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "feedkeys()" function -static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // This is not allowed in the sandbox. If the commands would still be // executed in the sandbox it would be OK, but it probably happens later, @@ -2195,17 +2003,17 @@ static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "filereadable()" function -static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const p = tv_get_string(&argvars[0]); rettv->vval.v_number = - (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p)); + (*p && !os_isdir(p) && os_file_is_readable(p)); } /// @return 0 for not writable /// 1 for writable file /// 2 for a dir which we have rights to write into. -static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_filewritable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *filename = tv_get_string(&argvars[0]); rettv->vval.v_number = os_file_is_writable(filename); @@ -2213,8 +2021,8 @@ static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) { - char_u *fresult = NULL; - char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; + char *fresult = NULL; + char *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; int count = 1; bool first = true; bool error = false; @@ -2231,7 +2039,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) error = true; } else { if (*p != NUL) { - path = (char_u *)p; + path = (char *)p; } if (argvars[2].v_type != VAR_UNKNOWN) { @@ -2249,12 +2057,12 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) { xfree(fresult); } - fresult = find_file_in_path_option(first ? (char_u *)fname : NULL, + fresult = find_file_in_path_option(first ? (char *)fname : NULL, first ? strlen(fname) : 0, 0, first, path, - find_what, (char_u *)curbuf->b_ffname, + find_what, curbuf->b_ffname, (find_what == FINDFILE_DIR - ? (char_u *)"" + ? "" : curbuf->b_p_sua)); first = false; @@ -2265,46 +2073,48 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = (char *)fresult; + rettv->vval.v_string = fresult; } } /// "filter()" function -static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { filter_map(argvars, rettv, false); } /// "finddir({fname}[, {path}[, {count}]])" function -static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { findfilendir(argvars, rettv, FINDFILE_DIR); } /// "findfile({fname}[, {path}[, {count}]])" function -static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_findfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { findfilendir(argvars, rettv, FINDFILE_FILE); } /// "float2nr({float})" function -static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_float2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T f; - if (tv_get_float_chk(argvars, &f)) { - if (f <= (float_T) - VARNUMBER_MAX + DBL_EPSILON) { - rettv->vval.v_number = -VARNUMBER_MAX; - } else if (f >= (float_T)VARNUMBER_MAX - DBL_EPSILON) { - rettv->vval.v_number = VARNUMBER_MAX; - } else { - rettv->vval.v_number = (varnumber_T)f; - } + if (!tv_get_float_chk(argvars, &f)) { + return; + } + + if (f <= (float_T) - VARNUMBER_MAX + DBL_EPSILON) { + rettv->vval.v_number = -VARNUMBER_MAX; + } else if (f >= (float_T)VARNUMBER_MAX - DBL_EPSILON) { + rettv->vval.v_number = VARNUMBER_MAX; + } else { + rettv->vval.v_number = (varnumber_T)f; } } /// "fmod()" function -static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_fmod(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T fx; float_T fy; @@ -2318,16 +2128,16 @@ static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "fnameescape({string})" function -static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_fnameescape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_string = vim_strsave_fnameescape(tv_get_string(&argvars[0]), VSE_NONE); rettv->v_type = VAR_STRING; } /// "fnamemodify({fname}, {mods})" function -static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char_u *fbuf = NULL; + char *fbuf = NULL; size_t len = 0; char buf[NUMBUFLEN]; const char *fname = tv_get_string_chk(&argvars[0]); @@ -2339,7 +2149,7 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (*mods != NUL) { size_t usedlen = 0; (void)modify_fname((char *)mods, false, &usedlen, - (char **)&fname, (char **)&fbuf, &len); + (char **)&fname, &fbuf, &len); } } @@ -2353,21 +2163,21 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "foreground()" function -static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_foreground(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) {} -static void f_funcref(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_funcref(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - common_function(argvars, rettv, true, fptr); + common_function(argvars, rettv, true); } -static void f_function(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_function(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - common_function(argvars, rettv, false, fptr); + common_function(argvars, rettv, false); } /// "garbagecollect()" function -static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_garbagecollect(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // This is postponed until we are back at the toplevel, because we may be // using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]". @@ -2379,7 +2189,7 @@ static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "get()" function -static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { typval_T *tv = NULL; bool what_is_dict = false; @@ -2426,7 +2236,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) pt = argvars[0].vval.v_partial; } else { CLEAR_FIELD(fref_pt); - fref_pt.pt_name = (char_u *)argvars[0].vval.v_string; + fref_pt.pt_name = argvars[0].vval.v_string; pt = &fref_pt; } @@ -2434,13 +2244,17 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const what = tv_get_string(&argvars[1]); if (strcmp(what, "func") == 0 || strcmp(what, "name") == 0) { + const char *name = partial_name(pt); rettv->v_type = (*what == 'f' ? VAR_FUNC : VAR_STRING); - const char *const n = (const char *)partial_name(pt); - assert(n != NULL); - rettv->vval.v_string = xstrdup(n); + assert(name != NULL); if (rettv->v_type == VAR_FUNC) { - func_ref((char_u *)rettv->vval.v_string); + func_ref((char *)name); + } + if (*what == 'n' && pt->pt_name == NULL && pt->pt_func != NULL) { + // use <SNR> instead of the byte code + name = printable_func_name(pt->pt_func); } + rettv->vval.v_string = xstrdup(name); } else if (strcmp(what, "dict") == 0) { what_is_dict = true; if (pt->pt_dict != NULL) { @@ -2475,120 +2289,8 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "getbufinfo()" function -static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *argbuf = NULL; - bool filtered = false; - bool sel_buflisted = false; - bool sel_bufloaded = false; - bool sel_bufmodified = false; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - // List of all the buffers or selected buffers - if (argvars[0].v_type == VAR_DICT) { - dict_T *sel_d = argvars[0].vval.v_dict; - - if (sel_d != NULL) { - dictitem_T *di; - - filtered = true; - - di = tv_dict_find(sel_d, S_LEN("buflisted")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_buflisted = true; - } - - di = tv_dict_find(sel_d, S_LEN("bufloaded")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_bufloaded = true; - } - di = tv_dict_find(sel_d, S_LEN("bufmodified")); - if (di != NULL && tv_get_number(&di->di_tv)) { - sel_bufmodified = true; - } - } - } else if (argvars[0].v_type != VAR_UNKNOWN) { - // Information about one buffer. Argument specifies the buffer - argbuf = tv_get_buf_from_arg(&argvars[0]); - if (argbuf == NULL) { - return; - } - } - - // Return information about all the buffers or a specified buffer - FOR_ALL_BUFFERS(buf) { - if (argbuf != NULL && argbuf != buf) { - continue; - } - if (filtered && ((sel_bufloaded && buf->b_ml.ml_mfp == NULL) - || (sel_buflisted && !buf->b_p_bl) - || (sel_bufmodified && !buf->b_changed))) { - continue; - } - - dict_T *const d = get_buffer_info(buf); - tv_list_append_dict(rettv->vval.v_list, d); - if (argbuf != NULL) { - return; - } - } -} - -/// Get line or list of lines from buffer "buf" into "rettv". -/// -/// @param retlist if TRUE, then the lines are returned as a Vim List. -/// -/// @return range (from start to end) of lines in rettv from the specified -/// buffer. -static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv) -{ - rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); - rettv->vval.v_string = NULL; - - if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { - if (retlist) { - tv_list_alloc_ret(rettv, 0); - } - return; - } - - if (retlist) { - if (start < 1) { - start = 1; - } - if (end > buf->b_ml.ml_line_count) { - end = buf->b_ml.ml_line_count; - } - tv_list_alloc_ret(rettv, end - start + 1); - while (start <= end) { - tv_list_append_string(rettv->vval.v_list, - (const char *)ml_get_buf(buf, start++, false), -1); - } - } else { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = - (char *)((start >= 1 && start <= buf->b_ml.ml_line_count) - ? vim_strsave(ml_get_buf(buf, start, false)) : NULL); - } -} - -/// "getbufline()" function -static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); - - const linenr_T lnum = tv_get_lnum_buf(&argvars[1], buf); - const linenr_T end = (argvars[2].v_type == VAR_UNKNOWN - ? lnum - : tv_get_lnum_buf(&argvars[2], buf)); - - get_buffer_lines(buf, lnum, end, true, rettv); -} - /// "getchangelist()" function -static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getchangelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, 2); @@ -2693,13 +2395,13 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool } /// "getcharpos()" function -static void f_getcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getpos_both(argvars, rettv, false, true); } /// "getcharsearch()" function -static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); @@ -2710,49 +2412,6 @@ static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until()); } -/// "getcmdcompltype()" function -static void f_getcmdcompltype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)get_cmdline_completion(); -} - -/// "getcmdline()" function -static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)get_cmdline_str(); -} - -/// "getcmdpos()" function -static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = get_cmdline_pos() + 1; -} - -/// "getcmdscreenpos()" function -static void f_getcmdscreenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = get_cmdline_screen_pos() + 1; -} - -/// "getcmdtype()" function -static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = (char)get_cmdline_type(); -} - -/// "getcmdwintype()" function -static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - rettv->vval.v_string = xmallocz(1); - rettv->vval.v_string[0] = (char)cmdwin_type; -} - /// `getcwd([{win}[, {tab}]])` function /// /// Every scope not specified implies the currently selected scope object. @@ -2762,7 +2421,7 @@ static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @pre An argument may not be -1 if preceding arguments are not all -1. /// /// @post The return value will be a string. -static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // Possible scope of working directory to return. CdScope scope = kCdScopeInvalid; @@ -2857,13 +2516,13 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } FALLTHROUGH; // In global directory, just need to get OS CWD. case kCdScopeInvalid: // If called without any arguments, get OS CWD. - if (os_dirname((char_u *)cwd, MAXPATHL) == FAIL) { + if (os_dirname(cwd, MAXPATHL) == FAIL) { from = ""; // Return empty string on failure. } } if (from) { - STRLCPY(cwd, from, MAXPATHL); + xstrlcpy(cwd, from, MAXPATHL); } rettv->vval.v_string = xstrdup(cwd); @@ -2875,17 +2534,17 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getfontname()" function -static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getfontname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; } /// "getfperm({fname})" function -static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *perm = NULL; - char_u flags[] = "rwx"; + char flags[] = "rwx"; const char *filename = tv_get_string(&argvars[0]); int32_t file_perm = os_getperm(filename); @@ -2893,7 +2552,7 @@ static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) perm = xstrdup("---------"); for (int i = 0; i < 9; i++) { if (file_perm & (1 << (8 - i))) { - perm[i] = (char)flags[i % 3]; + perm[i] = flags[i % 3]; } } } @@ -2902,7 +2561,7 @@ static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getfsize({fname})" function -static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getfsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *fname = tv_get_string(&argvars[0]); @@ -2911,7 +2570,7 @@ static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) FileInfo file_info; if (os_fileinfo(fname, &file_info)) { uint64_t filesize = os_fileinfo_size(&file_info); - if (os_isdir((const char_u *)fname)) { + if (os_isdir(fname)) { rettv->vval.v_number = 0; } else { rettv->vval.v_number = (varnumber_T)filesize; @@ -2927,7 +2586,7 @@ static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getftime({fname})" function -static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *fname = tv_get_string(&argvars[0]); @@ -2940,9 +2599,9 @@ static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getftype({fname})" function -static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getftype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char_u *type = NULL; + char *type = NULL; char *t; const char *fname = tv_get_string(&argvars[0]); @@ -2968,13 +2627,13 @@ static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { t = "other"; } - type = vim_strsave((char_u *)t); + type = xstrdup(t); } - rettv->vval.v_string = (char *)type; + rettv->vval.v_string = type; } /// "getjumplist()" function -static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getjumplist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenMayKnow); win_T *const wp = find_tabwin(&argvars[0], &argvars[1]); @@ -3004,26 +2663,8 @@ static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "getline(lnum, [end])" function -static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T end; - bool retlist; - - const linenr_T lnum = tv_get_lnum(argvars); - if (argvars[1].v_type == VAR_UNKNOWN) { - end = lnum; - retlist = false; - } else { - end = tv_get_lnum(&argvars[1]); - retlist = true; - } - - get_buffer_lines(curbuf, lnum, end, retlist, rettv); -} - /// "getmarklist()" function -static void f_getmarklist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getmarklist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenMayKnow); @@ -3041,7 +2682,7 @@ static void f_getmarklist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getmousepos()" function -static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getmousepos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int row = mouse_row; int col = mouse_col; @@ -3082,24 +2723,24 @@ static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getpid()" function -static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = os_get_pid(); } /// "getcurpos(string)" function -static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcurpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getpos_both(argvars, rettv, true, false); } -static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getpos_both(argvars, rettv, true, true); } /// "getpos(string)" function -static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getpos_both(argvars, rettv, false, false); } @@ -3109,23 +2750,23 @@ static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Returns zero on error. static int getreg_get_regname(typval_T *argvars) { - const char_u *strregname; + const char *strregname; if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = (const char_u *)tv_get_string_chk(&argvars[0]); + strregname = tv_get_string_chk(&argvars[0]); if (strregname == NULL) { // type error; errmsg already given return 0; } } else { // Default to v:register - strregname = (char_u *)get_vim_var_str(VV_REG); + strregname = get_vim_var_str(VV_REG); } - return *strregname == 0 ? '"' : *strregname; + return *strregname == 0 ? '"' : (uint8_t)(*strregname); } /// "getreg()" function -static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getreg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int arg2 = false; bool return_list = false; @@ -3161,7 +2802,7 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getregtype()" function -static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getregtype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // on error return an empty string rettv->v_type = VAR_STRING; @@ -3180,40 +2821,8 @@ static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = xstrdup(buf); } -/// "gettabinfo()" function -static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tabpage_T *tparg = NULL; - - tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN - ? 1 - : kListLenMayKnow)); - - if (argvars[0].v_type != VAR_UNKNOWN) { - // Information about one tab page - tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); - if (tparg == NULL) { - return; - } - } - - // Get information about a specific tab page or all tab pages - int tpnr = 0; - FOR_ALL_TABS(tp) { - tpnr++; - if (tparg != NULL && tp != tparg) { - continue; - } - dict_T *const d = get_tabpage_info(tp, tpnr); - tv_list_append_dict(rettv->vval.v_list, d); - if (tparg != NULL) { - return; - } - } -} - /// "gettagstack()" function -static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_gettagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = curwin; // default is current window @@ -3229,41 +2838,6 @@ static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_tagstack(wp, rettv->vval.v_dict); } -/// "getwininfo()" function -static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wparg = NULL; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - if (argvars[0].v_type != VAR_UNKNOWN) { - wparg = win_id2wp((int)tv_get_number(&argvars[0])); - if (wparg == NULL) { - return; - } - } - - // Collect information about either all the windows across all the tab - // pages or one particular window. - int16_t tabnr = 0; - FOR_ALL_TABS(tp) { - tabnr++; - int16_t winnr = 0; - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { - winnr++; - if (wparg != NULL && wp != wparg) { - continue; - } - dict_T *const d = get_win_info(wp, tabnr, winnr); - tv_list_append_dict(rettv->vval.v_list, d); - if (wparg != NULL) { - // found information about a specific window - return; - } - } - } -} - /// Dummy timer callback. Used by f_wait(). static void dummy_timer_due_cb(TimeWatcher *tw, void *data) {} @@ -3275,7 +2849,7 @@ static void dummy_timer_close_cb(TimeWatcher *tw, void *data) } /// "wait(timeout, condition[, interval])" function -static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = -1; @@ -3328,113 +2902,8 @@ static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) time_watcher_close(tw, dummy_timer_close_cb); } -/// "win_screenpos()" function -static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, 2); - const win_T *const wp = find_win_by_nr_or_id(&argvars[0]); - tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1); - tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); -} - -/// Move the window wp into a new split of targetwin in a given direction -static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags) -{ - int height = wp->w_height; - win_T *oldwin = curwin; - - if (wp == targetwin || wp == aucmd_win) { - return; - } - - // Jump to the target window - if (curwin != targetwin) { - win_goto(targetwin); - } - - // Remove the old window and frame from the tree of frames - int dir; - (void)winframe_remove(wp, &dir, NULL); - win_remove(wp, NULL); - last_status(false); // may need to remove last status line - (void)win_comp_pos(); // recompute window positions - - // Split a window on the desired side and put the old window there - (void)win_split_ins(size, flags, wp, dir); - - // If splitting horizontally, try to preserve height - if (size == 0 && !(flags & WSP_VERT)) { - win_setheight_win(height, wp); - if (p_ea) { - win_equal(wp, true, 'v'); - } - } - - if (oldwin != curwin) { - win_goto(oldwin); - } -} - -/// "win_splitmove()" function -static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - win_T *targetwin = find_win_by_nr_or_id(&argvars[1]); - - if (wp == NULL || targetwin == NULL || wp == targetwin - || !win_valid(wp) || !win_valid(targetwin) - || win_valid_floating(wp) || win_valid_floating(targetwin)) { - emsg(_(e_invalwindow)); - rettv->vval.v_number = -1; - return; - } - - int flags = 0, size = 0; - - if (argvars[2].v_type != VAR_UNKNOWN) { - dict_T *d; - dictitem_T *di; - - if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) { - emsg(_(e_invarg)); - return; - } - - d = argvars[2].vval.v_dict; - if (tv_dict_get_number(d, "vertical")) { - flags |= WSP_VERT; - } - if ((di = tv_dict_find(d, "rightbelow", -1)) != NULL) { - flags |= tv_get_number(&di->di_tv) ? WSP_BELOW : WSP_ABOVE; - } - size = (int)tv_dict_get_number(d, "size"); - } - - win_move_into_split(wp, targetwin, size, flags); -} - -/// "getwinpos({timeout})" function -static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, 2); - tv_list_append_number(rettv->vval.v_list, -1); - tv_list_append_number(rettv->vval.v_list, -1); -} - -/// "getwinposx()" function -static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; -} - -/// "getwinposy()" function -static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; -} - /// "glob()" function -static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int options = WILD_SILENT|WILD_USE_NL; expand_T xpc; @@ -3464,11 +2933,11 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) options += WILD_ICASE; } if (rettv->v_type == VAR_STRING) { - rettv->vval.v_string = (char *)ExpandOne(&xpc, (char_u *) - tv_get_string(&argvars[0]), NULL, options, - WILD_ALL); + rettv->vval.v_string = ExpandOne(&xpc, (char *) + tv_get_string(&argvars[0]), NULL, options, + WILD_ALL); } else { - ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, + ExpandOne(&xpc, (char *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL_KEEP); tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { @@ -3483,7 +2952,7 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "globpath()" function -static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath. bool error = false; @@ -3513,8 +2982,8 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const file = tv_get_string_buf_chk(&argvars[1], buf1); if (file != NULL && !error) { garray_T ga; - ga_init(&ga, (int)sizeof(char_u *), 10); - globpath((char *)tv_get_string(&argvars[0]), (char_u *)file, &ga, flags); + ga_init(&ga, (int)sizeof(char *), 10); + globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags); if (rettv->v_type == VAR_STRING) { rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); @@ -3533,7 +3002,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "glob2regpat()" function -static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error @@ -3541,8 +3010,21 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (pat == NULL) ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, false); } +/// "gettext()" function +static void f_gettext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + if (argvars[0].v_type != VAR_STRING + || argvars[0].vval.v_string == NULL + || *argvars[0].vval.v_string == NUL) { + semsg(_(e_invarg2), tv_get_string(&argvars[0])); + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string)); + } +} + /// "has()" function -static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { static const char *const has_list[] = { #if defined(BSD) && !defined(__APPLE__) @@ -3557,7 +3039,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) #ifdef UNIX "unix", #endif -#if defined(WIN32) +#ifdef MSWIN "win32", #endif #ifdef _WIN64 @@ -3580,7 +3062,6 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "cmdwin", "comments", "conceal", - "cscope", "cursorbind", "cursorshape", #ifdef DEBUG @@ -3601,9 +3082,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "fork", #endif "gettext", -#if defined(HAVE_ICONV) "iconv", -#endif "insert_expand", "jumplist", "keymap", @@ -3631,8 +3110,6 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "packages", "path_extra", "persistent_undo", - "postscript", - "printer", "profile", "pythonx", "reltime", @@ -3649,7 +3126,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "spell", "syntax", #if !defined(UNIX) - "system", // TODO(SplinterOfChaos): This IS defined for UNIX! + "system", #endif "tablineat", "tag_binary", @@ -3765,7 +3242,7 @@ static bool has_wsl(void) /// @pre An argument may not be -1 if preceding arguments are not all -1. /// /// @post The return value will be either the number `1` or `0`. -static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // Possible scope of working directory to return. CdScope scope = kCdScopeInvalid; @@ -3855,29 +3332,29 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "highlightID(name)" function -static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = syn_name2id(tv_get_string(&argvars[0])); } /// "highlight_exists()" function -static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_hlexists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = highlight_exists(tv_get_string(&argvars[0])); } /// "hostname()" function -static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_hostname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char hostname[256]; os_get_hostname(hostname, 256); rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)vim_strsave((char_u *)hostname); + rettv->vval.v_string = xstrdup(hostname); } /// iconv() function -static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { vimconv_T vimconv; @@ -3886,9 +3363,9 @@ static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const str = tv_get_string(&argvars[0]); char buf1[NUMBUFLEN]; - char_u *const from = enc_canonize(enc_skip((char_u *)tv_get_string_buf(&argvars[1], buf1))); + char *const from = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1))); char buf2[NUMBUFLEN]; - char_u *const to = enc_canonize(enc_skip((char_u *)tv_get_string_buf(&argvars[2], buf2))); + char *const to = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2))); vimconv.vc_type = CONV_NONE; convert_setup(&vimconv, from, to); @@ -3896,7 +3373,7 @@ static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (vimconv.vc_type == CONV_NONE) { rettv->vval.v_string = xstrdup(str); } else { - rettv->vval.v_string = (char *)string_convert(&vimconv, (char_u *)str, NULL); + rettv->vval.v_string = string_convert(&vimconv, (char *)str, NULL); } convert_setup(&vimconv, NULL, NULL); @@ -3905,7 +3382,7 @@ static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "indent()" function -static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const linenr_T lnum = tv_get_lnum(argvars); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { @@ -3916,7 +3393,7 @@ static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "index()" function -static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { long idx = 0; bool ic = false; @@ -3956,33 +3433,36 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg(_(e_listblobreq)); return; } + list_T *const l = argvars[0].vval.v_list; - if (l != NULL) { - listitem_T *item = tv_list_first(l); - if (argvars[2].v_type != VAR_UNKNOWN) { - bool error = false; + if (l == NULL) { + return; + } - // Start at specified item. - idx = tv_list_uidx(l, (int)tv_get_number_chk(&argvars[2], &error)); - if (error || idx == -1) { + listitem_T *item = tv_list_first(l); + if (argvars[2].v_type != VAR_UNKNOWN) { + bool error = false; + + // Start at specified item. + idx = tv_list_uidx(l, (int)tv_get_number_chk(&argvars[2], &error)); + if (error || idx == -1) { + item = NULL; + } else { + item = tv_list_find(l, (int)idx); + assert(item != NULL); + } + if (argvars[3].v_type != VAR_UNKNOWN) { + ic = !!tv_get_number_chk(&argvars[3], &error); + if (error) { item = NULL; - } else { - item = tv_list_find(l, (int)idx); - assert(item != NULL); - } - if (argvars[3].v_type != VAR_UNKNOWN) { - ic = !!tv_get_number_chk(&argvars[3], &error); - if (error) { - item = NULL; - } } } + } - for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { - if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { - rettv->vval.v_number = idx; - break; - } + for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { + if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { + rettv->vval.v_number = idx; + break; } } } @@ -3991,19 +3471,19 @@ static bool inputsecret_flag = false; /// "input()" function /// Also handles inputsecret() when inputsecret is set. -static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_input(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_user_input(argvars, rettv, false, inputsecret_flag); } /// "inputdialog()" function -static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputdialog(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_user_input(argvars, rettv, true, inputsecret_flag); } /// "inputlist()" function -static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_LIST) { semsg(_(e_listarg), "inputlist()"); @@ -4034,7 +3514,7 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) static garray_T ga_userinput = { 0, 0, sizeof(tasave_T), 4, NULL }; /// "inputrestore()" function -static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputrestore(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (!GA_EMPTY(&ga_userinput)) { ga_userinput.ga_len--; @@ -4048,7 +3528,7 @@ static void f_inputrestore(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "inputsave()" function -static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputsave(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // Add an entry to the stack of typeahead storage. tasave_T *p = GA_APPEND_VIA_PTR(tasave_T, &ga_userinput); @@ -4056,17 +3536,17 @@ static void f_inputsave(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "inputsecret()" function -static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_inputsecret(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { cmdline_star++; inputsecret_flag = true; - f_input(argvars, rettv, NULL); + f_input(argvars, rettv, fptr); cmdline_star--; inputsecret_flag = false; } /// "insert()" function -static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { list_T *l; bool error = false; @@ -4075,8 +3555,8 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) blob_T *const b = argvars[0].vval.v_blob; if (b == NULL - || var_check_lock(b->bv_lock, N_("insert() argument"), - TV_TRANSLATE)) { + || value_check_lock(b->bv_lock, N_("insert() argument"), + TV_TRANSLATE)) { return; } @@ -4103,16 +3583,16 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) } ga_grow(&b->bv_ga, 1); - char_u *const p = (char_u *)b->bv_ga.ga_data; + uint8_t *const p = (uint8_t *)b->bv_ga.ga_data; memmove(p + before + 1, p + before, (size_t)(len - before)); - *(p + before) = (char_u)val; + *(p + before) = (uint8_t)val; b->bv_ga.ga_len++; tv_copy(&argvars[0], rettv); } else if (argvars[0].v_type != VAR_LIST) { semsg(_(e_listblobarg), "insert()"); - } else if (!var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - N_("insert() argument"), TV_TRANSLATE)) { + } else if (!value_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("insert() argument"), TV_TRANSLATE)) { long before = 0; if (argvars[2].v_type != VAR_UNKNOWN) { before = tv_get_number_chk(&argvars[2], &error); @@ -4139,34 +3619,34 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "interrupt()" function static void f_interrupt(typval_T *argvars FUNC_ATTR_UNUSED, typval_T *rettv FUNC_ATTR_UNUSED, - FunPtr fptr FUNC_ATTR_UNUSED) + EvalFuncData fptr FUNC_ATTR_UNUSED) { got_int = true; } /// "invert(expr)" function -static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_invert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL); } /// "isdirectory()" function -static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_isdirectory(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0])); + rettv->vval.v_number = os_isdir(tv_get_string(&argvars[0])); } /// "islocked()" function -static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_islocked(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { lval_T lv; rettv->vval.v_number = -1; - const char_u *const end = (char_u *)get_lval((char *)tv_get_string(&argvars[0]), - NULL, - &lv, false, false, - GLV_NO_AUTOLOAD|GLV_READ_ONLY, - FNE_CHECK_START); + const char *const end = get_lval((char *)tv_get_string(&argvars[0]), + NULL, + &lv, false, false, + GLV_NO_AUTOLOAD|GLV_READ_ONLY, + FNE_CHECK_START); if (end != NULL && lv.ll_name != NULL) { if (*end != NUL) { semsg(_(e_trailing_arg), end); @@ -4199,7 +3679,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "isinf()" function -static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_isinf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type == VAR_FLOAT && xisinf(argvars[0].vval.v_float)) { @@ -4208,14 +3688,14 @@ static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "isnan()" function -static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_isnan(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT && xisnan(argvars[0].vval.v_float); } /// "id()" function -static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_id(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { const int len = vim_vsnprintf_typval(NULL, 0, "%p", dummy_ap, argvars); @@ -4225,7 +3705,7 @@ static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "jobpid(id)" function -static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -4249,7 +3729,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "jobresize(job, width, height)" function -static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobresize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -4281,7 +3761,7 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) } static const char *ignored_env_vars[] = { -#ifndef WIN32 +#ifndef MSWIN "COLUMNS", "LINES", "TERMCAP", @@ -4293,7 +3773,7 @@ static const char *ignored_env_vars[] = { /// According to comments in src/win/process.c of libuv, Windows has a few /// "essential" environment variables. static const char *required_env_vars[] = { -#ifdef WIN32 +#ifdef MSWIN "HOMEDRIVE", "HOMEPATH", "LOGONSERVER", @@ -4316,7 +3796,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en if (!clear_env) { typval_T temp_env = TV_INITIAL_VALUE; - f_environ(NULL, &temp_env, NULL); + f_environ(NULL, &temp_env, (EvalFuncData){ .nullptr = NULL }); tv_dict_extend(env, temp_env.vval.v_dict, "force"); tv_dict_free(temp_env.vval.v_dict); @@ -4332,7 +3812,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en tv_dict_item_remove(env, dv); } } -#ifndef WIN32 +#ifndef MSWIN // Set COLORTERM to "truecolor" if termguicolors is set and 256 // otherwise, but only if it was set in the parent terminal at all dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM")); @@ -4367,7 +3847,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en } if (job_env) { -#ifdef WIN32 +#ifdef MSWIN TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, { // Always use upper-case keys for Windows so we detect duplicate keys char *const key = strcase_save((const char *)var->di_key, true); @@ -4405,7 +3885,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en } /// "jobstart()" function -static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -4467,7 +3947,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } -#ifdef WIN32 +#ifdef MSWIN if (pty && overlapped) { semsg(_(e_invarg2), "job cannot have both 'pty' and 'overlapped' options set"); @@ -4480,7 +3960,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (new_cwd && *new_cwd != NUL) { cwd = new_cwd; // The new cwd must be a directory. - if (!os_isdir((const char_u *)cwd)) { + if (!os_isdir(cwd)) { semsg(_(e_invarg2), "expected valid directory"); shell_free_argv(argv); return; @@ -4525,7 +4005,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "jobstop()" function -static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -4558,7 +4038,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "jobwait(ids[, timeout])" function -static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -4657,7 +4137,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// json_decode() function -static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_json_decode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char numbuf[NUMBUFLEN]; const char *s = NULL; @@ -4691,14 +4171,28 @@ static void f_json_decode(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// json_encode() function -static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_json_encode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = encode_tv2json(&argvars[0], NULL); } +/// "keytrans()" function +static void f_keytrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + if (tv_check_for_string_arg(argvars, 0) == FAIL + || argvars[0].vval.v_string == NULL) { + return; + } + // Need to escape K_SPECIAL for mb_unescape(). + char *escaped = vim_strsave_escape_ks(argvars[0].vval.v_string); + rettv->vval.v_string = str2special_save(escaped, true, true); + xfree(escaped); +} + /// "last_buffer_nr()" function. -static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int n = 0; @@ -4712,7 +4206,7 @@ static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "len()" function -static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_len(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { switch (argvars[0].v_type) { case VAR_STRING: @@ -4783,19 +4277,19 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) } /// "libcall()" function -static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_libcall(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { libcall_common(argvars, rettv, VAR_STRING); } /// "libcallnr()" function -static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_libcallnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { libcall_common(argvars, rettv, VAR_NUMBER); } /// "line(string, [winid])" function -static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum = 0; pos_T *fp = NULL; @@ -4826,7 +4320,7 @@ static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "line2byte(lnum)" function -static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_line2byte(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const linenr_T lnum = tv_get_lnum(argvars); if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { @@ -4840,7 +4334,7 @@ static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "lispindent(lnum)" function -static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_lispindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const pos_T pos = curwin->w_cursor; const linenr_T lnum = tv_get_lnum(argvars); @@ -4854,13 +4348,13 @@ static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "localtime()" function -static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_localtime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = (varnumber_T)time(NULL); } /// luaeval() function implementation -static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_luaeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { const char *const str = tv_get_string_chk(&argvars[0]); @@ -4872,7 +4366,7 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "map()" function -static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { filter_map(argvars, rettv, true); } @@ -4880,9 +4374,9 @@ static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void find_some_match(typval_T *const argvars, typval_T *const rettv, const SomeMatchType type) { - char_u *str = NULL; + char *str = NULL; long len = 0; - char_u *expr = NULL; + char *expr = NULL; regmatch_T regmatch; long start = 0; long nth = 1; @@ -4890,11 +4384,11 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, bool match = false; list_T *l = NULL; long idx = 0; - char_u *tofree = NULL; + char *tofree = NULL; // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; - p_cpo = ""; + p_cpo = empty_option; rettv->vval.v_number = -1; switch (type) { @@ -4927,8 +4421,8 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, } li = tv_list_first(l); } else { - expr = str = (char_u *)tv_get_string(&argvars[0]); - len = (long)STRLEN(str); + expr = str = (char *)tv_get_string(&argvars[0]); + len = (long)strlen(str); } char patbuf[NUMBUFLEN]; @@ -4987,8 +4481,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, break; } xfree(tofree); - tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li), - NULL); + tofree = expr = str = encode_tv2echo(TV_LIST_ITEM_TV(li), NULL); if (str == NULL) { break; } @@ -5009,7 +4502,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, idx++; } else { startcol = (colnr_T)(regmatch.startp[0] - + utfc_ptr2len((char *)regmatch.startp[0]) - str); + + utfc_ptr2len(regmatch.startp[0]) - str); if (startcol > (colnr_T)len || str + startcol <= regmatch.startp[0]) { match = false; break; @@ -5028,7 +4521,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); - TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz((const char *)regmatch.startp[0], rd); + TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz(regmatch.startp[0], rd); TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)(regmatch.startp[0] - expr); TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)(regmatch.endp[0] - expr); if (l != NULL) { @@ -5064,11 +4557,9 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv, rettv->vval.v_number = idx; } else { if (type == kSomeMatch) { - rettv->vval.v_number = - (varnumber_T)(regmatch.startp[0] - str); + rettv->vval.v_number = (varnumber_T)(regmatch.startp[0] - str); } else { - rettv->vval.v_number = - (varnumber_T)(regmatch.endp[0] - str); + rettv->vval.v_number = (varnumber_T)(regmatch.endp[0] - str); } rettv->vval.v_number += (varnumber_T)(str - expr); } @@ -5090,31 +4581,31 @@ theend: } /// "match()" function -static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatch); } /// "matchend()" function -static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_matchend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatchEnd); } /// "matchlist()" function -static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_matchlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatchList); } /// "matchstr()" function -static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_matchstr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatchStr); } /// "matchstrpos()" function -static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_matchstrpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { find_some_match(argvars, rettv, kSomeMatchStrPos); } @@ -5142,7 +4633,7 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool TV_LIST_ITER_CONST(tv->vval.v_list, li, { const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); if (error) { - return; + return; // type error; errmsg already given } if (domax ? i > n : i < n) { n = i; @@ -5155,7 +4646,7 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool TV_DICT_ITER(tv->vval.v_dict, di, { const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); if (error) { - return; + return; // type error; errmsg already given } if (domax ? i > n : i < n) { n = i; @@ -5165,23 +4656,24 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool semsg(_(e_listdictarg), domax ? "max()" : "min()"); return; } + rettv->vval.v_number = n; } /// "max()" function -static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_max(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { max_min(argvars, rettv, true); } /// "min()" function -static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_min(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { max_min(argvars, rettv, false); } /// "mkdir()" function -static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int prot = 0755; // -V536 @@ -5216,17 +4708,16 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(failed_dir); rettv->vval.v_number = FAIL; return; - } else { - rettv->vval.v_number = OK; - return; } + rettv->vval.v_number = OK; + return; } } rettv->vval.v_number = vim_mkdir_emsg(dir, prot); } /// "mode()" function -static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_mode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[MODE_MAX_LENGTH]; @@ -5243,7 +4734,7 @@ static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "msgpackdump()" function -static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { if (argvars[0].v_type != VAR_LIST) { @@ -5382,7 +4873,7 @@ static void msgpackparse_unpack_blob(const blob_T *const blob, list_T *const ret } /// "msgpackparse" function -static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_msgpackparse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { @@ -5398,7 +4889,7 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "nextnonblank()" function -static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_nextnonblank(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum; @@ -5407,7 +4898,7 @@ static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) lnum = 0; break; } - if (*skipwhite((char *)ml_get(lnum)) != NUL) { + if (*skipwhite(ml_get(lnum)) != NUL) { break; } } @@ -5415,7 +4906,7 @@ static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "nr2char()" function -static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_nr2char(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[1].v_type != VAR_UNKNOWN) { if (!tv_check_num(&argvars[1])) { @@ -5446,14 +4937,14 @@ static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "or(expr, expr)" function -static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_or(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) | tv_get_number_chk(&argvars[1], NULL); } /// "pathshorten()" function -static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_pathshorten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int trim_len = 1; @@ -5465,17 +4956,17 @@ static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) } rettv->v_type = VAR_STRING; - const char_u *p = (char_u *)tv_get_string_chk(&argvars[0]); + const char *p = tv_get_string_chk(&argvars[0]); if (p == NULL) { rettv->vval.v_string = NULL; } else { - rettv->vval.v_string = (char *)vim_strsave(p); - shorten_dir_len((char_u *)rettv->vval.v_string, trim_len); + rettv->vval.v_string = xstrdup(p); + shorten_dir_len(rettv->vval.v_string, trim_len); } } /// "pow()" function -static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_pow(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { float_T fx; float_T fy; @@ -5489,13 +4980,13 @@ static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "prevnonblank()" function -static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prevnonblank(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { linenr_T lnum = tv_get_lnum(argvars); if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { lnum = 0; } else { - while (lnum >= 1 && *skipwhite((char *)ml_get(lnum)) == NUL) { + while (lnum >= 1 && *skipwhite(ml_get(lnum)) == NUL) { lnum--; } } @@ -5503,7 +4994,7 @@ static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "printf()" function -static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_printf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -5525,7 +5016,7 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "prompt_setcallback({buffer}, {callback})" function -static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { Callback prompt_callback = { .type = kCallbackNone }; @@ -5548,7 +5039,7 @@ static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, FunPtr fptr } /// "prompt_setinterrupt({buffer}, {callback})" function -static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { Callback interrupt_callback = { .type = kCallbackNone }; @@ -5571,7 +5062,7 @@ static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fpt } /// "prompt_getprompt({buffer})" function -static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { // return an empty string by default, e.g. it's not a prompt buffer @@ -5587,11 +5078,11 @@ static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - rettv->vval.v_string = (char *)vim_strsave(buf_prompt_text(buf)); + rettv->vval.v_string = xstrdup(buf_prompt_text(buf)); } /// "prompt_setprompt({buffer}, {text})" function -static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -5607,14 +5098,14 @@ static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "pum_getpos()" function -static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_pum_getpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); pum_set_event_info(rettv->vval.v_dict); } /// "pumvisible()" function -static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_pumvisible(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (pum_visible()) { rettv->vval.v_number = 1; @@ -5622,7 +5113,7 @@ static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "py3eval()" and "pyxeval()" functions (always python3) -static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_py3eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { script_host_eval("python3", argvars, rettv); } @@ -5657,10 +5148,11 @@ static void init_srand(uint32_t *const x) } } if (dev_urandom_state != OK) { - // Reading /dev/urandom doesn't work, fall back to time(). + // Reading /dev/urandom doesn't work, fall back to os_hrtime() XOR with process ID #endif // uncrustify:off - *x = (uint32_t)time(NULL); + *x = (uint32_t)os_hrtime(); + *x ^= (uint32_t)os_get_pid(); #ifndef MSWIN } #endif @@ -5694,7 +5186,7 @@ static inline uint32_t shuffle_xoshiro128starstar(uint32_t *const x, uint32_t *c } /// "rand()" function -static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { uint32_t result; @@ -5705,7 +5197,7 @@ static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr) // When no argument is given use the global seed list. if (!initialized) { // Initialize the global seed list. - uint32_t x; + uint32_t x = 0; init_srand(&x); gx = splitmix32(&x); @@ -5764,7 +5256,7 @@ theend: } /// "srand()" function -static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_srand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { uint32_t x = 0; @@ -5786,19 +5278,19 @@ static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "perleval()" function -static void f_perleval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_perleval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { script_host_eval("perl", argvars, rettv); } /// "rubyeval()" function -static void f_rubyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rubyeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { script_host_eval("ruby", argvars, rettv); } /// "range()" function -static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_range(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { varnumber_T end; varnumber_T stride = 1; @@ -5820,13 +5312,16 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (stride == 0) { emsg(_("E726: Stride is zero")); - } else if (stride > 0 ? end + 1 < start : end - 1 > start) { + return; + } + if (stride > 0 ? end + 1 < start : end - 1 > start) { emsg(_("E727: Start past end")); - } else { - tv_list_alloc_ret(rettv, (end - start) / stride); - for (varnumber_T i = start; stride > 0 ? i <= end : i >= end; i += stride) { - tv_list_append_number(rettv->vval.v_list, i); - } + return; + } + + tv_list_alloc_ret(rettv, (end - start) / stride); + for (varnumber_T i = start; stride > 0 ? i <= end : i >= end; i += stride) { + tv_list_append_number(rettv->vval.v_list, i); } } @@ -5868,7 +5363,7 @@ theend: } /// "readdir()" function -static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenUnknown); @@ -5885,17 +5380,17 @@ static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_clear_strings(&ga); } -/// "readfile()" function -static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// "readfile()" or "readblob()" function +static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_blob) { bool binary = false; - bool blob = false; + bool blob = always_blob; FILE *fd; - char_u buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 + char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1 int io_size = sizeof(buf); - char_u *prev = NULL; // previously read bytes, if any - long prevlen = 0; // length of data in prev - long prevsize = 0; // size of prev buffer + char *prev = NULL; // previously read bytes, if any + ptrdiff_t prevlen = 0; // length of data in prev + ptrdiff_t prevsize = 0; // size of prev buffer long maxline = MAXLNUM; if (argvars[1].v_type != VAR_UNKNOWN) { @@ -5913,7 +5408,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) // their own about CR-LF conversion. const char *const fname = tv_get_string(&argvars[0]); - if (os_isdir((const char_u *)fname)) { + if (os_isdir(fname)) { semsg(_(e_isadir2), fname); return; } @@ -5944,13 +5439,13 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) // - an incomplete line gets written // - a "binary" file gets an empty line at the end if it ends in a // newline. - char_u *p; // Position in buf. - char_u *start; // Start of current line. + char *p; // Position in buf. + char *start; // Start of current line. for (p = buf, start = buf; p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); p++) { - if (*p == '\n' || readlen <= 0) { - char_u *s = NULL; + if (readlen <= 0 || *p == '\n') { + char *s = NULL; size_t len = (size_t)(p - start); // Finished a line. Remove CRs before NL. @@ -5967,7 +5462,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (prevlen == 0) { assert(len < INT_MAX); - s = vim_strnsave(start, len); + s = xstrnsave(start, len); } else { // Change "prev" buffer to be the right size. This way // the bytes are only copied once, and very long lines are @@ -5982,7 +5477,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_append_owned_tv(l, (typval_T) { .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, - .vval.v_string = (char *)s, + .vval.v_string = s, }); start = p + 1; // Step over newline. @@ -6002,18 +5497,18 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) *p = '\n'; // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this // when finding the BF and check the previous two bytes. - } else if (*p == 0xbf && !binary) { + } else if ((uint8_t)(*p) == 0xbf && !binary) { // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, // these may be in the "prev" string. - char_u back1 = p >= buf + 1 ? p[-1] + char back1 = p >= buf + 1 ? p[-1] : prevlen >= 1 ? prev[prevlen - 1] : NUL; - char_u back2 = p >= buf + 2 ? p[-2] + char back2 = p >= buf + 2 ? p[-2] : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1] : prevlen >= - 2 ? prev[prevlen - 2] : NUL; + 2 ? prev[prevlen - 2] : NUL; - if (back2 == 0xef && back1 == 0xbb) { - char_u *dest = p - 2; + if ((uint8_t)back2 == 0xef && (uint8_t)back1 == 0xbb) { + char *dest = p - 2; // Usually a BOM is at the beginning of a file, and so at // the beginning of a line; then we can just step over it. @@ -6024,8 +5519,8 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) int adjust_prevlen = 0; if (dest < buf) { // -V782 - adjust_prevlen = (int)(buf - dest); // -V782 // adjust_prevlen must be 1 or 2. + adjust_prevlen = (int)(buf - dest); // -V782 dest = buf; } if (readlen > p - buf + 1) { @@ -6050,17 +5545,17 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) // small, to avoid repeatedly 'allocing' large and // 'reallocing' small. if (prevsize == 0) { - prevsize = (long)(p - start); + prevsize = p - start; } else { - long grow50pc = (prevsize * 3) / 2; - long growmin = (long)((p - start) * 2 + prevlen); + ptrdiff_t grow50pc = (prevsize * 3) / 2; + ptrdiff_t growmin = (p - start) * 2 + prevlen; prevsize = grow50pc > growmin ? grow50pc : growmin; } prev = xrealloc(prev, (size_t)prevsize); } // Add the line part to end of "prev". memmove(prev + prevlen, start, (size_t)(p - start)); - prevlen += (long)(p - start); + prevlen += p - start; } } // while @@ -6068,8 +5563,20 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) fclose(fd); } +/// "readblob()" function +static void f_readblob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + read_file_or_blob(argvars, rettv, true); +} + +/// "readfile()" function +static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + read_file_or_blob(argvars, rettv, false); +} + /// "getreginfo()" function -static void f_getreginfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getreginfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int regname = getreg_get_regname(argvars); if (regname == 0) { @@ -6119,18 +5626,18 @@ static void f_getreginfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reg_executing()" function -static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reg_executing(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { return_register(reg_executing, rettv); } /// "reg_recording()" function -static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reg_recording(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { return_register(reg_recording, rettv); } -static void f_reg_recorded(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reg_recorded(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { return_register(reg_recorded, rettv); } @@ -6172,7 +5679,7 @@ static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL /// one argument it returns the time passed since the argument. /// With two arguments it returns the time passed between /// the two arguments. -static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reltime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { proftime_T res; proftime_T start; @@ -6214,7 +5721,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reltimestr()" function -static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reltimestr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { proftime_T tm; @@ -6227,7 +5734,7 @@ static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "remove()" function -static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_remove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const arg_errmsg = N_("remove() argument"); @@ -6243,19 +5750,19 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "rename({from}, {to})" function -static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rename(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { rettv->vval.v_number = -1; } else { char buf[NUMBUFLEN]; - rettv->vval.v_number = vim_rename((const char_u *)tv_get_string(&argvars[0]), - (const char_u *)tv_get_string_buf(&argvars[1], buf)); + rettv->vval.v_number = vim_rename(tv_get_string(&argvars[0]), + tv_get_string_buf(&argvars[1], buf)); } } /// "repeat()" function -static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { varnumber_T n = tv_get_number(&argvars[1]); if (argvars[0].v_type == VAR_LIST) { @@ -6292,18 +5799,18 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "resolve()" function -static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; const char *fname = tv_get_string(&argvars[0]); -#ifdef WIN32 +#ifdef MSWIN char *v = os_resolve_shortcut(fname); if (v == NULL) { if (os_is_reparse_point_include(fname)) { v = os_realpath(fname, v); } } - rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v); + rettv->vval.v_string = (v == NULL ? xstrdup(fname) : v); #else # ifdef HAVE_READLINK { @@ -6365,7 +5872,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (*q != NUL) { cpy = remain; remain = (remain - ? (char *)concat_str((char_u *)q - 1, (char_u *)remain) + ? concat_str(q - 1, remain) : xstrdup(q - 1)); xfree(cpy); q[-1] = NUL; @@ -6377,7 +5884,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) q[-1] = NUL; q = path_tail(p); } - if (q > p && !path_is_absolute((const char_u *)buf)) { + if (q > p && !path_is_absolute(buf)) { // Symlink is relative to directory of argument. Replace the // symlink with the resolved name in the same directory. const size_t p_len = strlen(p); @@ -6424,7 +5931,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) && (p[2] == NUL || vim_ispathsep(p[2])))))) { // Prepend "./". - cpy = (char *)concat_str((const char_u *)"./", (const char_u *)p); + cpy = concat_str("./", p); xfree(p); p = cpy; } else if (!is_relative_to_current) { @@ -6457,18 +5964,18 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) # endif #endif - simplify_filename((char_u *)rettv->vval.v_string); + simplify_filename(rettv->vval.v_string); } /// "reverse({list})" function -static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type == VAR_BLOB) { blob_T *const b = argvars[0].vval.v_blob; const int len = tv_blob_len(b); for (int i = 0; i < len / 2; i++) { - const char_u tmp = tv_blob_get(b, i); + const uint8_t tmp = tv_blob_get(b, i); tv_blob_set(b, i, tv_blob_get(b, len - i - 1)); tv_blob_set(b, len - i - 1, tmp); } @@ -6477,8 +5984,8 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) semsg(_(e_listblobarg), "reverse()"); } else { list_T *const l = argvars[0].vval.v_list; - if (!var_check_lock(tv_list_locked(l), N_("reverse() argument"), - TV_TRANSLATE)) { + if (!value_check_lock(tv_list_locked(l), N_("reverse() argument"), + TV_TRANSLATE)) { tv_list_reverse(l); tv_list_set_ret(rettv, l); } @@ -6486,7 +5993,7 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reduce(list, { accumulator, element -> value } [, initial])" function -static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) { emsg(_(e_listblobreq)); @@ -6508,8 +6015,8 @@ static void f_reduce(typval_T *argvars, typval_T *rettv, FunPtr fptr) } funcexe_T funcexe = FUNCEXE_INIT; - funcexe.evaluate = true; - funcexe.partial = partial; + funcexe.fe_evaluate = true; + funcexe.fe_partial = partial; typval_T initial; typval_T argv[3]; @@ -6598,55 +6105,57 @@ static int get_search_arg(typval_T *varp, int *flagsp) { int dir = FORWARD; - if (varp->v_type != VAR_UNKNOWN) { - char nbuf[NUMBUFLEN]; - const char *flags = tv_get_string_buf_chk(varp, nbuf); - if (flags == NULL) { - return 0; // Type error; errmsg already given. - } - int mask; - while (*flags != NUL) { - switch (*flags) { - case 'b': - dir = BACKWARD; break; - case 'w': - p_ws = true; break; - case 'W': - p_ws = false; break; - default: - mask = 0; - if (flagsp != NULL) { - switch (*flags) { - case 'c': - mask = SP_START; break; - case 'e': - mask = SP_END; break; - case 'm': - mask = SP_RETCOUNT; break; - case 'n': - mask = SP_NOMOVE; break; - case 'p': - mask = SP_SUBPAT; break; - case 'r': - mask = SP_REPEAT; break; - case 's': - mask = SP_SETPCMARK; break; - case 'z': - mask = SP_COLUMN; break; - } - } - if (mask == 0) { - semsg(_(e_invarg2), flags); - dir = 0; - } else { - *flagsp |= mask; + if (varp->v_type == VAR_UNKNOWN) { + return FORWARD; + } + + char nbuf[NUMBUFLEN]; + const char *flags = tv_get_string_buf_chk(varp, nbuf); + if (flags == NULL) { + return 0; // Type error; errmsg already given. + } + int mask; + while (*flags != NUL) { + switch (*flags) { + case 'b': + dir = BACKWARD; break; + case 'w': + p_ws = true; break; + case 'W': + p_ws = false; break; + default: + mask = 0; + if (flagsp != NULL) { + switch (*flags) { + case 'c': + mask = SP_START; break; + case 'e': + mask = SP_END; break; + case 'm': + mask = SP_RETCOUNT; break; + case 'n': + mask = SP_NOMOVE; break; + case 'p': + mask = SP_SUBPAT; break; + case 'r': + mask = SP_REPEAT; break; + case 's': + mask = SP_SETPCMARK; break; + case 'z': + mask = SP_COLUMN; break; } } - if (dir == 0) { - break; + if (mask == 0) { + semsg(_(e_invarg2), flags); + dir = 0; + } else { + *flagsp |= mask; } - flags++; } + if (dir == 0) { + break; + } + flags++; } return dir; } @@ -6718,7 +6227,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) // Repeat until {skip} returns false. for (;;) { subpatnum - = searchit(curwin, curbuf, &pos, NULL, dir, (char_u *)pat, 1, options, RE_SEARCH, &sia); + = searchit(curwin, curbuf, &pos, NULL, dir, (char *)pat, 1, options, RE_SEARCH, &sia); // finding the first match again means there is no match where {skip} // evaluates to zero. if (firstpos.lnum != 0 && equalpos(pos, firstpos)) { @@ -6729,7 +6238,9 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) // didn't find it or no skip argument break; } - firstpos = pos; + if (firstpos.lnum == 0) { + firstpos = pos; + } // If the skip expression matches, ignore this match. { @@ -6786,7 +6297,7 @@ theend: } /// "rpcnotify()" function -static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rpcnotify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -6811,17 +6322,20 @@ static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) ADD(args, vim_to_object(tv)); } - if (!rpc_send_event((uint64_t)argvars[0].vval.v_number, - tv_get_string(&argvars[1]), args)) { + bool ok = rpc_send_event((uint64_t)argvars[0].vval.v_number, + tv_get_string(&argvars[1]), args); + + api_free_array(args); + + if (!ok) { semsg(_(e_invarg2), "Channel doesn't exist"); return; } - rettv->vval.v_number = 1; } /// "rpcrequest()" function -static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -6910,12 +6424,12 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) } end: - arena_mem_free(res_mem, NULL); + arena_mem_free(res_mem); api_clear_error(&err); } /// "rpcstart()" function (DEPRECATED) -static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -6955,7 +6469,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Allocate extra memory for the argument vector and the NULL pointer int argvl = argsl + 2; - char **argv = xmalloc(sizeof(char_u *) * (size_t)argvl); + char **argv = xmalloc(sizeof(char *) * (size_t)argvl); // Copy program name argv[0] = xstrdup(argvars[0].vval.v_string); @@ -6982,7 +6496,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "rpcstop()" function -static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_rpcstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; @@ -7000,7 +6514,7 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) // if called with a job, stop it, else closes the channel uint64_t id = (uint64_t)argvars[0].vval.v_number; if (find_job(id, false)) { - f_jobstop(argvars, rettv, NULL); + f_jobstop(argvars, rettv, fptr); } else { const char *error; rettv->vval.v_number = @@ -7012,7 +6526,7 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "screenattr()" function -static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; @@ -7030,7 +6544,7 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "screenchar()" function -static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; @@ -7048,7 +6562,7 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "screenchars()" function -static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int row = (int)tv_get_number_chk(&argvars[0], NULL) - 1; int col = (int)tv_get_number_chk(&argvars[1], NULL) - 1; @@ -7061,7 +6575,7 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } int pcc[MAX_MCO]; - int c = utfc_ptr2char(grid->chars[grid->line_offset[row] + (size_t)col], pcc); + int c = utfc_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col], pcc); int composing_len = 0; while (pcc[composing_len] != 0) { composing_len++; @@ -7076,45 +6590,19 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "screencol()" function /// /// First column is 1 to be consistent with virtcol(). -static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screencol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = ui_current_col() + 1; } -/// "screenpos({winid}, {lnum}, {col})" function -static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - dict_T *dict = rettv->vval.v_dict; - - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - return; - } - - pos_T pos = { - .lnum = (linenr_T)tv_get_number(&argvars[1]), - .col = (colnr_T)tv_get_number(&argvars[2]) - 1, - .coladd = 0 - }; - int row = 0; - int scol = 0, ccol = 0, ecol = 0; - textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false); - - tv_dict_add_nr(dict, S_LEN("row"), row); - tv_dict_add_nr(dict, S_LEN("col"), scol); - tv_dict_add_nr(dict, S_LEN("curscol"), ccol); - tv_dict_add_nr(dict, S_LEN("endcol"), ecol); -} - /// "screenrow()" function -static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenrow(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = ui_current_row() + 1; } /// "screenstring()" function -static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_screenstring(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_string = NULL; rettv->v_type = VAR_STRING; @@ -7129,11 +6617,11 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - rettv->vval.v_string = (char *)vim_strsave(grid->chars[grid->line_offset[row] + (size_t)col]); + rettv->vval.v_string = xstrdup((char *)grid->chars[grid->line_offset[row] + (size_t)col]); } /// "search()" function -static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_search(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int flags = 0; @@ -7141,7 +6629,7 @@ static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "searchdecl()" function -static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_searchdecl(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int locally = 1; int thisblock = 0; @@ -7157,7 +6645,7 @@ static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } if (!error && name != NULL) { - rettv->vval.v_number = find_decl((char_u *)name, strlen(name), locally, + rettv->vval.v_number = find_decl((char *)name, strlen(name), locally, thisblock, SEARCH_KEEP) == FAIL; } } @@ -7236,13 +6724,13 @@ theend: } /// "searchpair()" function -static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_searchpair(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = searchpair_cmn(argvars, NULL); } /// "searchpairpos()" function -static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_searchpairpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { pos_T match_pos; int lnum = 0; @@ -7284,7 +6772,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; - p_cpo = (char *)empty_option; + p_cpo = empty_option; // Set the time limit, if there is one. proftime_T tm = profile_setlimit(time_limit); @@ -7292,14 +6780,14 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir // Make two search patterns: start/end (pat2, for in nested pairs) and // start/middle/end (pat3, for the top pair). const size_t pat2_len = strlen(spat) + strlen(epat) + 17; - char_u *pat2 = xmalloc(pat2_len); + char *pat2 = xmalloc(pat2_len); const size_t pat3_len = strlen(spat) + strlen(mpat) + strlen(epat) + 25; - char_u *pat3 = xmalloc(pat3_len); - snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); + char *pat3 = xmalloc(pat3_len); + snprintf(pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); if (*mpat == NUL) { STRCPY(pat3, pat2); } else { - snprintf((char *)pat3, pat3_len, + snprintf(pat3, pat3_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat, mpat); } if (flags & SP_START) { @@ -7316,7 +6804,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir clearpos(&firstpos); pos_T foundpos; clearpos(&foundpos); - char_u *pat = pat3; + char *pat = pat3; for (;;) { searchit_arg_T sia = { .sa_stop_lnum = lnum_stop, @@ -7410,18 +6898,23 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir xfree(pat2); xfree(pat3); - if ((char_u *)p_cpo == empty_option) { + if (p_cpo == empty_option) { p_cpo = save_cpo; } else { // Darn, evaluating the {skip} expression changed the value. - free_string_option((char_u *)save_cpo); + // If it's still empty it was changed and restored, need to restore in + // the complicated way. + if (*p_cpo == NUL) { + set_option_value_give_err("cpo", 0L, save_cpo, 0); + } + free_string_option(save_cpo); } return retval; } /// "searchpos()" function -static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_searchpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { pos_T match_pos; int flags = 0; @@ -7441,7 +6934,7 @@ static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "serverlist()" function -static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_serverlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { size_t n; char **addrs = server_address_list(&n); @@ -7455,7 +6948,7 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "serverstart()" function -static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_serverstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; // Address of the new server @@ -7470,9 +6963,8 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type != VAR_STRING) { emsg(_(e_invarg)); return; - } else { - address = xstrdup(tv_get_string(argvars)); } + address = xstrdup(tv_get_string(argvars)); } else { address = server_address_new(NULL); } @@ -7500,7 +6992,7 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "serverstop()" function -static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_serverstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -7519,23 +7011,8 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "setbufline()" function -static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum; - buf_T *buf; - - buf = tv_get_buf(&argvars[0], false); - if (buf == NULL) { - rettv->vval.v_number = 1; // FAIL - } else { - lnum = tv_get_lnum_buf(&argvars[1], buf); - set_buffer_lines(buf, lnum, false, &argvars[2], rettv); - } -} - /// Set the cursor or mark position. -/// If 'charpos' is TRUE, then use the column number as a character offset. +/// If 'charpos' is true, then use the column number as a character offset. /// Otherwise use the column number as a byte offset. static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) { @@ -7543,41 +7020,45 @@ static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) rettv->vval.v_number = -1; const char *const name = tv_get_string_chk(argvars); - if (name != NULL) { - pos_T pos; - int fnum; - if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) { - if (pos.col != MAXCOL && --pos.col < 0) { - pos.col = 0; - } - if (name[0] == '.' && name[1] == NUL) { - // set cursor; "fnum" is ignored - curwin->w_cursor = pos; - if (curswant >= 0) { - curwin->w_curswant = curswant - 1; - curwin->w_set_curswant = false; - } - check_cursor(); - rettv->vval.v_number = 0; - } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { - // set mark - if (setmark_pos((uint8_t)name[1], &pos, fnum, NULL) == OK) { - rettv->vval.v_number = 0; - } - } else { - emsg(_(e_invarg)); - } + if (name == NULL) { + return; + } + + pos_T pos; + int fnum; + if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) != OK) { + return; + } + + if (pos.col != MAXCOL && --pos.col < 0) { + pos.col = 0; + } + if (name[0] == '.' && name[1] == NUL) { + // set cursor; "fnum" is ignored + curwin->w_cursor = pos; + if (curswant >= 0) { + curwin->w_curswant = curswant - 1; + curwin->w_set_curswant = false; + } + check_cursor(); + rettv->vval.v_number = 0; + } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { + // set mark + if (setmark_pos((uint8_t)name[1], &pos, fnum, NULL) == OK) { + rettv->vval.v_number = 0; } + } else { + emsg(_(e_invarg)); } } /// "setcharpos()" function -static void f_setcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { set_position(argvars, rettv, true); } -static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_DICT) { emsg(_(e_dictreq)); @@ -7585,44 +7066,36 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) } dict_T *d = argvars[0].vval.v_dict; - if (d != NULL) { - char_u *const csearch = (char_u *)tv_dict_get_string(d, "char", false); - if (csearch != NULL) { - int pcc[MAX_MCO]; - const int c = utfc_ptr2char(csearch, pcc); - set_last_csearch(c, csearch, utfc_ptr2len((char *)csearch)); - } - - dictitem_T *di = tv_dict_find(d, S_LEN("forward")); - if (di != NULL) { - set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD); - } + if (d == NULL) { + return; + } - di = tv_dict_find(d, S_LEN("until")); - if (di != NULL) { - set_csearch_until(!!tv_get_number(&di->di_tv)); - } + char *const csearch = tv_dict_get_string(d, "char", false); + if (csearch != NULL) { + int pcc[MAX_MCO]; + const int c = utfc_ptr2char(csearch, pcc); + set_last_csearch(c, csearch, utfc_ptr2len(csearch)); } -} -/// "setcmdpos()" function -static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int pos = (int)tv_get_number(&argvars[0]) - 1; + dictitem_T *di = tv_dict_find(d, S_LEN("forward")); + if (di != NULL) { + set_csearch_direction(tv_get_number(&di->di_tv) ? FORWARD : BACKWARD); + } - if (pos >= 0) { - rettv->vval.v_number = set_cmdline_pos(pos); + di = tv_dict_find(d, S_LEN("until")); + if (di != NULL) { + set_csearch_until(!!tv_get_number(&di->di_tv)); } } /// "setcursorcharpos" function -static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { set_cursorpos(argvars, rettv, true); } /// "setenv()" function -static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char namebuf[NUMBUFLEN]; char valbuf[NUMBUFLEN]; @@ -7637,7 +7110,7 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "setfperm({fname}, {mode})" function -static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = 0; @@ -7667,15 +7140,8 @@ static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = os_setperm(fname, mode) == OK; } -/// "setline()" function -static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - linenr_T lnum = tv_get_lnum(&argvars[0]); - set_buffer_lines(curbuf, lnum, false, &argvars[1], rettv); -} - /// "setpos()" function -static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { set_position(argvars, rettv, false); } @@ -7711,7 +7177,7 @@ static int get_yank_type(char **const pp, MotionType *const yank_type, long *con } /// "setreg()" function -static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_setreg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool append = false; @@ -7837,7 +7303,7 @@ free_lstval: if (strval == NULL) { return; } - write_reg_contents_ex(regname, (const char_u *)strval, (ssize_t)STRLEN(strval), + write_reg_contents_ex(regname, strval, (ssize_t)strlen(strval), append, yank_type, (colnr_T)block_len); } if (pointreg != 0) { @@ -7852,7 +7318,7 @@ free_lstval: } /// "settagstack()" function -static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { static char *e_invact2 = N_("E962: Invalid action: '%s'"); char action = 'r'; @@ -7903,7 +7369,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// f_sha256 - sha256({string}) function -static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_sha256(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *p = tv_get_string(&argvars[0]); const char *hash = sha256_bytes((const uint8_t *)p, strlen(p), NULL, 0); @@ -7914,18 +7380,17 @@ static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "shellescape({string})" function -static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_shellescape(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const bool do_special = non_zero_arg(&argvars[1]); rettv->vval.v_string = - (char *)vim_strsave_shellescape((const char_u *)tv_get_string(&argvars[0]), do_special, - do_special); + vim_strsave_shellescape(tv_get_string(&argvars[0]), do_special, do_special); rettv->v_type = VAR_STRING; } /// shiftwidth() function -static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_shiftwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = 0; @@ -7941,16 +7406,16 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "simplify()" function -static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_simplify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const p = tv_get_string(&argvars[0]); rettv->vval.v_string = xstrdup(p); - simplify_filename((char_u *)rettv->vval.v_string); // Simplify in place. + simplify_filename(rettv->vval.v_string); // Simplify in place. rettv->v_type = VAR_STRING; } /// "sockconnect()" function -static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_sockconnect(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { emsg(_(e_invarg)); @@ -8002,7 +7467,7 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "stdioopen()" function -static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_stdioopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_DICT) { emsg(_(e_invarg)); @@ -8036,7 +7501,7 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "reltimefloat()" function -static void f_reltimefloat(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_reltimefloat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) FUNC_ATTR_NONNULL_ALL { proftime_T tm; @@ -8049,7 +7514,7 @@ static void f_reltimefloat(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "soundfold({word})" function -static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_soundfold(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; const char *const s = tv_get_string(&argvars[0]); @@ -8057,7 +7522,7 @@ static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "spellbadword()" function -static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const int wo_spell_save = curwin->w_p_spell; @@ -8079,7 +7544,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Find the start and length of the badly spelled word. len = spell_move_to(curwin, FORWARD, true, true, &attr); if (len != 0) { - word = (char *)get_cursor_pos_ptr(); + word = get_cursor_pos_ptr(); curwin->w_set_curswant = true; } } else if (*curbuf->b_s.b_p_spl != NUL) { @@ -8089,7 +7554,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (str != NULL) { // Check the argument for spelling. while (*str != NUL) { - len = spell_check(curwin, (char_u *)str, &attr, &capcol, false); + len = spell_check(curwin, (char *)str, &attr, &capcol, false); if (attr != HLF_COUNT) { word = str; break; @@ -8113,7 +7578,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "spellsuggest()" function -static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_spellsuggest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { garray_T ga = GA_EMPTY_INIT_VALUE; const int wo_spell_save = curwin->w_p_spell; @@ -8148,7 +7613,7 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) maxcount = 25; } - spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false); + spell_suggest_list(&ga, (char *)str, maxcount, need_capital, false); f_spellsuggest_return: tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len); @@ -8160,7 +7625,7 @@ f_spellsuggest_return: curwin->w_p_spell = wo_spell_save; } -static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { colnr_T col = 0; bool keepempty = false; @@ -8168,7 +7633,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Make 'cpoptions' empty, the 'l' flag should not be used here. char *save_cpo = p_cpo; - p_cpo = ""; + p_cpo = empty_option; const char *str = tv_get_string(&argvars[0]); const char *pat = NULL; @@ -8179,7 +7644,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) typeerr = true; } if (argvars[2].v_type != VAR_UNKNOWN) { - keepempty = (bool)tv_get_number_chk(&argvars[2], &typeerr); + keepempty = (bool)tv_get_bool_chk(&argvars[2], &typeerr); } } if (pat == NULL || *pat == NUL) { @@ -8204,7 +7669,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (*str == NUL) { match = false; // Empty item at the end. } else { - match = vim_regexec_nl(®match, (char_u *)str, col); + match = vim_regexec_nl(®match, (char *)str, col); } const char *end; if (match) { @@ -8222,11 +7687,11 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } // Advance to just after the match. - if (regmatch.endp[0] > (char_u *)str) { + if (regmatch.endp[0] > str) { col = 0; } else { // Don't get stuck at the same match. - col = utfc_ptr2len((char *)regmatch.endp[0]); + col = utfc_ptr2len(regmatch.endp[0]); } str = (const char *)regmatch.endp[0]; } @@ -8239,7 +7704,7 @@ theend: } /// "stdpath(type)" function -static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_stdpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -8271,7 +7736,7 @@ static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "str2float()" function -static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_str2float(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *p = skipwhite(tv_get_string(&argvars[0])); bool isneg = (*p == '-'); @@ -8287,18 +7752,18 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "str2list()" function -static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_str2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenUnknown); - const char_u *p = (const char_u *)tv_get_string(&argvars[0]); + const char *p = tv_get_string(&argvars[0]); - for (; *p != NUL; p += utf_ptr2len((char *)p)) { - tv_list_append_number(rettv->vval.v_list, utf_ptr2char((char *)p)); + for (; *p != NUL; p += utf_ptr2len(p)) { + tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p)); } } /// "str2nr()" function -static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int base = 10; int what = 0; @@ -8309,15 +7774,15 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg(_(e_invarg)); return; } - if (argvars[2].v_type != VAR_UNKNOWN && tv_get_number(&argvars[2])) { + if (argvars[2].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[2])) { what |= STR2NR_QUOTE; } } - char_u *p = (char_u *)skipwhite(tv_get_string(&argvars[0])); + char *p = skipwhite(tv_get_string(&argvars[0])); bool isneg = (*p == '-'); if (*p == '+' || *p == '-') { - p = (char_u *)skipwhite((char *)p + 1); + p = skipwhite(p + 1); } switch (base) { case 2: @@ -8341,7 +7806,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strftime({format}[, {time}])" function -static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { time_t seconds; @@ -8361,18 +7826,15 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = xstrdup(_("(Invalid)")); } else { vimconv_T conv; - char_u *enc; conv.vc_type = CONV_NONE; - enc = enc_locale(); + char *enc = enc_locale(); convert_setup(&conv, p_enc, enc); if (conv.vc_type != CONV_NONE) { - p = (char *)string_convert(&conv, (char_u *)p, NULL); + p = string_convert(&conv, p, NULL); } char result_buf[256]; - if (p != NULL) { - (void)strftime(result_buf, sizeof(result_buf), p, curtime_ptr); - } else { + if (p == NULL || strftime(result_buf, sizeof(result_buf), p, curtime_ptr) == 0) { result_buf[0] = NUL; } @@ -8381,7 +7843,7 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } convert_setup(&conv, enc, p_enc); if (conv.vc_type != CONV_NONE) { - rettv->vval.v_string = (char *)string_convert(&conv, (char_u *)result_buf, NULL); + rettv->vval.v_string = string_convert(&conv, result_buf, NULL); } else { rettv->vval.v_string = xstrdup(result_buf); } @@ -8393,7 +7855,7 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strgetchar()" function -static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strgetchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -8407,7 +7869,7 @@ static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - const size_t len = STRLEN(str); + const size_t len = strlen(str); size_t byteidx = 0; while (charidx >= 0 && byteidx < len) { @@ -8421,7 +7883,7 @@ static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "stridx()" function -static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_stridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -8453,43 +7915,55 @@ static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "string()" function -static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_string(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = encode_tv2string(&argvars[0], NULL); } /// "strlen()" function -static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0])); } -/// "strchars()" function -static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void strchar_common(typval_T *argvars, typval_T *rettv, bool skipcc) { const char *s = tv_get_string(&argvars[0]); - int skipcc = 0; varnumber_T len = 0; - int (*func_mb_ptr2char_adv)(const char_u **pp); + int (*func_mb_ptr2char_adv)(const char **pp); + + func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv; + while (*s != NUL) { + func_mb_ptr2char_adv(&s); + len++; + } + rettv->vval.v_number = len; +} + +/// "strcharlen()" function +static void f_strcharlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + strchar_common(argvars, rettv, true); +} + +/// "strchars()" function +static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int skipcc = false; if (argvars[1].v_type != VAR_UNKNOWN) { - skipcc = (int)tv_get_number_chk(&argvars[1], NULL); + skipcc = (int)tv_get_bool(&argvars[1]); } if (skipcc < 0 || skipcc > 1) { - emsg(_(e_invarg)); + semsg(_(e_using_number_as_bool_nr), skipcc); } else { - func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv; - while (*s != NUL) { - func_mb_ptr2char_adv((const char_u **)&s); - len++; - } - rettv->vval.v_number = len; + strchar_common(argvars, rettv, skipcc); } } /// "strdisplaywidth()" function -static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const s = tv_get_string(&argvars[0]); int col = 0; @@ -8498,11 +7972,11 @@ static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) col = (int)tv_get_number(&argvars[1]); } - rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col); + rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char *)s) - col); } /// "strwidth()" function -static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const s = tv_get_string(&argvars[0]); @@ -8510,10 +7984,10 @@ static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strcharpart()" function -static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const p = tv_get_string(&argvars[0]); - const size_t slen = STRLEN(p); + const size_t slen = strlen(p); int nbyte = 0; bool error = false; @@ -8564,7 +8038,7 @@ static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strpart()" function -static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool error = false; @@ -8610,7 +8084,7 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strptime({format}, {timestring})" function -static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strptime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char fmt_buf[NUMBUFLEN]; char str_buf[NUMBUFLEN]; @@ -8624,10 +8098,10 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) vimconv_T conv = { .vc_type = CONV_NONE, }; - char_u *enc = enc_locale(); + char *enc = enc_locale(); convert_setup(&conv, p_enc, enc); if (conv.vc_type != CONV_NONE) { - fmt = (char *)string_convert(&conv, (char_u *)fmt, NULL); + fmt = string_convert(&conv, fmt, NULL); } if (fmt == NULL || os_strptime(str, fmt, &tmval) == NULL @@ -8642,7 +8116,7 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strridx()" function -static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; const char *const needle = tv_get_string_chk(&argvars[1]); @@ -8653,7 +8127,7 @@ static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; // Type error; errmsg already given. } - const size_t haystack_len = STRLEN(haystack); + const size_t haystack_len = strlen(haystack); ptrdiff_t end_idx; if (argvars[2].v_type != VAR_UNKNOWN) { // Third argument: upper limit for index. @@ -8685,14 +8159,14 @@ static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "strtrans()" function -static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_strtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = transstr(tv_get_string(&argvars[0]), true); } /// "submatch()" function -static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { bool error = false; int no = (int)tv_get_number_chk(&argvars[0], &error); @@ -8715,7 +8189,7 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (retList == 0) { rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)reg_submatch(no); + rettv->vval.v_string = reg_submatch(no); } else { rettv->v_type = VAR_LIST; rettv->vval.v_list = reg_submatch_list(no); @@ -8723,7 +8197,7 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "substitute()" function -static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_substitute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char patbuf[NUMBUFLEN]; char subbuf[NUMBUFLEN]; @@ -8752,14 +8226,14 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "swapinfo(swap_filename)" function -static void f_swapinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_swapinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict); } /// "swapname(expr)" function -static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_swapname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; buf_T *buf = tv_get_buf(&argvars[0], false); @@ -8768,12 +8242,12 @@ static void f_swapname(typval_T *argvars, typval_T *rettv, FunPtr fptr) || buf->b_ml.ml_mfp->mf_fname == NULL) { rettv->vval.v_string = NULL; } else { - rettv->vval.v_string = (char *)vim_strsave(buf->b_ml.ml_mfp->mf_fname); + rettv->vval.v_string = xstrdup(buf->b_ml.ml_mfp->mf_fname); } } /// "synID(lnum, col, trans)" function -static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // -1 on type error (both) const linenr_T lnum = tv_get_lnum(argvars); @@ -8784,7 +8258,7 @@ static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) int id = 0; if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count - && col >= 0 && (size_t)col < STRLEN(ml_get(lnum))) { + && col >= 0 && (size_t)col < strlen(ml_get(lnum))) { id = syn_get_id(curwin, lnum, col, trans, NULL, false); } @@ -8792,7 +8266,7 @@ static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synIDattr(id, what [, mode])" function -static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synIDattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const int id = (int)tv_get_number(&argvars[0]); const char *const what = tv_get_string(&argvars[1]); @@ -8850,7 +8324,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } break; case 'u': - if (STRLEN(what) >= 9) { + if (strlen(what) >= 9) { if (TOLOWER_ASC(what[5]) == 'l') { // underline p = highlight_has_attr(id, HL_UNDERLINE, modec); @@ -8879,7 +8353,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synIDtrans(id)" function -static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synIDtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int id = (int)tv_get_number(&argvars[0]); @@ -8893,12 +8367,12 @@ static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "synconcealed(lnum, col)" function -static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synconcealed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int syntax_flags = 0; int cchar; int matchid = 0; - char_u str[NUMBUFLEN]; + char str[NUMBUFLEN]; tv_list_set_ret(rettv, NULL); @@ -8909,7 +8383,7 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) CLEAR_FIELD(str); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 - && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) { + && (size_t)col <= strlen(ml_get(lnum)) && curwin->w_p_cole > 0) { (void)syn_get_id(curwin, lnum, col, false, NULL, false); syntax_flags = get_syntax_info(&matchid); @@ -8922,7 +8396,7 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) : curwin->w_p_lcs_chars.conceal; } if (cchar != NUL) { - utf_char2bytes(cchar, (char *)str); + utf_char2bytes(cchar, str); } } } @@ -8930,12 +8404,12 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_alloc_ret(rettv, 3); tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0); // -1 to auto-determine strlen - tv_list_append_string(rettv->vval.v_list, (const char *)str, -1); + tv_list_append_string(rettv->vval.v_list, str, -1); tv_list_append_number(rettv->vval.v_list, matchid); } /// "synstack(lnum, col)" function -static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_synstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_set_ret(rettv, NULL); @@ -8946,7 +8420,7 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 - && (size_t)col <= STRLEN(ml_get(lnum))) { + && (size_t)col <= strlen(ml_get(lnum))) { tv_list_alloc_ret(rettv, kListLenMayKnow); (void)syn_get_id(curwin, lnum, col, false, NULL, true); @@ -8959,18 +8433,18 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// f_system - the VimL system() function -static void f_system(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_system(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_system_output_as_rettv(argvars, rettv, false); } -static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_systemlist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_system_output_as_rettv(argvars, rettv, true); } /// "tabpagebuflist()" function -static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = NULL; @@ -8991,114 +8465,15 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// "tabpagenr()" function -static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int nr = 1; - - if (argvars[0].v_type != VAR_UNKNOWN) { - const char *const arg = tv_get_string_chk(&argvars[0]); - nr = 0; - if (arg != NULL) { - if (strcmp(arg, "$") == 0) { - nr = tabpage_index(NULL) - 1; - } else if (strcmp(arg, "#") == 0) { - nr = valid_tabpage(lastused_tabpage) ? tabpage_index(lastused_tabpage) : 0; - } else { - semsg(_(e_invexpr2), arg); - } - } - } else { - nr = tabpage_index(curtab); - } - rettv->vval.v_number = nr; -} - -/// Common code for tabpagewinnr() and winnr(). -static int get_winnr(tabpage_T *tp, typval_T *argvar) -{ - int nr = 1; - - win_T *twin = (tp == curtab) ? curwin : tp->tp_curwin; - if (argvar->v_type != VAR_UNKNOWN) { - bool invalid_arg = false; - const char *const arg = tv_get_string_chk(argvar); - if (arg == NULL) { - nr = 0; // Type error; errmsg already given. - } else if (strcmp(arg, "$") == 0) { - twin = (tp == curtab) ? lastwin : tp->tp_lastwin; - } else if (strcmp(arg, "#") == 0) { - twin = (tp == curtab) ? prevwin : tp->tp_prevwin; - if (twin == NULL) { - nr = 0; - } - } else { - // Extract the window count (if specified). e.g. winnr('3j') - char *endp; - long count = strtol((char *)arg, &endp, 10); - if (count <= 0) { - // if count is not specified, default to 1 - count = 1; - } - if (endp != NULL && *endp != '\0') { - if (strequal(endp, "j")) { - twin = win_vert_neighbor(tp, twin, false, count); - } else if (strequal(endp, "k")) { - twin = win_vert_neighbor(tp, twin, true, count); - } else if (strequal(endp, "h")) { - twin = win_horz_neighbor(tp, twin, true, count); - } else if (strequal(endp, "l")) { - twin = win_horz_neighbor(tp, twin, false, count); - } else { - invalid_arg = true; - } - } else { - invalid_arg = true; - } - } - - if (invalid_arg) { - semsg(_(e_invexpr2), arg); - nr = 0; - } - } - - if (nr > 0) { - for (win_T *wp = (tp == curtab) ? firstwin : tp->tp_firstwin; - wp != twin; wp = wp->w_next) { - if (wp == NULL) { - // didn't find it in this tabpage - nr = 0; - break; - } - nr++; - } - } - return nr; -} - -/// "tabpagewinnr()" function -static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int nr = 1; - tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); - if (tp == NULL) { - nr = 0; - } else { - nr = get_winnr(tp, &argvars[1]); - } - rettv->vval.v_number = nr; -} - /// "tagfiles()" function -static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tagfiles(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_list_alloc_ret(rettv, kListLenUnknown); char *fname = xmalloc(MAXPATHL); bool first = true; tagname_T tn; - while (get_tagfname(&tn, first, (char_u *)fname) == OK) { + while (get_tagfname(&tn, first, fname) == OK) { tv_list_append_string(rettv->vval.v_list, fname, -1); first = false; } @@ -9108,7 +8483,7 @@ static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "taglist()" function -static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_taglist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const tag_pattern = tv_get_string(&argvars[0]); @@ -9122,18 +8497,18 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) fname = tv_get_string(&argvars[1]); } (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown), - (char_u *)tag_pattern, (char_u *)fname); + (char *)tag_pattern, (char *)fname); } /// "tempname()" function -static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tempname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)vim_tempname(); + rettv->vval.v_string = vim_tempname(); } /// "termopen(cmd[, cwd])" function -static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure()) { return; @@ -9176,7 +8551,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (new_cwd && *new_cwd != NUL) { cwd = new_cwd; // The new cwd must be a directory. - if (!os_isdir((const char_u *)cwd)) { + if (!os_isdir(cwd)) { semsg(_(e_invarg2), "expected valid directory"); shell_free_argv(argv); return; @@ -9216,9 +8591,9 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) int pid = chan->stream.pty.process.pid; // "./…" => "/home/foo/…" - vim_FullName(cwd, (char *)NameBuff, sizeof(NameBuff), false); + vim_FullName(cwd, NameBuff, sizeof(NameBuff), false); // "/home/foo/…" => "~/…" - size_t len = home_replace(NULL, (char *)NameBuff, (char *)IObuff, sizeof(IObuff), true); + size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true); // Trim slash. if (len != 1 && (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/')) { IObuff[len - 1] = '\0'; @@ -9231,14 +8606,14 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // Terminal URI: "term://$CWD//$PID:$CMD" - snprintf((char *)NameBuff, sizeof(NameBuff), "term://%s//%d:%s", - (char *)IObuff, pid, cmd); + snprintf(NameBuff, sizeof(NameBuff), "term://%s//%d:%s", + IObuff, pid, cmd); // at this point the buffer has no terminal instance associated yet, so unset // the 'swapfile' option to ensure no swap file will be created curbuf->b_p_swf = false; apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf); - (void)setfname(curbuf, (char *)NameBuff, NULL, true); + (void)setfname(curbuf, NameBuff, NULL, true); apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); // Save the job id and pid in b:terminal_job_{id,pid} @@ -9256,7 +8631,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "timer_info([timer])" function -static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_timer_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_UNKNOWN) { if (argvars[0].v_type != VAR_NUMBER) { @@ -9274,7 +8649,7 @@ static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "timer_pause(timer, paused)" function -static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) +static void f_timer_pause(typval_T *argvars, typval_T *unused, EvalFuncData fptr) { if (argvars[0].v_type != VAR_NUMBER) { emsg(_(e_number_exp)); @@ -9294,7 +8669,7 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) } /// "timer_start(timeout, callback, opts)" function -static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int repeat = 1; @@ -9326,7 +8701,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "timer_stop(timerid)" function -static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_timer_stop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_NUMBER) { emsg(_(e_number_exp)); @@ -9341,27 +8716,27 @@ static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr) timer_stop(timer); } -static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr) +static void f_timer_stopall(typval_T *argvars, typval_T *unused, EvalFuncData fptr) { timer_stop_all(); } /// "tolower(string)" function -static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tolower(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), false); } /// "toupper(string)" function -static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_toupper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), true); } /// "tr(string, fromstr, tostr)" function -static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; char buf2[NUMBUFLEN]; @@ -9389,7 +8764,7 @@ static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr) int fromlen; for (const char *p = fromstr; *p != NUL; p += fromlen) { fromlen = utfc_ptr2len(p); - if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0) { + if (fromlen == inlen && strncmp(in_str, p, (size_t)inlen) == 0) { int tolen; for (p = tostr; *p != NUL; p += tolen) { tolen = utfc_ptr2len(p); @@ -9440,14 +8815,14 @@ error: } /// "trim({expr})" function -static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_trim(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; - const char_u *head = (const char_u *)tv_get_string_buf_chk(&argvars[0], buf1); - const char_u *mask = NULL; - const char_u *prev; - const char_u *p; + const char *head = tv_get_string_buf_chk(&argvars[0], buf1); + const char *mask = NULL; + const char *prev; + const char *p; int dir = 0; rettv->v_type = VAR_STRING; @@ -9456,8 +8831,13 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_STRING) { + semsg(_(e_invarg2), tv_get_string(&argvars[1])); + return; + } + if (argvars[1].v_type == VAR_STRING) { - mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2); + mask = tv_get_string_buf_chk(&argvars[1], buf2); if (argvars[2].v_type != VAR_UNKNOWN) { bool error = false; // leading or trailing characters to trim @@ -9495,7 +8875,7 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - const char_u *tail = head + STRLEN(head); + const char *tail = head + strlen(head); if (dir == 0 || dir == 2) { // Trim trailing characters for (; tail > head; tail = prev) { @@ -9518,11 +8898,11 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } - rettv->vval.v_string = (char *)vim_strnsave(head, (size_t)(tail - head)); + rettv->vval.v_string = xstrnsave(head, (size_t)(tail - head)); } /// "type(expr)" function -static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int n = -1; @@ -9554,7 +8934,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "undofile(name)" function -static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_undofile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; const char *const fname = tv_get_string(&argvars[0]); @@ -9573,7 +8953,7 @@ static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "undotree()" function -static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); @@ -9591,7 +8971,7 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "virtcol(string)" function -static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { colnr_T vcol = 0; int fnum = curbuf->b_fnum; @@ -9603,7 +8983,7 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (fp->col < 0) { fp->col = 0; } else { - const size_t len = STRLEN(ml_get(fp->lnum)); + const size_t len = strlen(ml_get(fp->lnum)); if (fp->col > (colnr_T)len) { fp->col = (colnr_T)len; } @@ -9616,14 +8996,14 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "visualmode()" function -static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_visualmode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char_u str[2]; + char str[2]; rettv->v_type = VAR_STRING; - str[0] = (char_u)curbuf->b_visual_mode_eval; + str[0] = (char)curbuf->b_visual_mode_eval; str[1] = NUL; - rettv->vval.v_string = (char *)vim_strsave(str); + rettv->vval.v_string = xstrdup(str); // A non-zero number or non-empty string argument: reset mode. if (non_zero_arg(&argvars[0])) { @@ -9632,287 +9012,29 @@ static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "wildmenumode()" function -static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_wildmenumode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (wild_menu_showing || ((State & MODE_CMDLINE) && cmdline_pum_active())) { rettv->vval.v_number = 1; } } -/// "win_findbuf()" function -static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_list_alloc_ret(rettv, kListLenMayKnow); - win_findbuf(argvars, rettv->vval.v_list); -} - -/// "win_getid()" function -static void f_win_getid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = win_getid(argvars); -} - -/// "win_gettype(nr)" function -static void f_win_gettype(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = curwin; - - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - if (argvars[0].v_type != VAR_UNKNOWN) { - wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_string = (char *)vim_strsave((char_u *)"unknown"); - return; - } - } - if (wp == aucmd_win) { - rettv->vval.v_string = xstrdup("autocmd"); - } else if (wp->w_p_pvw) { - rettv->vval.v_string = xstrdup("preview"); - } else if (wp->w_floating) { - rettv->vval.v_string = xstrdup("popup"); - } else if (wp == curwin && cmdwin_type != 0) { - rettv->vval.v_string = xstrdup("command"); - } else if (bt_quickfix(wp->w_buffer)) { - rettv->vval.v_string = xstrdup((wp->w_llist_ref != NULL ? "loclist" : "quickfix")); - } -} - -/// "win_gotoid()" function -static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = win_gotoid(argvars); -} - -/// "win_id2tabwin()" function -static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_id2tabwin(argvars, rettv); -} - -/// "win_id2win()" function -static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = win_id2win(argvars); -} - -/// "win_move_separator()" function -static void f_win_move_separator(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = false; - - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL || wp->w_floating) { - return; - } - - int offset = (int)tv_get_number(&argvars[1]); - win_drag_vsep_line(wp, offset); - rettv->vval.v_number = true; -} - -/// "win_move_statusline()" function -static void f_win_move_statusline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp; - int offset; - - rettv->vval.v_number = false; - - wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL || wp->w_floating) { - return; - } - - offset = (int)tv_get_number(&argvars[1]); - win_drag_status_line(wp, offset); - rettv->vval.v_number = true; -} - -/// "winbufnr(nr)" function -static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = wp->w_buffer->b_fnum; - } -} - -/// "wincol()" function -static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - validate_cursor(); - rettv->vval.v_number = curwin->w_wcol + 1; -} - -/// "winheight(nr)" function -static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = wp->w_height; - } -} - -/// "winlayout()" function -static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tabpage_T *tp; - - tv_list_alloc_ret(rettv, 2); - - if (argvars[0].v_type == VAR_UNKNOWN) { - tp = curtab; - } else { - tp = find_tabpage((int)tv_get_number(&argvars[0])); - if (tp == NULL) { - return; - } - } - - get_framelayout(tp->tp_topframe, rettv->vval.v_list, true); -} - -/// "winline()" function -static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - validate_cursor(); - rettv->vval.v_number = curwin->w_wrow + 1; -} - -/// "winnr()" function -static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = get_winnr(curtab, &argvars[0]); -} - -/// "winrestcmd()" function -static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char_u buf[50]; - - garray_T ga; - ga_init(&ga, (int)sizeof(char), 70); - - // Do this twice to handle some window layouts properly. - for (int i = 0; i < 2; i++) { - int winnr = 1; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - snprintf((char *)buf, sizeof(buf), "%dresize %d|", winnr, - wp->w_height); - ga_concat(&ga, (char *)buf); - snprintf((char *)buf, sizeof(buf), "vert %dresize %d|", winnr, - wp->w_width); - ga_concat(&ga, (char *)buf); - winnr++; - } - } - ga_append(&ga, NUL); - - rettv->vval.v_string = ga.ga_data; - rettv->v_type = VAR_STRING; -} - -/// "winrestview()" function -static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *dict = argvars[0].vval.v_dict; - - if (argvars[0].v_type != VAR_DICT || dict == NULL) { - emsg(_(e_invarg)); - } else { - dictitem_T *di; - if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) { - curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) { - curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) { - curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) { - curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv); - curwin->w_set_curswant = false; - } - if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) { - set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv)); - } - if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) { - curwin->w_topfill = (int)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) { - curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv); - } - if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) { - curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv); - } - - check_cursor(); - win_new_height(curwin, curwin->w_height); - win_new_width(curwin, curwin->w_width); - changed_window_setting(); - - if (curwin->w_topline <= 0) { - curwin->w_topline = 1; - } - if (curwin->w_topline > curbuf->b_ml.ml_line_count) { - curwin->w_topline = curbuf->b_ml.ml_line_count; - } - check_topfill(curwin, true); - } -} - -/// "winsaveview()" function -static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - tv_dict_alloc_ret(rettv); - dict_T *dict = rettv->vval.v_dict; - - tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum); - tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col); - tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd); - update_curswant(); - tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant); - - tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline); - tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill); - tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol); - tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol); -} - -/// "winwidth(nr)" function -static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *wp = find_win_by_nr_or_id(&argvars[0]); - if (wp == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = wp->w_width; - } -} - /// "windowsversion()" function -static void f_windowsversion(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_windowsversion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = xstrdup(windowsVersion); } /// "wordcount()" function -static void f_wordcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_wordcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); cursor_pos_info(rettv->vval.v_dict); } /// "writefile()" function -static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -9935,6 +9057,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool binary = false; bool append = false; bool do_fsync = !!p_fs; + bool mkdir_p = false; if (argvars[2].v_type != VAR_UNKNOWN) { const char *const flags = tv_get_string_chk(&argvars[2]); if (flags == NULL) { @@ -9950,6 +9073,8 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) do_fsync = true; break; case 'S': do_fsync = false; break; + case 'p': + mkdir_p = true; break; default: // Using %s, p and not %c, *p to preserve multibyte characters semsg(_("E5060: Unknown flag: %s"), p); @@ -9969,6 +9094,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg(_("E482: Can't open file with an empty name")); } else if ((error = file_open(&fp, fname, ((append ? kFileAppend : kFileTruncate) + | (mkdir_p ? kFileMkDir : kFileCreate) | kFileCreate), 0666)) != 0) { semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error)); @@ -9990,7 +9116,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "xor(expr, expr)" function -static void f_xor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_xor(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) ^ tv_get_number_chk(&argvars[1], NULL); diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h index 583ee0e75e..1ae031a952 100644 --- a/src/nvim/eval/funcs.h +++ b/src/nvim/eval/funcs.h @@ -1,11 +1,17 @@ #ifndef NVIM_EVAL_FUNCS_H #define NVIM_EVAL_FUNCS_H +#include <stdbool.h> +#include <stdint.h> + +#include "nvim/api/private/dispatch.h" #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/types.h" /// Prototype of C function that implements VimL function -typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); +typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, EvalFuncData data); /// Special flags for base_arg @see EvalFuncDef #define BASE_NONE 0 ///< Not a method (no base argument). @@ -13,13 +19,13 @@ typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, FunPtr data); /// Structure holding VimL function definition typedef struct { - char *name; ///< Name of the function. - uint8_t min_argc; ///< Minimal number of arguments. - uint8_t max_argc; ///< Maximal number of arguments. - uint8_t base_arg; ///< Method base arg # (1-indexed), BASE_NONE or BASE_LAST. - bool fast; ///< Can be run in |api-fast| events - VimLFunc func; ///< Function implementation. - FunPtr data; ///< Userdata for function implementation. + char *name; ///< Name of the function. + uint8_t min_argc; ///< Minimal number of arguments. + uint8_t max_argc; ///< Maximal number of arguments. + uint8_t base_arg; ///< Method base arg # (1-indexed), BASE_NONE or BASE_LAST. + bool fast; ///< Can be run in |api-fast| events + VimLFunc func; ///< Function implementation. + EvalFuncData data; ///< Userdata for function implementation. } EvalFuncDef; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/eval/gc.c b/src/nvim/eval/gc.c index 633e6abacf..6a54c4ddc1 100644 --- a/src/nvim/eval/gc.c +++ b/src/nvim/eval/gc.c @@ -1,11 +1,12 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <stddef.h> + #include "nvim/eval/gc.h" -#include "nvim/eval/typval.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "eval/gc.c.generated.h" +# include "eval/gc.c.generated.h" // IWYU pragma: export #endif /// Head of list of all dictionaries diff --git a/src/nvim/eval/gc.h b/src/nvim/eval/gc.h index c2e862e469..3185750c3b 100644 --- a/src/nvim/eval/gc.h +++ b/src/nvim/eval/gc.h @@ -2,6 +2,7 @@ #define NVIM_EVAL_GC_H #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" extern dict_T *gc_first_dict; extern list_T *gc_first_list; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 8822bb0491..05b4737206 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -28,9 +28,9 @@ #include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/mbyte.h" +#include "nvim/mbyte_defs.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/os/fileio.h" #include "nvim/os/input.h" #include "nvim/pos.h" #include "nvim/types.h" @@ -40,6 +40,15 @@ # include "eval/typval.c.generated.h" #endif +static char e_string_required_for_argument_nr[] + = N_("E1174: String required for argument %d"); +static char e_non_empty_string_required_for_argument_nr[] + = N_("E1175: Non-empty string required for argument %d"); +static char e_number_required_for_argument_nr[] + = N_("E1210: Number required for argument %d"); +static char e_string_or_list_required_for_argument_nr[] + = N_("E1222: String or List required for argument %d"); + bool tv_in_free_unref_items = false; // TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead @@ -317,10 +326,12 @@ void tv_list_free_list(list_T *const l) void tv_list_free(list_T *const l) FUNC_ATTR_NONNULL_ALL { - if (!tv_in_free_unref_items) { - tv_list_free_contents(l); - tv_list_free_list(l); + if (tv_in_free_unref_items) { + return; } + + tv_list_free_contents(l); + tv_list_free_list(l); } /// Unreference a list @@ -831,7 +842,7 @@ int tv_list_join(garray_T *const gap, list_T *const l, const char *const sep) } /// "join()" function -void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_join(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_LIST) { emsg(_(e_listreq)); @@ -855,7 +866,7 @@ void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "list2str()" function -void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_list2str(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { garray_T ga; @@ -889,8 +900,8 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) list_T *l; bool error = false; - if (var_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), - arg_errmsg, TV_TRANSLATE)) { + if (value_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + arg_errmsg, TV_TRANSLATE)) { return; } @@ -1016,7 +1027,7 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero) if (sortinfo->item_compare_lc) { res = strcoll(p1, p2); } else { - res = sortinfo->item_compare_ic ? STRICMP(p1, p2): STRCMP(p1, p2); + res = sortinfo->item_compare_ic ? STRICMP(p1, p2): strcmp(p1, p2); } } else { double n1, n2; @@ -1079,15 +1090,17 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this funcexe_T funcexe = FUNCEXE_INIT; - funcexe.evaluate = true; - funcexe.partial = partial; - funcexe.selfdict = sortinfo->item_compare_selfdict; + funcexe.fe_evaluate = true; + funcexe.fe_partial = partial; + funcexe.fe_selfdict = sortinfo->item_compare_selfdict; res = call_func(func_name, -1, &rettv, 2, argv, &funcexe); tv_clear(&argv[0]); tv_clear(&argv[1]); if (res == FAIL) { + // XXX: ITEM_COMPARE_FAIL is unused res = ITEM_COMPARE_FAIL; + sortinfo->item_compare_func_err = true; } else { res = (int)tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); if (res > 0) { @@ -1143,7 +1156,7 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) semsg(_(e_listarg), sort ? "sort()" : "uniq()"); } else { list_T *const l = argvars[0].vval.v_list; - if (var_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { + if (value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { goto theend; } tv_list_set_ret(rettv, l); @@ -1242,20 +1255,18 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) item_compare_func_ptr = item_compare_keeping_zero; } - int idx = 0; for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) ; li != NULL;) { listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); if (item_compare_func_ptr(&prev_li, &li) == 0) { - if (info.item_compare_func_err) { // -V547 - emsg(_("E882: Uniq compare function failed")); - break; - } li = tv_list_item_remove(l, li); } else { - idx++; li = TV_LIST_ITEM_NEXT(l, li); } + if (info.item_compare_func_err) { + emsg(_("E882: Uniq compare function failed")); + break; + } } } @@ -1267,13 +1278,13 @@ theend: } /// "sort"({list})" function -void f_sort(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sort(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_sort_uniq(argvars, rettv, true); } /// "uniq({list})" function -void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_uniq(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_sort_uniq(argvars, rettv, false); } @@ -1356,7 +1367,7 @@ void tv_list_reverse(list_T *const l) /// true list will not be modified. Must be initialized to false /// by the caller. void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs, - const ListSorter item_compare_func, bool *errp) + const ListSorter item_compare_func, const bool *errp) FUNC_ATTR_NONNULL_ARG(3, 4) { const int len = tv_list_len(l); @@ -1568,7 +1579,7 @@ bool tv_callback_equal(const Callback *cb1, const Callback *cb2) } switch (cb1->type) { case kCallbackFuncref: - return STRCMP(cb1->data.funcref, cb2->data.funcref) == 0; + return strcmp(cb1->data.funcref, cb2->data.funcref) == 0; case kCallbackPartial: // FIXME: this is inconsistent with tv_equal but is needed for precision // maybe change dictwatcheradd to return a watcher id instead? @@ -1588,7 +1599,7 @@ void callback_free(Callback *callback) { switch (callback->type) { case kCallbackFuncref: - func_unref((char_u *)callback->data.funcref); + func_unref(callback->data.funcref); xfree(callback->data.funcref); break; case kCallbackPartial: @@ -1617,7 +1628,7 @@ void callback_put(Callback *cb, typval_T *tv) case kCallbackFuncref: tv->v_type = VAR_FUNC; tv->vval.v_string = xstrdup(cb->data.funcref); - func_ref((char_u *)cb->data.funcref); + func_ref(cb->data.funcref); break; case kCallbackLua: // TODO(tjdevries): Unified Callback. @@ -1643,7 +1654,7 @@ void callback_copy(Callback *dest, Callback *src) break; case kCallbackFuncref: dest->data.funcref = xstrdup(src->data.funcref); - func_ref((char_u *)src->data.funcref); + func_ref(src->data.funcref); break; case kCallbackLua: dest->data.luaref = api_new_luaref(src->data.luaref); @@ -1740,9 +1751,8 @@ static bool tv_dict_watcher_matches(DictWatcher *watcher, const char *const key) const size_t len = watcher->key_pattern_len; if (len && watcher->key_pattern[len - 1] == '*') { return strncmp(key, watcher->key_pattern, len - 1) == 0; - } else { - return strcmp(key, watcher->key_pattern) == 0; } + return strcmp(key, watcher->key_pattern) == 0; } /// Send a change notification to all dictionary watchers that match given key @@ -1774,7 +1784,7 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, typval_T tv_dict_add(argv[2].vval.v_dict, v); } - if (oldtv) { + if (oldtv && oldtv->v_type != VAR_UNKNOWN) { dictitem_T *const v = tv_dict_item_alloc_len(S_LEN("old")); tv_copy(oldtv, &v->di_tv); tv_dict_add(argv[2].vval.v_dict, v); @@ -1834,6 +1844,7 @@ dictitem_T *tv_dict_item_alloc_len(const char *const key, const size_t key_len) di->di_key[key_len] = NUL; di->di_flags = DI_FLAGS_ALLOC; di->di_tv.v_lock = VAR_UNLOCKED; + di->di_tv.v_type = VAR_UNKNOWN; return di; } @@ -2053,9 +2064,24 @@ int tv_dict_get_tv(dict_T *d, const char *const key, typval_T *rettv) varnumber_T tv_dict_get_number(const dict_T *const d, const char *const key) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { + return tv_dict_get_number_def(d, key, 0); +} + +/// Get a number item from a dictionary. +/// +/// Returns "def" if the entry doesn't exist. +/// +/// @param[in] d Dictionary to get item from. +/// @param[in] key Key to find in dictionary. +/// @param[in] def Default value. +/// +/// @return Dictionary item. +varnumber_T tv_dict_get_number_def(const dict_T *const d, const char *const key, const int def) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ dictitem_T *const di = tv_dict_find(d, key, -1); if (di == NULL) { - return 0; + return def; } return tv_get_number(&di->di_tv); } @@ -2074,7 +2100,7 @@ char **tv_dict_to_env(dict_T *denv) TV_DICT_ITER(denv, var, { const char *str = tv_get_string(&var->di_tv); assert(str); - size_t len = STRLEN(var->di_key) + strlen(str) + strlen("=") + 1; + size_t len = strlen((char *)var->di_key) + strlen(str) + strlen("=") + 1; env[i] = xmalloc(len); snprintf(env[i], len, "%s=%s", (char *)var->di_key, str); i++; @@ -2183,6 +2209,15 @@ bool tv_dict_get_callback(dict_T *const d, const char *const key, const ptrdiff_ return res; } +/// Check for adding a function to g: or l:. +/// If the name is wrong give an error message and return true. +int tv_dict_wrong_func_name(dict_T *d, typval_T *tv, const char *name) +{ + return (d == &globvardict || &d->dv_hashtab == get_funccal_local_ht()) + && tv_is_func(*tv) + && var_wrong_func_name(name, true); +} + //{{{2 dict_add* /// Add item to dictionary @@ -2194,7 +2229,10 @@ bool tv_dict_get_callback(dict_T *const d, const char *const key, const ptrdiff_ int tv_dict_add(dict_T *const d, dictitem_T *const item) FUNC_ATTR_NONNULL_ALL { - return hash_add(&d->dv_hashtab, item->di_key); + if (tv_dict_wrong_func_name(d, &item->di_tv, (const char *)item->di_key)) { + return FAIL; + } + return hash_add(&d->dv_hashtab, (char *)item->di_key); } /// Add a list entry to dictionary @@ -2410,10 +2448,10 @@ void tv_dict_clear(dict_T *const d) /// /// @param d1 Dictionary to extend. /// @param[in] d2 Dictionary to extend with. -/// @param[in] action "error", "force", "keep": -/// +/// @param[in] action "error", "force", "move", "keep": /// e*, including "error": duplicate key gives an error. /// f*, including "force": duplicate d2 keys override d1. +/// m*, including "move": move items instead of copying. /// other, including "keep": duplicate d2 keys ignored. void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action) FUNC_ATTR_NONNULL_ALL @@ -2422,27 +2460,33 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action const char *const arg_errmsg = _("extend() argument"); const size_t arg_errmsg_len = strlen(arg_errmsg); - TV_DICT_ITER(d2, di2, { + if (*action == 'm') { + hash_lock(&d2->dv_hashtab); // don't rehash on hash_remove() + } + + HASHTAB_ITER(&d2->dv_hashtab, hi2, { + dictitem_T *const di2 = TV_DICT_HI2DI(hi2); dictitem_T *const di1 = tv_dict_find(d1, (const char *)di2->di_key, -1); - if (d1->dv_scope != VAR_NO_SCOPE) { - // Disallow replacing a builtin function in l: and g:. - // Check the key to be valid when adding to any scope. - if (d1->dv_scope == VAR_DEF_SCOPE - && tv_is_func(di2->di_tv) - && !var_check_func_name((const char *)di2->di_key, di1 == NULL)) { - break; - } - if (!valid_varname((const char *)di2->di_key)) { - break; - } + // Check the key to be valid when adding to any scope. + if (d1->dv_scope != VAR_NO_SCOPE && !valid_varname((const char *)di2->di_key)) { + break; } if (di1 == NULL) { - dictitem_T *const new_di = tv_dict_item_copy(di2); - if (tv_dict_add(d1, new_di) == FAIL) { - tv_dict_item_free(new_di); - } else if (watched) { - tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, - NULL); + if (*action == 'm') { + // Cheap way to move a dict item from "d2" to "d1". + // If dict_add() fails then "d2" won't be empty. + dictitem_T *const new_di = di2; + if (tv_dict_add(d1, new_di) == OK) { + hash_remove(&d2->dv_hashtab, hi2); + tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, NULL); + } + } else { + dictitem_T *const new_di = tv_dict_item_copy(di2); + if (tv_dict_add(d1, new_di) == FAIL) { + tv_dict_item_free(new_di); + } else if (watched) { + tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, NULL); + } } } else if (*action == 'e') { semsg(_("E737: Key already exists: %s"), di2->di_key); @@ -2450,10 +2494,14 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action } else if (*action == 'f' && di2 != di1) { typval_T oldtv; - if (var_check_lock(di1->di_tv.v_lock, arg_errmsg, arg_errmsg_len) + if (value_check_lock(di1->di_tv.v_lock, arg_errmsg, arg_errmsg_len) || var_check_ro(di1->di_flags, arg_errmsg, arg_errmsg_len)) { break; } + // Disallow replacing a builtin function. + if (tv_dict_wrong_func_name(d1, &di2->di_tv, (const char *)di2->di_key)) { + break; + } if (watched) { tv_copy(&di1->di_tv, &oldtv); @@ -2469,6 +2517,10 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action } } }); + + if (*action == 'm') { + hash_unlock(&d2->dv_hashtab); + } } /// Compare two dictionaries @@ -2483,10 +2535,14 @@ bool tv_dict_equal(dict_T *const d1, dict_T *const d2, const bool ic, const bool if (d1 == d2) { return true; } - if (d1 == NULL || d2 == NULL) { + if (tv_dict_len(d1) != tv_dict_len(d2)) { return false; } - if (tv_dict_len(d1) != tv_dict_len(d2)) { + if (tv_dict_len(d1) == 0) { + // empty and NULL dicts are considered equal + return true; + } + if (d1 == NULL || d2 == NULL) { return false; } @@ -2532,8 +2588,8 @@ dict_T *tv_dict_copy(const vimconv_T *const conv, dict_T *const orig, const bool if (conv == NULL || conv->vc_type == CONV_NONE) { new_di = tv_dict_item_alloc((const char *)di->di_key); } else { - size_t len = STRLEN(di->di_key); - char *const key = (char *)string_convert(conv, di->di_key, &len); + size_t len = strlen((char *)di->di_key); + char *const key = (char *)string_convert(conv, (char *)di->di_key, &len); if (key == NULL) { new_di = tv_dict_item_alloc_len((const char *)di->di_key, len); } else { @@ -2654,7 +2710,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) { blob_T *const b = argvars[0].vval.v_blob; - if (b != NULL && var_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE)) { + if (b != NULL && value_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE)) { return; } @@ -2674,7 +2730,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) } if (argvars[2].v_type == VAR_UNKNOWN) { // Remove one item, return its value. - char_u *const p = (char_u *)b->bv_ga.ga_data; + uint8_t *const p = (uint8_t *)b->bv_ga.ga_data; rettv->vval.v_number = (varnumber_T)(*(p + idx)); memmove(p + idx, p + idx + 1, (size_t)(len - idx - 1)); b->bv_ga.ga_len--; @@ -2696,9 +2752,8 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) blob->bv_ga.ga_len = (int)(end - idx + 1); ga_grow(&blob->bv_ga, (int)(end - idx + 1)); - char_u *const p = (char_u *)b->bv_ga.ga_data; - memmove((char_u *)blob->bv_ga.ga_data, p + idx, - (size_t)(end - idx + 1)); + uint8_t *const p = (uint8_t *)b->bv_ga.ga_data; + memmove(blob->bv_ga.ga_data, p + idx, (size_t)(end - idx + 1)); tv_blob_set_ret(rettv, blob); if (len - end - 1 > 0) { @@ -2777,7 +2832,7 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi switch (what) { case kDictListKeys: tv_item.v_type = VAR_STRING; - tv_item.vval.v_string = (char *)vim_strsave(di->di_key); + tv_item.vval.v_string = xstrdup((char *)di->di_key); break; case kDictListValues: tv_copy(&di->di_tv, &tv_item); @@ -2806,25 +2861,25 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi } /// "items(dict)" function -void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_items(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_list(argvars, rettv, 2); } /// "keys()" function -void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_keys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_list(argvars, rettv, 0); } /// "values(dict)" function -void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_values(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_list(argvars, rettv, 1); } /// "has_key()" function -void f_has_key(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_has_key(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_DICT) { emsg(_(e_dictreq)); @@ -2846,7 +2901,7 @@ void tv_dict_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg) if (argvars[2].v_type != VAR_UNKNOWN) { semsg(_(e_toomanyarg), "remove()"); } else if ((d = argvars[0].vval.v_dict) != NULL - && !var_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { + && !value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE)) { const char *key = tv_get_string_chk(&argvars[1]); if (key != NULL) { dictitem_T *di = tv_dict_find(d, key, -1); @@ -2951,7 +3006,7 @@ void tv_blob_copy(typval_T *const from, typval_T *const to) (tv)->v_lock = VAR_UNLOCKED; \ } while (0) -static inline int _nothing_conv_func_start(typval_T *const tv, char_u *const fun) +static inline int _nothing_conv_func_start(typval_T *const tv, char *const fun) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1) { tv->v_lock = VAR_UNLOCKED; @@ -3114,6 +3169,7 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, dict_T **const dic #define TYPVAL_ENCODE_FIRST_ARG_TYPE const void *const #define TYPVAL_ENCODE_FIRST_ARG_NAME ignored #include "nvim/eval/typval_encode.c.h" + #undef TYPVAL_ENCODE_SCOPE #undef TYPVAL_ENCODE_NAME #undef TYPVAL_ENCODE_FIRST_ARG_TYPE @@ -3178,7 +3234,7 @@ void tv_free(typval_T *tv) partial_unref(tv->vval.v_partial); break; case VAR_FUNC: - func_unref((char_u *)tv->vval.v_string); + func_unref(tv->vval.v_string); FALLTHROUGH; case VAR_STRING: xfree(tv->vval.v_string); @@ -3231,7 +3287,7 @@ void tv_copy(const typval_T *const from, typval_T *const to) if (from->vval.v_string != NULL) { to->vval.v_string = xstrdup(from->vval.v_string); if (from->v_type == VAR_FUNC) { - func_ref((char_u *)to->vval.v_string); + func_ref(to->vval.v_string); } } break; @@ -3403,12 +3459,12 @@ bool tv_check_lock(const typval_T *tv, const char *name, size_t name_len) default: break; } - return var_check_lock(tv->v_lock, name, name_len) - || (lock != VAR_UNLOCKED && var_check_lock(lock, name, name_len)); + return value_check_lock(tv->v_lock, name, name_len) + || (lock != VAR_UNLOCKED && value_check_lock(lock, name, name_len)); } -/// @return true if variable "name" is locked (immutable) -bool var_check_lock(VarLockStatus lock, const char *name, size_t name_len) +/// @return true if variable "name" has a locked (immutable) value +bool value_check_lock(VarLockStatus lock, const char *name, size_t name_len) { const char *error_message = NULL; switch (lock) { @@ -3583,13 +3639,13 @@ bool tv_check_str_or_nr(const typval_T *const tv) #define FUNC_ERROR "E703: Using a Funcref as a Number" static const char *const num_errors[] = { - [VAR_PARTIAL]=N_(FUNC_ERROR), - [VAR_FUNC]=N_(FUNC_ERROR), - [VAR_LIST]=N_("E745: Using a List as a Number"), - [VAR_DICT]=N_("E728: Using a Dictionary as a Number"), - [VAR_FLOAT]=N_("E805: Using a Float as a Number"), - [VAR_BLOB]=N_("E974: Using a Blob as a Number"), - [VAR_UNKNOWN]=N_("E685: using an invalid value as a Number"), + [VAR_PARTIAL]= N_(FUNC_ERROR), + [VAR_FUNC]= N_(FUNC_ERROR), + [VAR_LIST]= N_("E745: Using a List as a Number"), + [VAR_DICT]= N_("E728: Using a Dictionary as a Number"), + [VAR_FLOAT]= N_("E805: Using a Float as a Number"), + [VAR_BLOB]= N_("E974: Using a Blob as a Number"), + [VAR_UNKNOWN]= N_("E685: using an invalid value as a Number"), }; #undef FUNC_ERROR @@ -3628,13 +3684,13 @@ bool tv_check_num(const typval_T *const tv) #define FUNC_ERROR "E729: using Funcref as a String" static const char *const str_errors[] = { - [VAR_PARTIAL]=N_(FUNC_ERROR), - [VAR_FUNC]=N_(FUNC_ERROR), - [VAR_LIST]=N_("E730: using List as a String"), - [VAR_DICT]=N_("E731: using Dictionary as a String"), - [VAR_FLOAT]=((const char *)e_float_as_string), - [VAR_BLOB]=N_("E976: using Blob as a String"), - [VAR_UNKNOWN]=N_("E908: using an invalid value as a String"), + [VAR_PARTIAL]= N_(FUNC_ERROR), + [VAR_FUNC]= N_(FUNC_ERROR), + [VAR_LIST]= N_("E730: using List as a String"), + [VAR_DICT]= N_("E731: using Dictionary as a String"), + [VAR_FLOAT]= ((const char *)e_float_as_string), + [VAR_BLOB]= N_("E976: using Blob as a String"), + [VAR_UNKNOWN]= N_("E908: using an invalid value as a String"), }; #undef FUNC_ERROR @@ -3717,8 +3773,7 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error) case VAR_STRING: { varnumber_T n = 0; if (tv->vval.v_string != NULL) { - vim_str2nr((char_u *)tv->vval.v_string, NULL, NULL, STR2NR_ALL, &n, NULL, 0, - false); + vim_str2nr(tv->vval.v_string, NULL, NULL, STR2NR_ALL, &n, NULL, 0, false); } return n; } @@ -3746,9 +3801,11 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error) linenr_T tv_get_lnum(const typval_T *const tv) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { + const int did_emsg_before = did_emsg; linenr_T lnum = (linenr_T)tv_get_number_chk(tv, NULL); - if (lnum == 0) { // No valid number, try using same function as line() does. + if (lnum <= 0 && did_emsg_before == did_emsg && tv->v_type != VAR_NUMBER) { int fnum; + // No valid number, try using same function as line() does. pos_T *const fp = var2fpos(tv, true, &fnum, false); if (fp != NULL) { lnum = fp->lnum; @@ -3801,26 +3858,56 @@ float_T tv_get_float(const typval_T *const tv) return 0; } -// Give an error and return FAIL unless "tv" is a string. -int tv_check_for_string(const typval_T *const tv) +/// Give an error and return FAIL unless "args[idx]" is a string. +int tv_check_for_string_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_STRING) { + semsg(_(e_string_required_for_argument_nr), idx + 1); + return FAIL; + } + return OK; +} + +/// Give an error and return FAIL unless "args[idx]" is a non-empty string. +int tv_check_for_nonempty_string_arg(const typval_T *const args, const int idx) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { - if (tv->v_type != VAR_STRING) { - emsg(_(e_stringreq)); + if (tv_check_for_string_arg(args, idx) == FAIL) { + return FAIL; + } + if (args[idx].vval.v_string == NULL || *args[idx].vval.v_string == NUL) { + semsg(_(e_non_empty_string_required_for_argument_nr), idx + 1); return FAIL; } return OK; } -// Give an error and return FAIL unless "tv" is a non-empty string. -int tv_check_for_nonempty_string(const typval_T *const tv) +/// Give an error and return FAIL unless "args[idx]" is a number. +int tv_check_for_number_arg(const typval_T *const args, const int idx) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { - if (tv_check_for_string(tv) == FAIL) { + if (args[idx].v_type != VAR_NUMBER) { + semsg(_(e_number_required_for_argument_nr), idx + 1); return FAIL; } - if (tv->vval.v_string == NULL || *tv->vval.v_string == NUL) { - emsg(_(e_non_empty_string_required)); + return OK; +} + +/// Check for an optional number argument at "idx" +int tv_check_for_opt_number_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + return (args[idx].v_type == VAR_UNKNOWN + || tv_check_for_number_arg(args, idx) != FAIL) ? OK : FAIL; +} + +/// Give an error and return FAIL unless "args[idx]" is a string or a list. +int tv_check_for_string_or_list_arg(const typval_T *const args, const int idx) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_LIST) { + semsg(_(e_string_or_list_required_for_argument_nr), idx + 1); return FAIL; } return OK; diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c4bc9f603b..3f59cd3547 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -2,11 +2,12 @@ #define NVIM_EVAL_TYPVAL_H #include <assert.h> -#include <inttypes.h> #include <stdbool.h> #include <stddef.h> +#include <stdint.h> #include <string.h> +#include "nvim/eval/typval_defs.h" #include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/gettext.h" @@ -15,405 +16,10 @@ #include "nvim/macros.h" #include "nvim/mbyte_defs.h" #include "nvim/message.h" -#include "nvim/pos.h" // for linenr_T #include "nvim/types.h" -#ifdef LOG_LIST_ACTIONS -# include "nvim/memory.h" -#endif - -/// Type used for VimL VAR_NUMBER values -typedef int64_t varnumber_T; -typedef uint64_t uvarnumber_T; - -/// Type used for VimL VAR_FLOAT values -typedef double float_T; - -/// Refcount for dict or list that should not be freed -enum { DO_NOT_FREE_CNT = (INT_MAX / 2), }; - -/// Additional values for tv_list_alloc() len argument -enum ListLenSpecials { - /// List length is not known in advance - /// - /// To be used when there is neither a way to know how many elements will be - /// needed nor are any educated guesses. - kListLenUnknown = -1, - /// List length *should* be known, but is actually not - /// - /// All occurrences of this value should be eventually removed. This is for - /// the case when the only reason why list length is not known is that it - /// would be hard to code without refactoring, but refactoring is needed. - kListLenShouldKnow = -2, - /// List length may be known in advance, but it requires too much effort - /// - /// To be used when it looks impractical to determine list length. - kListLenMayKnow = -3, -}; - -/// Maximal possible value of varnumber_T variable -#define VARNUMBER_MAX INT64_MAX -#define UVARNUMBER_MAX UINT64_MAX - -/// Minimal possible value of varnumber_T variable -#define VARNUMBER_MIN INT64_MIN - -/// %d printf format specifier for varnumber_T -#define PRIdVARNUMBER PRId64 - -typedef struct listvar_S list_T; -typedef struct dictvar_S dict_T; -typedef struct partial_S partial_T; -typedef struct blobvar_S blob_T; - -typedef struct ufunc ufunc_T; - -typedef enum { - kCallbackNone = 0, - kCallbackFuncref, - kCallbackPartial, - kCallbackLua, -} CallbackType; - -typedef struct { - union { - char *funcref; - partial_T *partial; - LuaRef luaref; - } data; - CallbackType type; -} Callback; - -#define CALLBACK_INIT { .type = kCallbackNone } -#define CALLBACK_NONE ((Callback)CALLBACK_INIT) - -/// Structure holding dictionary watcher -typedef struct dict_watcher { - Callback callback; - char *key_pattern; - size_t key_pattern_len; - QUEUE node; - bool busy; // prevent recursion if the dict is changed in the callback - bool needs_free; -} DictWatcher; - -/// Bool variable values -typedef enum { - kBoolVarFalse, ///< v:false - kBoolVarTrue, ///< v:true -} BoolVarValue; - -/// Special variable values -typedef enum { - kSpecialVarNull, ///< v:null -} SpecialVarValue; - -/// Variable lock status for typval_T.v_lock -typedef enum { - VAR_UNLOCKED = 0, ///< Not locked. - VAR_LOCKED = 1, ///< User lock, can be unlocked. - VAR_FIXED = 2, ///< Locked forever. -} VarLockStatus; - -/// VimL variable types, for use in typval_T.v_type -typedef enum { - VAR_UNKNOWN = 0, ///< Unknown (unspecified) value. - VAR_NUMBER, ///< Number, .v_number is used. - VAR_STRING, ///< String, .v_string is used. - VAR_FUNC, ///< Function reference, .v_string is used as function name. - VAR_LIST, ///< List, .v_list is used. - VAR_DICT, ///< Dictionary, .v_dict is used. - VAR_FLOAT, ///< Floating-point value, .v_float is used. - VAR_BOOL, ///< true, false - VAR_SPECIAL, ///< Special value (null), .v_special - ///< is used. - VAR_PARTIAL, ///< Partial, .v_partial is used. - VAR_BLOB, ///< Blob, .v_blob is used. -} VarType; - -/// Structure that holds an internal variable value -typedef struct { - VarType v_type; ///< Variable type. - VarLockStatus v_lock; ///< Variable lock status. - union typval_vval_union { - varnumber_T v_number; ///< Number, for VAR_NUMBER. - BoolVarValue v_bool; ///< Bool value, for VAR_BOOL - SpecialVarValue v_special; ///< Special value, for VAR_SPECIAL. - float_T v_float; ///< Floating-point number, for VAR_FLOAT. - char *v_string; ///< String, for VAR_STRING and VAR_FUNC, can be NULL. - list_T *v_list; ///< List for VAR_LIST, can be NULL. - dict_T *v_dict; ///< Dictionary for VAR_DICT, can be NULL. - partial_T *v_partial; ///< Closure: function with args. - blob_T *v_blob; ///< Blob for VAR_BLOB, can be NULL. - } vval; ///< Actual value. -} typval_T; - -/// Values for (struct dictvar_S).dv_scope -typedef enum { - VAR_NO_SCOPE = 0, ///< Not a scope dictionary. - VAR_SCOPE = 1, ///< Scope dictionary which requires prefix (a:, v:, …). - VAR_DEF_SCOPE = 2, ///< Scope dictionary which may be accessed without prefix - ///< (l:, g:). -} ScopeType; - -/// Structure to hold an item of a list -typedef struct listitem_S listitem_T; - -struct listitem_S { - listitem_T *li_next; ///< Next item in list. - listitem_T *li_prev; ///< Previous item in list. - typval_T li_tv; ///< Item value. -}; - -/// Structure used by those that are using an item in a list -typedef struct listwatch_S listwatch_T; - -struct listwatch_S { - listitem_T *lw_item; ///< Item being watched. - listwatch_T *lw_next; ///< Next watcher. -}; - -/// Structure to hold info about a list -/// Order of members is optimized to reduce padding. -struct listvar_S { - listitem_T *lv_first; ///< First item, NULL if none. - listitem_T *lv_last; ///< Last item, NULL if none. - listwatch_T *lv_watch; ///< First watcher, NULL if none. - listitem_T *lv_idx_item; ///< When not NULL item at index "lv_idx". - list_T *lv_copylist; ///< Copied list used by deepcopy(). - list_T *lv_used_next; ///< next list in used lists list. - list_T *lv_used_prev; ///< Previous list in used lists list. - int lv_refcount; ///< Reference count. - int lv_len; ///< Number of items. - int lv_idx; ///< Index of a cached item, used for optimising repeated l[idx]. - int lv_copyID; ///< ID used by deepcopy(). - VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED. - - LuaRef lua_table_ref; -}; - -// Static list with 10 items. Use tv_list_init_static10() to initialize. -typedef struct { - list_T sl_list; // must be first - listitem_T sl_items[10]; -} staticList10_T; - -#define TV_LIST_STATIC10_INIT { \ - .sl_list = { \ - .lv_first = NULL, \ - .lv_last = NULL, \ - .lv_refcount = 0, \ - .lv_len = 0, \ - .lv_watch = NULL, \ - .lv_idx_item = NULL, \ - .lv_lock = VAR_FIXED, \ - .lv_used_next = NULL, \ - .lv_used_prev = NULL, \ - }, \ -} - -#define TV_DICTITEM_STRUCT(...) \ - struct { \ - typval_T di_tv; /* Structure that holds scope dictionary itself. */ \ - uint8_t di_flags; /* Flags. */ \ - char_u di_key[__VA_ARGS__]; /* Key value. */ \ - } - -/// Structure to hold a scope dictionary -/// -/// @warning Must be compatible with dictitem_T. -/// -/// For use in find_var_in_ht to pretend that it found dictionary item when it -/// finds scope dictionary. -typedef TV_DICTITEM_STRUCT(1) ScopeDictDictItem; - -/// Structure to hold an item of a Dictionary -/// -/// @warning Must be compatible with ScopeDictDictItem. -/// -/// Also used for a variable. -typedef TV_DICTITEM_STRUCT() dictitem_T; - -/// Flags for dictitem_T.di_flags -typedef enum { - DI_FLAGS_RO = 1, ///< Read-only value - DI_FLAGS_RO_SBX = 2, ///< Value, read-only in the sandbox - DI_FLAGS_FIX = 4, ///< Fixed value: cannot be :unlet or remove()d. - DI_FLAGS_LOCK = 8, ///< Locked value. - DI_FLAGS_ALLOC = 16, ///< Separately allocated. -} DictItemFlags; - -/// Structure representing a Dictionary -struct dictvar_S { - VarLockStatus dv_lock; ///< Whole dictionary lock status. - ScopeType dv_scope; ///< Non-zero (#VAR_SCOPE, #VAR_DEF_SCOPE) if - ///< dictionary represents a scope (i.e. g:, l: …). - int dv_refcount; ///< Reference count. - int dv_copyID; ///< ID used when recursivery traversing a value. - hashtab_T dv_hashtab; ///< Hashtab containing all items. - dict_T *dv_copydict; ///< Copied dict used by deepcopy(). - dict_T *dv_used_next; ///< Next dictionary in used dictionaries list. - dict_T *dv_used_prev; ///< Previous dictionary in used dictionaries list. - QUEUE watchers; ///< Dictionary key watchers set by user code. - - LuaRef lua_table_ref; -}; - -/// Structure to hold info about a Blob -struct blobvar_S { - garray_T bv_ga; ///< Growarray with the data. - int bv_refcount; ///< Reference count. - VarLockStatus bv_lock; ///< VAR_UNLOCKED, VAR_LOCKED, VAR_FIXED. -}; - -/// Type used for script ID -typedef int scid_T; -/// Format argument for scid_T -#define PRIdSCID "d" - -// SCript ConteXt (SCTX): identifies a script line. -// When sourcing a script "sc_lnum" is zero, "sourcing_lnum" is the current -// line number. When executing a user function "sc_lnum" is the line where the -// function was defined, "sourcing_lnum" is the line number inside the -// function. When stored with a function, mapping, option, etc. "sc_lnum" is -// the line number in the script "sc_sid". -typedef struct { - scid_T sc_sid; // script ID - int sc_seq; // sourcing sequence number - linenr_T sc_lnum; // line number -} sctx_T; - -/// Maximum number of function arguments -#define MAX_FUNC_ARGS 20 -/// Short variable name length -#define VAR_SHORT_LEN 20 -/// Number of fixed variables used for arguments -#define FIXVAR_CNT 12 - -/// Callback interface for C function reference> -/// Used for managing functions that were registered with |register_cfunc| -typedef int (*cfunc_T)(int argcount, typval_T *argvars, typval_T *rettv, void *state); // NOLINT -/// Callback to clear cfunc_T and any associated state. -typedef void (*cfunc_free_T)(void *state); - -// Structure to hold info for a function that is currently being executed. -typedef struct funccall_S funccall_T; - -struct funccall_S { - ufunc_T *func; ///< Function being called. - int linenr; ///< Next line to be executed. - int returned; ///< ":return" used. - /// Fixed variables for arguments. - TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT]; - dict_T l_vars; ///< l: local function variables. - ScopeDictDictItem l_vars_var; ///< Variable for l: scope. - dict_T l_avars; ///< a: argument variables. - ScopeDictDictItem l_avars_var; ///< Variable for a: scope. - list_T l_varlist; ///< List for a:000. - listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000. - typval_T *rettv; ///< Return value. - linenr_T breakpoint; ///< Next line with breakpoint or zero. - int dbg_tick; ///< Debug_tick when breakpoint was set. - int level; ///< Top nesting level of executed function. - proftime_T prof_child; ///< Time spent in a child. - funccall_T *caller; ///< Calling function or NULL; or next funccal in - ///< list pointed to by previous_funccal. - int fc_refcount; ///< Number of user functions that reference this funccall. - int fc_copyID; ///< CopyID used for garbage collection. - garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". -}; - -/// Structure to hold info for a user function. -struct ufunc { - int uf_varargs; ///< variable nr of arguments - int uf_flags; - int uf_calls; ///< nr of active calls - bool uf_cleared; ///< func_clear() was already called - garray_T uf_args; ///< arguments - garray_T uf_def_args; ///< default argument expressions - garray_T uf_lines; ///< function lines - int uf_profiling; ///< true when func is being profiled - int uf_prof_initialized; - // Managing cfuncs - cfunc_T uf_cb; ///< C function extension callback - cfunc_free_T uf_cb_free; ///< C function extension free callback - void *uf_cb_state; ///< State of C function extension. - // Profiling the function as a whole. - int uf_tm_count; ///< nr of calls - proftime_T uf_tm_total; ///< time spent in function + children - proftime_T uf_tm_self; ///< time spent in function itself - proftime_T uf_tm_children; ///< time spent in children this call - // Profiling the function per line. - int *uf_tml_count; ///< nr of times line was executed - proftime_T *uf_tml_total; ///< time spent in a line + children - proftime_T *uf_tml_self; ///< time spent in a line itself - proftime_T uf_tml_start; ///< start time for current line - proftime_T uf_tml_children; ///< time spent in children for this line - proftime_T uf_tml_wait; ///< start wait time for current line - int uf_tml_idx; ///< index of line being timed; -1 if none - int uf_tml_execed; ///< line being timed was executed - sctx_T uf_script_ctx; ///< SCTX where function was defined, - ///< used for s: variables - int uf_refcount; ///< reference count, see func_name_refcount() - funccall_T *uf_scoped; ///< l: local variables for closure - char_u *uf_name_exp; ///< if "uf_name[]" starts with SNR the name with - ///< "<SNR>" as a string, otherwise NULL - char_u uf_name[]; ///< Name of function (actual size equals name); - ///< can start with <SNR>123_ - ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR) -}; - -struct partial_S { - int pt_refcount; ///< Reference count. - char_u *pt_name; ///< Function name; when NULL use pt_func->name. - ufunc_T *pt_func; ///< Function pointer; when NULL lookup function with - ///< pt_name. - bool pt_auto; ///< When true the partial was created by using dict.member - ///< in handle_subscript(). - int pt_argc; ///< Number of arguments. - typval_T *pt_argv; ///< Arguments in allocated array. - dict_T *pt_dict; ///< Dict for "self". -}; - -/// Structure used for explicit stack while garbage collecting hash tables -typedef struct ht_stack_S { - hashtab_T *ht; - struct ht_stack_S *prev; -} ht_stack_T; - -/// Structure used for explicit stack while garbage collecting lists -typedef struct list_stack_S { - list_T *list; - struct list_stack_S *prev; -} list_stack_T; - -/// Structure representing one list item, used for sort array. -typedef struct { - listitem_T *item; ///< Sorted list item. - int idx; ///< Sorted list item index. -} ListSortItem; - -typedef int (*ListSorter)(const void *, const void *); #ifdef LOG_LIST_ACTIONS - -/// List actions log entry -typedef struct { - uintptr_t l; ///< List log entry belongs to. - uintptr_t li1; ///< First list item log entry belongs to, if applicable. - uintptr_t li2; ///< Second list item log entry belongs to, if applicable. - int len; ///< List length when log entry was created. - const char *action; ///< Logged action. -} ListLogEntry; - -typedef struct list_log ListLog; - -/// List actions log -struct list_log { - ListLog *next; ///< Next chunk or NULL. - size_t capacity; ///< Number of entries in current chunk. - size_t size; ///< Current chunk size. - ListLogEntry entries[]; ///< Actual log entries. -}; +# include "nvim/memory.h" extern ListLog *list_log_first; ///< First list log chunk, NULL if missing extern ListLog *list_log_last; ///< Last list log chunk @@ -442,10 +48,8 @@ static inline ListLog *list_log_new(const size_t size) return ret; } -static inline void list_log(const list_T *const l, - const listitem_T *const li1, - const listitem_T *const li2, - const char *const action) +static inline void list_log(const list_T *const l, const listitem_T *const li1, + const listitem_T *const li2, const char *const action) REAL_FATTR_ALWAYS_INLINE; /// Add new entry to log @@ -489,7 +93,7 @@ static inline void list_log(const list_T *const l, const listitem_T *const li1, #define TV_DICT_HI2DI(hi) \ ((dictitem_T *)((hi)->hi_key - offsetof(dictitem_T, di_key))) -static inline void tv_list_ref(list_T *const l) +static inline void tv_list_ref(list_T *l) REAL_FATTR_ALWAYS_INLINE; /// Increase reference count for a given list @@ -505,7 +109,7 @@ static inline void tv_list_ref(list_T *const l) l->lv_refcount++; } -static inline void tv_list_set_ret(typval_T *const tv, list_T *const l) +static inline void tv_list_set_ret(typval_T *tv, list_T *l) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); /// Set a list as the return value. Increments the reference count. @@ -519,7 +123,7 @@ static inline void tv_list_set_ret(typval_T *const tv, list_T *const l) tv_list_ref(l); } -static inline VarLockStatus tv_list_locked(const list_T *const l) +static inline VarLockStatus tv_list_locked(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get list lock status @@ -562,7 +166,7 @@ static inline void tv_list_set_copyid(list_T *const l, const int copyid) l->lv_copyID = copyid; } -static inline int tv_list_len(const list_T *const l) +static inline int tv_list_len(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the number of items in a list @@ -577,7 +181,7 @@ static inline int tv_list_len(const list_T *const l) return l->lv_len; } -static inline int tv_list_copyid(const list_T *const l) +static inline int tv_list_copyid(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Get list copyID @@ -590,7 +194,7 @@ static inline int tv_list_copyid(const list_T *const l) return l->lv_copyID; } -static inline list_T *tv_list_latest_copy(const list_T *const l) +static inline list_T *tv_list_latest_copy(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Get latest list copy @@ -605,7 +209,7 @@ static inline list_T *tv_list_latest_copy(const list_T *const l) return l->lv_copylist; } -static inline int tv_list_uidx(const list_T *const l, int n) +static inline int tv_list_uidx(const list_T *l, int n) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Normalize index: that is, return either -1 or non-negative index @@ -628,7 +232,7 @@ static inline int tv_list_uidx(const list_T *const l, int n) return n; } -static inline bool tv_list_has_watchers(const list_T *const l) +static inline bool tv_list_has_watchers(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Check whether list has watchers @@ -643,7 +247,7 @@ static inline bool tv_list_has_watchers(const list_T *const l) return l && l->lv_watch; } -static inline listitem_T *tv_list_first(const list_T *const l) +static inline listitem_T *tv_list_first(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get first list item @@ -661,7 +265,7 @@ static inline listitem_T *tv_list_first(const list_T *const l) return l->lv_first; } -static inline listitem_T *tv_list_last(const list_T *const l) +static inline listitem_T *tv_list_last(const list_T *l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get last list item @@ -679,7 +283,7 @@ static inline listitem_T *tv_list_last(const list_T *const l) return l->lv_last; } -static inline void tv_dict_set_ret(typval_T *const tv, dict_T *const d) +static inline void tv_dict_set_ret(typval_T *tv, dict_T *d) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); /// Set a dictionary as the return value @@ -695,7 +299,7 @@ static inline void tv_dict_set_ret(typval_T *const tv, dict_T *const d) } } -static inline long tv_dict_len(const dict_T *const d) +static inline long tv_dict_len(const dict_T *d) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the number of items in a Dictionary @@ -709,7 +313,7 @@ static inline long tv_dict_len(const dict_T *const d) return (long)d->dv_hashtab.ht_used; } -static inline bool tv_dict_is_watched(const dict_T *const d) +static inline bool tv_dict_is_watched(const dict_T *d) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Check if dictionary is watched @@ -722,7 +326,7 @@ static inline bool tv_dict_is_watched(const dict_T *const d) return d && !QUEUE_EMPTY(&d->watchers); } -static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b) +static inline void tv_blob_set_ret(typval_T *tv, blob_T *b) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1); /// Set a blob as the return value. @@ -740,7 +344,7 @@ static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b) } } -static inline int tv_blob_len(const blob_T *const b) +static inline int tv_blob_len(const blob_T *b) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the length of the data in the blob, in bytes. @@ -754,7 +358,7 @@ static inline int tv_blob_len(const blob_T *const b) return b->bv_ga.ga_len; } -static inline char_u tv_blob_get(const blob_T *const b, int idx) +static inline uint8_t tv_blob_get(const blob_T *b, int idx) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; /// Get the byte at index `idx` in the blob. @@ -763,12 +367,12 @@ static inline char_u tv_blob_get(const blob_T *const b, int idx) /// @param[in] idx Index in a blob. Must be valid. /// /// @return Byte value at the given index. -static inline char_u tv_blob_get(const blob_T *const b, int idx) +static inline uint8_t tv_blob_get(const blob_T *const b, int idx) { - return ((char_u *)b->bv_ga.ga_data)[idx]; + return ((uint8_t *)b->bv_ga.ga_data)[idx]; } -static inline void tv_blob_set(blob_T *const b, int idx, char_u c) +static inline void tv_blob_set(blob_T *b, int idx, uint8_t c) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; /// Store the byte `c` at index `idx` in the blob. @@ -776,9 +380,9 @@ static inline void tv_blob_set(blob_T *const b, int idx, char_u c) /// @param[in] b Blob to index. Cannot be NULL. /// @param[in] idx Index in a blob. Must be valid. /// @param[in] c Value to store. -static inline void tv_blob_set(blob_T *const b, int idx, char_u c) +static inline void tv_blob_set(blob_T *const b, int idx, uint8_t c) { - ((char_u *)b->bv_ga.ga_data)[idx] = c; + ((uint8_t *)b->bv_ga.ga_data)[idx] = c; } /// Initialize VimL object @@ -793,12 +397,6 @@ static inline void tv_init(typval_T *const tv) } } -#define TV_INITIAL_VALUE \ - ((typval_T) { \ - .v_type = VAR_UNKNOWN, \ - .v_lock = VAR_UNLOCKED, \ - }) - /// Empty string /// /// Needed for hack which allows not allocating empty string and still not @@ -836,7 +434,7 @@ extern bool tv_in_free_unref_items; /// @param li Name of the variable with current listitem_T entry. /// @param code Cycle body. #define TV_LIST_ITER(l, li, code) \ - _TV_LIST_ITER_MOD( , l, li, code) + _TV_LIST_ITER_MOD( , l, li, code) // NOLINT(whitespace/parens) /// Iterate over a list /// @@ -893,13 +491,9 @@ extern bool tv_in_free_unref_items; } \ }) -static inline bool tv_get_float_chk(const typval_T *const tv, - float_T *const ret_f) +static inline bool tv_get_float_chk(const typval_T *tv, float_T *ret_f) REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT; -// FIXME circular dependency, cannot import message.h. -bool semsg(const char *const fmt, ...); - /// Get the float value /// /// Raises an error if object is not number or floating-point. @@ -935,7 +529,7 @@ static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q) return QUEUE_DATA(q, DictWatcher, node); } -static inline bool tv_is_func(const typval_T tv) +static inline bool tv_is_func(typval_T tv) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST; /// Check whether given typval_T contains a function @@ -970,4 +564,9 @@ EXTERN const size_t kTVTranslate INIT(= TV_TRANSLATE); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.h.generated.h" #endif + +#define tv_get_bool tv_get_number +#define tv_get_bool_chk tv_get_number_chk +#define tv_dict_get_bool tv_dict_get_number_def + #endif // NVIM_EVAL_TYPVAL_H diff --git a/src/nvim/eval/typval_defs.h b/src/nvim/eval/typval_defs.h new file mode 100644 index 0000000000..4615198441 --- /dev/null +++ b/src/nvim/eval/typval_defs.h @@ -0,0 +1,399 @@ +#ifndef NVIM_EVAL_TYPVAL_DEFS_H +#define NVIM_EVAL_TYPVAL_DEFS_H + +#include <inttypes.h> +#include <limits.h> + +#include "nvim/garray.h" +#include "nvim/hashtab.h" +#include "nvim/lib/queue.h" +#include "nvim/pos.h" +#include "nvim/types.h" + +/// Type used for VimL VAR_NUMBER values +typedef int64_t varnumber_T; +typedef uint64_t uvarnumber_T; + +/// Refcount for dict or list that should not be freed +enum { DO_NOT_FREE_CNT = (INT_MAX / 2), }; + +/// Additional values for tv_list_alloc() len argument +enum ListLenSpecials { + /// List length is not known in advance + /// + /// To be used when there is neither a way to know how many elements will be + /// needed nor are any educated guesses. + kListLenUnknown = -1, + /// List length *should* be known, but is actually not + /// + /// All occurrences of this value should be eventually removed. This is for + /// the case when the only reason why list length is not known is that it + /// would be hard to code without refactoring, but refactoring is needed. + kListLenShouldKnow = -2, + /// List length may be known in advance, but it requires too much effort + /// + /// To be used when it looks impractical to determine list length. + kListLenMayKnow = -3, +}; + +/// Maximal possible value of varnumber_T variable +#define VARNUMBER_MAX INT64_MAX +#define UVARNUMBER_MAX UINT64_MAX + +/// Minimal possible value of varnumber_T variable +#define VARNUMBER_MIN INT64_MIN + +/// %d printf format specifier for varnumber_T +#define PRIdVARNUMBER PRId64 + +typedef struct listvar_S list_T; +typedef struct dictvar_S dict_T; +typedef struct partial_S partial_T; +typedef struct blobvar_S blob_T; + +typedef struct ufunc ufunc_T; + +typedef enum { + kCallbackNone = 0, + kCallbackFuncref, + kCallbackPartial, + kCallbackLua, +} CallbackType; + +typedef struct { + union { + char *funcref; + partial_T *partial; + LuaRef luaref; + } data; + CallbackType type; +} Callback; + +#define CALLBACK_INIT { .type = kCallbackNone } +#define CALLBACK_NONE ((Callback)CALLBACK_INIT) + +/// Structure holding dictionary watcher +typedef struct dict_watcher { + Callback callback; + char *key_pattern; + size_t key_pattern_len; + QUEUE node; + bool busy; // prevent recursion if the dict is changed in the callback + bool needs_free; +} DictWatcher; + +/// Bool variable values +typedef enum { + kBoolVarFalse, ///< v:false + kBoolVarTrue, ///< v:true +} BoolVarValue; + +/// Special variable values +typedef enum { + kSpecialVarNull, ///< v:null +} SpecialVarValue; + +/// Variable lock status for typval_T.v_lock +typedef enum { + VAR_UNLOCKED = 0, ///< Not locked. + VAR_LOCKED = 1, ///< User lock, can be unlocked. + VAR_FIXED = 2, ///< Locked forever. +} VarLockStatus; + +/// VimL variable types, for use in typval_T.v_type +typedef enum { + VAR_UNKNOWN = 0, ///< Unknown (unspecified) value. + VAR_NUMBER, ///< Number, .v_number is used. + VAR_STRING, ///< String, .v_string is used. + VAR_FUNC, ///< Function reference, .v_string is used as function name. + VAR_LIST, ///< List, .v_list is used. + VAR_DICT, ///< Dictionary, .v_dict is used. + VAR_FLOAT, ///< Floating-point value, .v_float is used. + VAR_BOOL, ///< true, false + VAR_SPECIAL, ///< Special value (null), .v_special is used. + VAR_PARTIAL, ///< Partial, .v_partial is used. + VAR_BLOB, ///< Blob, .v_blob is used. +} VarType; + +/// Structure that holds an internal variable value +typedef struct { + VarType v_type; ///< Variable type. + VarLockStatus v_lock; ///< Variable lock status. + union typval_vval_union { + varnumber_T v_number; ///< Number, for VAR_NUMBER. + BoolVarValue v_bool; ///< Bool value, for VAR_BOOL + SpecialVarValue v_special; ///< Special value, for VAR_SPECIAL. + float_T v_float; ///< Floating-point number, for VAR_FLOAT. + char *v_string; ///< String, for VAR_STRING and VAR_FUNC, can be NULL. + list_T *v_list; ///< List for VAR_LIST, can be NULL. + dict_T *v_dict; ///< Dictionary for VAR_DICT, can be NULL. + partial_T *v_partial; ///< Closure: function with args. + blob_T *v_blob; ///< Blob for VAR_BLOB, can be NULL. + } vval; ///< Actual value. +} typval_T; + +#define TV_INITIAL_VALUE \ + ((typval_T) { \ + .v_type = VAR_UNKNOWN, \ + .v_lock = VAR_UNLOCKED, \ + }) + +/// Values for (struct dictvar_S).dv_scope +typedef enum { + VAR_NO_SCOPE = 0, ///< Not a scope dictionary. + VAR_SCOPE = 1, ///< Scope dictionary which requires prefix (a:, v:, …). + VAR_DEF_SCOPE = 2, ///< Scope dictionary which may be accessed without prefix + ///< (l:, g:). +} ScopeType; + +/// Structure to hold an item of a list +typedef struct listitem_S listitem_T; + +struct listitem_S { + listitem_T *li_next; ///< Next item in list. + listitem_T *li_prev; ///< Previous item in list. + typval_T li_tv; ///< Item value. +}; + +/// Structure used by those that are using an item in a list +typedef struct listwatch_S listwatch_T; + +struct listwatch_S { + listitem_T *lw_item; ///< Item being watched. + listwatch_T *lw_next; ///< Next watcher. +}; + +/// Structure to hold info about a list +/// Order of members is optimized to reduce padding. +struct listvar_S { + listitem_T *lv_first; ///< First item, NULL if none. + listitem_T *lv_last; ///< Last item, NULL if none. + listwatch_T *lv_watch; ///< First watcher, NULL if none. + listitem_T *lv_idx_item; ///< When not NULL item at index "lv_idx". + list_T *lv_copylist; ///< Copied list used by deepcopy(). + list_T *lv_used_next; ///< next list in used lists list. + list_T *lv_used_prev; ///< Previous list in used lists list. + int lv_refcount; ///< Reference count. + int lv_len; ///< Number of items. + int lv_idx; ///< Index of a cached item, used for optimising repeated l[idx]. + int lv_copyID; ///< ID used by deepcopy(). + VarLockStatus lv_lock; ///< Zero, VAR_LOCKED, VAR_FIXED. + + LuaRef lua_table_ref; +}; + +/// Static list with 10 items. Use tv_list_init_static10() to initialize. +typedef struct { + list_T sl_list; // must be first + listitem_T sl_items[10]; +} staticList10_T; + +#define TV_LIST_STATIC10_INIT { \ + .sl_list = { \ + .lv_first = NULL, \ + .lv_last = NULL, \ + .lv_refcount = 0, \ + .lv_len = 0, \ + .lv_watch = NULL, \ + .lv_idx_item = NULL, \ + .lv_lock = VAR_FIXED, \ + .lv_used_next = NULL, \ + .lv_used_prev = NULL, \ + }, \ +} + +#define TV_DICTITEM_STRUCT(...) \ + struct { \ + typval_T di_tv; /* Structure that holds scope dictionary itself. */ \ + uint8_t di_flags; /* Flags. */ \ + char_u di_key[__VA_ARGS__]; /* Key value. */ /* NOLINT(runtime/arrays)*/ \ + } + +/// Structure to hold a scope dictionary +/// +/// @warning Must be compatible with dictitem_T. +/// +/// For use in find_var_in_ht to pretend that it found dictionary item when it +/// finds scope dictionary. +typedef TV_DICTITEM_STRUCT(1) ScopeDictDictItem; + +/// Structure to hold an item of a Dictionary +/// +/// @warning Must be compatible with ScopeDictDictItem. +/// +/// Also used for a variable. +typedef TV_DICTITEM_STRUCT() dictitem_T; + +/// Flags for dictitem_T.di_flags +typedef enum { + DI_FLAGS_RO = 1, ///< Read-only value + DI_FLAGS_RO_SBX = 2, ///< Value, read-only in the sandbox + DI_FLAGS_FIX = 4, ///< Fixed value: cannot be :unlet or remove()d. + DI_FLAGS_LOCK = 8, ///< Locked value. + DI_FLAGS_ALLOC = 16, ///< Separately allocated. +} DictItemFlags; + +/// Structure representing a Dictionary +struct dictvar_S { + VarLockStatus dv_lock; ///< Whole dictionary lock status. + ScopeType dv_scope; ///< Non-zero (#VAR_SCOPE, #VAR_DEF_SCOPE) if + ///< dictionary represents a scope (i.e. g:, l: …). + int dv_refcount; ///< Reference count. + int dv_copyID; ///< ID used when recursivery traversing a value. + hashtab_T dv_hashtab; ///< Hashtab containing all items. + dict_T *dv_copydict; ///< Copied dict used by deepcopy(). + dict_T *dv_used_next; ///< Next dictionary in used dictionaries list. + dict_T *dv_used_prev; ///< Previous dictionary in used dictionaries list. + QUEUE watchers; ///< Dictionary key watchers set by user code. + + LuaRef lua_table_ref; +}; + +/// Structure to hold info about a Blob +struct blobvar_S { + garray_T bv_ga; ///< Growarray with the data. + int bv_refcount; ///< Reference count. + VarLockStatus bv_lock; ///< VAR_UNLOCKED, VAR_LOCKED, VAR_FIXED. +}; + +/// Type used for script ID +typedef int scid_T; +/// Format argument for scid_T +#define PRIdSCID "d" + +/// SCript ConteXt (SCTX): identifies a script line. +/// When sourcing a script "sc_lnum" is zero, "sourcing_lnum" is the current +/// line number. When executing a user function "sc_lnum" is the line where the +/// function was defined, "sourcing_lnum" is the line number inside the +/// function. When stored with a function, mapping, option, etc. "sc_lnum" is +/// the line number in the script "sc_sid". +typedef struct { + scid_T sc_sid; ///< script ID + int sc_seq; ///< sourcing sequence number + linenr_T sc_lnum; ///< line number +} sctx_T; + +/// Maximum number of function arguments +enum { MAX_FUNC_ARGS = 20, }; +/// Short variable name length +enum { VAR_SHORT_LEN = 20, }; +/// Number of fixed variables used for arguments +enum { FIXVAR_CNT = 12, }; + +/// Structure to hold info for a function that is currently being executed. +typedef struct funccall_S funccall_T; + +struct funccall_S { + ufunc_T *func; ///< Function being called. + int linenr; ///< Next line to be executed. + int returned; ///< ":return" used. + /// Fixed variables for arguments. + TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT]; + dict_T l_vars; ///< l: local function variables. + ScopeDictDictItem l_vars_var; ///< Variable for l: scope. + dict_T l_avars; ///< a: argument variables. + ScopeDictDictItem l_avars_var; ///< Variable for a: scope. + list_T l_varlist; ///< List for a:000. + listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000. + typval_T *rettv; ///< Return value. + linenr_T breakpoint; ///< Next line with breakpoint or zero. + int dbg_tick; ///< debug_tick when breakpoint was set. + int level; ///< Top nesting level of executed function. + proftime_T prof_child; ///< Time spent in a child. + funccall_T *caller; ///< Calling function or NULL; or next funccal in + ///< list pointed to by previous_funccal. + int fc_refcount; ///< Number of user functions that reference this funccall. + int fc_copyID; ///< CopyID used for garbage collection. + garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func". +}; + +/// Structure to hold info for a user function. +struct ufunc { + int uf_varargs; ///< variable nr of arguments + int uf_flags; + int uf_calls; ///< nr of active calls + bool uf_cleared; ///< func_clear() was already called + garray_T uf_args; ///< arguments + garray_T uf_def_args; ///< default argument expressions + garray_T uf_lines; ///< function lines + int uf_profiling; ///< true when func is being profiled + int uf_prof_initialized; + LuaRef uf_luaref; ///< lua callback, used if (uf_flags & FC_LUAREF) + // Profiling the function as a whole. + int uf_tm_count; ///< nr of calls + proftime_T uf_tm_total; ///< time spent in function + children + proftime_T uf_tm_self; ///< time spent in function itself + proftime_T uf_tm_children; ///< time spent in children this call + // Profiling the function per line. + int *uf_tml_count; ///< nr of times line was executed + proftime_T *uf_tml_total; ///< time spent in a line + children + proftime_T *uf_tml_self; ///< time spent in a line itself + proftime_T uf_tml_start; ///< start time for current line + proftime_T uf_tml_children; ///< time spent in children for this line + proftime_T uf_tml_wait; ///< start wait time for current line + int uf_tml_idx; ///< index of line being timed; -1 if none + int uf_tml_execed; ///< line being timed was executed + sctx_T uf_script_ctx; ///< SCTX where function was defined, + ///< used for s: variables + int uf_refcount; ///< reference count, see func_name_refcount() + funccall_T *uf_scoped; ///< l: local variables for closure + char *uf_name_exp; ///< if "uf_name[]" starts with SNR the name with + ///< "<SNR>" as a string, otherwise NULL + char uf_name[]; ///< Name of function (actual size equals name); + ///< can start with <SNR>123_ + ///< (<SNR> is K_SPECIAL KS_EXTRA KE_SNR) +}; + +struct partial_S { + int pt_refcount; ///< Reference count. + char *pt_name; ///< Function name; when NULL use pt_func->name. + ufunc_T *pt_func; ///< Function pointer; when NULL lookup function with pt_name. + bool pt_auto; ///< When true the partial was created by using dict.member + ///< in handle_subscript(). + int pt_argc; ///< Number of arguments. + typval_T *pt_argv; ///< Arguments in allocated array. + dict_T *pt_dict; ///< Dict for "self". +}; + +/// Structure used for explicit stack while garbage collecting hash tables +typedef struct ht_stack_S { + hashtab_T *ht; + struct ht_stack_S *prev; +} ht_stack_T; + +/// Structure used for explicit stack while garbage collecting lists +typedef struct list_stack_S { + list_T *list; + struct list_stack_S *prev; +} list_stack_T; + +/// Structure representing one list item, used for sort array. +typedef struct { + listitem_T *item; ///< Sorted list item. + int idx; ///< Sorted list item index. +} ListSortItem; + +typedef int (*ListSorter)(const void *, const void *); + +#ifdef LOG_LIST_ACTIONS +/// List actions log entry +typedef struct { + uintptr_t l; ///< List log entry belongs to. + uintptr_t li1; ///< First list item log entry belongs to, if applicable. + uintptr_t li2; ///< Second list item log entry belongs to, if applicable. + int len; ///< List length when log entry was created. + const char *action; ///< Logged action. +} ListLogEntry; + +typedef struct list_log ListLog; + +/// List actions log +struct list_log { + ListLog *next; ///< Next chunk or NULL. + size_t capacity; ///< Number of entries in current chunk. + size_t size; ///< Current chunk size. + ListLogEntry entries[]; ///< Actual log entries. +}; +#endif + +#endif // NVIM_EVAL_TYPVAL_DEFS_H diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 73b36b8611..6d29286a58 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -250,7 +250,7 @@ #include "nvim/eval/typval.h" #include "nvim/eval/typval_encode.h" #include "nvim/func_attr.h" -#include "nvim/lib/kvec.h" +#include "klib/kvec.h" // -V::1063 @@ -266,7 +266,7 @@ static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE( const MPConvStack *const mpstack, const int copyID, const MPConvStackValType conv_type, const char *const objname) -REAL_FATTR_NONNULL_ARG(2, 3, 4, 7) REAL_FATTR_WARN_UNUSED_RESULT + REAL_FATTR_NONNULL_ARG(2, 3, 4, 7) REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE; /// Function for checking whether container references itself @@ -301,7 +301,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv, typval_T *const tv, const int copyID, const char *const objname) -REAL_FATTR_NONNULL_ARG(2, 4, 6) REAL_FATTR_WARN_UNUSED_RESULT; + REAL_FATTR_NONNULL_ARG(2, 4, 6) REAL_FATTR_WARN_UNUSED_RESULT; /// Convert single value /// @@ -339,7 +339,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( tv_blob_len(tv->vval.v_blob)); break; case VAR_FUNC: - TYPVAL_ENCODE_CONV_FUNC_START(tv, (char_u *)tv->vval.v_string); + TYPVAL_ENCODE_CONV_FUNC_START(tv, tv->vval.v_string); TYPVAL_ENCODE_CONV_FUNC_BEFORE_ARGS(tv, 0); TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1); TYPVAL_ENCODE_CONV_FUNC_END(tv); @@ -347,7 +347,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( case VAR_PARTIAL: { partial_T *const pt = tv->vval.v_partial; (void)pt; - TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : (char_u *)partial_name(pt))); // -V547 + TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt))); // -V547 _mp_push(*mpstack, ((MPConvStackVal) { // -V779 .type = kMPConvPartial, .tv = tv, @@ -358,7 +358,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .pt = tv->vval.v_partial, }, }, - })); + })); break; } case VAR_LIST: { @@ -381,7 +381,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .li = tv_list_first(tv->vval.v_list), }, }, - })); + })); TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, _mp_last(*mpstack)); break; } @@ -459,8 +459,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( const listitem_T *const highest_bits_li = ( TV_LIST_ITEM_NEXT(val_list, sign_li)); if (TV_LIST_ITEM_TV(highest_bits_li)->v_type != VAR_NUMBER - || ((highest_bits - = TV_LIST_ITEM_TV(highest_bits_li)->vval.v_number) + || ((highest_bits = TV_LIST_ITEM_TV(highest_bits_li)->vval.v_number) < 0)) { goto _convert_one_value_regular_dict; } @@ -536,7 +535,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .li = tv_list_first(val_di->di_tv.vval.v_list), }, }, - })); + })); break; } case kMPMap: { @@ -571,7 +570,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .li = tv_list_first(val_list), }, }, - })); + })); break; } case kMPExt: { @@ -581,8 +580,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( || tv_list_len((val_list = val_di->di_tv.vval.v_list)) != 2 || (TV_LIST_ITEM_TV(tv_list_first(val_list))->v_type != VAR_NUMBER) - || ((type - = TV_LIST_ITEM_TV(tv_list_first(val_list))->vval.v_number) + || ((type = TV_LIST_ITEM_TV(tv_list_first(val_list))->vval.v_number) > INT8_MAX) || type < INT8_MIN || (TV_LIST_ITEM_TV(tv_list_last(val_list))->v_type @@ -622,7 +620,7 @@ _convert_one_value_regular_dict: {} .todo = tv->vval.v_dict->dv_hashtab.ht_used, }, }, - })); + })); TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, tv->vval.v_dict, _mp_last(*mpstack)); break; @@ -640,7 +638,7 @@ typval_encode_stop_converting_one_item: TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE( TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, typval_T *const tv, const char *const objname) -REAL_FATTR_NONNULL_ARG(2, 3) REAL_FATTR_WARN_UNUSED_RESULT; + REAL_FATTR_NONNULL_ARG(2, 3) REAL_FATTR_WARN_UNUSED_RESULT; /// Convert the whole typval /// @@ -758,7 +756,7 @@ typval_encode_stop_converting_one_item: .todo = (size_t)pt->pt_argc, }, }, - })); + })); } break; case kMPConvPartialSelf: { @@ -797,7 +795,7 @@ typval_encode_stop_converting_one_item: .todo = dict->dv_hashtab.ht_used, }, }, - })); + })); TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(NULL, pt->pt_dict, _mp_last(mpstack)); } else { diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h index ed70ba87ec..2f19144da3 100644 --- a/src/nvim/eval/typval_encode.h +++ b/src/nvim/eval/typval_encode.h @@ -10,9 +10,9 @@ #include <stddef.h> #include <string.h> +#include "klib/kvec.h" #include "nvim/eval/typval.h" #include "nvim/func_attr.h" -#include "nvim/lib/kvec.h" /// Type of the stack entry typedef enum { @@ -71,7 +71,7 @@ typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack; #define _mp_pop kv_pop #define _mp_last kv_last -static inline size_t tv_strlen(const typval_T *const tv) +static inline size_t tv_strlen(const typval_T *tv) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index c46cb6ba5d..22c5b1954d 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -3,45 +3,52 @@ // User defined function support +#include <assert.h> +#include <ctype.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "lauxlib.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/debugger.h" -#include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" -#include "nvim/fileio.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/insexpand.h" +#include "nvim/keycodes.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memline_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/os/input.h" +#include "nvim/path.h" #include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/search.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" -// flags used in uf_flags -#define FC_ABORT 0x01 // abort function on error -#define FC_RANGE 0x02 // function accepts range -#define FC_DICT 0x04 // Dict function, uses "self" -#define FC_CLOSURE 0x08 // closure, uses outer scope variables -#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 -#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 -#define FC_SANDBOX 0x40 // function defined in the sandbox -#define FC_DEAD 0x80 // function kept only for reference to dfunc -#define FC_EXPORT 0x100 // "export def Func()" -#define FC_NOARGS 0x200 // no a: variables in lambda -#define FC_VIM9 0x400 // defined in vim9 script file -#define FC_CFUNC 0x800 // C function extension - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/userfunc.c.generated.h" #endif @@ -62,6 +69,8 @@ static char *e_funcexts = N_("E122: Function %s already exists, add ! to replace static char *e_funcdict = N_("E717: Dictionary entry already exists"); static char *e_funcref = N_("E718: Funcref required"); static char *e_nofunc = N_("E130: Unknown function: %s"); +static char e_no_white_space_allowed_before_str_str[] + = N_("E1068: No white space allowed before '%s': %s"); void func_init(void) { @@ -75,7 +84,7 @@ hashtab_T *func_tbl_get(void) } /// Get function arguments. -static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int *varargs, +static int get_function_args(char **argp, char endchar, garray_T *newargs, int *varargs, garray_T *default_args, bool skip) { bool mustend = false; @@ -85,10 +94,10 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int int i; if (newargs != NULL) { - ga_init(newargs, (int)sizeof(char_u *), 3); + ga_init(newargs, (int)sizeof(char *), 3); } if (default_args != NULL) { - ga_init(default_args, (int)sizeof(char_u *), 3); + ga_init(default_args, (int)sizeof(char *), 3); } if (varargs != NULL) { @@ -97,7 +106,7 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int // Isolate the arguments: "arg1, arg2, ...)" bool any_default = false; - while (*p != (char)endchar) { + while (*p != endchar) { if (p[0] == '.' && p[1] == '.' && p[2] == '.') { if (varargs != NULL) { *varargs = true; @@ -109,9 +118,9 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int while (ASCII_ISALNUM(*p) || *p == '_') { p++; } - if (arg == p || isdigit(*arg) - || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) - || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { + if (arg == p || isdigit((uint8_t)(*arg)) + || (p - arg == 9 && strncmp(arg, "firstline", 9) == 0) + || (p - arg == 8 && strncmp(arg, "lastline", 8) == 0)) { if (!skip) { semsg(_("E125: Illegal argument: %s"), arg); } @@ -125,7 +134,7 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int // Check for duplicate argument name. for (i = 0; i < newargs->ga_len; i++) { - if (STRCMP(((char **)(newargs->ga_data))[i], arg) == 0) { + if (strcmp(((char **)(newargs->ga_data))[i], arg) == 0) { semsg(_("E853: Duplicate argument name: %s"), arg); xfree(arg); goto err_ret; @@ -142,18 +151,18 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int any_default = true; p = skipwhite(p) + 1; p = skipwhite(p); - char_u *expr = (char_u *)p; + char *expr = p; if (eval1(&p, &rettv, false) != FAIL) { ga_grow(default_args, 1); // trim trailing whitespace - while (p > (char *)expr && ascii_iswhite(p[-1])) { + while (p > expr && ascii_iswhite(p[-1])) { p--; } c = (char_u)(*p); *p = NUL; - expr = vim_strsave(expr); - ((char **)(default_args->ga_data))[default_args->ga_len] = (char *)expr; + expr = xstrdup(expr); + ((char **)(default_args->ga_data))[default_args->ga_len] = expr; default_args->ga_len++; *p = (char)c; } else { @@ -163,6 +172,15 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int emsg(_("E989: Non-default argument follows default argument")); mustend = true; } + + if (ascii_iswhite(*p) && *skipwhite(p) == ',') { + // Be tolerant when skipping + if (!skip) { + semsg(_(e_no_white_space_allowed_before_str_str), ",", p); + goto err_ret; + } + p = skipwhite(p); + } if (*p == ',') { p++; } else { @@ -170,14 +188,14 @@ static int get_function_args(char **argp, char_u endchar, garray_T *newargs, int } } p = skipwhite(p); - if (mustend && *p != (char)endchar) { + if (mustend && *p != endchar) { if (!skip) { semsg(_(e_invarg2), *argp); } break; } } - if (*p != (char)endchar) { + if (*p != endchar) { goto err_ret; } p++; // skip "endchar" @@ -211,21 +229,21 @@ static void register_closure(ufunc_T *fp) } /// @return a name for a lambda. Returned in static memory. -char_u *get_lambda_name(void) +char *get_lambda_name(void) { - static char_u name[30]; + static char name[30]; static int lambda_no = 0; - snprintf((char *)name, sizeof(name), "<lambda>%d", ++lambda_no); + snprintf(name, sizeof(name), "<lambda>%d", ++lambda_no); return name; } -static void set_ufunc_name(ufunc_T *fp, char_u *name) +static void set_ufunc_name(ufunc_T *fp, char *name) { STRCPY(fp->uf_name, name); - if (name[0] == K_SPECIAL) { - fp->uf_name_exp = xmalloc(STRLEN(name) + 3); + if ((uint8_t)name[0] == K_SPECIAL) { + fp->uf_name_exp = xmalloc(strlen(name) + 3); STRCPY(fp->uf_name_exp, "<SNR>"); STRCAT(fp->uf_name_exp, fp->uf_name + 3); } @@ -242,13 +260,13 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) partial_T *pt = NULL; int varargs; int ret; - char_u *start = (char_u *)skipwhite(*arg + 1); - char_u *s, *e; + char *start = skipwhite(*arg + 1); + char *s, *e; bool *old_eval_lavars = eval_lavars_used; bool eval_lavars = false; // First, check if this is a lambda expression. "->" must exists. - ret = get_function_args((char **)&start, '-', NULL, NULL, NULL, true); + ret = get_function_args(&start, '-', NULL, NULL, NULL, true); if (ret == FAIL || *start != '>') { return NOTDONE; } @@ -272,38 +290,39 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) // Get the start and the end of the expression. *arg = skipwhite((*arg) + 1); - s = (char_u *)(*arg); + s = *arg; ret = skip_expr(arg); if (ret == FAIL) { goto errret; } - e = (char_u *)(*arg); + e = *arg; *arg = skipwhite(*arg); if (**arg != '}') { + semsg(_("E451: Expected }: %s"), *arg); goto errret; } (*arg)++; if (evaluate) { int flags = 0; - char_u *p; + char *p; garray_T newlines; - char_u *name = get_lambda_name(); + char *name = get_lambda_name(); - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); pt = xcalloc(1, sizeof(partial_T)); - ga_init(&newlines, (int)sizeof(char_u *), 1); + ga_init(&newlines, (int)sizeof(char *), 1); ga_grow(&newlines, 1); // Add "return " before the expression. size_t len = (size_t)(7 + e - s + 1); - p = (char_u *)xmalloc(len); - ((char **)(newlines.ga_data))[newlines.ga_len++] = (char *)p; + p = xmalloc(len); + ((char **)(newlines.ga_data))[newlines.ga_len++] = p; STRCPY(p, "return "); - STRLCPY(p + 7, s, e - s + 1); - if (strstr((char *)p + 7, "a:") == NULL) { + xstrlcpy(p + 7, s, (size_t)(e - s) + 1); + if (strstr(p + 7, "a:") == NULL) { // No a: variables are used for sure. flags |= FC_NOARGS; } @@ -312,7 +331,7 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate) set_ufunc_name(fp, name); hash_add(&func_hashtab, UF2HIKEY(fp)); fp->uf_args = newargs; - ga_init(&fp->uf_def_args, (int)sizeof(char_u *), 1); + ga_init(&fp->uf_def_args, (int)sizeof(char *), 1); fp->uf_lines = newlines; if (current_funccal != NULL && eval_lavars) { flags |= FC_CLOSURE; @@ -365,7 +384,7 @@ errret: /// was not found. /// /// @return name of the function. -char_u *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload) +char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload) FUNC_ATTR_NONNULL_ARG(1, 2) { if (partialp != NULL) { @@ -376,10 +395,10 @@ char_u *deref_func_name(const char *name, int *lenp, partial_T **const partialp, if (v != NULL && v->di_tv.v_type == VAR_FUNC) { if (v->di_tv.vval.v_string == NULL) { // just in case *lenp = 0; - return (char_u *)""; + return ""; } - *lenp = (int)STRLEN(v->di_tv.vval.v_string); - return (char_u *)v->di_tv.vval.v_string; + *lenp = (int)strlen(v->di_tv.vval.v_string); + return v->di_tv.vval.v_string; } if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) { @@ -387,31 +406,31 @@ char_u *deref_func_name(const char *name, int *lenp, partial_T **const partialp, if (pt == NULL) { // just in case *lenp = 0; - return (char_u *)""; + return ""; } if (partialp != NULL) { *partialp = pt; } - char_u *s = (char_u *)partial_name(pt); - *lenp = (int)STRLEN(s); + char *s = partial_name(pt); + *lenp = (int)strlen(s); return s; } - return (char_u *)name; + return (char *)name; } /// Give an error message with a function name. Handle <SNR> things. /// /// @param ermsg must be passed without translation (use N_() instead of _()). /// @param name function name -void emsg_funcname(char *ermsg, const char_u *name) +void emsg_funcname(char *ermsg, const char *name) { - char_u *p; + char *p; - if (*name == K_SPECIAL) { - p = concat_str((char_u *)"<SNR>", name + 3); + if ((uint8_t)(*name) == K_SPECIAL) { + p = concat_str("<SNR>", name + 3); } else { - p = (char_u *)name; + p = (char *)name; } semsg(_(ermsg), p); @@ -429,7 +448,7 @@ void emsg_funcname(char *ermsg, const char_u *name) /// @param funcexe various values /// /// @return OK or FAIL. -int get_func_tv(const char_u *name, int len, typval_T *rettv, char **arg, funcexe_T *funcexe) +int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, funcexe_T *funcexe) { char *argp; int ret = OK; @@ -439,12 +458,12 @@ int get_func_tv(const char_u *name, int len, typval_T *rettv, char **arg, funcex // Get the arguments. argp = *arg; while (argcount < MAX_FUNC_ARGS - - (funcexe->partial == NULL ? 0 : funcexe->partial->pt_argc)) { + - (funcexe->fe_partial == NULL ? 0 : funcexe->fe_partial->pt_argc)) { argp = skipwhite(argp + 1); // skip the '(' or ',' if (*argp == ')' || *argp == ',' || *argp == NUL) { break; } - if (eval1(&argp, &argvars[argcount], funcexe->evaluate) == FAIL) { + if (eval1(&argp, &argvars[argcount], funcexe->fe_evaluate) == FAIL) { ret = FAIL; break; } @@ -463,7 +482,7 @@ int get_func_tv(const char_u *name, int len, typval_T *rettv, char **arg, funcex int i = 0; if (get_vim_var_nr(VV_TESTING)) { - // Prepare for calling garbagecollect_for_testing(), need to know + // Prepare for calling test_garbagecollect_now(), need to know // what variables are used on the call stack. if (funcargs.ga_itemsize == 0) { ga_init(&funcargs, (int)sizeof(typval_T *), 50); @@ -473,7 +492,7 @@ int get_func_tv(const char_u *name, int len, typval_T *rettv, char **arg, funcex ((typval_T **)funcargs.ga_data)[funcargs.ga_len++] = &argvars[i]; } } - ret = call_func((char *)name, len, rettv, argcount, argvars, funcexe); + ret = call_func(name, len, rettv, argcount, argvars, funcexe); funcargs.ga_len -= i; } else if (!aborting()) { @@ -525,48 +544,47 @@ static inline bool eval_fname_sid(const char *const name) /// /// @return transformed name: either `fname_buf` or a pointer to an allocated /// memory. -static char_u *fname_trans_sid(const char_u *const name, char_u *const fname_buf, - char_u **const tofree, int *const error) +static char *fname_trans_sid(const char *const name, char *const fname_buf, char **const tofree, + int *const error) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - char_u *fname; - const int llen = eval_fname_script((const char *)name); - if (llen > 0) { - fname_buf[0] = K_SPECIAL; - fname_buf[1] = KS_EXTRA; - fname_buf[2] = KE_SNR; - int i = 3; - if (eval_fname_sid((const char *)name)) { // "<SID>" or "s:" - if (current_sctx.sc_sid <= 0) { - *error = ERROR_SCRIPT; - } else { - snprintf((char *)fname_buf + i, (size_t)(FLEN_FIXED + 1 - i), "%" PRId64 "_", - (int64_t)current_sctx.sc_sid); - i = (int)STRLEN(fname_buf); - } - } - if ((size_t)i + STRLEN(name + llen) < FLEN_FIXED) { - STRCPY(fname_buf + i, name + llen); - fname = fname_buf; + const int llen = eval_fname_script(name); + if (llen == 0) { + return (char *)name; // no prefix + } + + fname_buf[0] = (char)K_SPECIAL; + fname_buf[1] = (char)KS_EXTRA; + fname_buf[2] = KE_SNR; + int i = 3; + if (eval_fname_sid(name)) { // "<SID>" or "s:" + if (current_sctx.sc_sid <= 0) { + *error = FCERR_SCRIPT; } else { - fname = xmalloc((size_t)i + STRLEN(name + llen) + 1); - *tofree = fname; - memmove(fname, fname_buf, (size_t)i); - STRCPY(fname + i, name + llen); + snprintf(fname_buf + i, (size_t)(FLEN_FIXED + 1 - i), "%" PRId64 "_", + (int64_t)current_sctx.sc_sid); + i = (int)strlen(fname_buf); } + } + char *fname; + if ((size_t)i + strlen(name + llen) < FLEN_FIXED) { + STRCPY(fname_buf + i, name + llen); + fname = fname_buf; } else { - fname = (char_u *)name; + fname = xmalloc((size_t)i + strlen(name + llen) + 1); + *tofree = fname; + memmove(fname, fname_buf, (size_t)i); + STRCPY(fname + i, name + llen); } - return fname; } /// Find a function by name, return pointer to it in ufuncs. /// /// @return NULL for unknown function. -ufunc_T *find_func(const char_u *name) +ufunc_T *find_func(const char *name) { - hashitem_T *hi = hash_find(&func_hashtab, (char *)name); + hashitem_T *hi = hash_find(&func_hashtab, name); if (!HASHITEM_EMPTY(hi)) { return HI2UF(hi); } @@ -576,9 +594,9 @@ ufunc_T *find_func(const char_u *name) /// Copy the function name of "fp" to buffer "buf". /// "buf" must be able to hold the function name plus three bytes. /// Takes care of script-local function names. -static void cat_func_name(char_u *buf, ufunc_T *fp) +static void cat_func_name(char *buf, ufunc_T *fp) { - if (fp->uf_name[0] == K_SPECIAL) { + if ((uint8_t)fp->uf_name[0] == K_SPECIAL) { STRCPY(buf, "<SNR>"); STRCAT(buf, fp->uf_name + 3); } else { @@ -593,7 +611,7 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) STRCPY(v->di_key, name); #endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(dp, v); + hash_add(&dp->dv_hashtab, (char *)v->di_key); v->di_tv.v_type = VAR_NUMBER; v->di_tv.v_lock = VAR_FIXED; v->di_tv.vval.v_number = nr; @@ -758,9 +776,9 @@ static void func_clear_items(ufunc_T *fp) ga_clear_strings(&(fp->uf_lines)); XFREE_CLEAR(fp->uf_name_exp); - if (fp->uf_cb_free != NULL) { - fp->uf_cb_free(fp->uf_cb_state); - fp->uf_cb_free = NULL; + if (fp->uf_flags & FC_LUAREF) { + api_free_luaref(fp->uf_luaref); + fp->uf_luaref = LUA_NOREF; } XFREE_CLEAR(fp->uf_tml_count); @@ -828,7 +846,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett int fixvar_idx = 0; // index in fixvar[] int ai; bool islambda = false; - char_u numbuf[NUMBUFLEN]; + char numbuf[NUMBUFLEN]; char *name; typval_T *tv_to_free[MAX_FUNC_ARGS]; int tv_to_free_len = 0; @@ -852,7 +870,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett saveRedobuff(&save_redo); did_save_redo = true; } - ++fp->uf_calls; + fp->uf_calls++; // check for CTRL-C hit line_breakcheck(); // prepare the funccall_T structure @@ -863,14 +881,14 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett fc->rettv = rettv; fc->level = ex_nesting_level; // Check if this function has a breakpoint. - fc->breakpoint = dbg_find_breakpoint(false, fp->uf_name, (linenr_T)0); + fc->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, (linenr_T)0); fc->dbg_tick = debug_tick; // Set up fields for closure. ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1); func_ptr_ref(fp); - if (STRNCMP(fp->uf_name, "<lambda>", 8) == 0) { + if (strncmp(fp->uf_name, "<lambda>", 8) == 0) { islambda = true; } @@ -889,11 +907,11 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett STRCPY(name, "self"); #endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(&fc->l_vars, v); + hash_add(&fc->l_vars.dv_hashtab, (char *)v->di_key); v->di_tv.v_type = VAR_DICT; v->di_tv.v_lock = VAR_UNLOCKED; v->di_tv.vval.v_dict = selfdict; - ++selfdict->dv_refcount; + selfdict->dv_refcount++; } // Init a: variables, unless none found (in lambda). @@ -915,7 +933,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett STRCPY(name, "000"); #endif v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; - tv_dict_add(&fc->l_avars, v); + hash_add(&fc->l_avars.dv_hashtab, (char *)v->di_key); v->di_tv.v_type = VAR_LIST; v->di_tv.v_lock = VAR_FIXED; v->di_tv.vval.v_list = &fc->l_varlist; @@ -967,14 +985,14 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett break; } // "..." argument a:1, a:2, etc. - snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1); - name = (char *)numbuf; + snprintf(numbuf, sizeof(numbuf), "%d", ai + 1); + name = numbuf; } - if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { + if (fixvar_idx < FIXVAR_CNT && strlen(name) <= VAR_SHORT_LEN) { v = (dictitem_T *)&fc->fixvar[fixvar_idx++]; v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; } else { - v = xmalloc(sizeof(dictitem_T) + STRLEN(name)); + v = xmalloc(sizeof(dictitem_T) + strlen(name)); v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX | DI_FLAGS_ALLOC; } STRCPY(v->di_key, name); @@ -993,16 +1011,16 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett // Named arguments can be accessed without the "a:" prefix in lambda // expressions. Add to the l: dict. tv_copy(&v->di_tv, &v->di_tv); - tv_dict_add(&fc->l_vars, v); + hash_add(&fc->l_vars.dv_hashtab, (char *)v->di_key); } else { - tv_dict_add(&fc->l_avars, v); + hash_add(&fc->l_avars.dv_hashtab, (char *)v->di_key); } if (ai >= 0 && ai < MAX_FUNC_ARGS) { listitem_T *li = &fc->l_listitems[ai]; *TV_LIST_ITEM_TV(li) = argvars[i]; - TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED; + TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED; tv_list_append(&fc->l_varlist, li); } } @@ -1058,7 +1076,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett bool func_not_yet_profiling_but_should = do_profiling_yes - && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); + && !fp->uf_profiling && has_profiling(false, (char *)fp->uf_name, NULL); if (func_not_yet_profiling_but_should) { started_profiling = true; @@ -1071,7 +1089,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett || (fc->caller != NULL && fc->caller->func->uf_profiling)); if (func_or_func_caller_profiling) { - ++fp->uf_tm_count; + fp->uf_tm_count++; call_start = profile_start(); fp->uf_tm_children = profile_zero(); } @@ -1083,7 +1101,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett const sctx_T save_current_sctx = current_sctx; current_sctx = fp->uf_script_ctx; save_did_emsg = did_emsg; - did_emsg = FALSE; + did_emsg = false; if (default_arg_err && (fp->uf_flags & FC_ABORT)) { did_emsg = true; @@ -1208,9 +1226,37 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett /// For the first we only count the name stored in func_hashtab as a reference, /// using function() does not count as a reference, because the function is /// looked up by name. -static bool func_name_refcount(char_u *name) +static bool func_name_refcount(const char *name) +{ + return isdigit((uint8_t)(*name)) || *name == '<'; +} + +/// Call a user function after checking the arguments. +static int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, + funcexe_T *funcexe, dict_T *selfdict) + FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5) { - return isdigit(*name) || *name == '<'; + if (fp->uf_flags & FC_LUAREF) { + return typval_exec_lua_callable(fp->uf_luaref, argcount, argvars, rettv); + } + + if ((fp->uf_flags & FC_RANGE) && funcexe->fe_doesrange != NULL) { + *funcexe->fe_doesrange = true; + } + int error; + if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) { + error = FCERR_TOOFEW; + } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { + error = FCERR_TOOMANY; + } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { + error = FCERR_DICT; + } else { + // Call the user function. + call_user_func(fp, argcount, argvars, rettv, funcexe->fe_firstline, funcexe->fe_lastline, + (fp->uf_flags & FC_DICT) ? selfdict : NULL); + error = FCERR_NONE; + } + return error; } static funccal_entry_T *funccal_stack = NULL; @@ -1252,7 +1298,7 @@ void free_all_functions(void) ufunc_T *fp; uint64_t skipped = 0; uint64_t todo = 1; - uint64_t used; + int changed; // Clean up the current_funccal chain and the funccal stack. while (current_funccal != NULL) { @@ -1276,9 +1322,9 @@ void free_all_functions(void) if (func_name_refcount(fp->uf_name)) { skipped++; } else { - used = func_hashtab.ht_used; + changed = func_hashtab.ht_changed; func_clear(fp, true); - if (used != func_hashtab.ht_used) { + if (changed != func_hashtab.ht_changed) { skipped = 0; break; } @@ -1322,10 +1368,10 @@ void free_all_functions(void) /// @param[in] len length of "name", or -1 for NUL terminated. /// /// @return true if "name" looks like a builtin function name: starts with a -/// lower case letter and doesn't contain AUTOLOAD_CHAR. +/// lower case letter and doesn't contain AUTOLOAD_CHAR or ':'. static bool builtin_function(const char *name, int len) { - if (!ASCII_ISLOWER(name[0])) { + if (!ASCII_ISLOWER(name[0]) || name[1] == ':') { return false; } @@ -1336,7 +1382,7 @@ static bool builtin_function(const char *name, int len) return p == NULL; } -int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv) +int func_call(char *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv) { typval_T argv[MAX_FUNC_ARGS + 1]; int argc = 0; @@ -1353,12 +1399,12 @@ int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict }); funcexe_T funcexe = FUNCEXE_INIT; - funcexe.firstline = curwin->w_cursor.lnum; - funcexe.lastline = curwin->w_cursor.lnum; - funcexe.evaluate = true; - funcexe.partial = partial; - funcexe.selfdict = selfdict; - r = call_func((char *)name, -1, rettv, argc, argv, &funcexe); + funcexe.fe_firstline = curwin->w_cursor.lnum; + funcexe.fe_lastline = curwin->w_cursor.lnum; + funcexe.fe_evaluate = true; + funcexe.fe_partial = partial; + funcexe.fe_selfdict = selfdict; + r = call_func(name, -1, rettv, argc, argv, &funcexe); func_call_skip_call: // Free the arguments. @@ -1369,35 +1415,50 @@ func_call_skip_call: return r; } +/// call the 'callback' function and return the result as a number. +/// Returns -2 when calling the function fails. Uses argv[0] to argv[argc - 1] +/// for the function arguments. argv[argc] should have type VAR_UNKNOWN. +/// +/// @param argcount number of "argvars" +/// @param argvars vars for arguments, must have "argcount" PLUS ONE elements! +varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argvars) +{ + typval_T rettv; + if (!callback_call(callback, argcount, argvars, &rettv)) { + return -2; + } + + varnumber_T retval = tv_get_number_chk(&rettv, NULL); + tv_clear(&rettv); + return retval; +} + /// Give an error message for the result of a function. /// Nothing if "error" is FCERR_NONE. -static void user_func_error(int error, const char_u *name) +static void user_func_error(int error, const char *name) FUNC_ATTR_NONNULL_ALL { switch (error) { - case ERROR_UNKNOWN: + case FCERR_UNKNOWN: emsg_funcname(N_("E117: Unknown function: %s"), name); break; - case ERROR_NOTMETHOD: + case FCERR_NOTMETHOD: emsg_funcname(N_("E276: Cannot use function as a method: %s"), name); break; - case ERROR_DELETED: + case FCERR_DELETED: emsg_funcname(N_("E933: Function was deleted: %s"), name); break; - case ERROR_TOOMANY: + case FCERR_TOOMANY: emsg_funcname(_(e_toomanyarg), name); break; - case ERROR_TOOFEW: - emsg_funcname(N_("E119: Not enough arguments for function: %s"), - name); + case FCERR_TOOFEW: + emsg_funcname(N_("E119: Not enough arguments for function: %s"), name); break; - case ERROR_SCRIPT: - emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), - name); + case FCERR_SCRIPT: + emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), name); break; - case ERROR_DICT: - emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), - name); + case FCERR_DICT: + emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), name); break; } } @@ -1435,27 +1496,27 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6) { int ret = FAIL; - int error = ERROR_NONE; + int error = FCERR_NONE; ufunc_T *fp = NULL; - char_u fname_buf[FLEN_FIXED + 1]; - char_u *tofree = NULL; - char_u *fname = NULL; - char_u *name = NULL; + char fname_buf[FLEN_FIXED + 1]; + char *tofree = NULL; + char *fname = NULL; + char *name = NULL; int argcount = argcount_in; typval_T *argvars = argvars_in; - dict_T *selfdict = funcexe->selfdict; + dict_T *selfdict = funcexe->fe_selfdict; typval_T argv[MAX_FUNC_ARGS + 1]; // used when "partial" or - // "funcexe->basetv" is not NULL + // "funcexe->fe_basetv" is not NULL int argv_clear = 0; int argv_base = 0; - partial_T *partial = funcexe->partial; + partial_T *partial = funcexe->fe_partial; // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv) // even when call_func() returns FAIL. rettv->v_type = VAR_UNKNOWN; if (len <= 0) { - len = (int)STRLEN(funcname); + len = (int)strlen(funcname); } if (partial != NULL) { fp = partial->pt_func; @@ -1463,12 +1524,12 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t if (fp == NULL) { // Make a copy of the name, if it comes from a funcref variable it could // be changed or deleted in the called function. - name = vim_strnsave((char_u *)funcname, (size_t)len); + name = xstrnsave(funcname, (size_t)len); fname = fname_trans_sid(name, fname_buf, &tofree, &error); } - if (funcexe->doesrange != NULL) { - *funcexe->doesrange = false; + if (funcexe->fe_doesrange != NULL) { + *funcexe->fe_doesrange = false; } if (partial != NULL) { @@ -1478,10 +1539,10 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t if (partial->pt_dict != NULL && (selfdict == NULL || !partial->pt_auto)) { selfdict = partial->pt_dict; } - if (error == ERROR_NONE && partial->pt_argc > 0) { + if (error == FCERR_NONE && partial->pt_argc > 0) { for (argv_clear = 0; argv_clear < partial->pt_argc; argv_clear++) { if (argv_clear + argcount_in >= MAX_FUNC_ARGS) { - error = ERROR_TOOMANY; + error = FCERR_TOOMANY; goto theend; } tv_copy(&partial->pt_argv[argv_clear], &argv[argv_clear]); @@ -1494,8 +1555,8 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t } } - if (error == ERROR_NONE && funcexe->evaluate) { - char_u *rfname = fname; + if (error == FCERR_NONE && funcexe->fe_evaluate) { + char *rfname = fname; // Ignore "g:" before a function name. if (fp == NULL && fname[0] == 'g' && fname[1] == ':') { @@ -1504,12 +1565,12 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t rettv->v_type = VAR_NUMBER; // default rettv is number zero rettv->vval.v_number = 0; - error = ERROR_UNKNOWN; + error = FCERR_UNKNOWN; if (is_luafunc(partial)) { if (len > 0) { - error = ERROR_NONE; - argv_add_base(funcexe->basetv, &argvars, &argcount, argv, &argv_base); + error = FCERR_NONE; + argv_add_base(funcexe->fe_basetv, &argvars, &argcount, argv, &argv_base); nlua_typval_call(funcname, (size_t)len, argvars, argcount, rettv); } else { // v:lua was called directly; show its name in the emsg @@ -1524,71 +1585,50 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t // Trigger FuncUndefined event, may load the function. if (fp == NULL - && apply_autocmds(EVENT_FUNCUNDEFINED, (char *)rfname, (char *)rfname, true, NULL) + && apply_autocmds(EVENT_FUNCUNDEFINED, rfname, rfname, true, NULL) && !aborting()) { // executed an autocommand, search for the function again fp = find_func(rfname); } // Try loading a package. - if (fp == NULL && script_autoload((const char *)rfname, STRLEN(rfname), + if (fp == NULL && script_autoload((const char *)rfname, strlen(rfname), true) && !aborting()) { // Loaded a package, search for the function again. fp = find_func(rfname); } if (fp != NULL && (fp->uf_flags & FC_DELETED)) { - error = ERROR_DELETED; - } else if (fp != NULL && (fp->uf_flags & FC_CFUNC)) { - cfunc_T cb = fp->uf_cb; - error = (*cb)(argcount, argvars, rettv, fp->uf_cb_state); + error = FCERR_DELETED; } else if (fp != NULL) { - if (funcexe->argv_func != NULL) { + if (funcexe->fe_argv_func != NULL) { // postponed filling in the arguments, do it now - argcount = funcexe->argv_func(argcount, argvars, argv_clear, - fp->uf_args.ga_len); + argcount = funcexe->fe_argv_func(argcount, argvars, argv_clear, fp); } - argv_add_base(funcexe->basetv, &argvars, &argcount, argv, &argv_base); + argv_add_base(funcexe->fe_basetv, &argvars, &argcount, argv, &argv_base); - if (fp->uf_flags & FC_RANGE && funcexe->doesrange != NULL) { - *funcexe->doesrange = true; - } - if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) { - error = ERROR_TOOFEW; - } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) { - error = ERROR_TOOMANY; - } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) { - error = ERROR_DICT; - } else { - // Call the user function. - call_user_func(fp, argcount, argvars, rettv, funcexe->firstline, - funcexe->lastline, - (fp->uf_flags & FC_DICT) ? selfdict : NULL); - error = ERROR_NONE; - } + error = call_user_func_check(fp, argcount, argvars, rettv, funcexe, selfdict); } - } else if (funcexe->basetv != NULL) { + } else if (funcexe->fe_basetv != NULL) { // expr->method(): Find the method name in the table, call its // implementation with the base as one of the arguments. error = call_internal_method(fname, argcount, argvars, rettv, - funcexe->basetv); + funcexe->fe_basetv); } else { // Find the function name in the table, call its implementation. error = call_internal_func(fname, argcount, argvars, rettv); } - /* - * The function call (or "FuncUndefined" autocommand sequence) might - * have been aborted by an error, an interrupt, or an explicitly thrown - * exception that has not been caught so far. This situation can be - * tested for by calling aborting(). For an error in an internal - * function or for the "E132" error in call_user_func(), however, the - * throw point at which the "force_abort" flag (temporarily reset by - * emsg()) is normally updated has not been reached yet. We need to - * update that flag first to make aborting() reliable. - */ + // The function call (or "FuncUndefined" autocommand sequence) might + // have been aborted by an error, an interrupt, or an explicitly thrown + // exception that has not been caught so far. This situation can be + // tested for by calling aborting(). For an error in an internal + // function or for the "E132" error in call_user_func(), however, the + // throw point at which the "force_abort" flag (temporarily reset by + // emsg()) is normally updated has not been reached yet. We need to + // update that flag first to make aborting() reliable. update_force_abort(); } - if (error == ERROR_NONE) { + if (error == FCERR_NONE) { ret = OK; } @@ -1596,7 +1636,7 @@ theend: // Report an error unless the argument evaluation or function call has been // cancelled due to an aborting error, an interrupt, or an exception. if (!aborting()) { - user_func_error(error, (name != NULL) ? name : (char_u *)funcname); + user_func_error(error, (name != NULL) ? name : funcname); } // clear the copies made from the partial @@ -1610,6 +1650,11 @@ theend: return ret; } +char *printable_func_name(ufunc_T *fp) +{ + return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name; +} + /// List the head of the function: "name(arg1, arg2)". /// /// @param[in] fp Function pointer. @@ -1679,12 +1724,12 @@ static void list_func_head(ufunc_T *fp, int indent, bool force) /// @param partial return: partial of a FuncRef /// /// @return the function name in allocated memory, or NULL for failure. -char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, partial_T **partial) +char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, partial_T **partial) FUNC_ATTR_NONNULL_ARG(1) { - char_u *name = NULL; - const char_u *start; - const char_u *end; + char *name = NULL; + const char *start; + const char *end; int lead; int len; lval_T lv; @@ -1692,7 +1737,7 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa if (fdp != NULL) { CLEAR_POINTER(fdp); } - start = (char_u *)(*pp); + start = *pp; // Check for hard coded <SNR>: already translated function ID (from a user // command). @@ -1700,19 +1745,19 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa && (*pp)[2] == KE_SNR) { *pp += 3; len = get_id_len((const char **)pp) + 3; - return (char_u *)xmemdupz(start, (size_t)len); + return xmemdupz(start, (size_t)len); } // A name starting with "<SID>" or "<SNR>" is local to a script. But // don't skip over "s:", get_lval() needs it for "s:dict.func". - lead = eval_fname_script((const char *)start); + lead = eval_fname_script(start); if (lead > 2) { start += lead; } // Note that TFN_ flags use the same values as GLV_ flags. - end = (char_u *)get_lval((char *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, - lead > 2 ? 0 : FNE_CHECK_START); + end = get_lval((char *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY, + lead > 2 ? 0 : FNE_CHECK_START); if (end == start) { if (!skip) { emsg(_("E129: Function name required")); @@ -1720,11 +1765,9 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa goto theend; } if (end == NULL || (lv.ll_tv != NULL && (lead > 2 || lv.ll_range))) { - /* - * Report an invalid expression in braces, unless the expression - * evaluation has been cancelled due to an aborting error, an - * interrupt, or an exception. - */ + // Report an invalid expression in braces, unless the expression + // evaluation has been cancelled due to an aborting error, an + // interrupt, or an exception. if (!aborting()) { if (end != NULL) { semsg(_(e_invarg2), start); @@ -1738,17 +1781,17 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa if (lv.ll_tv != NULL) { if (fdp != NULL) { fdp->fd_dict = lv.ll_dict; - fdp->fd_newkey = (char_u *)lv.ll_newkey; + fdp->fd_newkey = lv.ll_newkey; lv.ll_newkey = NULL; fdp->fd_di = lv.ll_di; } if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { - name = vim_strsave((char_u *)lv.ll_tv->vval.v_string); + name = xstrdup(lv.ll_tv->vval.v_string); *pp = (char *)end; } else if (lv.ll_tv->v_type == VAR_PARTIAL && lv.ll_tv->vval.v_partial != NULL) { if (is_luafunc(lv.ll_tv->vval.v_partial) && *end == '.') { - len = check_luafunc_name((const char *)end + 1, true); + len = check_luafunc_name(end + 1, true); if (len == 0) { semsg(e_invexpr2, "v:lua"); goto theend; @@ -1757,7 +1800,7 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa memcpy(name, end + 1, (size_t)len); *pp = (char *)end + 1 + len; } else { - name = vim_strsave((char_u *)partial_name(lv.ll_tv->vval.v_partial)); + name = xstrdup(partial_name(lv.ll_tv->vval.v_partial)); *pp = (char *)end; } if (partial != NULL) { @@ -1791,22 +1834,21 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa name = NULL; } } else if (!(flags & TFN_NO_DEREF)) { - len = (int)(end - (char_u *)(*pp)); - name = deref_func_name((const char *)(*pp), &len, partial, - flags & TFN_NO_AUTOLOAD); - if (name == (char_u *)(*pp)) { + len = (int)(end - *pp); + name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD); + if (name == *pp) { name = NULL; } } if (name != NULL) { - name = vim_strsave(name); + name = xstrdup(name); *pp = (char *)end; - if (STRNCMP(name, "<SNR>", 5) == 0) { + if (strncmp(name, "<SNR>", 5) == 0) { // Change "<SNR>" to the byte sequence. - name[0] = K_SPECIAL; - name[1] = KS_EXTRA; + name[0] = (char)K_SPECIAL; + name[1] = (char)KS_EXTRA; name[2] = KE_SNR; - memmove(name + 3, name + 5, strlen((char *)name + 5) + 1); + memmove(name + 3, name + 5, strlen(name + 5) + 1); } goto theend; } @@ -1828,7 +1870,7 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa lv.ll_name += 2; lv.ll_name_len -= 2; } - len = (int)((const char *)end - lv.ll_name); + len = (int)(end - lv.ll_name); } size_t sid_buf_len = 0; @@ -1859,7 +1901,7 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa } if (!skip && !(flags & TFN_QUIET) && !(flags & TFN_NO_DEREF)) { - char_u *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len); + char *cp = xmemrchr(lv.ll_name, ':', lv.ll_name_len); if (cp != NULL && cp < end) { semsg(_("E884: Function name cannot contain a colon: %s"), start); @@ -1869,8 +1911,8 @@ char_u *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, pa name = xmalloc((size_t)len + (size_t)lead + 1); if (!skip && lead > 0) { - name[0] = K_SPECIAL; - name[1] = KS_EXTRA; + name[0] = (char)K_SPECIAL; + name[1] = (char)KS_EXTRA; name[2] = KE_SNR; if (sid_buf_len > 0) { // If it's "<SID>" memcpy(name + 3, sid_buf, sid_buf_len); @@ -1885,18 +1927,105 @@ theend: return name; } +/// If the "funcname" starts with "s:" or "<SID>", then expands it to the +/// current script ID and returns the expanded function name. The caller should +/// free the returned name. If not called from a script context or the function +/// name doesn't start with these prefixes, then returns NULL. +/// This doesn't check whether the script-local function exists or not. +char *get_scriptlocal_funcname(char *funcname) +{ + if (funcname == NULL) { + return NULL; + } + + if (strncmp(funcname, "s:", 2) != 0 + && strncmp(funcname, "<SID>", 5) != 0) { + // The function name is not a script-local function name + return NULL; + } + + if (!SCRIPT_ID_VALID(current_sctx.sc_sid)) { + emsg(_(e_usingsid)); + return NULL; + } + + char sid_buf[25]; + // Expand s: and <SID> prefix into <SNR>nr_<name> + snprintf(sid_buf, sizeof(sid_buf), "<SNR>%" PRId64 "_", + (int64_t)current_sctx.sc_sid); + const int off = *funcname == 's' ? 2 : 5; + char *newname = xmalloc(strlen(sid_buf) + strlen(funcname + off) + 1); + STRCPY(newname, sid_buf); + STRCAT(newname, funcname + off); + + return newname; +} + +/// Call trans_function_name(), except that a lambda is returned as-is. +/// Returns the name in allocated memory. +char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi) +{ + char *p = *name; + char *saved; + + if (strncmp(p, "<lambda>", 8) == 0) { + p += 8; + (void)getdigits(&p, false, 0); + saved = xstrndup(*name, (size_t)(p - *name)); + if (fudi != NULL) { + CLEAR_POINTER(fudi); + } + } else { + saved = trans_function_name(&p, skip, flags, fudi, NULL); + } + *name = p; + return saved; +} + +#define MAX_FUNC_NESTING 50 + +/// List functions. +/// +/// @param regmatch When NULL, all of them. +/// Otherwise functions matching "regmatch". +static void list_functions(regmatch_T *regmatch) +{ + const int changed = func_hashtab.ht_changed; + size_t todo = func_hashtab.ht_used; + const hashitem_T *const ht_array = func_hashtab.ht_array; + + for (const hashitem_T *hi = ht_array; todo > 0 && !got_int; hi++) { + if (!HASHITEM_EMPTY(hi)) { + ufunc_T *fp = HI2UF(hi); + todo--; + if ((fp->uf_flags & FC_DEAD) == 0 + && (regmatch == NULL + ? (!message_filtered((char *)fp->uf_name) + && !func_name_refcount(fp->uf_name)) + : (!isdigit((uint8_t)(*fp->uf_name)) + && vim_regexec(regmatch, (char *)fp->uf_name, 0)))) { + list_func_head(fp, false, false); + if (changed != func_hashtab.ht_changed) { + emsg(_("E454: function list was modified")); + return; + } + } + } + } +} + /// ":function" void ex_function(exarg_T *eap) { - char_u *theline; - char_u *line_to_free = NULL; - char_u c; + char *theline; + char *line_to_free = NULL; + char c; int saved_did_emsg; bool saved_wait_return = need_wait_return; - char_u *name = NULL; - char_u *p; - char_u *arg; - char_u *line_arg = NULL; + char *name = NULL; + char *p; + char *arg; + char *line_arg = NULL; garray_T newargs; garray_T default_args; garray_T newlines; @@ -1911,44 +2040,27 @@ void ex_function(exarg_T *eap) static int func_nr = 0; // number for nameless function int paren; hashtab_T *ht; - int todo; hashitem_T *hi; linenr_T sourcing_lnum_off; linenr_T sourcing_lnum_top; bool is_heredoc = false; - char_u *skip_until = NULL; - char_u *heredoc_trimmed = NULL; + char *skip_until = NULL; + char *heredoc_trimmed = NULL; bool show_block = false; bool do_concat = true; - /* - * ":function" without argument: list functions. - */ + // ":function" without argument: list functions. if (ends_excmd(*eap->arg)) { if (!eap->skip) { - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - fp = HI2UF(hi); - if (message_filtered(fp->uf_name)) { - continue; - } - if (!func_name_refcount(fp->uf_name)) { - list_func_head(fp, false, false); - } - } - } + list_functions(NULL); } - eap->nextcmd = (char *)check_nextcmd((char_u *)eap->arg); + eap->nextcmd = check_nextcmd(eap->arg); return; } - /* - * ":function /pat": list functions matching pattern. - */ + // ":function /pat": list functions matching pattern. if (*eap->arg == '/') { - p = skip_regexp((char_u *)eap->arg + 1, '/', true, NULL); + p = skip_regexp(eap->arg + 1, '/', true); if (!eap->skip) { regmatch_T regmatch; @@ -1958,25 +2070,14 @@ void ex_function(exarg_T *eap) *p = c; if (regmatch.regprog != NULL) { regmatch.rm_ic = p_ic; - - todo = (int)func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { - if (!HASHITEM_EMPTY(hi)) { - todo--; - fp = HI2UF(hi); - if (!isdigit(*fp->uf_name) - && vim_regexec(®match, (char *)fp->uf_name, 0)) { - list_func_head(fp, false, false); - } - } - } + list_functions(®match); vim_regfree(regmatch.regprog); } } if (*p == '/') { p++; } - eap->nextcmd = (char *)check_nextcmd(p); + eap->nextcmd = check_nextcmd(p); return; } @@ -1994,30 +2095,27 @@ void ex_function(exarg_T *eap) // "fudi.fd_di" set, "fudi.fd_newkey" == NULL // s:func script-local function name // g:func global function name, same as "func" - p = (char_u *)eap->arg; - name = trans_function_name((char **)&p, eap->skip, TFN_NO_AUTOLOAD, &fudi, NULL); - paren = (vim_strchr((char *)p, '(') != NULL); + p = eap->arg; + name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi); + paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { - /* - * Return on an invalid expression in braces, unless the expression - * evaluation has been cancelled due to an aborting error, an - * interrupt, or an exception. - */ + // Return on an invalid expression in braces, unless the expression + // evaluation has been cancelled due to an aborting error, an + // interrupt, or an exception. if (!aborting()) { if (fudi.fd_newkey != NULL) { semsg(_(e_dictkey), fudi.fd_newkey); } xfree(fudi.fd_newkey); return; - } else { - eap->skip = TRUE; } + eap->skip = true; } // An error in a function call during evaluation of an expression in magic // braces should not cause the function not to be defined. saved_did_emsg = did_emsg; - did_emsg = FALSE; + did_emsg = false; // // ":function func" with only function name: list function. @@ -2026,11 +2124,11 @@ void ex_function(exarg_T *eap) // - exclude line numbers from function body // if (!paren) { - if (!ends_excmd(*skipwhite((char *)p))) { + if (!ends_excmd(*skipwhite(p))) { semsg(_(e_trailing_arg), p); goto ret_free; } - eap->nextcmd = (char *)check_nextcmd(p); + eap->nextcmd = check_nextcmd(p); if (eap->nextcmd != NULL) { *p = NUL; } @@ -2052,7 +2150,7 @@ void ex_function(exarg_T *eap) msg_putchar(' '); } } - msg_prt_line((char_u *)FUNCLINE(fp, j), false); + msg_prt_line(FUNCLINE(fp, j), false); ui_flush(); // show a line at a time os_breakcheck(); } @@ -2067,24 +2165,22 @@ void ex_function(exarg_T *eap) goto ret_free; } - /* - * ":function name(arg1, arg2)" Define function. - */ - p = (char_u *)skipwhite((char *)p); + // ":function name(arg1, arg2)" Define function. + p = skipwhite(p); if (*p != '(') { if (!eap->skip) { semsg(_("E124: Missing '(': %s"), eap->arg); goto ret_free; } // attempt to continue by skipping some text - if (vim_strchr((char *)p, '(') != NULL) { - p = (char_u *)vim_strchr((char *)p, '('); + if (vim_strchr(p, '(') != NULL) { + p = vim_strchr(p, '('); } } - p = (char_u *)skipwhite((char *)p + 1); + p = skipwhite(p + 1); - ga_init(&newargs, (int)sizeof(char_u *), 3); - ga_init(&newlines, (int)sizeof(char_u *), 3); + ga_init(&newargs, (int)sizeof(char *), 3); + ga_init(&newlines, (int)sizeof(char *), 3); if (!eap->skip) { // Check the name of the function. Unless it's a dictionary function @@ -2095,7 +2191,7 @@ void ex_function(exarg_T *eap) arg = fudi.fd_newkey; } if (arg != NULL && (fudi.fd_di == NULL || !tv_is_func(fudi.fd_di->di_tv))) { - int j = (*arg == K_SPECIAL) ? 3 : 0; + int j = ((uint8_t)(*arg) == K_SPECIAL) ? 3 : 0; while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) : eval_isnamec(arg[j]))) { j++; } @@ -2109,7 +2205,7 @@ void ex_function(exarg_T *eap) } } - if (get_function_args((char **)&p, ')', &newargs, &varargs, + if (get_function_args(&p, ')', &newargs, &varargs, &default_args, eap->skip) == FAIL) { goto errret_2; } @@ -2121,23 +2217,22 @@ void ex_function(exarg_T *eap) // find extra arguments "range", "dict", "abort" and "closure" for (;;) { - p = (char_u *)skipwhite((char *)p); - if (STRNCMP(p, "range", 5) == 0) { + p = skipwhite(p); + if (strncmp(p, "range", 5) == 0) { flags |= FC_RANGE; p += 5; - } else if (STRNCMP(p, "dict", 4) == 0) { + } else if (strncmp(p, "dict", 4) == 0) { flags |= FC_DICT; p += 4; - } else if (STRNCMP(p, "abort", 5) == 0) { + } else if (strncmp(p, "abort", 5) == 0) { flags |= FC_ABORT; p += 5; - } else if (STRNCMP(p, "closure", 7) == 0) { + } else if (strncmp(p, "closure", 7) == 0) { flags |= FC_CLOSURE; p += 7; if (current_funccal == NULL) { - emsg_funcname(N_ - ("E932: Closure function should not be at top level: %s"), - name == NULL ? (char_u *)"" : name); + emsg_funcname(N_("E932: Closure function should not be at top level: %s"), + name == NULL ? "" : name); goto erret; } } else { @@ -2153,9 +2248,7 @@ void ex_function(exarg_T *eap) semsg(_(e_trailing_arg), p); } - /* - * Read the body of the function, until ":endfunction" is found. - */ + // Read the body of the function, until ":endfunction" is found. if (KeyTyped) { // Check if the function already exists, don't let the user type the // whole function before telling him it doesn't work! For a script we @@ -2193,9 +2286,9 @@ void ex_function(exarg_T *eap) if (line_arg != NULL) { // Use eap->arg, split up in parts by line breaks. theline = line_arg; - p = (char_u *)vim_strchr((char *)theline, '\n'); + p = vim_strchr(theline, '\n'); if (p == NULL) { - line_arg += STRLEN(line_arg); + line_arg += strlen(line_arg); } else { *p = NUL; line_arg = p + 1; @@ -2205,7 +2298,7 @@ void ex_function(exarg_T *eap) if (eap->getline == NULL) { theline = getcmdline(':', 0L, indent, do_concat); } else { - theline = (char_u *)eap->getline(':', eap->cookie, indent, do_concat); + theline = eap->getline(':', eap->cookie, indent, do_concat); } line_to_free = theline; } @@ -2235,18 +2328,18 @@ void ex_function(exarg_T *eap) // * ":python <<EOF" and "EOF" // * ":let {var-name} =<< [trim] {marker}" and "{marker}" if (heredoc_trimmed == NULL - || (is_heredoc && (char_u *)skipwhite((char *)theline) == theline) - || STRNCMP(theline, heredoc_trimmed, - STRLEN(heredoc_trimmed)) == 0) { + || (is_heredoc && skipwhite(theline) == theline) + || strncmp(theline, heredoc_trimmed, + strlen(heredoc_trimmed)) == 0) { if (heredoc_trimmed == NULL) { p = theline; } else if (is_heredoc) { - p = (char_u *)skipwhite((char *)theline) == theline - ? theline : theline + STRLEN(heredoc_trimmed); + p = skipwhite(theline) == theline + ? theline : theline + strlen(heredoc_trimmed); } else { - p = theline + STRLEN(heredoc_trimmed); + p = theline + strlen(heredoc_trimmed); } - if (STRCMP(p, skip_until) == 0) { + if (strcmp(p, skip_until) == 0) { XFREE_CLEAR(skip_until); XFREE_CLEAR(heredoc_trimmed); do_concat = true; @@ -2258,27 +2351,26 @@ void ex_function(exarg_T *eap) for (p = theline; ascii_iswhite(*p) || *p == ':'; p++) {} // Check for "endfunction". - if (checkforcmd((char **)&p, "endfunction", 4) && nesting-- == 0) { + if (checkforcmd(&p, "endfunction", 4) && nesting-- == 0) { if (*p == '!') { p++; } - char_u *nextcmd = NULL; + char *nextcmd = NULL; if (*p == '|') { nextcmd = p + 1; - } else if (line_arg != NULL && *skipwhite((char *)line_arg) != NUL) { + } else if (line_arg != NULL && *skipwhite(line_arg) != NUL) { nextcmd = line_arg; } else if (*p != NUL && *p != '"' && p_verbose > 0) { - give_warning2((char_u *)_("W22: Text found after :endfunction: %s"), - p, true); + give_warning2(_("W22: Text found after :endfunction: %s"), p, true); } if (nextcmd != NULL) { // Another command follows. If the line came from "eap" we // can simply point into it, otherwise we need to change // "eap->cmdlinep". - eap->nextcmd = (char *)nextcmd; + eap->nextcmd = nextcmd; if (line_to_free != NULL) { xfree(*eap->cmdlinep); - *eap->cmdlinep = (char *)line_to_free; + *eap->cmdlinep = line_to_free; line_to_free = NULL; } } @@ -2287,46 +2379,50 @@ void ex_function(exarg_T *eap) // Increase indent inside "if", "while", "for" and "try", decrease // at "end". - if (indent > 2 && STRNCMP(p, "end", 3) == 0) { + if (indent > 2 && strncmp(p, "end", 3) == 0) { indent -= 2; - } else if (STRNCMP(p, "if", 2) == 0 - || STRNCMP(p, "wh", 2) == 0 - || STRNCMP(p, "for", 3) == 0 - || STRNCMP(p, "try", 3) == 0) { + } else if (strncmp(p, "if", 2) == 0 + || strncmp(p, "wh", 2) == 0 + || strncmp(p, "for", 3) == 0 + || strncmp(p, "try", 3) == 0) { indent += 2; } // Check for defining a function inside this function. - if (checkforcmd((char **)&p, "function", 2)) { + if (checkforcmd(&p, "function", 2)) { if (*p == '!') { - p = (char_u *)skipwhite((char *)p + 1); + p = skipwhite(p + 1); } p += eval_fname_script((const char *)p); - xfree(trans_function_name((char **)&p, true, 0, NULL, NULL)); - if (*skipwhite((char *)p) == '(') { - nesting++; - indent += 2; + xfree(trans_function_name(&p, true, 0, NULL, NULL)); + if (*skipwhite(p) == '(') { + if (nesting == MAX_FUNC_NESTING - 1) { + emsg(_("E1058: function nesting too deep")); + } else { + nesting++; + indent += 2; + } } } // Check for ":append", ":change", ":insert". - p = (char_u *)skip_range((char *)p, NULL); + p = skip_range(p, NULL); if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p')) || (p[0] == 'c' && (!ASCII_ISALPHA(p[1]) || (p[1] == 'h' && (!ASCII_ISALPHA(p[2]) || (p[2] == 'a' - && (STRNCMP(&p[3], "nge", 3) != 0 + && (strncmp(&p[3], "nge", 3) != 0 || !ASCII_ISALPHA(p[6]))))))) || (p[0] == 'i' && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' && (!ASCII_ISALPHA(p[2]) || (p[2] == 's')))))) { - skip_until = vim_strsave((char_u *)"."); + skip_until = xstrdup("."); } // heredoc: Check for ":python <<EOF", ":lua <<EOF", etc. - arg = (char_u *)skipwhite((char *)skiptowhite(p)); + arg = skipwhite(skiptowhite(p)); if (arg[0] == '<' && arg[1] == '<' && ((p[0] == 'p' && p[1] == 'y' && (!ASCII_ISALNUM(p[2]) || p[2] == 't' @@ -2343,22 +2439,22 @@ void ex_function(exarg_T *eap) || (p[0] == 'm' && p[1] == 'z' && (!ASCII_ISALPHA(p[2]) || p[2] == 's')))) { // ":python <<" continues until a dot, like ":append" - p = (char_u *)skipwhite((char *)arg + 2); + p = skipwhite(arg + 2); if (*p == NUL) { - skip_until = vim_strsave((char_u *)"."); + skip_until = xstrdup("."); } else { - skip_until = vim_strsave(p); + skip_until = xstrdup(p); } } // Check for ":let v =<< [trim] EOF" // and ":let [a, b] =<< [trim] EOF" - arg = (char_u *)skipwhite((char *)skiptowhite(p)); + arg = skipwhite(skiptowhite(p)); if (*arg == '[') { - arg = (char_u *)vim_strchr((char *)arg, ']'); + arg = vim_strchr(arg, ']'); } if (arg != NULL) { - arg = (char_u *)skipwhite((char *)skiptowhite(arg)); + arg = skipwhite(skiptowhite(arg)); if (arg[0] == '=' && arg[1] == '<' && arg[2] == '<' @@ -2366,14 +2462,13 @@ void ex_function(exarg_T *eap) && p[1] == 'e' && (!ASCII_ISALNUM(p[2]) || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) { - p = (char_u *)skipwhite((char *)arg + 3); - if (STRNCMP(p, "trim", 4) == 0) { + p = skipwhite(arg + 3); + if (strncmp(p, "trim", 4) == 0) { // Ignore leading white space. - p = (char_u *)skipwhite((char *)p + 4); - heredoc_trimmed = - vim_strnsave(theline, (size_t)((char_u *)skipwhite((char *)theline) - theline)); + p = skipwhite(p + 4); + heredoc_trimmed = xstrnsave(theline, (size_t)(skipwhite(theline) - theline)); } - skip_until = vim_strnsave(p, (size_t)(skiptowhite(p) - p)); + skip_until = xstrnsave(p, (size_t)(skiptowhite(p) - p)); do_concat = false; is_heredoc = true; } @@ -2386,8 +2481,8 @@ void ex_function(exarg_T *eap) // Copy the line to newly allocated memory. get_one_sourceline() // allocates 250 bytes per line, this saves 80% on average. The cost // is an extra alloc/free. - p = vim_strsave(theline); - ((char **)(newlines.ga_data))[newlines.ga_len++] = (char *)p; + p = xstrdup(theline); + ((char **)(newlines.ga_data))[newlines.ga_len++] = p; // Add NULL lines for continuation lines, so that the line count is // equal to the index in the growarray. @@ -2407,14 +2502,11 @@ void ex_function(exarg_T *eap) goto erret; } - /* - * If there are no errors, add the function - */ + // If there are no errors, add the function if (fudi.fd_dict == NULL) { - v = find_var((const char *)name, STRLEN(name), &ht, false); + v = find_var((const char *)name, strlen(name), &ht, false); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { - emsg_funcname(N_("E707: Function name conflicts with variable: %s"), - name); + emsg_funcname(N_("E707: Function name conflicts with variable: %s"), name); goto erret; } @@ -2429,8 +2521,7 @@ void ex_function(exarg_T *eap) goto erret; } if (fp->uf_calls > 0) { - emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), - name); + emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), name); goto erret; } if (fp->uf_refcount > 1) { @@ -2441,7 +2532,7 @@ void ex_function(exarg_T *eap) fp = NULL; overwrite = true; } else { - char_u *exp_name = fp->uf_name_exp; + char *exp_name = fp->uf_name_exp; // redefine existing function, keep the expanded name XFREE_CLEAR(name); fp->uf_name_exp = NULL; @@ -2460,13 +2551,13 @@ void ex_function(exarg_T *eap) goto erret; } if (fudi.fd_di == NULL) { - if (var_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, - TV_CSTRING)) { + if (value_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg, + TV_CSTRING)) { // Can't add a function to a locked dictionary goto erret; } - } else if (var_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, - TV_CSTRING)) { + } else if (value_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg, + TV_CSTRING)) { // Can't change an existing function if it is locked goto erret; } @@ -2474,23 +2565,23 @@ void ex_function(exarg_T *eap) // Give the function a sequential number. Can only be used with a // Funcref! xfree(name); - sprintf(numbuf, "%d", ++func_nr); - name = vim_strsave((char_u *)numbuf); + sprintf(numbuf, "%d", ++func_nr); // NOLINT(runtime/printf) + name = xstrdup(numbuf); } if (fp == NULL) { - if (fudi.fd_dict == NULL && vim_strchr((char *)name, AUTOLOAD_CHAR) != NULL) { + if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) { int slen, plen; - char_u *scriptname; + char *scriptname; // Check that the autoload name matches the script name. int j = FAIL; if (SOURCING_NAME != NULL) { - scriptname = (char_u *)autoload_name((const char *)name, STRLEN(name)); - p = (char_u *)vim_strchr((char *)scriptname, '/'); - plen = (int)STRLEN(p); - slen = (int)STRLEN(SOURCING_NAME); - if (slen > plen && FNAMECMP(p, SOURCING_NAME + slen - plen) == 0) { + scriptname = autoload_name(name, strlen(name)); + p = vim_strchr(scriptname, '/'); + plen = (int)strlen(p); + slen = (int)strlen(SOURCING_NAME); + if (slen > plen && path_fnamecmp(p, SOURCING_NAME + slen - plen) == 0) { j = OK; } xfree(scriptname); @@ -2502,7 +2593,7 @@ void ex_function(exarg_T *eap) } } - fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); if (fudi.fd_dict != NULL) { if (fudi.fd_di == NULL) { @@ -2518,7 +2609,7 @@ void ex_function(exarg_T *eap) tv_clear(&fudi.fd_di->di_tv); } fudi.fd_di->di_tv.v_type = VAR_FUNC; - fudi.fd_di->di_tv.vval.v_string = (char *)vim_strsave(name); + fudi.fd_di->di_tv.vval.v_string = xstrdup(name); // behave like "dict" was used flags |= FC_DICT; @@ -2527,8 +2618,8 @@ void ex_function(exarg_T *eap) // insert the new function in the function list set_ufunc_name(fp, name); if (overwrite) { - hi = hash_find(&func_hashtab, (char *)name); - hi->hi_key = UF2HIKEY(fp); + hi = hash_find(&func_hashtab, name); + hi->hi_key = (char *)UF2HIKEY(fp); } else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) { xfree(fp); goto erret; @@ -2584,8 +2675,8 @@ int eval_fname_script(const char *const p) // Use mb_strnicmp() because in Turkish comparing the "I" may not work with // the standard library function. if (p[0] == '<' - && (mb_strnicmp((char_u *)p + 1, (char_u *)"SID>", 4) == 0 - || mb_strnicmp((char_u *)p + 1, (char_u *)"SNR>", 4) == 0)) { + && (mb_strnicmp(p + 1, "SID>", 4) == 0 + || mb_strnicmp(p + 1, "SNR>", 4) == 0)) { return 5; } if (p[0] == 's' && p[1] == ':') { @@ -2599,7 +2690,7 @@ bool translated_function_exists(const char *name) if (builtin_function(name, -1)) { return find_internal_func((char *)name) != NULL; } - return find_func((const char_u *)name) != NULL; + return find_func(name) != NULL; } /// Check whether function with the given name exists @@ -2610,15 +2701,15 @@ bool translated_function_exists(const char *name) /// @return true if it exists, false otherwise. bool function_exists(const char *const name, bool no_deref) { - const char_u *nm = (const char_u *)name; + const char *nm = name; bool n = false; int flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; if (no_deref) { flag |= TFN_NO_DEREF; } - char *const p = (char *)trans_function_name((char **)&nm, false, flag, NULL, NULL); - nm = (char_u *)skipwhite((char *)nm); + char *const p = trans_function_name((char **)&nm, false, flag, NULL, NULL); + nm = skipwhite(nm); // Only accept "funcname", "funcname ", "funcname (..." and // "funcname(...", not "funcname!...". @@ -2634,15 +2725,17 @@ bool function_exists(const char *const name, bool no_deref) char *get_user_func_name(expand_T *xp, int idx) { static size_t done; + static int changed; static hashitem_T *hi; ufunc_T *fp; if (idx == 0) { done = 0; hi = func_hashtab.ht_array; + changed = func_hashtab.ht_changed; } assert(hi); - if (done < func_hashtab.ht_used) { + if (changed == func_hashtab.ht_changed && done < func_hashtab.ht_used) { if (done++ > 0) { hi++; } @@ -2652,11 +2745,11 @@ char *get_user_func_name(expand_T *xp, int idx) fp = HI2UF(hi); if ((fp->uf_flags & FC_DICT) - || STRNCMP(fp->uf_name, "<lambda>", 8) == 0) { + || strncmp(fp->uf_name, "<lambda>", 8) == 0) { return ""; // don't show dict and lambda functions } - if (STRLEN(fp->uf_name) + 4 >= IOSIZE) { + if (strlen(fp->uf_name) + 4 >= IOSIZE) { return (char *)fp->uf_name; // Prevent overflow. } @@ -2667,7 +2760,7 @@ char *get_user_func_name(expand_T *xp, int idx) STRCAT(IObuff, ")"); } } - return (char *)IObuff; + return IObuff; } return NULL; } @@ -2676,12 +2769,12 @@ char *get_user_func_name(expand_T *xp, int idx) void ex_delfunction(exarg_T *eap) { ufunc_T *fp = NULL; - char_u *p; - char_u *name; + char *p; + char *name; funcdict_T fudi; - p = (char_u *)eap->arg; - name = trans_function_name((char **)&p, eap->skip, 0, &fudi, NULL); + p = eap->arg; + name = trans_function_name(&p, eap->skip, 0, &fudi, NULL); xfree(fudi.fd_newkey); if (name == NULL) { if (fudi.fd_dict != NULL && !eap->skip) { @@ -2689,16 +2782,23 @@ void ex_delfunction(exarg_T *eap) } return; } - if (!ends_excmd(*skipwhite((char *)p))) { + if (!ends_excmd(*skipwhite(p))) { xfree(name); semsg(_(e_trailing_arg), p); return; } - eap->nextcmd = (char *)check_nextcmd(p); + eap->nextcmd = check_nextcmd(p); if (eap->nextcmd != NULL) { *p = NUL; } + if (isdigit((uint8_t)(*name)) && fudi.fd_dict == NULL) { + if (!eap->skip) { + semsg(_(e_invarg2), eap->arg); + } + xfree(name); + return; + } if (!eap->skip) { fp = find_func(name); } @@ -2749,7 +2849,7 @@ void ex_delfunction(exarg_T *eap) /// Unreference a Function: decrement the reference count and free it when it /// becomes zero. -void func_unref(char_u *name) +void func_unref(char *name) { ufunc_T *fp = NULL; @@ -2758,7 +2858,7 @@ void func_unref(char_u *name) } fp = find_func(name); - if (fp == NULL && isdigit(*name)) { + if (fp == NULL && isdigit((uint8_t)(*name))) { #ifdef EXITFREE if (!entered_free_all_mem) { internal_error("func_unref()"); @@ -2791,7 +2891,7 @@ void func_ptr_unref(ufunc_T *fp) } /// Count a reference to a Function. -void func_ref(char_u *name) +void func_ref(char *name) { ufunc_T *fp; @@ -2801,7 +2901,7 @@ void func_ref(char_u *name) fp = find_func(name); if (fp != NULL) { (fp->uf_refcount)++; - } else if (isdigit(*name)) { + } else if (isdigit((uint8_t)(*name))) { // Only give an error for a numbered function. // Fail silently, when named or lambda function isn't found. internal_error("func_ref()"); @@ -2845,9 +2945,9 @@ static int can_free_funccal(funccall_T *fc, int copyID) /// ":return [expr]" void ex_return(exarg_T *eap) { - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; typval_T rettv; - int returning = FALSE; + int returning = false; if (current_funccal == NULL) { emsg(_("E133: :return not inside a function")); @@ -2860,7 +2960,7 @@ void ex_return(exarg_T *eap) eap->nextcmd = NULL; if ((*arg != NUL && *arg != '|' && *arg != '\n') - && eval0((char *)arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { + && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { if (!eap->skip) { returning = do_return(eap, false, true, &rettv); } else { @@ -2883,7 +2983,7 @@ void ex_return(exarg_T *eap) if (returning) { eap->nextcmd = NULL; } else if (eap->nextcmd == NULL) { // no argument - eap->nextcmd = (char *)check_nextcmd(arg); + eap->nextcmd = check_nextcmd(arg); } if (eap->skip) { @@ -2891,15 +2991,13 @@ void ex_return(exarg_T *eap) } } -// TODO(ZyX-I): move to eval/ex_cmds - /// ":1,25call func(arg1, arg2)" function call. void ex_call(exarg_T *eap) { - char_u *arg = (char_u *)eap->arg; - char_u *startarg; - char_u *name; - char_u *tofree; + char *arg = eap->arg; + char *startarg; + char *name; + char *tofree; int len; typval_T rettv; linenr_T lnum; @@ -2920,7 +3018,7 @@ void ex_call(exarg_T *eap) return; } - tofree = trans_function_name((char **)&arg, false, TFN_INT, &fudi, &partial); + tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial); if (fudi.fd_newkey != NULL) { // Still need to give an error message for missing key. semsg(_(e_dictkey), fudi.fd_newkey); @@ -2939,13 +3037,12 @@ void ex_call(exarg_T *eap) // If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its // contents. For VAR_PARTIAL get its partial, unless we already have one // from trans_function_name(). - len = (int)STRLEN(tofree); - name = deref_func_name((const char *)tofree, &len, - partial != NULL ? NULL : &partial, false); + len = (int)strlen(tofree); + name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false); // Skip white space to allow ":call func ()". Not good, but required for // backward compatibility. - startarg = (char_u *)skipwhite((char *)arg); + startarg = skipwhite(arg); rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this. if (*startarg != '(') { @@ -2969,13 +3066,13 @@ void ex_call(exarg_T *eap) arg = startarg; funcexe_T funcexe = FUNCEXE_INIT; - funcexe.firstline = eap->line1; - funcexe.lastline = eap->line2; - funcexe.doesrange = &doesrange; - funcexe.evaluate = true; - funcexe.partial = partial; - funcexe.selfdict = fudi.fd_dict; - if (get_func_tv(name, -1, &rettv, (char **)&arg, &funcexe) == FAIL) { + funcexe.fe_firstline = eap->line1; + funcexe.fe_lastline = eap->line2; + funcexe.fe_doesrange = &doesrange; + funcexe.fe_evaluate = true; + funcexe.fe_partial = partial; + funcexe.fe_selfdict = fudi.fd_dict; + if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) { failed = true; break; } @@ -3004,7 +3101,7 @@ void ex_call(exarg_T *eap) // When inside :try we need to check for following "| catch" or "| endtry". // Not when there was an error, but do check if an exception was thrown. - if ((!aborting() || current_exception != NULL) && (!failed || eap->cstack->cs_trylevel > 0)) { + if ((!aborting() || did_throw) && (!failed || eap->cstack->cs_trylevel > 0)) { // Check for trailing illegal characters and a following command. if (!ends_excmd(*arg)) { if (!failed && !aborting()) { @@ -3012,7 +3109,7 @@ void ex_call(exarg_T *eap) semsg(_(e_trailing_arg), arg); } } else { - eap->nextcmd = (char *)check_nextcmd(arg); + eap->nextcmd = check_nextcmd(arg); } } @@ -3029,8 +3126,8 @@ end: /// @param is_cmd set when called due to a ":return" command. /// @param rettv may point to a typval_T with the return rettv. /// -/// @return TRUE when the return can be carried out, -/// FALSE when the return gets pending. +/// @return true when the return can be carried out, +/// false when the return gets pending. int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) { int idx; @@ -3080,7 +3177,7 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) } report_make_pending(CSTP_RETURN, rettv); } else { - current_funccal->returned = TRUE; + current_funccal->returned = true; // If the return is carried out now, store the return value. For // a return immediately after reanimation, the value is already @@ -3099,25 +3196,25 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) /// Generate a return command for producing the value of "rettv". The result /// is an allocated string. Used by report_pending() for verbose messages. -char_u *get_return_cmd(void *rettv) +char *get_return_cmd(void *rettv) { - char_u *s = NULL; - char_u *tofree = NULL; + char *s = NULL; + char *tofree = NULL; if (rettv != NULL) { - tofree = s = (char_u *)encode_tv2echo((typval_T *)rettv, NULL); + tofree = s = encode_tv2echo((typval_T *)rettv, NULL); } if (s == NULL) { - s = (char_u *)""; + s = ""; } STRCPY(IObuff, ":return "); - STRLCPY(IObuff + 8, s, IOSIZE - 8); - if (STRLEN(s) + 8 >= IOSIZE) { + xstrlcpy(IObuff + 8, s, IOSIZE - 8); + if (strlen(s) + 8 >= IOSIZE) { STRCPY(IObuff + IOSIZE - 4, "..."); } xfree(tofree); - return vim_strsave(IObuff); + return xstrdup(IObuff); } /// Get next function line. @@ -3128,12 +3225,12 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) { funccall_T *fcp = (funccall_T *)cookie; ufunc_T *fp = fcp->func; - char_u *retval; + char *retval; garray_T *gap; // growarray with function lines // If breakpoints have been added/deleted need to check for it. if (fcp->dbg_tick != debug_tick) { - fcp->breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM); + fcp->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, SOURCING_LNUM); fcp->dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { @@ -3153,7 +3250,7 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) if (fcp->linenr >= gap->ga_len) { retval = NULL; } else { - retval = (char_u *)xstrdup(((char **)(gap->ga_data))[fcp->linenr++]); + retval = xstrdup(((char **)(gap->ga_data))[fcp->linenr++]); SOURCING_LNUM = fcp->linenr; if (do_profiling == PROF_YES) { func_line_start(cookie); @@ -3163,16 +3260,16 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat) // Did we encounter a breakpoint? if (fcp->breakpoint != 0 && fcp->breakpoint <= SOURCING_LNUM) { - dbg_breakpoint(fp->uf_name, SOURCING_LNUM); + dbg_breakpoint((char *)fp->uf_name, SOURCING_LNUM); // Find next breakpoint. - fcp->breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM); + fcp->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, SOURCING_LNUM); fcp->dbg_tick = debug_tick; } - return (char *)retval; + return retval; } -/// @return TRUE if the currently active function should be ended, because a +/// @return true if the currently active function should be ended, because a /// return was encountered or an error occurred. Used inside a ":while". int func_has_ended(void *cookie) { @@ -3184,7 +3281,7 @@ int func_has_ended(void *cookie) || fcp->returned; } -/// @return TRUE if cookie indicates a function which "abort"s on errors. +/// @return true if cookie indicates a function which "abort"s on errors. int func_has_abort(void *cookie) { return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; @@ -3194,17 +3291,17 @@ int func_has_abort(void *cookie) /// Changes "rettv" in-place. void make_partial(dict_T *const selfdict, typval_T *const rettv) { - char_u *fname; - char_u *tofree = NULL; + char *fname; + char *tofree = NULL; ufunc_T *fp; - char_u fname_buf[FLEN_FIXED + 1]; + char fname_buf[FLEN_FIXED + 1]; int error; if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) { fp = rettv->vval.v_partial->pt_func; } else { fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING - ? (char_u *)rettv->vval.v_string + ? rettv->vval.v_string : rettv->vval.v_partial->pt_name; // Translate "s:func" to the stored function name. fname = fname_trans_sid(fname, fname_buf, &tofree, &error); @@ -3221,7 +3318,7 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) pt->pt_auto = true; if (rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING) { // Just a function: Take over the function name and use selfdict. - pt->pt_name = (char_u *)rettv->vval.v_string; + pt->pt_name = rettv->vval.v_string; } else { partial_T *ret_pt = rettv->vval.v_partial; int i; @@ -3230,7 +3327,7 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) // args. Can't take over name or args, the partial might // be referenced elsewhere. if (ret_pt->pt_name != NULL) { - pt->pt_name = vim_strsave(ret_pt->pt_name); + pt->pt_name = xstrdup(ret_pt->pt_name); func_ref(pt->pt_name); } else { pt->pt_func = ret_pt->pt_func; @@ -3252,7 +3349,7 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) } /// @return the name of the executed function. -char_u *func_name(void *cookie) +char *func_name(void *cookie) { return ((funccall_T *)cookie)->func->uf_name; } @@ -3275,7 +3372,7 @@ int func_level(void *cookie) return ((funccall_T *)cookie)->level; } -/// @return TRUE when a function was ended by a ":return" command. +/// @return true when a function was ended by a ":return" command. int current_func_returned(void) { return current_funccal->returned; @@ -3539,14 +3636,14 @@ bool set_ref_in_func_args(int copyID) /// "ht_stack" is used to add hashtabs to be marked. Can be NULL. /// /// @return true if setting references failed somehow. -bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) +bool set_ref_in_func(char *name, ufunc_T *fp_in, int copyID) { ufunc_T *fp = fp_in; funccall_T *fc; - int error = ERROR_NONE; - char_u fname_buf[FLEN_FIXED + 1]; - char_u *tofree = NULL; - char_u *fname; + int error = FCERR_NONE; + char fname_buf[FLEN_FIXED + 1]; + char *tofree = NULL; + char *fname; bool abort = false; if (name == NULL && fp_in == NULL) { return false; @@ -3565,20 +3662,18 @@ bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) return abort; } -/// Registers a C extension user function. -char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state) +/// Registers a luaref as a lambda. +char *register_luafunc(LuaRef ref) { - char_u *name = get_lambda_name(); - ufunc_T *fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1); + char *name = get_lambda_name(); + ufunc_T *fp = xcalloc(1, offsetof(ufunc_T, uf_name) + strlen(name) + 1); fp->uf_refcount = 1; fp->uf_varargs = true; - fp->uf_flags = FC_CFUNC; + fp->uf_flags = FC_LUAREF; fp->uf_calls = 0; fp->uf_script_ctx = current_sctx; - fp->uf_cb = cb; - fp->uf_cb_free = cb_free; - fp->uf_cb_state = state; + fp->uf_luaref = ref; STRCPY(fp->uf_name, name); hash_add(&func_hashtab, UF2HIKEY(fp)); diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h index 4b7007aae9..c8583f232c 100644 --- a/src/nvim/eval/userfunc.h +++ b/src/nvim/eval/userfunc.h @@ -1,18 +1,42 @@ #ifndef NVIM_EVAL_USERFUNC_H #define NVIM_EVAL_USERFUNC_H +#include <stdbool.h> +#include <stddef.h> + #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/garray.h" +#include "nvim/hashtab.h" +#include "nvim/pos.h" +#include "nvim/types.h" + +struct funccal_entry; // From user function to hashitem and back. #define UF2HIKEY(fp) ((fp)->uf_name) #define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name))) #define HI2UF(hi) HIKEY2UF((hi)->hi_key) -///< Structure used by trans_function_name() +// flags used in uf_flags +#define FC_ABORT 0x01 // abort function on error +#define FC_RANGE 0x02 // function accepts range +#define FC_DICT 0x04 // Dict function, uses "self" +#define FC_CLOSURE 0x08 // closure, uses outer scope variables +#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0 +#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0 +#define FC_SANDBOX 0x40 // function defined in the sandbox +#define FC_DEAD 0x80 // function kept only for reference to dfunc +#define FC_EXPORT 0x100 // "export def Func()" +#define FC_NOARGS 0x200 // no a: variables in lambda +#define FC_VIM9 0x400 // defined in vim9 script file +#define FC_LUAREF 0x800 // luaref callback + +/// Structure used by trans_function_name() typedef struct { dict_T *fd_dict; ///< Dictionary used. - char_u *fd_newkey; ///< New key in "dict" in allocated memory. + char *fd_newkey; ///< New key in "dict" in allocated memory. dictitem_T *fd_di; ///< Dictionary item used. } funcdict_T; @@ -24,44 +48,43 @@ struct funccal_entry { /// errors for when calling a function typedef enum { - ERROR_UNKNOWN = 0, - ERROR_TOOMANY, - ERROR_TOOFEW, - ERROR_SCRIPT, - ERROR_DICT, - ERROR_NONE, - ERROR_OTHER, - ERROR_BOTH, - ERROR_DELETED, - ERROR_NOTMETHOD, + FCERR_UNKNOWN = 0, + FCERR_TOOMANY = 1, + FCERR_TOOFEW = 2, + FCERR_SCRIPT = 3, + FCERR_DICT = 4, + FCERR_NONE = 5, + FCERR_OTHER = 6, + FCERR_DELETED = 7, + FCERR_NOTMETHOD = 8, ///< function cannot be used as a method } FnameTransError; /// Used in funcexe_T. Returns the new argcount. -typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, int argskip, - int called_func_argcount); +typedef int (*ArgvFunc)(int current_argcount, typval_T *argv, int partial_argcount, + ufunc_T *called_func); /// Structure passed between functions dealing with function call execution. typedef struct { - ArgvFunc argv_func; ///< when not NULL, can be used to fill in arguments only - ///< when the invoked function uses them - linenr_T firstline; ///< first line of range - linenr_T lastline; ///< last line of range - bool *doesrange; ///< [out] if not NULL: function handled range - bool evaluate; ///< actually evaluate expressions - partial_T *partial; ///< for extra arguments - dict_T *selfdict; ///< Dictionary for "self" - typval_T *basetv; ///< base for base->method() + ArgvFunc fe_argv_func; ///< when not NULL, can be used to fill in arguments only + ///< when the invoked function uses them + linenr_T fe_firstline; ///< first line of range + linenr_T fe_lastline; ///< last line of range + bool *fe_doesrange; ///< [out] if not NULL: function handled range + bool fe_evaluate; ///< actually evaluate expressions + partial_T *fe_partial; ///< for extra arguments + dict_T *fe_selfdict; ///< Dictionary for "self" + typval_T *fe_basetv; ///< base for base->method() } funcexe_T; #define FUNCEXE_INIT (funcexe_T) { \ - .argv_func = NULL, \ - .firstline = 0, \ - .lastline = 0, \ - .doesrange = NULL, \ - .evaluate = false, \ - .partial = NULL, \ - .selfdict = NULL, \ - .basetv = NULL, \ + .fe_argv_func = NULL, \ + .fe_firstline = 0, \ + .fe_lastline = 0, \ + .fe_doesrange = NULL, \ + .fe_evaluate = false, \ + .fe_partial = NULL, \ + .fe_selfdict = NULL, \ + .fe_basetv = NULL, \ } #define FUNCARG(fp, j) ((char **)(fp->uf_args.ga_data))[j] diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c index b38849730a..3e593151fc 100644 --- a/src/nvim/eval/vars.c +++ b/src/nvim/eval/vars.c @@ -3,24 +3,43 @@ // eval/vars.c: functions for dealing with variables +#include <assert.h> +#include <ctype.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> + #include "nvim/ascii.h" #include "nvim/autocmd.h" -#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" +#include "nvim/eval/window.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" +#include "nvim/macros.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/ops.h" #include "nvim/option.h" -#include "nvim/screen.h" +#include "nvim/os/os.h" #include "nvim/search.h" +#include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -62,7 +81,7 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) // Check for the optional 'trim' word before the marker cmd = skipwhite(cmd); - if (STRNCMP(cmd, "trim", 4) == 0 + if (strncmp(cmd, "trim", 4) == 0 && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) { cmd = skipwhite(cmd + 4); @@ -81,13 +100,13 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) // The marker is the next word. if (*cmd != NUL && *cmd != '"') { marker = skipwhite(cmd); - p = (char *)skiptowhite((char_u *)marker); + p = skiptowhite(marker); if (*skipwhite(p) != NUL && *skipwhite(p) != '"') { semsg(_(e_trailing_arg), p); return NULL; } *p = NUL; - if (islower(*marker)) { + if (islower((uint8_t)(*marker))) { emsg(_("E221: Marker cannot start with lower case letter")); return NULL; } @@ -110,10 +129,10 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd) // with "trim": skip the indent matching the :let line to find the // marker if (marker_indent_len > 0 - && STRNCMP(theline, *eap->cmdlinep, marker_indent_len) == 0) { + && strncmp(theline, *eap->cmdlinep, (size_t)marker_indent_len) == 0) { mi = marker_indent_len; } - if (STRCMP(marker, theline + mi) == 0) { + if (strcmp(marker, theline + mi) == 0) { xfree(theline); break; } @@ -189,8 +208,8 @@ static void ex_let_const(exarg_T *eap, const bool is_const) argend--; } expr = skipwhite(argend); - if (*expr != '=' && !((vim_strchr("+-*/%.", *expr) != NULL - && expr[1] == '=') || STRNCMP(expr, "..=", 3) == 0)) { + if (*expr != '=' && !((vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL + && expr[1] == '=') || strncmp(expr, "..=", 3) == 0)) { // ":let" without "=": list variables if (*arg == '[') { emsg(_(e_invarg)); @@ -207,7 +226,7 @@ static void ex_let_const(exarg_T *eap, const bool is_const) list_func_vars(&first); list_vim_vars(&first); } - eap->nextcmd = (char *)check_nextcmd((char_u *)arg); + eap->nextcmd = check_nextcmd(arg); } else if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') { // HERE document list_T *l = heredoc_get(eap, expr + 3); @@ -225,17 +244,19 @@ static void ex_let_const(exarg_T *eap, const bool is_const) op[0] = '='; op[1] = NUL; if (*expr != '=') { - if (vim_strchr("+-*/%.", *expr) != NULL) { + if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) { op[0] = *expr; // +=, -=, *=, /=, %= or .= if (expr[0] == '.' && expr[1] == '.') { // ..= expr++; } } - expr = skipwhite(expr + 2); + expr += 2; } else { - expr = skipwhite(expr + 1); + expr += 1; } + expr = skipwhite(expr); + if (eap->skip) { emsg_skip++; } @@ -378,9 +399,8 @@ const char *skip_var_list(const char *arg, int *var_count, int *semicolon) } } return p + 1; - } else { - return skip_var_one((char *)arg); } + return skip_var_one((char *)arg); } /// Skip one (assignable) variable name, including @r, $VAR, &option, d.key, @@ -413,7 +433,7 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *firs // apply :filter /pat/ to variable name xstrlcpy(buf, prefix, IOSIZE); xstrlcat(buf, (char *)di->di_key, IOSIZE); - if (message_filtered((char_u *)buf)) { + if (message_filtered(buf)) { continue; } @@ -539,8 +559,6 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) return arg; } -// TODO(ZyX-I): move to eval/ex_cmds - /// Set one item of `:let var = expr` or `:let [v1, v2] = list` to its value /// /// @param[in] arg Start of the variable name. @@ -558,8 +576,6 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo { char *arg_end = NULL; int len; - int opt_flags; - char *tofree = NULL; // ":let $VAR = expr": Set environment variable. if (*arg == '$') { @@ -574,20 +590,20 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo if (len == 0) { semsg(_(e_invarg2), name - 1); } else { - if (op != NULL && vim_strchr("+-*/%", *op) != NULL) { + if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { semsg(_(e_letwrong), op); } else if (endchars != NULL - && vim_strchr(endchars, *skipwhite(arg)) == NULL) { + && vim_strchr(endchars, (uint8_t)(*skipwhite(arg))) == NULL) { emsg(_(e_letunexp)); } else if (!check_secure()) { + char *tofree = NULL; const char c1 = name[len]; name[len] = NUL; const char *p = tv_get_string_chk(tv); if (p != NULL && op != NULL && *op == '.') { char *s = vim_getenv(name); - if (s != NULL) { - tofree = (char *)concat_str((const char_u *)s, (const char_u *)p); + tofree = concat_str(s, p); p = (const char *)tofree; xfree(s); } @@ -609,10 +625,11 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo return NULL; } // Find the end of the name. - char *const p = (char *)find_option_end((const char **)&arg, &opt_flags); + int scope; + char *const p = (char *)find_option_end((const char **)&arg, &scope); if (p == NULL || (endchars != NULL - && vim_strchr(endchars, *skipwhite(p)) == NULL)) { + && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) { emsg(_(e_letunexp)); } else { varnumber_T n = 0; @@ -621,11 +638,13 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo char *stringval = NULL; const char *s = NULL; bool failed = false; + uint32_t opt_p_flags; + char *tofree = NULL; const char c1 = *p; *p = NUL; - opt_type = get_option_value(arg, &numval, &stringval, opt_flags); + opt_type = get_option_value(arg, &numval, &stringval, &opt_p_flags, scope); if (opt_type == gov_bool || opt_type == gov_number || opt_type == gov_hidden_bool @@ -634,8 +653,13 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo n = (long)tv_get_number(tv); } - // Avoid setting a string option to the text "v:false" or similar. - if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) { + if ((opt_p_flags & P_FUNC) && tv_is_func(*tv)) { + // If the option can be set to a function reference or a lambda + // and the passed value is a function reference, then convert it to + // the name (string) of the function reference. + s = tofree = encode_tv2string(tv, NULL); + } else if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) { + // Avoid setting a string option to the text "v:false" or similar. s = tv_get_string_chk(tv); } @@ -663,8 +687,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo } else if (opt_type == gov_string && stringval != NULL && s != NULL) { // string char *const oldstringval = stringval; - stringval = (char *)concat_str((const char_u *)stringval, - (const char_u *)s); + stringval = concat_str(stringval, s); xfree(oldstringval); s = stringval; } @@ -673,7 +696,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo if (!failed) { if (opt_type != gov_string || s != NULL) { - char *err = set_option_value(arg, n, s, opt_flags); + char *err = set_option_value(arg, n, s, scope); arg_end = p; if (err != NULL) { emsg(_(err)); @@ -684,6 +707,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo } *p = c1; xfree(stringval); + xfree(tofree); } // ":let @r = expr": Set register contents. } else if (*arg == '@') { @@ -692,10 +716,10 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo return NULL; } arg++; - if (op != NULL && vim_strchr("+-*/%", *op) != NULL) { + if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) { semsg(_(e_letwrong), op); } else if (endchars != NULL - && vim_strchr(endchars, *skipwhite(arg + 1)) == NULL) { + && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + 1))) == NULL) { emsg(_(e_letunexp)); } else { char *s; @@ -705,14 +729,13 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo if (p != NULL && op != NULL && *op == '.') { s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc); if (s != NULL) { - ptofree = (char *)concat_str((char_u *)s, (const char_u *)p); + ptofree = concat_str(s, p); p = (const char *)ptofree; xfree(s); } } if (p != NULL) { - write_reg_contents(*arg == '@' ? '"' : *arg, - (const char_u *)p, (ssize_t)STRLEN(p), false); + write_reg_contents(*arg == '@' ? '"' : *arg, p, (ssize_t)strlen(p), false); arg_end = arg + 1; } xfree(ptofree); @@ -724,7 +747,7 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo char *const p = get_lval(arg, tv, &lv, false, false, 0, FNE_CHECK_START); if (p != NULL && lv.ll_name != NULL) { - if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) { + if (endchars != NULL && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL) { emsg(_(e_letunexp)); } else { set_var_lval(&lv, p, tv, copy, is_const, op); @@ -745,8 +768,6 @@ void ex_unlet(exarg_T *eap) ex_unletlock(eap, eap->arg, 0, do_unlet_var); } -// TODO(ZyX-I): move to eval/ex_cmds - /// ":lockvar" and ":unlockvar" commands void ex_lockvar(exarg_T *eap) { @@ -763,8 +784,6 @@ void ex_lockvar(exarg_T *eap) ex_unletlock(eap, arg, deep, do_lock_var); } -// TODO(ZyX-I): move to eval/ex_cmds - /// Common parsing logic for :unlet, :lockvar and :unlockvar. /// /// Invokes `callback` afterwards if successful and `eap->skip == false`. @@ -791,6 +810,7 @@ static void ex_unletlock(exarg_T *eap, char *argstart, int deep, ex_unletlock_ca semsg(_(e_invarg2), arg - 1); return; } + assert(*lv.ll_name == '$'); // suppress clang "Uninitialized argument value" if (!error && !eap->skip && callback(&lv, arg, eap, deep) == FAIL) { error = true; } @@ -825,11 +845,9 @@ static void ex_unletlock(exarg_T *eap, char *argstart, int deep, ex_unletlock_ca arg = skipwhite(name_end); } while (!ends_excmd(*arg)); - eap->nextcmd = (char *)check_nextcmd((char_u *)arg); + eap->nextcmd = check_nextcmd(arg); } -// TODO(ZyX-I): move to eval/ex_cmds - /// Unlet a variable indicated by `lp`. /// /// @param[in] lp The lvalue. @@ -846,7 +864,7 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ int cc; if (lp->ll_tv == NULL) { - cc = (char_u)(*name_end); + cc = (uint8_t)(*name_end); *name_end = NUL; // Environment variable, normal name or expanded name. @@ -859,13 +877,13 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ } else if ((lp->ll_list != NULL // ll_list is not NULL when lvalue is not in a list, NULL lists // yield E689. - && var_check_lock(tv_list_locked(lp->ll_list), - lp->ll_name, - lp->ll_name_len)) + && value_check_lock(tv_list_locked(lp->ll_list), + lp->ll_name, + lp->ll_name_len)) || (lp->ll_dict != NULL - && var_check_lock(lp->ll_dict->dv_lock, - lp->ll_name, - lp->ll_name_len))) { + && value_check_lock(lp->ll_dict->dv_lock, + lp->ll_name, + lp->ll_name_len))) { return FAIL; } else if (lp->ll_range) { assert(lp->ll_list != NULL); @@ -874,18 +892,17 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ listitem_T *last_li = first_li; for (;;) { listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); - if (var_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, - lp->ll_name, - lp->ll_name_len)) { + if (value_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, + lp->ll_name, + lp->ll_name_len)) { return false; } lp->ll_li = li; lp->ll_n1++; if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) { break; - } else { - last_li = lp->ll_li; } + last_li = lp->ll_li; } tv_list_remove_items(lp->ll_list, first_li, last_li); } else { @@ -920,8 +937,6 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_ return ret; } -// TODO(ZyX-I): move to eval/ex_cmds - /// unlet a variable /// /// @param[in] name Variable name to unlet. @@ -961,11 +976,11 @@ int do_unlet(const char *const name, const size_t name_len, const bool forceit) dictitem_T *const di = TV_DICT_HI2DI(hi); if (var_check_fixed(di->di_flags, name, TV_CSTRING) || var_check_ro(di->di_flags, name, TV_CSTRING) - || var_check_lock(d->dv_lock, name, TV_CSTRING)) { + || value_check_lock(d->dv_lock, name, TV_CSTRING)) { return FAIL; } - if (var_check_lock(d->dv_lock, name, TV_CSTRING)) { + if (value_check_lock(d->dv_lock, name, TV_CSTRING)) { return FAIL; } @@ -992,8 +1007,6 @@ int do_unlet(const char *const name, const size_t name_len, const bool forceit) return FAIL; } -// TODO(ZyX-I): move to eval/ex_cmds - /// Lock or unlock variable indicated by `lp`. /// /// Locks if `eap->cmdidx == CMD_lockvar`, unlocks otherwise. @@ -1010,10 +1023,6 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap bool lock = eap->cmdidx == CMD_lockvar; int ret = OK; - if (deep == 0) { // Nothing to do. - return OK; - } - if (lp->ll_tv == NULL) { if (*lp->ll_name == '$') { semsg(_(e_lock_unlock), lp->ll_name); @@ -1037,9 +1046,13 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap } else { di->di_flags &= (uint8_t)(~DI_FLAGS_LOCK); } - tv_item_lock(&di->di_tv, deep, lock, false); + if (deep != 0) { + tv_item_lock(&di->di_tv, deep, lock, false); + } } } + } else if (deep == 0) { + // nothing to do } else if (lp->ll_range) { listitem_T *li = lp->ll_li; @@ -1099,7 +1112,7 @@ int get_var_tv(const char *name, int len, typval_T *rettv, dictitem_T **dip, boo /// NULL when it doesn't exist. /// /// @see tv_get_string() for how long the pointer remains valid. -char_u *get_var_value(const char *const name) +char *get_var_value(const char *const name) { dictitem_T *v; @@ -1107,7 +1120,7 @@ char_u *get_var_value(const char *const name) if (v == NULL) { return NULL; } - return (char_u *)tv_get_string(&v->di_tv); + return (char *)tv_get_string(&v->di_tv); } /// Clean up a list of internal variables. @@ -1118,7 +1131,7 @@ void vars_clear(hashtab_T *ht) vars_clear_ext(ht, true); } -/// Like vars_clear(), but only free the value if "free_val" is TRUE. +/// Like vars_clear(), but only free the value if "free_val" is true. void vars_clear_ext(hashtab_T *ht, int free_val) { int todo; @@ -1162,7 +1175,7 @@ void delete_var(hashtab_T *ht, hashitem_T *hi) static void list_one_var(dictitem_T *v, const char *prefix, int *first) { char *const s = encode_tv2echo(&v->di_tv, NULL); - list_one_var_a(prefix, (const char *)v->di_key, (ptrdiff_t)STRLEN(v->di_key), + list_one_var_a(prefix, (const char *)v->di_key, (ptrdiff_t)strlen((char *)v->di_key), v->di_tv.v_type, (s == NULL ? "" : s), first); xfree(s); } @@ -1258,7 +1271,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, v = find_var_in_scoped_ht(name, name_len, true); } - if (tv_is_func(*tv) && !var_check_func_name(name, v == NULL)) { + if (tv_is_func(*tv) && var_wrong_func_name(name, v == NULL)) { return; } @@ -1269,12 +1282,18 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, return; } - // existing variable, need to clear the value + // Check in this order for backwards compatibility: + // - Whether the variable is read-only + // - Whether the variable value is locked + // - Whether the variable is locked if (var_check_ro(v->di_flags, name, name_len) - || var_check_lock(v->di_tv.v_lock, name, name_len)) { + || value_check_lock(v->di_tv.v_lock, name, name_len) + || var_check_lock(v->di_flags, name, name_len)) { return; } + // existing variable, need to clear the value + // Handle setting internal v: variables separately where needed to // prevent changing the type. if (is_vimvarht(ht)) { @@ -1300,7 +1319,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, set_search_direction(v->di_tv.vval.v_number ? '/' : '?'); } else if (strcmp(varname, "hlsearch") == 0) { no_hlsearch = !v->di_tv.vval.v_number; - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } return; } else if (v->di_tv.v_type != tv->v_type) { @@ -1330,7 +1349,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, v = xmalloc(sizeof(dictitem_T) + strlen(varname)); STRCPY(v->di_key, varname); - if (tv_dict_add(dict, v) == FAIL) { + if (hash_add(ht, (char *)v->di_key) == FAIL) { xfree(v); return; } @@ -1349,12 +1368,8 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv, } if (watched) { - if (oldtv.v_type == VAR_UNKNOWN) { - tv_dict_watcher_notify(dict, (char *)v->di_key, &v->di_tv, NULL); - } else { - tv_dict_watcher_notify(dict, (char *)v->di_key, &v->di_tv, &oldtv); - tv_clear(&oldtv); - } + tv_dict_watcher_notify(dict, (char *)v->di_key, &v->di_tv, &oldtv); + tv_clear(&oldtv); } if (is_const) { @@ -1409,6 +1424,26 @@ bool var_check_ro(const int flags, const char *name, size_t name_len) return true; } +/// Return true if di_flags "flags" indicates variable "name" is locked. +/// Also give an error message. +bool var_check_lock(const int flags, const char *name, size_t name_len) +{ + if (!(flags & DI_FLAGS_LOCK)) { + return false; + } + + if (name_len == TV_TRANSLATE) { + name = _(name); + name_len = strlen(name); + } else if (name_len == TV_CSTRING) { + name_len = strlen(name); + } + + semsg(_("E1122: Variable is locked: %*s"), (int)name_len, name); + + return true; +} + /// Check whether variable is fixed (DI_FLAGS_FIX) /// /// Also gives an error message. @@ -1443,37 +1478,34 @@ bool var_check_fixed(const int flags, const char *name, size_t name_len) return false; } -// TODO(ZyX-I): move to eval/expressions - /// Check if name is a valid name to assign funcref to /// /// @param[in] name Possible function/funcref name. /// @param[in] new_var True if it is a name for a variable. /// -/// @return false in case of error, true in case of success. Also gives an +/// @return false in case of success, true in case of failure. Also gives an /// error message if appropriate. -bool var_check_func_name(const char *const name, const bool new_var) +bool var_wrong_func_name(const char *const name, const bool new_var) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { // Allow for w: b: s: and t:. - if (!(vim_strchr("wbst", name[0]) != NULL && name[1] == ':') - && !ASCII_ISUPPER((name[0] != NUL && name[1] == ':') - ? name[2] : name[0])) { + // Allow autoload variable. + if (!(vim_strchr("wbst", (uint8_t)name[0]) != NULL && name[1] == ':') + && !ASCII_ISUPPER((name[0] != NUL && name[1] == ':') ? name[2] : name[0]) + && vim_strchr(name, '#') == NULL) { semsg(_("E704: Funcref variable name must start with a capital: %s"), name); - return false; + return true; } // Don't allow hiding a function. When "v" is not NULL we might be // assigning another function to the same var, the type is checked // below. if (new_var && function_exists(name, false)) { semsg(_("E705: Variable name conflicts with existing function: %s"), name); - return false; + return true; } - return true; + return false; } -// TODO(ZyX-I): move to eval/expressions - /// Check if a variable name is valid /// /// @param[in] varname Variable name to check. @@ -1627,7 +1659,7 @@ static void set_option_from_tv(const char *varname, typval_T *varp) strval = tv_get_string_buf_chk(varp, nbuf); } if (!error && strval != NULL) { - set_option_value(varname, numval, strval, OPT_LOCAL); + set_option_value_give_err(varname, numval, strval, OPT_LOCAL); } } @@ -1648,25 +1680,27 @@ static void setwinvar(typval_T *argvars, typval_T *rettv, int off) const char *varname = tv_get_string_chk(&argvars[off + 1]); typval_T *varp = &argvars[off + 2]; - if (win != NULL && varname != NULL && varp != NULL) { - bool need_switch_win = !(tp == curtab && win == curwin); - switchwin_T switchwin; - if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) { - if (*varname == '&') { - set_option_from_tv(varname + 1, varp); - } else { - const size_t varname_len = strlen(varname); - char *const winvarname = xmalloc(varname_len + 3); - memcpy(winvarname, "w:", 2); - memcpy(winvarname + 2, varname, varname_len + 1); - set_var(winvarname, varname_len + 2, varp, true); - xfree(winvarname); - } - } - if (need_switch_win) { - restore_win(&switchwin, true); + if (win == NULL || varname == NULL) { + return; + } + + bool need_switch_win = !(tp == curtab && win == curwin); + switchwin_T switchwin; + if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) { + if (*varname == '&') { + set_option_from_tv(varname + 1, varp); + } else { + const size_t varname_len = strlen(varname); + char *const winvarname = xmalloc(varname_len + 3); + memcpy(winvarname, "w:", 2); + memcpy(winvarname + 2, varname, varname_len + 1); + set_var(winvarname, varname_len + 2, varp, true); + xfree(winvarname); } } + if (need_switch_win) { + restore_win(&switchwin, true); + } } bool var_exists(const char *var) @@ -1702,7 +1736,7 @@ bool var_exists(const char *var) } /// "gettabvar()" function -void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_gettabvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const varname = tv_get_string_chk(&argvars[1]); tabpage_T *const tp = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); @@ -1716,19 +1750,19 @@ void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "gettabwinvar()" function -void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_gettabwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getwinvar(argvars, rettv, 1); } /// "getwinvar()" function -void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getwinvar(argvars, rettv, 0); } /// "getbufvar()" function -void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getbufvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const varname = tv_get_string_chk(&argvars[1]); buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); @@ -1737,7 +1771,7 @@ void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "settabvar()" function -void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_settabvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = 0; @@ -1749,38 +1783,40 @@ void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *const varname = tv_get_string_chk(&argvars[1]); typval_T *const varp = &argvars[2]; - if (varname != NULL && tp != NULL) { - tabpage_T *const save_curtab = curtab; - goto_tabpage_tp(tp, false, false); + if (varname == NULL || tp == NULL) { + return; + } - const size_t varname_len = strlen(varname); - char *const tabvarname = xmalloc(varname_len + 3); - memcpy(tabvarname, "t:", 2); - memcpy(tabvarname + 2, varname, varname_len + 1); - set_var(tabvarname, varname_len + 2, varp, true); - xfree(tabvarname); - - // Restore current tabpage. - if (valid_tabpage(save_curtab)) { - goto_tabpage_tp(save_curtab, false, false); - } + tabpage_T *const save_curtab = curtab; + goto_tabpage_tp(tp, false, false); + + const size_t varname_len = strlen(varname); + char *const tabvarname = xmalloc(varname_len + 3); + memcpy(tabvarname, "t:", 2); + memcpy(tabvarname + 2, varname, varname_len + 1); + set_var(tabvarname, varname_len + 2, varp, true); + xfree(tabvarname); + + // Restore current tabpage. + if (valid_tabpage(save_curtab)) { + goto_tabpage_tp(save_curtab, false, false); } } /// "settabwinvar()" function -void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_settabwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { setwinvar(argvars, rettv, 1); } /// "setwinvar()" function -void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_setwinvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { setwinvar(argvars, rettv, 0); } /// "setbufvar()" function -void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_setbufvar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (check_secure() || !tv_check_str_or_nr(&argvars[0])) { @@ -1790,27 +1826,29 @@ void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) buf_T *const buf = tv_get_buf(&argvars[0], false); typval_T *varp = &argvars[2]; - if (buf != NULL && varname != NULL) { - if (*varname == '&') { - aco_save_T aco; + if (buf == NULL || varname == NULL) { + return; + } - // set curbuf to be our buf, temporarily - aucmd_prepbuf(&aco, buf); + if (*varname == '&') { + aco_save_T aco; - set_option_from_tv(varname + 1, varp); + // Set curbuf to be our buf, temporarily. + aucmd_prepbuf(&aco, buf); - // reset notion of buffer - aucmd_restbuf(&aco); - } else { - const size_t varname_len = STRLEN(varname); - char *const bufvarname = xmalloc(varname_len + 3); - buf_T *const save_curbuf = curbuf; - curbuf = buf; - memcpy(bufvarname, "b:", 2); - memcpy(bufvarname + 2, varname, varname_len + 1); - set_var(bufvarname, varname_len + 2, varp, true); - xfree(bufvarname); - curbuf = save_curbuf; - } + set_option_from_tv(varname + 1, varp); + + // reset notion of buffer + aucmd_restbuf(&aco); + } else { + const size_t varname_len = strlen(varname); + char *const bufvarname = xmalloc(varname_len + 3); + buf_T *const save_curbuf = curbuf; + curbuf = buf; + memcpy(bufvarname, "b:", 2); + memcpy(bufvarname + 2, varname, varname_len + 1); + set_var(bufvarname, varname_len + 2, varp, true); + xfree(bufvarname); + curbuf = save_curbuf; } } diff --git a/src/nvim/eval/vars.h b/src/nvim/eval/vars.h index 73efc4938a..b87c9d62cb 100644 --- a/src/nvim/eval/vars.h +++ b/src/nvim/eval/vars.h @@ -1,7 +1,7 @@ #ifndef NVIM_EVAL_VARS_H #define NVIM_EVAL_VARS_H -#include "nvim/ex_cmds_defs.h" // For exarg_T +#include "nvim/ex_cmds_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/vars.h.generated.h" diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c new file mode 100644 index 0000000000..f58a0c488a --- /dev/null +++ b/src/nvim/eval/window.c @@ -0,0 +1,954 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// eval/window.c: Window related builtin functions + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "nvim/ascii.h" +#include "nvim/autocmd.h" +#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" +#include "nvim/cursor.h" +#include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/window.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/memline_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/move.h" +#include "nvim/option_defs.h" +#include "nvim/pos.h" +#include "nvim/types.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/window.c.generated.h" +#endif + +static char *e_invalwindow = N_("E957: Invalid window number"); +static char e_cannot_resize_window_in_another_tab_page[] + = N_("E1308: Cannot resize a window in another tab page"); + +static int win_getid(typval_T *argvars) +{ + if (argvars[0].v_type == VAR_UNKNOWN) { + return curwin->handle; + } + int winnr = (int)tv_get_number(&argvars[0]); + win_T *wp; + if (winnr <= 0) { + return 0; + } + + if (argvars[1].v_type == VAR_UNKNOWN) { + wp = firstwin; + } else { + tabpage_T *tp = NULL; + int tabnr = (int)tv_get_number(&argvars[1]); + FOR_ALL_TABS(tp2) { + if (--tabnr == 0) { + tp = tp2; + break; + } + } + if (tp == NULL) { + return -1; + } + if (tp == curtab) { + wp = firstwin; + } else { + wp = tp->tp_firstwin; + } + } + for (; wp != NULL; wp = wp->w_next) { + if (--winnr == 0) { + return wp->handle; + } + } + return 0; +} + +static void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) +{ + handle_T id = (handle_T)tv_get_number(&argvars[0]); + + int winnr = 1; + int tabnr = 1; + win_get_tabwin(id, &tabnr, &winnr); + + list_T *const list = tv_list_alloc_ret(rettv, 2); + tv_list_append_number(list, tabnr); + tv_list_append_number(list, winnr); +} + +win_T *win_id2wp(int id) +{ + return win_id2wp_tp(id, NULL); +} + +/// Return the window and tab pointer of window "id". +win_T *win_id2wp_tp(int id, tabpage_T **tpp) +{ + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->handle == id) { + if (tpp != NULL) { + *tpp = tp; + } + return wp; + } + } + + return NULL; +} + +static int win_id2win(typval_T *argvars) +{ + int nr = 1; + int id = (int)tv_get_number(&argvars[0]); + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->handle == id) { + return nr; + } + nr++; + } + return 0; +} + +void win_findbuf(typval_T *argvars, list_T *list) +{ + int bufnr = (int)tv_get_number(&argvars[0]); + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer->b_fnum == bufnr) { + tv_list_append_number(list, wp->handle); + } + } +} + +/// Find window specified by "vp" in tabpage "tp". +/// +/// @param tp NULL for current tab page +/// @return current window if "vp" is number zero. +/// NULL if not found. +win_T *find_win_by_nr(typval_T *vp, tabpage_T *tp) +{ + int nr = (int)tv_get_number_chk(vp, NULL); + + if (nr < 0) { + return NULL; + } + + if (nr == 0) { + return curwin; + } + + // This method accepts NULL as an alias for curtab. + if (tp == NULL) { + tp = curtab; + } + + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { + if (nr >= LOWEST_WIN_ID) { + if (wp->handle == nr) { + return wp; + } + } else if (--nr <= 0) { + return wp; + } + } + return NULL; +} + +/// Find a window: When using a Window ID in any tab page, when using a number +/// in the current tab page. +win_T *find_win_by_nr_or_id(typval_T *vp) +{ + int nr = (int)tv_get_number_chk(vp, NULL); + + if (nr >= LOWEST_WIN_ID) { + return win_id2wp((int)tv_get_number(vp)); + } + + return find_win_by_nr(vp, NULL); +} + +/// Find window specified by "wvp" in tabpage "tvp". +win_T *find_tabwin(typval_T *wvp, typval_T *tvp) +{ + win_T *wp = NULL; + tabpage_T *tp = NULL; + + if (wvp->v_type != VAR_UNKNOWN) { + if (tvp->v_type != VAR_UNKNOWN) { + long n = tv_get_number(tvp); + if (n >= 0) { + tp = find_tabpage((int)n); + } + } else { + tp = curtab; + } + + if (tp != NULL) { + wp = find_win_by_nr(wvp, tp); + } + } else { + wp = curwin; + } + + return wp; +} + +/// Get the layout of the given tab page for winlayout(). +static void get_framelayout(const frame_T *fr, list_T *l, bool outer) +{ + if (fr == NULL) { + return; + } + + list_T *fr_list; + if (outer) { + // outermost call from f_winlayout() + fr_list = l; + } else { + fr_list = tv_list_alloc(2); + tv_list_append_list(l, fr_list); + } + + if (fr->fr_layout == FR_LEAF) { + if (fr->fr_win != NULL) { + tv_list_append_string(fr_list, "leaf", -1); + tv_list_append_number(fr_list, fr->fr_win->handle); + } + } else { + tv_list_append_string(fr_list, fr->fr_layout == FR_ROW ? "row" : "col", -1); + + list_T *const win_list = tv_list_alloc(kListLenUnknown); + tv_list_append_list(fr_list, win_list); + const frame_T *child = fr->fr_child; + while (child != NULL) { + get_framelayout(child, win_list, false); + child = child->fr_next; + } + } +} + +/// Common code for tabpagewinnr() and winnr(). +static int get_winnr(tabpage_T *tp, typval_T *argvar) +{ + int nr = 1; + + win_T *twin = (tp == curtab) ? curwin : tp->tp_curwin; + if (argvar->v_type != VAR_UNKNOWN) { + bool invalid_arg = false; + const char *const arg = tv_get_string_chk(argvar); + if (arg == NULL) { + nr = 0; // Type error; errmsg already given. + } else if (strcmp(arg, "$") == 0) { + twin = (tp == curtab) ? lastwin : tp->tp_lastwin; + } else if (strcmp(arg, "#") == 0) { + twin = (tp == curtab) ? prevwin : tp->tp_prevwin; + if (twin == NULL) { + nr = 0; + } + } else { + // Extract the window count (if specified). e.g. winnr('3j') + char *endp; + long count = strtol((char *)arg, &endp, 10); + if (count <= 0) { + // if count is not specified, default to 1 + count = 1; + } + if (endp != NULL && *endp != '\0') { + if (strequal(endp, "j")) { + twin = win_vert_neighbor(tp, twin, false, count); + } else if (strequal(endp, "k")) { + twin = win_vert_neighbor(tp, twin, true, count); + } else if (strequal(endp, "h")) { + twin = win_horz_neighbor(tp, twin, true, count); + } else if (strequal(endp, "l")) { + twin = win_horz_neighbor(tp, twin, false, count); + } else { + invalid_arg = true; + } + } else { + invalid_arg = true; + } + } + + if (invalid_arg) { + semsg(_(e_invexpr2), arg); + nr = 0; + } + } + + if (nr <= 0) { + return 0; + } + + for (win_T *wp = (tp == curtab) ? firstwin : tp->tp_firstwin; + wp != twin; wp = wp->w_next) { + if (wp == NULL) { + // didn't find it in this tabpage + nr = 0; + break; + } + nr++; + } + return nr; +} + +/// @return information about a window as a dictionary. +static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) +{ + dict_T *const dict = tv_dict_alloc(); + + // make sure w_botline is valid + validate_botline(wp); + + tv_dict_add_nr(dict, S_LEN("tabnr"), tpnr); + tv_dict_add_nr(dict, S_LEN("winnr"), winnr); + tv_dict_add_nr(dict, S_LEN("winid"), wp->handle); + tv_dict_add_nr(dict, S_LEN("height"), wp->w_height_inner); + tv_dict_add_nr(dict, S_LEN("winrow"), wp->w_winrow + 1); + tv_dict_add_nr(dict, S_LEN("topline"), wp->w_topline); + tv_dict_add_nr(dict, S_LEN("botline"), wp->w_botline - 1); + tv_dict_add_nr(dict, S_LEN("winbar"), wp->w_winbar_height); + tv_dict_add_nr(dict, S_LEN("width"), wp->w_width_inner); + tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); + tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1); + tv_dict_add_nr(dict, S_LEN("textoff"), win_col_off(wp)); + tv_dict_add_nr(dict, S_LEN("terminal"), bt_terminal(wp->w_buffer)); + tv_dict_add_nr(dict, S_LEN("quickfix"), bt_quickfix(wp->w_buffer)); + tv_dict_add_nr(dict, S_LEN("loclist"), + (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)); + + // Add a reference to window variables + tv_dict_add_dict(dict, S_LEN("variables"), wp->w_vars); + + return dict; +} + +/// @return information (variables, options, etc.) about a tab page +/// as a dictionary. +static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) +{ + dict_T *const dict = tv_dict_alloc(); + + tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); + + list_T *const l = tv_list_alloc(kListLenMayKnow); + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { + tv_list_append_number(l, (varnumber_T)wp->handle); + } + tv_dict_add_list(dict, S_LEN("windows"), l); + + // Make a reference to tabpage variables + tv_dict_add_dict(dict, S_LEN("variables"), tp->tp_vars); + + return dict; +} + +/// "gettabinfo()" function +void f_gettabinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tabpage_T *tparg = NULL; + + tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN + ? 1 + : kListLenMayKnow)); + + if (argvars[0].v_type != VAR_UNKNOWN) { + // Information about one tab page + tparg = find_tabpage((int)tv_get_number_chk(&argvars[0], NULL)); + if (tparg == NULL) { + return; + } + } + + // Get information about a specific tab page or all tab pages + int tpnr = 0; + FOR_ALL_TABS(tp) { + tpnr++; + if (tparg != NULL && tp != tparg) { + continue; + } + dict_T *const d = get_tabpage_info(tp, tpnr); + tv_list_append_dict(rettv->vval.v_list, d); + if (tparg != NULL) { + return; + } + } +} + +/// "getwininfo()" function +void f_getwininfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wparg = NULL; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (argvars[0].v_type != VAR_UNKNOWN) { + wparg = win_id2wp((int)tv_get_number(&argvars[0])); + if (wparg == NULL) { + return; + } + } + + // Collect information about either all the windows across all the tab + // pages or one particular window. + int16_t tabnr = 0; + FOR_ALL_TABS(tp) { + tabnr++; + int16_t winnr = 0; + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { + winnr++; + if (wparg != NULL && wp != wparg) { + continue; + } + dict_T *const d = get_win_info(wp, tabnr, winnr); + tv_list_append_dict(rettv->vval.v_list, d); + if (wparg != NULL) { + // found information about a specific window + return; + } + } + } +} + +/// "getwinpos({timeout})" function +void f_getwinpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, 2); + tv_list_append_number(rettv->vval.v_list, -1); + tv_list_append_number(rettv->vval.v_list, -1); +} + +/// "getwinposx()" function +void f_getwinposx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; +} + +/// "getwinposy()" function +void f_getwinposy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; +} + +/// "tabpagenr()" function +void f_tabpagenr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int nr = 1; + + if (argvars[0].v_type != VAR_UNKNOWN) { + const char *const arg = tv_get_string_chk(&argvars[0]); + nr = 0; + if (arg != NULL) { + if (strcmp(arg, "$") == 0) { + nr = tabpage_index(NULL) - 1; + } else if (strcmp(arg, "#") == 0) { + nr = valid_tabpage(lastused_tabpage) ? tabpage_index(lastused_tabpage) : 0; + } else { + semsg(_(e_invexpr2), arg); + } + } + } else { + nr = tabpage_index(curtab); + } + rettv->vval.v_number = nr; +} + +/// "tabpagewinnr()" function +void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int nr = 1; + tabpage_T *const tp = find_tabpage((int)tv_get_number(&argvars[0])); + if (tp == NULL) { + nr = 0; + } else { + nr = get_winnr(tp, &argvars[1]); + } + rettv->vval.v_number = nr; +} + +/// "win_execute(win_id, command)" function +void f_win_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + // Return an empty string if something fails. + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + int id = (int)tv_get_number(argvars); + tabpage_T *tp; + win_T *wp = win_id2wp_tp(id, &tp); + if (wp == NULL || tp == NULL) { + return; + } + + WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, 1)); +} + +/// "win_findbuf()" function +void f_win_findbuf(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, kListLenMayKnow); + win_findbuf(argvars, rettv->vval.v_list); +} + +/// "win_getid()" function +void f_win_getid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = win_getid(argvars); +} + +/// "win_gotoid()" function +void f_win_gotoid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + int id = (int)tv_get_number(&argvars[0]); + + if (cmdwin_type != 0) { + emsg(_(e_cmdwin)); + return; + } + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->handle == id) { + goto_tabpage_win(tp, wp); + rettv->vval.v_number = 1; + return; + } + } +} + +/// "win_id2tabwin()" function +void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_id2tabwin(argvars, rettv); +} + +/// "win_id2win()" function +void f_win_id2win(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = win_id2win(argvars); +} + +/// "win_move_separator()" function +void f_win_move_separator(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = false; + + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL || wp->w_floating) { + return; + } + if (!win_valid(wp)) { + emsg(_(e_cannot_resize_window_in_another_tab_page)); + return; + } + + int offset = (int)tv_get_number(&argvars[1]); + win_drag_vsep_line(wp, offset); + rettv->vval.v_number = true; +} + +/// "win_move_statusline()" function +void f_win_move_statusline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wp; + int offset; + + rettv->vval.v_number = false; + + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL || wp->w_floating) { + return; + } + if (!win_valid(wp)) { + emsg(_(e_cannot_resize_window_in_another_tab_page)); + return; + } + + offset = (int)tv_get_number(&argvars[1]); + win_drag_status_line(wp, offset); + rettv->vval.v_number = true; +} + +/// "win_screenpos()" function +void f_win_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, 2); + const win_T *const wp = find_win_by_nr_or_id(&argvars[0]); + tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_winrow + 1); + tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); +} + +/// Move the window wp into a new split of targetwin in a given direction +static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags) +{ + int height = wp->w_height; + win_T *oldwin = curwin; + + if (wp == targetwin || is_aucmd_win(wp)) { + return; + } + + // Jump to the target window + if (curwin != targetwin) { + win_goto(targetwin); + } + + // Remove the old window and frame from the tree of frames + int dir; + (void)winframe_remove(wp, &dir, NULL); + win_remove(wp, NULL); + last_status(false); // may need to remove last status line + (void)win_comp_pos(); // recompute window positions + + // Split a window on the desired side and put the old window there + (void)win_split_ins(size, flags, wp, dir); + + // If splitting horizontally, try to preserve height + if (size == 0 && !(flags & WSP_VERT)) { + win_setheight_win(height, wp); + if (p_ea) { + win_equal(wp, true, 'v'); + } + } + + if (oldwin != curwin) { + win_goto(oldwin); + } +} + +/// "win_splitmove()" function +void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + win_T *targetwin = find_win_by_nr_or_id(&argvars[1]); + + if (wp == NULL || targetwin == NULL || wp == targetwin + || !win_valid(wp) || !win_valid(targetwin) + || win_valid_floating(wp) || win_valid_floating(targetwin)) { + emsg(_(e_invalwindow)); + rettv->vval.v_number = -1; + return; + } + + int flags = 0, size = 0; + + if (argvars[2].v_type != VAR_UNKNOWN) { + dict_T *d; + dictitem_T *di; + + if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) { + emsg(_(e_invarg)); + return; + } + + d = argvars[2].vval.v_dict; + if (tv_dict_get_number(d, "vertical")) { + flags |= WSP_VERT; + } + if ((di = tv_dict_find(d, "rightbelow", -1)) != NULL) { + flags |= tv_get_number(&di->di_tv) ? WSP_BELOW : WSP_ABOVE; + } + size = (int)tv_dict_get_number(d, "size"); + } + + win_move_into_split(wp, targetwin, size, flags); +} + +/// "win_gettype(nr)" function +void f_win_gettype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wp = curwin; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (argvars[0].v_type != VAR_UNKNOWN) { + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_string = xstrdup("unknown"); + return; + } + } + if (is_aucmd_win(wp)) { + rettv->vval.v_string = xstrdup("autocmd"); + } else if (wp->w_p_pvw) { + rettv->vval.v_string = xstrdup("preview"); + } else if (wp->w_floating) { + rettv->vval.v_string = xstrdup("popup"); + } else if (wp == curwin && cmdwin_type != 0) { + rettv->vval.v_string = xstrdup("command"); + } else if (bt_quickfix(wp->w_buffer)) { + rettv->vval.v_string = xstrdup((wp->w_llist_ref != NULL ? "loclist" : "quickfix")); + } +} + +/// "getcmdwintype()" function +void f_getcmdwintype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + rettv->vval.v_string = xmallocz(1); + rettv->vval.v_string[0] = (char)cmdwin_type; +} + +/// "winbufnr(nr)" function +void f_winbufnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = wp->w_buffer->b_fnum; + } +} + +/// "wincol()" function +void f_wincol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + validate_cursor(); + rettv->vval.v_number = curwin->w_wcol + 1; +} + +/// "winheight(nr)" function +void f_winheight(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = wp->w_height_inner; + } +} + +/// "winlayout()" function +void f_winlayout(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tabpage_T *tp; + + tv_list_alloc_ret(rettv, 2); + + if (argvars[0].v_type == VAR_UNKNOWN) { + tp = curtab; + } else { + tp = find_tabpage((int)tv_get_number(&argvars[0])); + if (tp == NULL) { + return; + } + } + + get_framelayout(tp->tp_topframe, rettv->vval.v_list, true); +} + +/// "winline()" function +void f_winline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + validate_cursor(); + rettv->vval.v_number = curwin->w_wrow + 1; +} + +/// "winnr()" function +void f_winnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = get_winnr(curtab, &argvars[0]); +} + +/// "winrestcmd()" function +void f_winrestcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + char buf[50]; + + garray_T ga; + ga_init(&ga, (int)sizeof(char), 70); + + // Do this twice to handle some window layouts properly. + for (int i = 0; i < 2; i++) { + int winnr = 1; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + snprintf(buf, sizeof(buf), "%dresize %d|", winnr, + wp->w_height); + ga_concat(&ga, buf); + snprintf(buf, sizeof(buf), "vert %dresize %d|", winnr, + wp->w_width); + ga_concat(&ga, buf); + winnr++; + } + } + ga_append(&ga, NUL); + + rettv->vval.v_string = ga.ga_data; + rettv->v_type = VAR_STRING; +} + +/// "winrestview()" function +void f_winrestview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + dict_T *dict = argvars[0].vval.v_dict; + + if (argvars[0].v_type != VAR_DICT || dict == NULL) { + emsg(_(e_invarg)); + } else { + dictitem_T *di; + if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) { + curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) { + curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) { + curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) { + curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv); + curwin->w_set_curswant = false; + } + if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) { + set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv)); + } + if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) { + curwin->w_topfill = (int)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) { + curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv); + } + if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) { + curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv); + } + + check_cursor(); + win_new_height(curwin, curwin->w_height); + win_new_width(curwin, curwin->w_width); + changed_window_setting(); + + if (curwin->w_topline <= 0) { + curwin->w_topline = 1; + } + if (curwin->w_topline > curbuf->b_ml.ml_line_count) { + curwin->w_topline = curbuf->b_ml.ml_line_count; + } + check_topfill(curwin, true); + } +} + +/// "winsaveview()" function +void f_winsaveview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_dict_alloc_ret(rettv); + dict_T *dict = rettv->vval.v_dict; + + tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)curwin->w_cursor.lnum); + tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)curwin->w_cursor.col); + tv_dict_add_nr(dict, S_LEN("coladd"), (varnumber_T)curwin->w_cursor.coladd); + update_curswant(); + tv_dict_add_nr(dict, S_LEN("curswant"), (varnumber_T)curwin->w_curswant); + + tv_dict_add_nr(dict, S_LEN("topline"), (varnumber_T)curwin->w_topline); + tv_dict_add_nr(dict, S_LEN("topfill"), (varnumber_T)curwin->w_topfill); + tv_dict_add_nr(dict, S_LEN("leftcol"), (varnumber_T)curwin->w_leftcol); + tv_dict_add_nr(dict, S_LEN("skipcol"), (varnumber_T)curwin->w_skipcol); +} + +/// "winwidth(nr)" function +void f_winwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = wp->w_width_inner; + } +} + +/// Set "win" to be the curwin and "tp" to be the current tab page. +/// restore_win() MUST be called to undo, also when FAIL is returned. +/// No autocommands will be executed until restore_win() is called. +/// +/// @param no_display if true the display won't be affected, no redraw is +/// triggered, another tabpage access is limited. +/// +/// @return FAIL if switching to "win" failed. +int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) +{ + block_autocmds(); + return switch_win_noblock(switchwin, win, tp, no_display); +} + +// As switch_win() but without blocking autocommands. +int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) +{ + CLEAR_POINTER(switchwin); + switchwin->sw_curwin = curwin; + if (win == curwin) { + switchwin->sw_same_win = true; + } else { + // Disable Visual selection, because redrawing may fail. + switchwin->sw_visual_active = VIsual_active; + VIsual_active = false; + } + + if (tp != NULL) { + switchwin->sw_curtab = curtab; + if (no_display) { + curtab->tp_firstwin = firstwin; + curtab->tp_lastwin = lastwin; + curtab = tp; + firstwin = curtab->tp_firstwin; + lastwin = curtab->tp_lastwin; + } else { + goto_tabpage_tp(tp, false, false); + } + } + if (!win_valid(win)) { + return FAIL; + } + curwin = win; + curbuf = curwin->w_buffer; + return OK; +} + +// Restore current tabpage and window saved by switch_win(), if still valid. +// When "no_display" is true the display won't be affected, no redraw is +// triggered. +void restore_win(switchwin_T *switchwin, bool no_display) +{ + restore_win_noblock(switchwin, no_display); + unblock_autocmds(); +} + +// As restore_win() but without unblocking autocommands. +void restore_win_noblock(switchwin_T *switchwin, bool no_display) +{ + if (switchwin->sw_curtab != NULL && valid_tabpage(switchwin->sw_curtab)) { + if (no_display) { + curtab->tp_firstwin = firstwin; + curtab->tp_lastwin = lastwin; + curtab = switchwin->sw_curtab; + firstwin = curtab->tp_firstwin; + lastwin = curtab->tp_lastwin; + } else { + goto_tabpage_tp(switchwin->sw_curtab, false, false); + } + } + + if (!switchwin->sw_same_win) { + VIsual_active = switchwin->sw_visual_active; + } + + if (win_valid(switchwin->sw_curwin)) { + curwin = switchwin->sw_curwin; + curbuf = curwin->w_buffer; + } +} diff --git a/src/nvim/eval/window.h b/src/nvim/eval/window.h new file mode 100644 index 0000000000..995f0a55a9 --- /dev/null +++ b/src/nvim/eval/window.h @@ -0,0 +1,78 @@ +#ifndef NVIM_EVAL_WINDOW_H +#define NVIM_EVAL_WINDOW_H + +#include <stdbool.h> +#include <string.h> + +#include "nvim/buffer.h" +#include "nvim/buffer_defs.h" +#include "nvim/cursor.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/globals.h" +#include "nvim/mark.h" +#include "nvim/option_defs.h" +#include "nvim/os/os.h" +#include "nvim/pos.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +/// Structure used by switch_win() to pass values to restore_win() +typedef struct { + win_T *sw_curwin; + tabpage_T *sw_curtab; + bool sw_same_win; ///< VIsual_active was not reset + bool sw_visual_active; +} switchwin_T; + +/// Execute a block of code in the context of window `wp` in tabpage `tp`. +/// Ensures the status line is redrawn and cursor position is valid if it is moved. +#define WIN_EXECUTE(wp, tp, block) \ + do { \ + win_T *const wp_ = (wp); \ + const pos_T curpos_ = wp_->w_cursor; \ + char cwd_[MAXPATHL]; \ + char autocwd_[MAXPATHL]; \ + bool apply_acd_ = false; \ + int cwd_status_ = FAIL; \ + /* Getting and setting directory can be slow on some systems, only do */ \ + /* this when the current or target window/tab have a local directory or */ \ + /* 'acd' is set. */ \ + if (curwin != wp \ + && (curwin->w_localdir != NULL || wp->w_localdir != NULL \ + || (curtab != tp && (curtab->tp_localdir != NULL || tp->tp_localdir != NULL)) \ + || p_acd)) { \ + cwd_status_ = os_dirname(cwd_, MAXPATHL); \ + } \ + /* If 'acd' is set, check we are using that directory. If yes, then */ \ + /* apply 'acd' afterwards, otherwise restore the current directory. */ \ + if (cwd_status_ == OK && p_acd) { \ + do_autochdir(); \ + apply_acd_ = os_dirname(autocwd_, MAXPATHL) == OK && strcmp(cwd_, autocwd_) == 0; \ + } \ + switchwin_T switchwin_; \ + if (switch_win_noblock(&switchwin_, wp_, (tp), true) == OK) { \ + check_cursor(); \ + block; \ + } \ + restore_win_noblock(&switchwin_, true); \ + if (apply_acd_) { \ + do_autochdir(); \ + } else if (cwd_status_ == OK) { \ + os_chdir(cwd_); \ + } \ + /* Update the status line if the cursor moved. */ \ + if (win_valid(wp_) && !equalpos(curpos_, wp_->w_cursor)) { \ + wp_->w_redr_status = true; \ + } \ + /* In case the command moved the cursor or changed the Visual area, */ \ + /* check it is valid. */ \ + check_cursor(); \ + if (VIsual_active) { \ + check_pos(curbuf, &VIsual); \ + } \ + } while (false) + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval/window.h.generated.h" +#endif +#endif // NVIM_EVAL_WINDOW_H diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 0251ea9957..e528d21a71 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -2,16 +2,18 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <stdint.h> #include <uv.h> +#include "nvim/eval/typval.h" #include "nvim/event/libuv_process.h" #include "nvim/event/loop.h" #include "nvim/event/process.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" +#include "nvim/event/stream.h" #include "nvim/log.h" #include "nvim/macros.h" #include "nvim/os/os.h" +#include "nvim/ui_client.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/libuv_process.c.generated.h" @@ -25,7 +27,7 @@ int libuv_process_spawn(LibuvProcess *uvproc) uvproc->uvopts.file = proc->argv[0]; uvproc->uvopts.args = proc->argv; uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE; -#ifdef WIN32 +#ifdef MSWIN // libuv collapses the argv to a CommandLineToArgvW()-style string. cmd.exe // expects a different syntax (must be prepared by the caller before now). if (os_shell_is_cmdexe(proc->argv[0])) { @@ -40,11 +42,19 @@ int libuv_process_spawn(LibuvProcess *uvproc) #endif uvproc->uvopts.exit_cb = exit_cb; uvproc->uvopts.cwd = proc->cwd; + uvproc->uvopts.stdio = uvproc->uvstdio; uvproc->uvopts.stdio_count = 3; uvproc->uvstdio[0].flags = UV_IGNORE; uvproc->uvstdio[1].flags = UV_IGNORE; uvproc->uvstdio[2].flags = UV_IGNORE; + + if (ui_client_forward_stdin) { + assert(UI_CLIENT_STDIN_FD == 3); + uvproc->uvopts.stdio_count = 4; + uvproc->uvstdio[3].data.fd = 0; + uvproc->uvstdio[3].flags = UV_INHERIT_FD; + } uvproc->uv.data = proc; if (proc->env) { @@ -55,7 +65,7 @@ int libuv_process_spawn(LibuvProcess *uvproc) if (!proc->in.closed) { uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; -#ifdef WIN32 +#ifdef MSWIN uvproc->uvstdio[0].flags |= proc->overlapped ? UV_OVERLAPPED_PIPE : 0; #endif uvproc->uvstdio[0].data.stream = STRUCT_CAST(uv_stream_t, @@ -64,7 +74,7 @@ int libuv_process_spawn(LibuvProcess *uvproc) if (!proc->out.closed) { uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; -#ifdef WIN32 +#ifdef MSWIN // pipe must be readable for IOCP to work on Windows. uvproc->uvstdio[1].flags |= proc->overlapped ? (UV_READABLE_PIPE | UV_OVERLAPPED_PIPE) : 0; @@ -77,6 +87,9 @@ int libuv_process_spawn(LibuvProcess *uvproc) uvproc->uvstdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; uvproc->uvstdio[2].data.stream = STRUCT_CAST(uv_stream_t, &proc->err.uv.pipe); + } else if (proc->fwd_err) { + uvproc->uvstdio[2].flags = UV_INHERIT_FD; + uvproc->uvstdio[2].data.fd = STDERR_FILENO; } int status; @@ -113,7 +126,7 @@ static void close_cb(uv_handle_t *handle) static void exit_cb(uv_process_t *handle, int64_t status, int term_signal) { Process *proc = handle->data; -#if defined(WIN32) +#if defined(MSWIN) // Use stored/expected signal. term_signal = proc->exit_signal; #endif diff --git a/src/nvim/event/libuv_process.h b/src/nvim/event/libuv_process.h index 1132ce79ca..4472839944 100644 --- a/src/nvim/event/libuv_process.h +++ b/src/nvim/event/libuv_process.h @@ -3,13 +3,14 @@ #include <uv.h> +#include "nvim/event/loop.h" #include "nvim/event/process.h" typedef struct libuv_process { Process process; uv_process_t uv; uv_process_options_t uvopts; - uv_stdio_container_t uvstdio[3]; + uv_stdio_container_t uvstdio[4]; } LibuvProcess; static inline LibuvProcess libuv_process_init(Loop *loop, void *data) diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 1b5cc23b09..ab2524c1a9 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -1,13 +1,16 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <stdarg.h> +#include <stdbool.h> #include <stdint.h> +#include <stdlib.h> #include <uv.h> +#include "nvim/event/defs.h" #include "nvim/event/loop.h" -#include "nvim/event/process.h" #include "nvim/log.h" +#include "nvim/memory.h" +#include "nvim/os/time.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/loop.c.generated.h" @@ -31,41 +34,57 @@ void loop_init(Loop *loop, void *data) loop->poll_timer.data = xmalloc(sizeof(bool)); // "timeout expired" flag } -/// Processes one `Loop.uv` event (at most). -/// Processes all `Loop.fast_events` events. -/// Does NOT process `Loop.events`, that is an application-specific decision. +/// Process `Loop.uv` events with a timeout. /// /// @param loop -/// @param ms 0: non-blocking poll. -/// >0: timeout after `ms`. -/// <0: wait forever. -/// @returns true if `ms` timeout was reached -bool loop_poll_events(Loop *loop, int ms) +/// @param ms 0: non-blocking poll. +/// > 0: timeout after `ms`. +/// < 0: wait forever. +/// @param once true: process at most one `Loop.uv` event. +/// false: process until `ms` timeout (only has effect if `ms` > 0). +/// @return true if `ms` > 0 and was reached +bool loop_uv_run(Loop *loop, int64_t ms, bool once) { if (loop->recursive++) { abort(); // Should not re-enter uv_run } uv_run_mode mode = UV_RUN_ONCE; - bool timeout_expired = false; + bool *timeout_expired = loop->poll_timer.data; + *timeout_expired = false; if (ms > 0) { - *((bool *)loop->poll_timer.data) = false; // reset "timeout expired" flag - // Dummy timer to ensure UV_RUN_ONCE does not block indefinitely for I/O. + // This timer ensures UV_RUN_ONCE does not block indefinitely for I/O. uv_timer_start(&loop->poll_timer, timer_cb, (uint64_t)ms, (uint64_t)ms); } else if (ms == 0) { // For ms == 0, do a non-blocking event poll. mode = UV_RUN_NOWAIT; } - uv_run(&loop->uv, mode); + do { // -V1044 + uv_run(&loop->uv, mode); + } while (ms > 0 && !once && !*timeout_expired); // -V560 if (ms > 0) { - timeout_expired = *((bool *)loop->poll_timer.data); uv_timer_stop(&loop->poll_timer); } loop->recursive--; // Can re-enter uv_run now + return *timeout_expired; +} + +/// Processes one `Loop.uv` event (at most). +/// Processes all `Loop.fast_events` events. +/// Does NOT process `Loop.events`, that is an application-specific decision. +/// +/// @param loop +/// @param ms 0: non-blocking poll. +/// > 0: timeout after `ms`. +/// < 0: wait forever. +/// @return true if `ms` > 0 and was reached +bool loop_poll_events(Loop *loop, int64_t ms) +{ + bool timeout_expired = loop_uv_run(loop, ms, true); multiqueue_process_events(loop->fast_events); return timeout_expired; } diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h index 65980c6c05..b2265a726d 100644 --- a/src/nvim/event/loop.h +++ b/src/nvim/event/loop.h @@ -4,8 +4,8 @@ #include <stdint.h> #include <uv.h> +#include "klib/klist.h" #include "nvim/event/multiqueue.h" -#include "nvim/lib/klist.h" #include "nvim/os/time.h" typedef void *WatcherPtr; @@ -58,7 +58,7 @@ typedef struct loop { // Poll for events until a condition or timeout #define LOOP_PROCESS_EVENTS_UNTIL(loop, multiqueue, timeout, condition) \ do { \ - int remaining = timeout; \ + int64_t remaining = timeout; \ uint64_t before = (remaining > 0) ? os_hrtime() : 0; \ while (!(condition)) { \ LOOP_PROCESS_EVENTS(loop, multiqueue, remaining); \ @@ -66,7 +66,7 @@ typedef struct loop { break; \ } else if (remaining > 0) { \ uint64_t now = os_hrtime(); \ - remaining -= (int)((now - before) / 1000000); \ + remaining -= (int64_t)((now - before) / 1000000); \ before = now; \ if (remaining <= 0) { \ break; \ diff --git a/src/nvim/event/multiqueue.c b/src/nvim/event/multiqueue.c index 40d20033e0..e05084b656 100644 --- a/src/nvim/event/multiqueue.c +++ b/src/nvim/event/multiqueue.c @@ -46,14 +46,14 @@ // other sources and focus on a specific channel. #include <assert.h> -#include <stdarg.h> #include <stdbool.h> -#include <stdint.h> +#include <stddef.h> #include <uv.h> +#include "nvim/event/defs.h" #include "nvim/event/multiqueue.h" +#include "nvim/lib/queue.h" #include "nvim/memory.h" -#include "nvim/os/time.h" typedef struct multiqueue_item MultiQueueItem; struct multiqueue_item { diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index e029f778f6..1a524a56ca 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -2,20 +2,25 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <inttypes.h> +#include <signal.h> #include <stdlib.h> #include <uv.h> +#include "klib/klist.h" #include "nvim/event/libuv_process.h" #include "nvim/event/loop.h" #include "nvim/event/process.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" #include "nvim/globals.h" #include "nvim/log.h" #include "nvim/macros.h" +#include "nvim/main.h" #include "nvim/os/process.h" #include "nvim/os/pty_process.h" #include "nvim/os/shell.h" +#include "nvim/os/time.h" +#include "nvim/rbuffer.h" +#include "nvim/ui_client.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/process.c.generated.h" @@ -32,10 +37,16 @@ void __gcov_flush(void); static bool process_is_tearing_down = false; +// Delay exit until handles are closed, to avoid deadlocks +static int exit_need_delay = 0; + /// @returns zero on success, or negative error code int process_spawn(Process *proc, bool in, bool out, bool err) FUNC_ATTR_NONNULL_ALL { + // forwarding stderr contradicts with processing it internally + assert(!(err && proc->fwd_err)); + if (in) { uv_pipe_init(&proc->loop->uv, &proc->in.uv.pipe, 0); } else { @@ -395,12 +406,41 @@ static void process_close_handles(void **argv) exit_need_delay--; } +static void exit_delay_cb(uv_timer_t *handle) +{ + uv_timer_stop(&main_loop.exit_delay_timer); + multiqueue_put(main_loop.fast_events, exit_event, 1, main_loop.exit_delay_timer.data); +} + +static void exit_event(void **argv) +{ + int status = (int)(intptr_t)argv[0]; + if (exit_need_delay) { + main_loop.exit_delay_timer.data = argv[0]; + uv_timer_start(&main_loop.exit_delay_timer, exit_delay_cb, 0, 0); + return; + } + + if (!exiting) { + os_exit(status); + } +} + +void exit_from_channel(int status) +{ + multiqueue_put(main_loop.fast_events, exit_event, 1, status); +} + static void on_process_exit(Process *proc) { Loop *loop = proc->loop; ILOG("exited: pid=%d status=%d stoptime=%" PRIu64, proc->pid, proc->status, proc->stopped_time); + if (ui_client_channel_id) { + exit_from_channel(proc->status); + } + // Process has terminated, but there could still be data to be read from the // OS. We are still in the libuv loop, so we cannot call code that polls for // more data directly. Instead delay the reading after the libuv loop by diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index 30254bfe07..e0057faffb 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -1,11 +1,20 @@ #ifndef NVIM_EVENT_PROCESS_H #define NVIM_EVENT_PROCESS_H +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/rstream.h" +#include "nvim/event/stream.h" #include "nvim/event/wstream.h" +struct process; + typedef enum { kProcessTypeUv, kProcessTypePty, @@ -29,7 +38,7 @@ struct process { /// Exit handler. If set, user must call process_free(). process_exit_cb cb; internal_process_cb internal_exit_cb, internal_close_cb; - bool closed, detach, overlapped; + bool closed, detach, overlapped, fwd_err; MultiQueue *events; }; @@ -53,7 +62,8 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data) .closed = false, .internal_close_cb = NULL, .internal_exit_cb = NULL, - .detach = false + .detach = false, + .fwd_err = false, }; } diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index 2847788ef8..a88d62fd6b 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -3,17 +3,18 @@ #include <assert.h> #include <stdbool.h> +#include <stddef.h> #include <stdint.h> -#include <stdlib.h> #include <uv.h> -#include "nvim/ascii.h" #include "nvim/event/loop.h" #include "nvim/event/rstream.h" +#include "nvim/event/stream.h" #include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/memory.h" -#include "nvim/vim.h" +#include "nvim/os/os_defs.h" +#include "nvim/rbuffer.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/rstream.c.generated.h" diff --git a/src/nvim/event/signal.c b/src/nvim/event/signal.c index 4a45a2ec2f..8256ca2091 100644 --- a/src/nvim/event/signal.c +++ b/src/nvim/event/signal.c @@ -1,6 +1,7 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <stddef.h> #include <uv.h> #include "nvim/event/loop.h" diff --git a/src/nvim/event/signal.h b/src/nvim/event/signal.h index 7fe352edef..f9adf62c20 100644 --- a/src/nvim/event/signal.h +++ b/src/nvim/event/signal.h @@ -4,6 +4,9 @@ #include <uv.h> #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" + +struct signal_watcher; typedef struct signal_watcher SignalWatcher; typedef void (*signal_cb)(SignalWatcher *watcher, int signum, void *data); diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c index 9496a568b9..10756015ad 100644 --- a/src/nvim/event/socket.c +++ b/src/nvim/event/socket.c @@ -2,23 +2,24 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <stdint.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> #include <uv.h> #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/event/loop.h" -#include "nvim/event/rstream.h" #include "nvim/event/socket.h" -#include "nvim/event/wstream.h" +#include "nvim/event/stream.h" +#include "nvim/gettext.h" #include "nvim/log.h" #include "nvim/macros.h" #include "nvim/main.h" #include "nvim/memory.h" #include "nvim/os/os.h" #include "nvim/path.h" -#include "nvim/strings.h" -#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/socket.c.generated.h" @@ -125,7 +126,7 @@ int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb) // Libuv converts ENOENT to EACCES for Windows compatibility, but if // the parent directory does not exist, ENOENT would be more accurate. *path_tail(watcher->addr) = NUL; - if (!os_path_exists((char_u *)watcher->addr)) { + if (!os_path_exists(watcher->addr)) { result = UV_ENOENT; } } diff --git a/src/nvim/event/socket.h b/src/nvim/event/socket.h index d30ae45502..c6fcdec4bb 100644 --- a/src/nvim/event/socket.h +++ b/src/nvim/event/socket.h @@ -4,9 +4,12 @@ #include <uv.h> #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/rstream.h" #include "nvim/event/wstream.h" +struct socket_watcher; + #define ADDRESS_MAX_SIZE 256 typedef struct socket_watcher SocketWatcher; diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index b34fd73d52..0a4918636a 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -3,14 +3,16 @@ #include <assert.h> #include <stdbool.h> -#include <stdio.h> +#include <stddef.h> #include <uv.h> +#include <uv/version.h> +#include "nvim/event/loop.h" #include "nvim/event/stream.h" #include "nvim/log.h" #include "nvim/macros.h" #include "nvim/rbuffer.h" -#ifdef WIN32 +#ifdef MSWIN # include "nvim/os/os_win_console.h" #endif @@ -60,7 +62,7 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream) stream->uv.idle.data = stream; } else { assert(type == UV_NAMED_PIPE || type == UV_TTY); -#ifdef WIN32 +#ifdef MSWIN if (type == UV_TTY) { uv_tty_init(&loop->uv, &stream->uv.tty, fd, 0); uv_tty_set_mode(&stream->uv.tty, UV_TTY_MODE_RAW); @@ -75,7 +77,7 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream) uv_pipe_init(&loop->uv, &stream->uv.pipe, 0); uv_pipe_open(&stream->uv.pipe, fd); stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.pipe); -#ifdef WIN32 +#ifdef MSWIN } #endif } @@ -109,7 +111,7 @@ void stream_close(Stream *stream, stream_close_cb on_stream_close, void *data) stream->close_cb = on_stream_close; stream->close_cb_data = data; -#ifdef WIN32 +#ifdef MSWIN if (UV_TTY == uv_guess_handle(stream->fd)) { // Undo UV_TTY_MODE_RAW from stream_init(). #10801 uv_tty_set_mode(&stream->uv.tty, UV_TTY_MODE_NORMAL); diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h index 02e816b4be..33d2d6e775 100644 --- a/src/nvim/event/stream.h +++ b/src/nvim/event/stream.h @@ -6,8 +6,11 @@ #include <uv.h> #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/rbuffer.h" +struct stream; + typedef struct stream Stream; /// Type of function called when the Stream buffer is filled with data /// @@ -16,8 +19,7 @@ typedef struct stream Stream; /// @param count Number of bytes that was read. /// @param data User-defined data /// @param eof If the stream reached EOF. -typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, size_t count, - void *data, bool eof); +typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof); /// Type of function called when the Stream has information about a write /// request. @@ -35,7 +37,7 @@ struct stream { uv_pipe_t pipe; uv_tcp_t tcp; uv_idle_t idle; -#ifdef WIN32 +#ifdef MSWIN uv_tty_t tty; #endif } uv; diff --git a/src/nvim/event/time.h b/src/nvim/event/time.h index a6de89ad6e..e84488fdd6 100644 --- a/src/nvim/event/time.h +++ b/src/nvim/event/time.h @@ -1,9 +1,13 @@ #ifndef NVIM_EVENT_TIME_H #define NVIM_EVENT_TIME_H +#include <stdbool.h> #include <uv.h> #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" + +struct time_watcher; typedef struct time_watcher TimeWatcher; typedef void (*time_cb)(TimeWatcher *watcher, void *data); diff --git a/src/nvim/event/wstream.c b/src/nvim/event/wstream.c index d81ffa5c15..65391ba5cf 100644 --- a/src/nvim/event/wstream.c +++ b/src/nvim/event/wstream.c @@ -3,15 +3,13 @@ #include <assert.h> #include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> #include <uv.h> #include "nvim/event/loop.h" +#include "nvim/event/stream.h" #include "nvim/event/wstream.h" -#include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/memory.h" -#include "nvim/vim.h" #define DEFAULT_MAXMEM 1024 * 1024 * 2000 diff --git a/src/nvim/event/wstream.h b/src/nvim/event/wstream.h index d599d29913..ef1311c619 100644 --- a/src/nvim/event/wstream.h +++ b/src/nvim/event/wstream.h @@ -2,12 +2,15 @@ #define NVIM_EVENT_WSTREAM_H #include <stdbool.h> +#include <stddef.h> #include <stdint.h> #include <uv.h> #include "nvim/event/loop.h" #include "nvim/event/stream.h" +struct wbuffer; + typedef struct wbuffer WBuffer; typedef void (*wbuffer_data_finalizer)(void *data); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 06a372bb93..437a05f61d 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1,25 +1,30 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * ex_cmds.c: some functions for command line commands - */ +// ex_cmds.c: some functions for command line commands #include <assert.h> +#include <ctype.h> #include <float.h> #include <inttypes.h> #include <math.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <time.h> -#include "nvim/api/buffer.h" -#include "nvim/api/private/defs.h" +#include "auto/config.h" +#include "klib/kvec.h" #include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" +#include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cmdhist.h" #include "nvim/cursor.h" @@ -29,22 +34,27 @@ #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/fold.h" -#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/help.h" -#include "nvim/highlight.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/input.h" -#include "nvim/log.h" +#include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -57,21 +67,22 @@ #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/shell.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/plines.h" #include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/spell.h" #include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/tag.h" +#include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -147,14 +158,14 @@ void do_ascii(const exarg_T *const eap) char buf2[20]; buf2[0] = NUL; - dig = (char *)get_digraph_for_char(cval); + dig = get_digraph_for_char(cval); if (dig != NULL) { - iobuff_len += (size_t)vim_snprintf((char *)IObuff + iobuff_len, + iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len, _("<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s"), transchar(c), buf1, buf2, cval, cval, cval, dig); } else { - iobuff_len += (size_t)vim_snprintf((char *)IObuff + iobuff_len, + iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len, _("<%s>%s%s %d, Hex %02x, Octal %03o"), transchar(c), buf1, buf2, cval, cval, cval); @@ -193,18 +204,18 @@ void do_ascii(const exarg_T *const eap) if (utf_iscomposing(c)) { IObuff[iobuff_len++] = ' '; // Draw composing char on top of a space. } - iobuff_len += (size_t)utf_char2bytes(c, (char *)IObuff + iobuff_len); + iobuff_len += (size_t)utf_char2bytes(c, IObuff + iobuff_len); - dig = (char *)get_digraph_for_char(c); + dig = get_digraph_for_char(c); if (dig != NULL) { - iobuff_len += (size_t)vim_snprintf((char *)IObuff + iobuff_len, + iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len, (c < 0x10000 ? _("> %d, Hex %04x, Oct %o, Digr %s") : _("> %d, Hex %08x, Oct %o, Digr %s")), c, c, c, dig); } else { - iobuff_len += (size_t)vim_snprintf((char *)IObuff + iobuff_len, + iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len, (c < 0x10000 ? _("> %d, Hex %04x, Octal %o") @@ -217,10 +228,10 @@ void do_ascii(const exarg_T *const eap) c = cc[ci++]; } if (ci != MAX_MCO && c != 0) { - xstrlcpy((char *)IObuff + iobuff_len, " ...", sizeof(IObuff) - iobuff_len); + xstrlcpy(IObuff + iobuff_len, " ...", sizeof(IObuff) - iobuff_len); } - msg((char *)IObuff); + msg(IObuff); } /// ":left", ":center" and ":right": align text. @@ -249,11 +260,9 @@ void ex_align(exarg_T *eap) indent = width; } } else { - /* - * if 'textwidth' set, use it - * else if 'wrapmargin' set, use it - * if invalid value, use 80 - */ + // if 'textwidth' set, use it + // else if 'wrapmargin' set, use it + // if invalid value, use 80 if (width <= 0) { width = (int)curbuf->b_p_tw; } @@ -270,13 +279,12 @@ void ex_align(exarg_T *eap) } for (curwin->w_cursor.lnum = eap->line1; - curwin->w_cursor.lnum <= eap->line2; ++curwin->w_cursor.lnum) { + curwin->w_cursor.lnum <= eap->line2; curwin->w_cursor.lnum++) { if (eap->cmdidx == CMD_left) { // left align new_indent = indent; } else { - has_tab = FALSE; // avoid uninit warnings - len = linelen(eap->cmdidx == CMD_right ? &has_tab - : NULL) - get_indent(); + has_tab = false; // avoid uninit warnings + len = linelen(eap->cmdidx == CMD_right ? &has_tab : NULL) - get_indent(); if (len <= 0) { // skip blank lines continue; @@ -287,18 +295,14 @@ void ex_align(exarg_T *eap) } else { new_indent = width - len; // right align - /* - * Make sure that embedded TABs don't make the text go too far - * to the right. - */ + // Make sure that embedded TABs don't make the text go too far + // to the right. if (has_tab) { while (new_indent > 0) { (void)set_indent(new_indent, 0); if (linelen(NULL) <= width) { - /* - * Now try to move the line as much as possible to - * the right. Stop when it moves too far. - */ + // Now try to move the line as much as possible to + // the right. Stop when it moves too far. do { (void)set_indent(++new_indent, 0); } while (linelen(NULL) <= width); @@ -330,7 +334,7 @@ static int linelen(int *has_tab) // Get the line. If it's empty bail out early (could be the empty string // for an unloaded buffer). - line = (char *)get_cursor_line_ptr(); + line = get_cursor_line_ptr(); if (*line == NUL) { return 0; } @@ -338,12 +342,12 @@ static int linelen(int *has_tab) first = skipwhite(line); // find the character after the last non-blank character - for (last = first + STRLEN(first); + for (last = first + strlen(first); last > first && ascii_iswhite(last[-1]); last--) {} char save = *last; *last = NUL; // Get line length. - len = linetabsize((char_u *)line); + len = linetabsize(line); // Check for embedded TAB. if (has_tab != NULL) { *has_tab = vim_strchr(first, TAB) != NULL; @@ -387,7 +391,7 @@ static int string_compare(const void *s1, const void *s2) FUNC_ATTR_NONNULL_ALL if (sort_lc) { return strcoll((char *)s1, (char *)s2); } - return sort_ic ? STRICMP(s1, s2) : STRCMP(s1, s2); + return sort_ic ? STRICMP(s1, s2) : strcmp(s1, s2); } static int sort_compare(const void *s1, const void *s2) @@ -404,7 +408,7 @@ static int sort_compare(const void *s1, const void *s2) } fast_breakcheck(); if (got_int) { - sort_abort = TRUE; + sort_abort = true; } // When sorting numbers "start_col_nr" is the number, not the column @@ -509,13 +513,12 @@ void ex_sort(exarg_T *eap) } else if (*p == '"') { // comment start break; - } else if (check_nextcmd((char_u *)p) != NULL) { - eap->nextcmd = (char *)check_nextcmd((char_u *)p); + } else if (check_nextcmd(p) != NULL) { + eap->nextcmd = check_nextcmd(p); break; } else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) { - s = (char *)skip_regexp((char_u *)p + 1, *p, true, NULL); - if (*s != *p) { - emsg(_(e_invalpat)); + s = skip_regexp_err(p + 1, *p, true); + if (s == NULL) { goto sortend; } *s = NUL; @@ -525,7 +528,7 @@ void ex_sort(exarg_T *eap) emsg(_(e_noprevre)); goto sortend; } - regmatch.regprog = vim_regcomp((char *)last_search_pat(), RE_MAGIC); + regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC); } else { regmatch.regprog = vim_regcomp(p + 1, RE_MAGIC); } @@ -557,8 +560,8 @@ void ex_sort(exarg_T *eap) // matching and number conversion only has to be done once per line. // Also get the longest line length for allocating "sortbuf". for (lnum = eap->line1; lnum <= eap->line2; lnum++) { - s = (char *)ml_get(lnum); - len = (int)STRLEN(s); + s = ml_get(lnum); + len = (int)strlen(s); if (maxlen < len) { maxlen = len; } @@ -567,10 +570,10 @@ void ex_sort(exarg_T *eap) end_col = len; if (regmatch.regprog != NULL && vim_regexec(®match, s, 0)) { if (sort_rx) { - start_col = (colnr_T)(regmatch.startp[0] - (char_u *)s); - end_col = (colnr_T)(regmatch.endp[0] - (char_u *)s); + start_col = (colnr_T)(regmatch.startp[0] - s); + end_col = (colnr_T)(regmatch.endp[0] - s); } else { - start_col = (colnr_T)(regmatch.endp[0] - (char_u *)s); + start_col = (colnr_T)(regmatch.endp[0] - s); } } else if (regmatch.regprog != NULL) { end_col = 0; @@ -586,11 +589,11 @@ void ex_sort(exarg_T *eap) p = s + start_col; if (sort_nr) { if (sort_what & STR2NR_HEX) { - s = (char *)skiptohex((char_u *)p); + s = skiptohex(p); } else if (sort_what & STR2NR_BIN) { s = (char *)skiptobin(p); } else { - s = (char *)skiptodigit((char_u *)p); + s = skiptodigit(p); } if (s > p && s[-1] == '-') { s--; // include preceding negative sign @@ -601,7 +604,7 @@ void ex_sort(exarg_T *eap) nrs[lnum - eap->line1].st_u.num.value = 0; } else { nrs[lnum - eap->line1].st_u.num.is_number = true; - vim_str2nr((char_u *)s, NULL, NULL, sort_what, + vim_str2nr(s, NULL, NULL, sort_what, &nrs[lnum - eap->line1].st_u.num.value, NULL, 0, false); } } else { @@ -658,8 +661,8 @@ void ex_sort(exarg_T *eap) change_occurred = true; } - s = (char *)ml_get(get_lnum); - size_t bytelen = STRLEN(s) + 1; // include EOL in bytelen + s = ml_get(get_lnum); + size_t bytelen = strlen(s) + 1; // include EOL in bytelen old_count += (bcount_t)bytelen; if (!unique || i == 0 || string_compare(s, sortbuf1) != 0) { // Copy the line into a buffer, it may become invalid in @@ -678,7 +681,7 @@ void ex_sort(exarg_T *eap) // delete the original lines if appending worked if (i == count) { - for (i = 0; i < count; ++i) { + for (i = 0; i < count; i++) { ml_delete(eap->line1, false); } } else { @@ -716,187 +719,6 @@ sortend: } } -/// ":retab". -void ex_retab(exarg_T *eap) -{ - linenr_T lnum; - bool got_tab = false; - long num_spaces = 0; - long num_tabs; - long len; - long col; - long vcol; - long start_col = 0; // For start of white-space string - long start_vcol = 0; // For start of white-space string - long old_len; - char *ptr; - char *new_line = (char *)1; // init to non-NULL - bool did_undo; // called u_save for current line - long *new_vts_array = NULL; - char *new_ts_str; // string value of tab argument - - int save_list; - linenr_T first_line = 0; // first changed line - linenr_T last_line = 0; // last changed line - - save_list = curwin->w_p_list; - curwin->w_p_list = 0; // don't want list mode here - - new_ts_str = eap->arg; - if (!tabstop_set((char_u *)eap->arg, &new_vts_array)) { - return; - } - while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') { - (eap->arg)++; - } - - // This ensures that either new_vts_array and new_ts_str are freshly - // allocated, or new_vts_array points to an existing array and new_ts_str - // is null. - if (new_vts_array == NULL) { - new_vts_array = curbuf->b_p_vts_array; - new_ts_str = NULL; - } else { - new_ts_str = xstrnsave(new_ts_str, (size_t)(eap->arg - new_ts_str)); - } - for (lnum = eap->line1; !got_int && lnum <= eap->line2; lnum++) { - ptr = (char *)ml_get(lnum); - col = 0; - vcol = 0; - did_undo = false; - for (;;) { - if (ascii_iswhite(ptr[col])) { - if (!got_tab && num_spaces == 0) { - // First consecutive white-space - start_vcol = vcol; - start_col = col; - } - if (ptr[col] == ' ') { - num_spaces++; - } else { - got_tab = true; - } - } else { - if (got_tab || (eap->forceit && num_spaces > 1)) { - // Retabulate this string of white-space - - // len is virtual length of white string - len = num_spaces = vcol - start_vcol; - num_tabs = 0; - if (!curbuf->b_p_et) { - int t, s; - - tabstop_fromto((colnr_T)start_vcol, (colnr_T)vcol, - curbuf->b_p_ts, new_vts_array, &t, &s); - num_tabs = t; - num_spaces = s; - } - if (curbuf->b_p_et || got_tab - || (num_spaces + num_tabs < len)) { - if (did_undo == false) { - did_undo = true; - if (u_save((linenr_T)(lnum - 1), - (linenr_T)(lnum + 1)) == FAIL) { - new_line = NULL; // flag out-of-memory - break; - } - } - - // len is actual number of white characters used - len = num_spaces + num_tabs; - old_len = (long)STRLEN(ptr); - const long new_len = old_len - col + start_col + len + 1; - if (new_len <= 0 || new_len >= MAXCOL) { - emsg(_(e_resulting_text_too_long)); - break; - } - new_line = xmalloc((size_t)new_len); - - if (start_col > 0) { - memmove(new_line, ptr, (size_t)start_col); - } - memmove(new_line + start_col + len, - ptr + col, (size_t)(old_len - col + 1)); - ptr = new_line + start_col; - for (col = 0; col < len; col++) { - ptr[col] = (col < num_tabs) ? '\t' : ' '; - } - if (ml_replace(lnum, new_line, false) == OK) { - // "new_line" may have been copied - new_line = (char *)curbuf->b_ml.ml_line_ptr; - extmark_splice_cols(curbuf, lnum - 1, 0, (colnr_T)old_len, - (colnr_T)new_len - 1, kExtmarkUndo); - } - if (first_line == 0) { - first_line = lnum; - } - last_line = lnum; - ptr = new_line; - col = start_col + len; - } - } - got_tab = false; - num_spaces = 0; - } - if (ptr[col] == NUL) { - break; - } - vcol += win_chartabsize(curwin, (char_u *)ptr + col, (colnr_T)vcol); - if (vcol >= MAXCOL) { - emsg(_(e_resulting_text_too_long)); - break; - } - col += utfc_ptr2len(ptr + col); - } - if (new_line == NULL) { // out of memory - break; - } - line_breakcheck(); - } - if (got_int) { - emsg(_(e_interr)); - } - - // If a single value was given then it can be considered equal to - // either the value of 'tabstop' or the value of 'vartabstop'. - if (tabstop_count(curbuf->b_p_vts_array) == 0 - && tabstop_count(new_vts_array) == 1 - && curbuf->b_p_ts == tabstop_first(new_vts_array)) { - // not changed - } else if (tabstop_count(curbuf->b_p_vts_array) > 0 - && tabstop_eq(curbuf->b_p_vts_array, new_vts_array)) { - // not changed - } else { - redraw_curbuf_later(NOT_VALID); - } - if (first_line != 0) { - changed_lines(first_line, 0, last_line + 1, 0L, true); - } - - curwin->w_p_list = save_list; // restore 'list' - - if (new_ts_str != NULL) { // set the new tabstop - // If 'vartabstop' is in use or if the value given to retab has more - // than one tabstop then update 'vartabstop'. - long *old_vts_ary = curbuf->b_p_vts_array; - - if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1) { - set_string_option_direct("vts", -1, new_ts_str, OPT_FREE | OPT_LOCAL, 0); - curbuf->b_p_vts_array = new_vts_array; - xfree(old_vts_ary); - } else { - // 'vartabstop' wasn't in use and a single value was given to - // retab then update 'tabstop'. - curbuf->b_p_ts = tabstop_first(new_vts_array); - xfree(new_vts_array); - } - xfree(new_ts_str); - } - coladvance(curwin->w_curswant); - - u_clearline(); -} - /// :move command - move lines line1-line2 to line dest /// /// @return FAIL for failure, OK otherwise @@ -933,15 +755,13 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) num_lines = line2 - line1 + 1; - /* - * First we copy the old text to its new location -- webb - * Also copy the flag that ":global" command uses. - */ + // First we copy the old text to its new location -- webb + // Also copy the flag that ":global" command uses. if (u_save(dest, dest + 1) == FAIL) { return FAIL; } for (extra = 0, l = line1; l <= line2; l++) { - str = (char *)vim_strsave(ml_get(l + extra)); + str = xstrdup(ml_get(l + extra)); ml_append(dest + l - line1, str, (colnr_T)0, false); xfree(str); if (dest < line1) { @@ -949,21 +769,19 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) } } - /* - * Now we must be careful adjusting our marks so that we don't overlap our - * mark_adjust() calls. - * - * We adjust the marks within the old text so that they refer to the - * last lines of the file (temporarily), because we know no other marks - * will be set there since these line numbers did not exist until we added - * our new lines. - * - * Then we adjust the marks on lines between the old and new text positions - * (either forwards or backwards). - * - * And Finally we adjust the marks we put at the end of the file back to - * their final destination at the new text position -- webb - */ + // Now we must be careful adjusting our marks so that we don't overlap our + // mark_adjust() calls. + // + // We adjust the marks within the old text so that they refer to the + // last lines of the file (temporarily), because we know no other marks + // will be set there since these line numbers did not exist until we added + // our new lines. + // + // Then we adjust the marks on lines between the old and new text positions + // (either forwards or backwards). + // + // And Finally we adjust the marks we put at the end of the file back to + // their final destination at the new text position -- webb last_line = curbuf->b_ml.ml_line_count; mark_adjust_nofold(line1, line2, last_line - line2, 0L, kExtmarkNOOP); @@ -1011,9 +829,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) // send update regarding the new lines that were added buf_updates_send_changes(curbuf, dest + 1, num_lines, 0); - /* - * Now we delete the original text -- webb - */ + // Now we delete the original text -- webb if (u_save(line1 + extra - 1, line2 + extra + 1) == FAIL) { return FAIL; } @@ -1022,7 +838,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) ml_delete(line1 + extra, true); } if (!global_busy && num_lines > p_report) { - smsg(NGETTEXT("1 line moved", "%" PRId64 " lines moved", num_lines), (int64_t)num_lines); + smsg(NGETTEXT("%" PRId64 " line moved", + "%" PRId64 " lines moved", num_lines), + (int64_t)num_lines); } extmark_move_region(curbuf, line1 - 1, 0, start_byte, @@ -1030,9 +848,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) dest + line_off, 0, dest_byte + byte_off, kExtmarkUndo); - /* - * Leave the cursor on the last of the moved lines. - */ + // Leave the cursor on the last of the moved lines. if (dest >= line1) { curwin->w_cursor.lnum = dest; } else { @@ -1069,26 +885,24 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n) curbuf->b_op_start.col = curbuf->b_op_end.col = 0; } - /* - * there are three situations: - * 1. destination is above line1 - * 2. destination is between line1 and line2 - * 3. destination is below line2 - * - * n = destination (when starting) - * curwin->w_cursor.lnum = destination (while copying) - * line1 = start of source (while copying) - * line2 = end of source (while copying) - */ + // there are three situations: + // 1. destination is above line1 + // 2. destination is between line1 and line2 + // 3. destination is below line2 + // + // n = destination (when starting) + // curwin->w_cursor.lnum = destination (while copying) + // line1 = start of source (while copying) + // line2 = end of source (while copying) if (u_save(n, n + 1) == FAIL) { return; } curwin->w_cursor.lnum = n; while (line1 <= line2) { - // need to use vim_strsave() because the line will be unlocked within + // need to use xstrdup() because the line will be unlocked within // ml_append() - p = (char *)vim_strsave(ml_get(line1)); + p = xstrdup(ml_get(line1)); ml_append(curwin->w_cursor.lnum, p, (colnr_T)0, false); xfree(p); @@ -1103,7 +917,7 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n) if (curwin->w_cursor.lnum < line2) { line2++; } - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; } appended_lines_mark(n, count); @@ -1142,29 +956,28 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out int scroll_save = msg_scroll; // - // Disallow shell commands from .exrc and .vimrc in current directory for - // security reasons. + // Disallow shell commands in secure mode // if (check_secure()) { return; } if (addr_count == 0) { // :! - msg_scroll = FALSE; // don't scroll here + msg_scroll = false; // don't scroll here autowrite_all(); msg_scroll = scroll_save; } - /* - * Try to find an embedded bang, like in :!<cmd> ! [args] - * (:!! is indicated by the 'forceit' variable) - */ + // Try to find an embedded bang, like in ":!<cmd> ! [args]" + // ":!!" is indicated by the 'forceit' variable. bool ins_prevcmd = forceit; - trailarg = arg; + + // Skip leading white space to avoid a strange error with some shells. + trailarg = skipwhite(arg); do { - len = STRLEN(trailarg) + 1; + len = strlen(trailarg) + 1; if (newcmd != NULL) { - len += STRLEN(newcmd); + len += strlen(newcmd); } if (ins_prevcmd) { if (prevcmd == NULL) { @@ -1172,7 +985,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out xfree(newcmd); return; } - len += STRLEN(prevcmd); + len += strlen(prevcmd); } t = xmalloc(len); *t = NUL; @@ -1182,15 +995,13 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out if (ins_prevcmd) { STRCAT(t, prevcmd); } - p = t + STRLEN(t); + p = t + strlen(t); STRCAT(t, trailarg); xfree(newcmd); newcmd = t; - /* - * Scan the rest of the argument for '!', which is replaced by the - * previous command. "\!" is replaced by "!" (this is vi compatible). - */ + // Scan the rest of the argument for '!', which is replaced by the + // previous command. "\!" is replaced by "!" (this is vi compatible). trailarg = NULL; while (*p) { if (*p == '!') { @@ -1214,18 +1025,16 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out // If % or # appears in the command, it must have been escaped. // Reescape them, so that redoing them does not substitute them by the // buffername. - char *cmd = (char *)vim_strsave_escaped((char_u *)prevcmd, (char_u *)"%#"); + char *cmd = vim_strsave_escaped(prevcmd, "%#"); AppendToRedobuffLit(cmd, -1); xfree(cmd); AppendToRedobuff("\n"); bangredo = false; } - /* - * Add quotes around the command, for shells that need them. - */ + // Add quotes around the command, for shells that need them. if (*p_shq != NUL) { - newcmd = xmalloc(STRLEN(prevcmd) + 2 * STRLEN(p_shq) + 1); + newcmd = xmalloc(strlen(prevcmd) + 2 * strlen(p_shq) + 1); STRCPY(newcmd, p_shq); STRCAT(newcmd, prevcmd); STRCAT(newcmd, p_shq); @@ -1298,18 +1107,16 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b changed_line_abv_curs(); invalidate_botline(); - /* - * When using temp files: - * 1. * Form temp file names - * 2. * Write the lines to a temp file - * 3. Run the filter command on the temp file - * 4. * Read the output of the command into the buffer - * 5. * Delete the original lines to be filtered - * 6. * Remove the temp files - * - * When writing the input with a pipe or when catching the output with a - * pipe only need to do 3. - */ + // When using temp files: + // 1. * Form temp file names + // 2. * Write the lines to a temp file + // 3. Run the filter command on the temp file + // 4. * Read the output of the command into the buffer + // 5. * Delete the original lines to be filtered + // 6. * Remove the temp files + // + // When writing the input with a pipe or when catching the output with a + // pipe only need to do 3. if (do_out) { shell_flags |= kShellOptDoOut; @@ -1331,23 +1138,22 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b curbuf->b_op_start.lnum = line1; curbuf->b_op_end.lnum = line2; curwin->w_cursor.lnum = line2; - } else if ((do_in && (itmp = (char *)vim_tempname()) == NULL) - || (do_out && (otmp = (char *)vim_tempname()) == NULL)) { + } else if ((do_in && (itmp = vim_tempname()) == NULL) + || (do_out && (otmp = vim_tempname()) == NULL)) { emsg(_(e_notmp)); goto filterend; } - /* - * The writing and reading of temp files will not be shown. - * Vi also doesn't do this and the messages are not very informative. - */ + // The writing and reading of temp files will not be shown. + // Vi also doesn't do this and the messages are not very informative. no_wait_return++; // don't call wait_return() while busy if (itmp != NULL && buf_write(curbuf, itmp, NULL, line1, line2, eap, false, false, false, true) == FAIL) { msg_putchar('\n'); // Keep message from buf_write(). no_wait_return--; if (!aborting()) { - semsg(_("E482: Can't create file %s"), itmp); // Will call wait_return. + // will call wait_return() + semsg(_("E482: Can't create file %s"), itmp); } goto filterend; } @@ -1368,22 +1174,22 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b xfree(cmd_buf); goto error; } - redraw_curbuf_later(VALID); + redraw_curbuf_later(UPD_VALID); } read_linecount = curbuf->b_ml.ml_line_count; // Pass on the kShellOptDoOut flag when the output is being redirected. - call_shell((char_u *)cmd_buf, (ShellOpts)(kShellOptFilter | shell_flags), NULL); + call_shell(cmd_buf, (ShellOpts)(kShellOptFilter | shell_flags), NULL); xfree(cmd_buf); - did_check_timestamps = FALSE; - need_check_timestamps = TRUE; + did_check_timestamps = false; + need_check_timestamps = true; // When interrupting the shell command, it may still have produced some // useful output. Reset got_int here, so that readfile() won't cancel // reading. os_breakcheck(); - got_int = FALSE; + got_int = false; if (do_out) { if (otmp != NULL) { @@ -1427,10 +1233,8 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b } } - /* - * Put cursor on first filtered line for ":range!cmd". - * Adjust '[ and '] (set by buf_write()). - */ + // Put cursor on first filtered line for ":range!cmd". + // Adjust '[ and '] (set by buf_write()). curwin->w_cursor.lnum = line1; del_lines(linecount, true); curbuf->b_op_start.lnum -= linecount; // adjust '[ @@ -1439,9 +1243,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b // for next write foldUpdate(curwin, curbuf->b_op_start.lnum, curbuf->b_op_end.lnum); } else { - /* - * Put cursor on last new line for ":r !cmd". - */ + // Put cursor on last new line for ":r !cmd". linecount = curbuf->b_op_end.lnum - curbuf->b_op_start.lnum + 1; curwin->w_cursor.lnum = curbuf->b_op_end.lnum; } @@ -1496,17 +1298,14 @@ filterend: /// @param flags may be SHELL_DOOUT when output is redirected void do_shell(char *cmd, int flags) { - // Disallow shell commands from .exrc and .vimrc in current directory for - // security reasons. + // Disallow shell commands in secure mode if (check_secure()) { msg_end(); return; } - /* - * For autocommands we want to get the output on the current screen, to - * avoid having to type return below. - */ + // For autocommands we want to get the output on the current screen, to + // avoid having to type return below. msg_putchar('\r'); // put cursor at start of line msg_putchar('\n'); // may shift screen one line up @@ -1525,7 +1324,7 @@ void do_shell(char *cmd, int flags) // This ui_cursor_goto is required for when the '\n' resulted in a "delete line // 1" command to the terminal. ui_cursor_goto(msg_row, msg_col); - (void)call_shell((char_u *)cmd, (ShellOpts)flags, NULL); + (void)call_shell(cmd, (ShellOpts)flags, NULL); msg_didout = true; did_check_timestamps = false; need_check_timestamps = true; @@ -1545,11 +1344,11 @@ static char *find_pipe(const char *cmd) for (const char *p = cmd; *p != NUL; p++) { if (!inquote && *p == '|') { - return p; + return (char *)p; } if (*p == '"') { inquote = !inquote; - } else if (rem_backslash((const char_u *)p)) { + } else if (rem_backslash(p)) { p++; } } @@ -1568,95 +1367,84 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp) { bool is_fish_shell = #if defined(UNIX) - STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0; + strncmp((char *)invocation_path_tail(p_sh, NULL), "fish", 4) == 0; #else false; #endif - bool is_pwsh = STRNCMP(invocation_path_tail(p_sh, NULL), "pwsh", 4) == 0 - || STRNCMP(invocation_path_tail(p_sh, NULL), "powershell", 10) == 0; + bool is_pwsh = strncmp((char *)invocation_path_tail(p_sh, NULL), "pwsh", 4) == 0 + || strncmp((char *)invocation_path_tail(p_sh, NULL), "powershell", + 10) == 0; - size_t len = STRLEN(cmd) + 1; // At least enough space for cmd + NULL. + size_t len = strlen(cmd) + 1; // At least enough space for cmd + NULL. - len += is_fish_shell ? sizeof("begin; " "; end") - 1 - : is_pwsh ? sizeof("Start-Process ") - : sizeof("(" ")") - 1; + len += is_fish_shell ? sizeof("begin; " "; end") - 1 + : !is_pwsh ? sizeof("(" ")") - 1 + : 0; if (itmp != NULL) { - len += is_pwsh ? STRLEN(itmp) + sizeof(" -RedirectStandardInput ") - : STRLEN(itmp) + sizeof(" { " " < " " } ") - 1; + len += is_pwsh ? strlen(itmp) + sizeof("& { Get-Content " " | & " " }") - 1 + 6 // +6: #20530 + : strlen(itmp) + sizeof(" { " " < " " } ") - 1; } if (otmp != NULL) { - len += STRLEN(otmp) + STRLEN(p_srr) + 2; // two extra spaces (" "), + len += strlen(otmp) + strlen(p_srr) + 2; // two extra spaces (" "), } - const char *const cmd_args = strchr(cmd, ' '); - len += (is_pwsh && cmd_args) - ? STRLEN(" -ArgumentList ") + 2 // two extra quotes - : 0; - char *const buf = xmalloc(len); if (is_pwsh) { - xstrlcpy(buf, "Start-Process ", len); - if (cmd_args == NULL) { - xstrlcat(buf, cmd, len); + if (itmp != NULL) { + xstrlcpy(buf, "& { Get-Content ", len - 1); // FIXME: should we add "-Encoding utf8"? + xstrlcat(buf, (const char *)itmp, len - 1); + xstrlcat(buf, " | & ", len - 1); // FIXME: add `&` ourself or leave to user? + xstrlcat(buf, cmd, len - 1); + xstrlcat(buf, " }", len - 1); } else { - xstrlcpy(buf + STRLEN(buf), cmd, (size_t)(cmd_args - cmd + 1)); - xstrlcat(buf, " -ArgumentList \"", len); - xstrlcat(buf, cmd_args + 1, len); // +1 to skip the leading space. - xstrlcat(buf, "\"", len); + xstrlcpy(buf, cmd, len - 1); } + } else { #if defined(UNIX) // Put delimiters around the command (for concatenated commands) when // redirecting input and/or output. - } else if (itmp != NULL || otmp != NULL) { - char *fmt = is_fish_shell ? "begin; %s; end" - : "(%s)"; - vim_snprintf(buf, len, fmt, cmd); -#endif - // For shells that don't understand braces around commands, at least allow - // the use of commands in a pipe. - } else { - xstrlcpy(buf, cmd, len); - } - -#if defined(UNIX) - if (itmp != NULL) { - if (is_pwsh) { - xstrlcat(buf, " -RedirectStandardInput ", len - 1); + if (itmp != NULL || otmp != NULL) { + char *fmt = is_fish_shell ? "begin; %s; end" + : "(%s)"; + vim_snprintf(buf, len, fmt, cmd); } else { + xstrlcpy(buf, cmd, len); + } + + if (itmp != NULL) { xstrlcat(buf, " < ", len - 1); + xstrlcat(buf, (const char *)itmp, len - 1); } - xstrlcat(buf, itmp, len - 1); - } #else - if (itmp != NULL) { - // If there is a pipe, we have to put the '<' in front of it. - // Don't do this when 'shellquote' is not empty, otherwise the - // redirection would be inside the quotes. - if (*p_shq == NUL) { - char *const p = find_pipe(buf); - if (p != NULL) { - *p = NUL; + // For shells that don't understand braces around commands, at least allow + // the use of commands in a pipe. + xstrlcpy(buf, cmd, len); + if (itmp != NULL) { + // If there is a pipe, we have to put the '<' in front of it. + // Don't do this when 'shellquote' is not empty, otherwise the + // redirection would be inside the quotes. + if (*p_shq == NUL) { + char *const p = find_pipe(buf); + if (p != NULL) { + *p = NUL; + } } - } - if (is_pwsh) { - xstrlcat(buf, " -RedirectStandardInput ", len); - } else { xstrlcat(buf, " < ", len); - } - xstrlcat(buf, itmp, len); - if (*p_shq == NUL) { - const char *const p = find_pipe(cmd); - if (p != NULL) { - xstrlcat(buf, " ", len - 1); // Insert a space before the '|' for DOS - xstrlcat(buf, p, len - 1); + xstrlcat(buf, (const char *)itmp, len); + if (*p_shq == NUL) { + const char *const p = find_pipe((const char *)cmd); + if (p != NULL) { + xstrlcat(buf, " ", len - 1); // Insert a space before the '|' for DOS + xstrlcat(buf, p, len - 1); + } } } - } #endif + } if (otmp != NULL) { - append_redir(buf, len, (char *)p_srr, otmp); + append_redir(buf, len, p_srr, otmp); } return buf; } @@ -1714,8 +1502,8 @@ void print_line(linenr_T lnum, int use_number, int list) } msg_start(); - silent_mode = FALSE; - info_message = true; // use mch_msg(), not mch_errmsg() + silent_mode = false; + info_message = true; // use os_msg(), not os_errmsg() print_line_no_prefix(lnum, use_number, list); if (save_silent) { msg_putchar('\n'); @@ -1739,13 +1527,11 @@ int rename_buffer(char *new_fname) if (aborting()) { // autocmds may abort script processing return FAIL; } - /* - * The name of the current buffer will be changed. - * A new (unlisted) buffer entry needs to be made to hold the old file - * name, which will become the alternate file name. - * But don't set the alternate file name if the buffer didn't have a - * name. - */ + // The name of the current buffer will be changed. + // A new (unlisted) buffer entry needs to be made to hold the old file + // name, which will become the alternate file name. + // But don't set the alternate file name if the buffer didn't have a + // name. fname = curbuf->b_ffname; sfname = curbuf->b_sfname; xfname = curbuf->b_fname; @@ -1821,12 +1607,23 @@ void ex_write(exarg_T *eap) } } -/// write current buffer to file 'eap->arg' -/// if 'eap->append' is TRUE, append to the file +#ifdef UNIX +static int check_writable(const char *fname) +{ + if (os_nodetype(fname) == NODE_OTHER) { + semsg(_("E503: \"%s\" is not a file or writable device"), fname); + return FAIL; + } + return OK; +} +#endif + +/// Write current buffer to file "eap->arg". +/// If "eap->append" is true, append to the file. /// -/// if *eap->arg == NUL write to current file +/// If "*eap->arg == NUL" write to current file. /// -/// @return FAIL for failure, OK otherwise +/// @return FAIL for failure, OK otherwise. int do_write(exarg_T *eap) { int other; @@ -1847,7 +1644,7 @@ int do_write(exarg_T *eap) emsg(_(e_argreq)); goto theend; } - other = FALSE; + other = false; } else { fname = ffname; free_fname = fix_fname(ffname); @@ -1859,9 +1656,7 @@ int do_write(exarg_T *eap) other = otherfile(ffname); } - /* - * If we have a new file, put its name in the list of alternate file names. - */ + // If we have a new file, put its name in the list of alternate file names. if (other) { if (vim_strchr(p_cpo, CPO_ALTWRITE) != NULL || eap->cmdidx == CMD_saveas) { @@ -1882,6 +1677,9 @@ int do_write(exarg_T *eap) // "nofile" and "nowrite" buffers cannot be written implicitly either. if (!other && (bt_dontwrite_msg(curbuf) || check_fname() == FAIL +#ifdef UNIX + || check_writable(curbuf->b_ffname) == FAIL +#endif || check_readonly(&eap->forceit, curbuf))) { goto theend; } @@ -1897,10 +1695,10 @@ int do_write(exarg_T *eap) && !p_wa) { if (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) { if (vim_dialog_yesno(VIM_QUESTION, NULL, - (char_u *)_("Write partial file?"), 2) != VIM_YES) { + _("Write partial file?"), 2) != VIM_YES) { goto theend; } - eap->forceit = TRUE; + eap->forceit = true; } else { emsg(_("E140: Use ! to write partial buffer")); goto theend; @@ -1937,8 +1735,8 @@ int do_write(exarg_T *eap) apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf); apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, alt_buf); if (!alt_buf->b_p_bl) { - alt_buf->b_p_bl = TRUE; - apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, alt_buf); + alt_buf->b_p_bl = true; + apply_autocmds(EVENT_BUFADD, NULL, NULL, false, alt_buf); } if (curbuf != was_curbuf || aborting()) { // buffer changed, don't write the file @@ -1959,6 +1757,13 @@ int do_write(exarg_T *eap) fname = curbuf->b_sfname; } + if (eap->mkdir_p) { + if (os_file_mkdir(fname, 0755) < 0) { + retval = FAIL; + goto theend; + } + } + name_was_missing = curbuf->b_ffname == NULL; retval = buf_write(curbuf, ffname, fname, eap->line1, eap->line2, eap, eap->append, eap->forceit, true, false); @@ -1966,7 +1771,7 @@ int do_write(exarg_T *eap) // After ":saveas fname" reset 'readonly'. if (eap->cmdidx == CMD_saveas) { if (retval == OK) { - curbuf->b_p_ro = FALSE; + curbuf->b_p_ro = false; redraw_tabline = true; } } @@ -1996,17 +1801,22 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth { // Write to another file or b_flags set or not writing the whole file: // overwriting only allowed with '!' + // If "other" is false and bt_nofilename(buf) is true, this must be + // writing an "acwrite" buffer to the same file as its b_ffname, and + // buf_write() will only allow writing with BufWriteCmd autocommands, + // so there is no need for an overwrite check. if ((other - || (buf->b_flags & BF_NOTEDITED) - || ((buf->b_flags & BF_NEW) - && vim_strchr(p_cpo, CPO_OVERNEW) == NULL) - || (buf->b_flags & BF_READERR)) + || (!bt_nofilename(buf) + && ((buf->b_flags & BF_NOTEDITED) + || ((buf->b_flags & BF_NEW) + && vim_strchr(p_cpo, CPO_OVERNEW) == NULL) + || (buf->b_flags & BF_READERR)))) && !p_wa - && os_path_exists((char_u *)ffname)) { + && os_path_exists(ffname)) { if (!eap->forceit && !eap->append) { #ifdef UNIX // It is possible to open a directory on Unix. - if (os_isdir((char_u *)ffname)) { + if (os_isdir(ffname)) { semsg(_(e_isadir2), ffname); return FAIL; } @@ -2015,10 +1825,10 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth char buff[DIALOG_MSG_SIZE]; dialog_msg((char *)buff, _("Overwrite existing file \"%s\"?"), fname); - if (vim_dialog_yesno(VIM_QUESTION, NULL, (char_u *)buff, 2) != VIM_YES) { + if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) != VIM_YES) { return FAIL; } - eap->forceit = TRUE; + eap->forceit = true; } else { emsg(_(e_exists)); return FAIL; @@ -2041,24 +1851,24 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth STRCPY(dir, "."); } else { dir = xmalloc(MAXPATHL); - p = (char *)p_dir; + p = p_dir; copy_option_part(&p, dir, MAXPATHL, ","); } - swapname = (char *)makeswapname((char_u *)fname, (char_u *)ffname, curbuf, (char_u *)dir); + swapname = makeswapname(fname, ffname, curbuf, dir); xfree(dir); - if (os_path_exists((char_u *)swapname)) { + if (os_path_exists(swapname)) { if (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) { char buff[DIALOG_MSG_SIZE]; dialog_msg((char *)buff, _("Swap file \"%s\" exists, overwrite anyway?"), swapname); - if (vim_dialog_yesno(VIM_QUESTION, NULL, (char_u *)buff, 2) + if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) != VIM_YES) { xfree(swapname); return FAIL; } - eap->forceit = TRUE; + eap->forceit = true; } else { semsg(_("E768: Swap file exists: %s (:silent! overrides)"), swapname); @@ -2108,13 +1918,11 @@ void do_wqall(exarg_T *eap) } else if (!bufIsChanged(buf) || bt_dontwrite(buf)) { continue; } - /* - * Check if there is a reason the buffer cannot be written: - * 1. if the 'write' option is set - * 2. if there is no file name (even after browsing) - * 3. if the 'readonly' is set (even after a dialog) - * 4. if overwriting is allowed (even after a dialog) - */ + // Check if there is a reason the buffer cannot be written: + // 1. if the 'write' option is set + // 2. if there is no file name (even after browsing) + // 3. if the 'readonly' is set (even after a dialog) + // 4. if overwriting is allowed (even after a dialog) if (not_writing()) { error++; break; @@ -2159,14 +1967,14 @@ bool not_writing(void) } /// Check if a buffer is read-only (either 'readonly' option is set or file is -/// read-only). Ask for overruling in a dialog. Return TRUE and give an error +/// read-only). Ask for overruling in a dialog. Return true and give an error /// message when the buffer is readonly. static int check_readonly(int *forceit, buf_T *buf) { // Handle a file being readonly when the 'readonly' option is set or when // the file exists and permissions are read-only. if (!*forceit && (buf->b_p_ro - || (os_path_exists((char_u *)buf->b_ffname) + || (os_path_exists(buf->b_ffname) && !os_file_is_writable(buf->b_ffname)))) { if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && buf->b_fname != NULL) { char buff[DIALOG_MSG_SIZE]; @@ -2182,23 +1990,22 @@ static int check_readonly(int *forceit, buf_T *buf) buf->b_fname); } - if (vim_dialog_yesno(VIM_QUESTION, NULL, (char_u *)buff, 2) == VIM_YES) { + if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) == VIM_YES) { // Set forceit, to force the writing of a readonly file - *forceit = TRUE; - return FALSE; - } else { - return TRUE; + *forceit = true; + return false; } + return true; } else if (buf->b_p_ro) { emsg(_(e_readonly)); } else { semsg(_("E505: \"%s\" is read-only (add ! to override)"), buf->b_fname); } - return TRUE; + return true; } - return FALSE; + return false; } /// Try to abandon the current file and edit a new or existing file. @@ -2292,7 +2099,7 @@ theend: /// ECMD_LASTL: use last position in loaded file /// ECMD_LAST: use last position in all files /// ECMD_ONE: use first line -/// @param flags ECMD_HIDE: if TRUE don't free the current buffer +/// @param flags ECMD_HIDE: if true don't free the current buffer /// ECMD_SET_HELP: set b_help flag of (new) buffer before /// opening file /// ECMD_OLDBUF: use existing buffer if it exists @@ -2310,7 +2117,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum win_T *oldwin) { bool other_file; // true if editing another file - int oldbuf; // TRUE if using existing buffer + int oldbuf; // true if using existing buffer bool auto_buf = false; // true if autocommands brought us // into the buffer unexpectedly char *new_name = NULL; @@ -2386,7 +2193,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // If the file was changed we may not be allowed to abandon it: // - if we are going to re-edit the same file - // - or if we are the only window on this file and if ECMD_HIDE is FALSE + // - or if we are the only window on this file and if ECMD_HIDE is false if (((!other_file && !(flags & ECMD_OLDBUF)) || (curbuf->b_nwindows == 1 && !(flags & (ECMD_HIDE | ECMD_ADDBUF | ECMD_ALTBUF)))) @@ -2400,16 +2207,14 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum goto theend; } - /* - * End Visual mode before switching to another buffer, so the text can be - * copied into the GUI selection buffer. - */ + // End Visual mode before switching to another buffer, so the text can be + // copied into the GUI selection buffer. reset_VIsual(); if ((command != NULL || newlnum > (linenr_T)0) && *get_vim_var_str(VV_SWAPCOMMAND) == NUL) { // Set v:swapcommand for the SwapExists autocommands. - const size_t len = (command != NULL) ? STRLEN(command) + 3 : 30; + const size_t len = (command != NULL) ? strlen(command) + 3 : 30; char *const p = xmalloc(len); if (command != NULL) { vim_snprintf(p, len, ":%s\r", command); @@ -2421,10 +2226,8 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum xfree(p); } - /* - * If we are starting to edit another file, open a (new) buffer. - * Otherwise we re-use the current buffer. - */ + // If we are starting to edit another file, open a (new) buffer. + // Otherwise we re-use the current buffer. if (other_file) { const int prev_alt_fnum = curwin->w_alt_fnum; @@ -2502,12 +2305,10 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum solcol = pos->col; } - /* - * Make the (new) buffer the one used by the current window. - * If the old buffer becomes unused, free it if ECMD_HIDE is FALSE. - * If the current buffer was empty and has no file name, curbuf - * is returned by buflist_new(), nothing to do here. - */ + // Make the (new) buffer the one used by the current window. + // If the old buffer becomes unused, free it if ECMD_HIDE is false. + // If the current buffer was empty and has no file name, curbuf + // is returned by buflist_new(), nothing to do here. if (buf != curbuf) { const int save_cmdwin_type = cmdwin_type; @@ -2601,11 +2402,11 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum curwin->w_buffer = buf; curbuf = buf; - ++curbuf->b_nwindows; + curbuf->b_nwindows++; // Set 'fileformat', 'binary' and 'fenc' when forced. if (!oldbuf && eap != NULL) { - set_file_options(TRUE, eap); + set_file_options(true, eap); set_forced_fenc(eap); } } @@ -2641,7 +2442,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum } else if (!curbuf->b_help) { // Don't make a buffer listed if it's a help buffer. Useful when using // CTRL-O to go back to a help file. - set_buflisted(TRUE); + set_buflisted(true); } // If autocommands change buffers under our fingers, forget about @@ -2658,13 +2459,11 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // highlighting to work in the other file. did_filetype = false; - /* - * other_file oldbuf - * FALSE FALSE re-edit same file, buffer is re-used - * FALSE TRUE re-edit same file, nothing changes - * TRUE FALSE start editing new file, new buffer - * TRUE TRUE start editing in existing buffer (nothing to do) - */ + // other_file oldbuf + // false false re-edit same file, buffer is re-used + // false true re-edit same file, nothing changes + // true false start editing new file, new buffer + // true true start editing in existing buffer (nothing to do) if (!other_file && !oldbuf) { // re-use the buffer set_last_cursor(curwin); // may set b_last_cursor if (newlnum == ECMD_LAST || newlnum == ECMD_LASTL) { @@ -2673,7 +2472,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum } buf = curbuf; if (buf->b_fname != NULL) { - new_name = (char *)vim_strsave((char_u *)buf->b_fname); + new_name = xstrdup(buf->b_fname); } else { new_name = NULL; } @@ -2723,9 +2522,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum curbuf->b_op_end.lnum = 0; } - /* - * If we get here we are sure to start editing - */ + // If we get here we are sure to start editing // Assume success now retval = OK; @@ -2736,17 +2533,13 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum curbuf->b_flags &= ~BF_NOTEDITED; } - /* - * Check if we are editing the w_arg_idx file in the argument list. - */ + // Check if we are editing the w_arg_idx file in the argument list. check_arg_idx(curwin); if (!auto_buf) { - /* - * Set cursor and init window before reading the file and executing - * autocommands. This allows for the autocommands to position the - * cursor. - */ + // Set cursor and init window before reading the file and executing + // autocommands. This allows for the autocommands to position the + // cursor. curwin_init(); // It's possible that all lines in the buffer changed. Need to update @@ -2760,19 +2553,15 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // Change directories when the 'acd' option is set. do_autochdir(); - /* - * Careful: open_buffer() and apply_autocmds() may change the current - * buffer and window. - */ + // Careful: open_buffer() and apply_autocmds() may change the current + // buffer and window. orig_pos = curwin->w_cursor; topline = curwin->w_topline; if (!oldbuf) { // need to read the file swap_exists_action = SEA_DIALOG; curbuf->b_flags |= BF_CHECK_RO; // set/reset 'ro' flag - /* - * Open the buffer and read the file. - */ + // Open the buffer and read the file. if (flags & ECMD_NOWINENTER) { readfile_flags |= READ_NOWINENTER; } @@ -2790,7 +2579,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // changed by the user. do_modelines(OPT_WINONLY); - apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, + apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, false, curbuf, &retval); if ((flags & ECMD_NOWINENTER) == 0) { apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, false, curbuf, @@ -2803,7 +2592,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // keep it. Also when it moves within a line. But not when it moves // to the first non-blank. if (!equalpos(curwin->w_cursor, orig_pos)) { - const char *text = (char *)get_cursor_line_ptr(); + const char *text = get_cursor_line_ptr(); if (curwin->w_cursor.lnum != orig_pos.lnum || curwin->w_cursor.col != (int)(skipwhite(text) - text)) { @@ -2848,7 +2637,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum curwin->w_cursor.col = solcol; check_cursor_col(); curwin->w_cursor.coladd = 0; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } else { beginline(BL_SOL | BL_FIX); } @@ -2863,10 +2652,8 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // Check if cursors in other windows on the same buffer are still valid check_lnums(false); - /* - * Did not read the file, need to show some info about the file. - * Do this after setting the cursor. - */ + // Did not read the file, need to show some info about the file. + // Do this after setting the cursor. if (oldbuf && !auto_buf) { int msg_scroll_save = msg_scroll; @@ -2874,7 +2661,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum // Obey the 'O' flag in 'cpoptions': overwrite any previous file // message. if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) { - msg_scroll = FALSE; + msg_scroll = false; } if (!msg_scroll) { // wait a bit when overwriting an error msg check_for_delay(false); @@ -2910,7 +2697,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum update_topline(curwin); curwin->w_scbind_pos = curwin->w_topline; *so_ptr = n; - redraw_curbuf_later(NOT_VALID); // redraw this buffer later + redraw_curbuf_later(UPD_NOT_VALID); // redraw this buffer later } // Change directories when the 'acd' option is set. @@ -2996,7 +2783,7 @@ void ex_append(exarg_T *eap) } p = vim_strchr(eap->nextcmd, NL); if (p == NULL) { - p = eap->nextcmd + STRLEN(eap->nextcmd); + p = eap->nextcmd + strlen(eap->nextcmd); } theline = xstrnsave(eap->nextcmd, (size_t)(p - eap->nextcmd)); if (*p != NUL) { @@ -3018,7 +2805,7 @@ void ex_append(exarg_T *eap) // Look for the "." after automatic indent. vcol = 0; - for (p = theline; indent > vcol; ++p) { + for (p = theline; indent > vcol; p++) { if (*p == ' ') { vcol++; } else if (*p == TAB) { @@ -3098,7 +2885,7 @@ void ex_change(exarg_T *eap) append_indent = get_indent_lnum(eap->line1); } - for (lnum = eap->line2; lnum >= eap->line1; --lnum) { + for (lnum = eap->line2; lnum >= eap->line1; lnum--) { if (curbuf->b_ml.ml_flags & ML_EMPTY) { // nothing to delete break; } @@ -3249,8 +3036,7 @@ void ex_z(exarg_T *eap) ex_no_reprint = true; } -/// @return true if the secure flag is set (.exrc or .vimrc in current directory) -/// and also give an error message. +/// @return true if the secure flag is set and also give an error message. /// Otherwise, return false. bool check_secure(void) { @@ -3307,13 +3093,13 @@ void sub_set_replacement(SubReplacementString sub) /// @param[in] save Save pattern to options, history /// /// @returns true if :substitute can be replaced with a join command -static bool sub_joining_lines(exarg_T *eap, char *pat, char *sub, char *cmd, bool save) +static bool sub_joining_lines(exarg_T *eap, char *pat, const char *sub, const char *cmd, bool save) FUNC_ATTR_NONNULL_ARG(1, 3, 4) { // TODO(vim): find a generic solution to make line-joining operations more // efficient, avoid allocating a string that grows in size. if (pat != NULL - && STRCMP(pat, "\\n") == 0 + && strcmp(pat, "\\n") == 0 && *sub == NUL && (*cmd == NUL || (cmd[1] == NUL && (*cmd == 'g' @@ -3346,10 +3132,10 @@ static bool sub_joining_lines(exarg_T *eap, char *pat, char *sub, char *cmd, boo if (save) { if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) { - save_re_pat(RE_SUBST, (char_u *)pat, p_magic); + save_re_pat(RE_SUBST, pat, magic_isset()); } // put pattern in history - add_to_history(HIST_SEARCH, (char_u *)pat, true, NUL); + add_to_history(HIST_SEARCH, pat, true, NUL); } return true; @@ -3384,7 +3170,7 @@ static char *sub_grow_buf(char **new_start, int needed_len) // Check if the temporary buffer is long enough to do the // substitution into. If not, make it larger (with a bit // extra to avoid too many calls to xmalloc()/free()). - size_t len = STRLEN(*new_start); + size_t len = strlen(*new_start); needed_len += (int)len; if (needed_len > new_start_len) { new_start_len = needed_len + 50; @@ -3477,6 +3263,30 @@ static int check_regexp_delim(int c) /// @return 0, 1 or 2. See show_cmdpreview() for more information on what the return value means. static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T cmdpreview_bufnr) { +#define ADJUST_SUB_FIRSTLNUM() \ + do { \ + /* For a multi-line match, make a copy of the last matched */ \ + /* line and continue in that one. */ \ + if (nmatch > 1) { \ + sub_firstlnum += (linenr_T)nmatch - 1; \ + xfree(sub_firstline); \ + sub_firstline = xstrdup(ml_get(sub_firstlnum)); \ + /* When going beyond the last line, stop substituting. */ \ + if (sub_firstlnum <= line2) { \ + do_again = true; \ + } else { \ + subflags.do_all = false; \ + } \ + } \ + if (skip_match) { \ + /* Already hit end of the buffer, sub_firstlnum is one */ \ + /* less than what it ought to be. */ \ + xfree(sub_firstline); \ + sub_firstline = xstrdup(""); \ + copycol = 0; \ + } \ + } while (0) + long i = 0; regmmatch_T regmatch; static subflags_T subflags = { @@ -3522,7 +3332,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T } // new pattern and substitution if (eap->cmd[0] == 's' && *cmd != NUL && !ascii_iswhite(*cmd) - && vim_strchr("0123456789cegriIp|\"", *cmd) == NULL) { + && vim_strchr("0123456789cegriIp|\"", (uint8_t)(*cmd)) == NULL) { // don't accept alphanumeric for separator if (check_regexp_delim(*cmd) == FAIL) { return 0; @@ -3533,7 +3343,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T // //sub/r). "\&sub&" use last substitute pattern (like //sub/). if (*cmd == '\\') { cmd++; - if (vim_strchr("/?&", *cmd) == NULL) { + if (vim_strchr("/?&", (uint8_t)(*cmd)) == NULL) { emsg(_(e_backslash)); return 0; } @@ -3541,23 +3351,21 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T which_pat = RE_SEARCH; // use last '/' pattern } pat = ""; // empty search pattern - delimiter = (char_u)(*cmd++); // remember delimiter character + delimiter = (uint8_t)(*cmd++); // remember delimiter character has_second_delim = true; } else { // find the end of the regexp which_pat = RE_LAST; // use last used regexp - delimiter = (char_u)(*cmd++); // remember delimiter character + delimiter = (uint8_t)(*cmd++); // remember delimiter character pat = cmd; // remember start of search pat - cmd = (char *)skip_regexp((char_u *)cmd, delimiter, p_magic, &eap->arg); + cmd = skip_regexp_ex(cmd, delimiter, magic_isset(), &eap->arg, NULL, NULL); if (cmd[0] == delimiter) { // end delimiter found *cmd++ = NUL; // replace it with a NUL has_second_delim = true; } } - /* - * Small incompatibility: vi sees '\n' as end of the command, but in - * Vim we want to use '\n' to find/substitute a NUL. - */ + // Small incompatibility: vi sees '\n' as end of the command, but in + // Vim we want to use '\n' to find/substitute a NUL. sub = cmd; // remember the start of the substitution while (cmd[0]) { @@ -3615,12 +3423,10 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T } } - /* - * check for trailing command or garbage - */ + // check for trailing command or garbage cmd = skipwhite(cmd); if (*cmd && *cmd != '"') { // if not end-of-line or comment - eap->nextcmd = (char *)check_nextcmd((char_u *)cmd); + eap->nextcmd = check_nextcmd(cmd); if (eap->nextcmd == NULL) { semsg(_(e_trailing_arg), cmd); return 0; @@ -3637,8 +3443,8 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T return 0; } - if (search_regcomp((char_u *)pat, RE_SUBST, which_pat, (cmdpreview ? 0 : SEARCH_HIS), - ®match) == FAIL) { + if (search_regcomp(pat, NULL, RE_SUBST, which_pat, + (cmdpreview ? 0 : SEARCH_HIS), ®match) == FAIL) { if (subflags.do_error) { emsg(_(e_invcmd)); } @@ -3667,7 +3473,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T sub = xstrdup(sub); sub_copy = sub; } else { - char *newsub = (char *)regtilde((char_u *)sub, p_magic, cmdpreview); + char *newsub = regtilde(sub, magic_isset(), cmdpreview); if (newsub != sub) { // newsub was allocated, free it later. sub_copy = newsub; @@ -3675,13 +3481,6 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T } } - const bool cmdheight0 = !ui_has_messages(); - if (cmdheight0) { - // If cmdheight is 0, cmdheight must be set to 1 when we enter command line. - set_option_value("ch", 1L, NULL, 0); - redraw_statuslines(); - } - // Check for a match on each line. // If preview: limit to max('cmdwinheight', viewport). linenr_T line2 = eap->line2; @@ -3706,50 +3505,48 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T bool skip_match = false; linenr_T sub_firstlnum; // nr of first sub line - /* - * The new text is build up step by step, to avoid too much - * copying. There are these pieces: - * sub_firstline The old text, unmodified. - * copycol Column in the old text where we started - * looking for a match; from here old text still - * needs to be copied to the new text. - * matchcol Column number of the old text where to look - * for the next match. It's just after the - * previous match or one further. - * prev_matchcol Column just after the previous match (if any). - * Mostly equal to matchcol, except for the first - * match and after skipping an empty match. - * regmatch.*pos Where the pattern matched in the old text. - * new_start The new text, all that has been produced so - * far. - * new_end The new text, where to append new text. - * - * lnum The line number where we found the start of - * the match. Can be below the line we searched - * when there is a \n before a \zs in the - * pattern. - * sub_firstlnum The line number in the buffer where to look - * for a match. Can be different from "lnum" - * when the pattern or substitute string contains - * line breaks. - * - * Special situations: - * - When the substitute string contains a line break, the part up - * to the line break is inserted in the text, but the copy of - * the original line is kept. "sub_firstlnum" is adjusted for - * the inserted lines. - * - When the matched pattern contains a line break, the old line - * is taken from the line at the end of the pattern. The lines - * in the match are deleted later, "sub_firstlnum" is adjusted - * accordingly. - * - * The new text is built up in new_start[]. It has some extra - * room to avoid using xmalloc()/free() too often. - * - * Make a copy of the old line, so it won't be taken away when - * updating the screen or handling a multi-line match. The "old_" - * pointers point into this copy. - */ + // The new text is build up step by step, to avoid too much + // copying. There are these pieces: + // sub_firstline The old text, unmodified. + // copycol Column in the old text where we started + // looking for a match; from here old text still + // needs to be copied to the new text. + // matchcol Column number of the old text where to look + // for the next match. It's just after the + // previous match or one further. + // prev_matchcol Column just after the previous match (if any). + // Mostly equal to matchcol, except for the first + // match and after skipping an empty match. + // regmatch.*pos Where the pattern matched in the old text. + // new_start The new text, all that has been produced so + // far. + // new_end The new text, where to append new text. + // + // lnum The line number where we found the start of + // the match. Can be below the line we searched + // when there is a \n before a \zs in the + // pattern. + // sub_firstlnum The line number in the buffer where to look + // for a match. Can be different from "lnum" + // when the pattern or substitute string contains + // line breaks. + // + // Special situations: + // - When the substitute string contains a line break, the part up + // to the line break is inserted in the text, but the copy of + // the original line is kept. "sub_firstlnum" is adjusted for + // the inserted lines. + // - When the matched pattern contains a line break, the old line + // is taken from the line at the end of the pattern. The lines + // in the match are deleted later, "sub_firstlnum" is adjusted + // accordingly. + // + // The new text is built up in new_start[]. It has some extra + // room to avoid using xmalloc()/free() too often. + // + // Make a copy of the old line, so it won't be taken away when + // updating the screen or handling a multi-line match. The "old_" + // pointers point into this copy. sub_firstlnum = lnum; copycol = 0; matchcol = 0; @@ -3760,14 +3557,12 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T got_match = true; } - /* - * Loop until nothing more to replace in this line. - * 1. Handle match with empty string. - * 2. If subflags.do_ask is set, ask for confirmation. - * 3. substitute the string. - * 4. if subflags.do_all is set, find next match - * 5. break if there isn't another match in this line - */ + // Loop until nothing more to replace in this line. + // 1. Handle match with empty string. + // 2. If subflags.do_ask is set, ask for confirmation. + // 3. substitute the string. + // 4. if subflags.do_all is set, find next match + // 5. break if there isn't another match in this line for (;;) { SubResult current_match = { .start = { 0, 0 }, @@ -3798,19 +3593,17 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T break; } if (sub_firstline == NULL) { - sub_firstline = (char *)vim_strsave(ml_get(sub_firstlnum)); + sub_firstline = xstrdup(ml_get(sub_firstlnum)); } // Save the line number of the last change for the final // cursor position (just like Vi). curwin->w_cursor.lnum = lnum; - do_again = FALSE; + do_again = false; - /* - * 1. Match empty string does not count, except for first - * match. This reproduces the strange vi behaviour. - * This also catches endless loops. - */ + // 1. Match empty string does not count, except for first + // match. This reproduces the strange vi behaviour. + // This also catches endless loops. if (matchcol == prev_matchcol && regmatch.endpos[0].lnum == 0 && matchcol == regmatch.endpos[0].col) { @@ -3842,7 +3635,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T // we continue looking for a match on the next line. // Avoids that ":s/\nB\@=//gc" get stuck. if (nmatch > 1) { - matchcol = (colnr_T)STRLEN(sub_firstline); + matchcol = (colnr_T)strlen(sub_firstline); nmatch = 1; skip_match = true; } @@ -3875,9 +3668,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T no_u_sync++; } - /* - * Loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed. - */ + // Loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed. while (subflags.do_ask) { if (exmode_active) { char *prompt; @@ -3906,7 +3697,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T msg_putchar('\n'); xfree(prompt); if (resp != NULL) { - typed = (char_u)(*resp); + typed = (uint8_t)(*resp); xfree(resp); } else { // getcmdline_prompt() returns NULL if there is no command line to return. @@ -3939,14 +3730,13 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T // really update the line, it would change // what matches. Temporarily replace the line // and change it back afterwards. - orig_line = (char *)vim_strsave(ml_get(lnum)); - char *new_line = (char *)concat_str((char_u *)new_start, - (char_u *)sub_firstline + copycol); + orig_line = xstrdup(ml_get(lnum)); + char *new_line = concat_str(new_start, sub_firstline + copycol); // Position the cursor relative to the end of the line, the // previous substitute may have inserted or deleted characters // before the cursor. - len_change = (int)STRLEN(new_line) - (int)STRLEN(orig_line); + len_change = (int)strlen(new_line) - (int)strlen(orig_line); curwin->w_cursor.col += len_change; ml_replace(lnum, new_line, false); } @@ -3955,13 +3745,18 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T - regmatch.startpos[0].lnum; search_match_endcol = regmatch.endpos[0].col + len_change; - highlight_match = TRUE; + if (search_match_lines == 0 && search_match_endcol == 0) { + // highlight at least one character for /^/ + search_match_endcol = 1; + } + highlight_match = true; update_topline(curwin); validate_cursor(); - update_screen(SOME_VALID); + redraw_later(curwin, UPD_SOME_VALID); + update_screen(); highlight_match = false; - redraw_later(curwin, SOME_VALID); + redraw_later(curwin, UPD_SOME_VALID); curwin->w_p_fen = save_p_fen; if (msg_row == Rows - 1) { @@ -3978,7 +3773,9 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T msg_no_more = false; msg_scroll = (int)i; show_cursor_info(true); - ui_cursor_goto(msg_row, msg_col); + if (!ui_has(kUIMessages)) { + ui_cursor_goto(msg_row, msg_col); + } RedrawingDisabled = temp; no_mapping++; // don't map this key @@ -4039,7 +3836,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T // Avoids that ":%s/\nB\@=//gc" and ":%s/\n/,\r/gc" // get stuck when pressing 'n'. if (nmatch > 1) { - matchcol = (colnr_T)STRLEN(sub_firstline); + matchcol = (colnr_T)strlen(sub_firstline); skip_match = true; } goto skip; @@ -4061,30 +3858,6 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T skip_match = true; } -#define ADJUST_SUB_FIRSTLNUM() \ - do { \ - /* For a multi-line match, make a copy of the last matched */ \ - /* line and continue in that one. */ \ - if (nmatch > 1) { \ - sub_firstlnum += (linenr_T)nmatch - 1; \ - xfree(sub_firstline); \ - sub_firstline = (char *)vim_strsave(ml_get(sub_firstlnum)); \ - /* When going beyond the last line, stop substituting. */ \ - if (sub_firstlnum <= line2) { \ - do_again = true; \ - } else { \ - subflags.do_all = false; \ - } \ - } \ - if (skip_match) { \ - /* Already hit end of the buffer, sub_firstlnum is one */ \ - /* less than what it ought to be. */ \ - xfree(sub_firstline); \ - sub_firstline = xstrdup(""); \ - copycol = 0; \ - } \ - } while (0) - // Save the line numbers for the preview buffer // NOTE: If the pattern matches a final newline, the next line will // be shown also, but should not be highlighted. Intentional for now. @@ -4122,8 +3895,9 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T // When it fails sublen is zero. sublen = vim_regsub_multi(®match, sub_firstlnum - regmatch.startpos[0].lnum, - (char_u *)sub, (char_u *)sub_firstline, 0, - REGSUB_BACKSLASH | (p_magic ? REGSUB_MAGIC : 0)); + sub, sub_firstline, 0, + REGSUB_BACKSLASH + | (magic_isset() ? REGSUB_MAGIC : 0)); textlock--; // If getting the substitute string caused an error, don't do @@ -4144,12 +3918,12 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T if (nmatch == 1) { p1 = sub_firstline; } else { - p1 = (char *)ml_get(sub_firstlnum + (linenr_T)nmatch - 1); + p1 = ml_get(sub_firstlnum + (linenr_T)nmatch - 1); nmatch_tl += nmatch - 1; } size_t copy_len = (size_t)(regmatch.startpos[0].col - copycol); new_end = sub_grow_buf(&new_start, - (colnr_T)STRLEN(p1) - regmatch.endpos[0].col + (colnr_T)strlen(p1) - regmatch.endpos[0].col + (colnr_T)copy_len + sublen + 1); // copy the text up to the part that matched @@ -4164,8 +3938,9 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T textlock++; (void)vim_regsub_multi(®match, sub_firstlnum - regmatch.startpos[0].lnum, - (char_u *)sub, (char_u *)new_end, sublen, - REGSUB_COPY | REGSUB_BACKSLASH | (p_magic ? REGSUB_MAGIC : 0)); + sub, new_end, sublen, + REGSUB_COPY | REGSUB_BACKSLASH + | (magic_isset() ? REGSUB_MAGIC : 0)); textlock--; sub_nsubs++; did_sub = true; @@ -4183,7 +3958,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T bcount_t replaced_bytes = 0; lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0]; for (i = 0; i < nmatch - 1; i++) { - replaced_bytes += (bcount_t)STRLEN(ml_get((linenr_T)(lnum_start + i))) + 1; + replaced_bytes += (bcount_t)strlen(ml_get((linenr_T)(lnum_start + i))) + 1; } replaced_bytes += end.col - start.col; @@ -4226,7 +4001,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T p1 += utfc_ptr2len(p1) - 1; } } - colnr_T new_endcol = (colnr_T)STRLEN(new_start); + colnr_T new_endcol = (colnr_T)strlen(new_start); current_match.end.col = new_endcol; current_match.end.lnum = lnum; @@ -4262,15 +4037,13 @@ skip: && !re_multiline(regmatch.regprog))); nmatch = -1; - /* - * Replace the line in the buffer when needed. This is - * skipped when there are more matches. - * The check for nmatch_tl is needed for when multi-line - * matching must replace the lines before trying to do another - * match, otherwise "\@<=" won't work. - * When the match starts below where we start searching also - * need to replace the line first (using \zs after \n). - */ + // Replace the line in the buffer when needed. This is + // skipped when there are more matches. + // The check for nmatch_tl is needed for when multi-line + // matching must replace the lines before trying to do another + // match, otherwise "\@<=" won't work. + // When the match starts below where we start searching also + // need to replace the line first (using \zs after \n). if (lastone || nmatch_tl > 0 || (nmatch = vim_regexec_multi(®match, curwin, @@ -4278,16 +4051,14 @@ skip: matchcol, NULL, NULL)) == 0 || regmatch.startpos[0].lnum > 0) { if (new_start != NULL) { - /* - * Copy the rest of the line, that didn't match. - * "matchcol" has to be adjusted, we use the end of - * the line as reference, because the substitute may - * have changed the number of characters. Same for - * "prev_matchcol". - */ + // Copy the rest of the line, that didn't match. + // "matchcol" has to be adjusted, we use the end of + // the line as reference, because the substitute may + // have changed the number of characters. Same for + // "prev_matchcol". STRCAT(new_start, sub_firstline + copycol); - matchcol = (colnr_T)STRLEN(sub_firstline) - matchcol; - prev_matchcol = (colnr_T)STRLEN(sub_firstline) + matchcol = (colnr_T)strlen(sub_firstline) - matchcol; + prev_matchcol = (colnr_T)strlen(sub_firstline) - prev_matchcol; if (u_savesub(lnum) != OK) { @@ -4296,12 +4067,10 @@ skip: ml_replace(lnum, new_start, true); if (nmatch_tl > 0) { - /* - * Matched lines have now been substituted and are - * useless, delete them. The part after the match - * has been appended to new_start, we don't need - * it in the buffer. - */ + // Matched lines have now been substituted and are + // useless, delete them. The part after the match + // has been appended to new_start, we don't need + // it in the buffer. lnum++; if (u_savedel(lnum, nmatch_tl) != OK) { break; @@ -4334,8 +4103,8 @@ skip: xfree(sub_firstline); // free the temp buffer sub_firstline = new_start; new_start = NULL; - matchcol = (colnr_T)STRLEN(sub_firstline) - matchcol; - prev_matchcol = (colnr_T)STRLEN(sub_firstline) + matchcol = (colnr_T)strlen(sub_firstline) - matchcol; + prev_matchcol = (colnr_T)strlen(sub_firstline) - prev_matchcol; copycol = 0; } @@ -4344,9 +4113,7 @@ skip: sub_firstlnum, matchcol, NULL, NULL); } - /* - * 5. break if there isn't another match in this line - */ + // 5. break if there isn't another match in this line if (nmatch <= 0) { // If the match found didn't start where we were // searching, do the next search in the line where we @@ -4442,7 +4209,7 @@ skip: beginline(BL_WHITE | BL_FIX); } } - if (!cmdpreview && !do_sub_msg(subflags.do_count) && subflags.do_ask) { + if (!cmdpreview && !do_sub_msg(subflags.do_count) && subflags.do_ask && p_ch > 0) { msg(""); } } else { @@ -4457,7 +4224,9 @@ skip: emsg(_(e_interr)); } else if (got_match) { // did find something but nothing substituted - msg(""); + if (p_ch > 0) { + msg(""); + } } else if (subflags.do_error) { // nothing found semsg(_(e_patnotf2), get_search_pat()); @@ -4490,11 +4259,6 @@ skip: } } - if (cmdheight0) { - // Restore cmdheight - set_option_value("ch", 0L, NULL, 0); - } - kv_destroy(preview_lines.subresults); return retv; #undef ADJUST_SUB_FIRSTLNUM @@ -4509,12 +4273,10 @@ skip: /// @return true if a message was given. bool do_sub_msg(bool count_only) { - /* - * Only report substitutions when: - * - more than 'report' substitutions - * - command was typed by user, or number of changed lines > 'report' - * - giving messages is not disabled by 'lazyredraw' - */ + // Only report substitutions when: + // - more than 'report' substitutions + // - command was typed by user, or number of changed lines > 'report' + // - giving messages is not disabled by 'lazyredraw' if (((sub_nsubs > p_report && (KeyTyped || sub_nlines > 1 || p_report < 1)) || count_only) && messaging()) { @@ -4605,14 +4367,12 @@ void ex_global(exarg_T *eap) cmd = eap->arg; which_pat = RE_LAST; // default: use last used regexp - /* - * undocumented vi feature: - * "\/" and "\?": use previous search pattern. - * "\&": use previous substitute pattern. - */ + // undocumented vi feature: + // "\/" and "\?": use previous search pattern. + // "\&": use previous substitute pattern. if (*cmd == '\\') { cmd++; - if (vim_strchr("/?&", *cmd) == NULL) { + if (vim_strchr("/?&", (uint8_t)(*cmd)) == NULL) { emsg(_(e_backslash)); return; } @@ -4632,13 +4392,15 @@ void ex_global(exarg_T *eap) delim = *cmd; // get the delimiter cmd++; // skip delimiter if there is one pat = cmd; // remember start of pattern - cmd = (char *)skip_regexp((char_u *)cmd, delim, p_magic, &eap->arg); + cmd = skip_regexp_ex(cmd, delim, magic_isset(), &eap->arg, NULL, NULL); if (cmd[0] == delim) { // end delimiter found *cmd++ = NUL; // replace it with a NUL } } - if (search_regcomp((char_u *)pat, RE_BOTH, which_pat, SEARCH_HIS, ®match) == FAIL) { + char *used_pat; + if (search_regcomp(pat, &used_pat, RE_BOTH, which_pat, + SEARCH_HIS, ®match) == FAIL) { emsg(_(e_invcmd)); return; } @@ -4669,9 +4431,9 @@ void ex_global(exarg_T *eap) msg(_(e_interr)); } else if (ndone == 0) { if (type == 'v') { - smsg(_("Pattern found in every line: %s"), pat); + smsg(_("Pattern found in every line: %s"), used_pat); } else { - smsg(_("Pattern not found: %s"), pat); + smsg(_("Pattern not found: %s"), used_pat); } } else { global_exe(cmd); @@ -4748,37 +4510,30 @@ void free_old_sub(void) /// @return true when it was created. bool prepare_tagpreview(bool undo_sync) { - /* - * If there is already a preview window open, use that one. - */ - if (!curwin->w_p_pvw) { - bool found_win = false; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_p_pvw) { - win_enter(wp, undo_sync); - found_win = true; - break; - } - } - if (!found_win) { - /* - * There is no preview window open yet. Create one. - */ - if (win_split(g_do_tagpreview > 0 ? g_do_tagpreview : 0, 0) - == FAIL) { - return false; - } - curwin->w_p_pvw = TRUE; - curwin->w_p_wfh = TRUE; - RESET_BINDING(curwin); /* don't take over 'scrollbind' - and 'cursorbind' */ - curwin->w_p_diff = false; // no 'diff' - set_string_option_direct("fdc", -1, // no 'foldcolumn' - "0", OPT_FREE, SID_NONE); - return true; + if (curwin->w_p_pvw) { + return false; + } + + // If there is already a preview window open, use that one. + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_p_pvw) { + win_enter(wp, undo_sync); + return false; } } - return false; + + // There is no preview window open yet. Create one. + if (win_split(g_do_tagpreview > 0 ? g_do_tagpreview : 0, 0) + == FAIL) { + return false; + } + curwin->w_p_pvw = true; + curwin->w_p_wfh = true; + RESET_BINDING(curwin); // don't take over 'scrollbind' and 'cursorbind' + curwin->w_p_diff = false; // no 'diff' + set_string_option_direct("fdc", -1, // no 'foldcolumn' + "0", OPT_FREE, SID_NONE); + return true; } /// Shows the effects of the :substitute command being typed ('inccommand'). @@ -4789,7 +4544,7 @@ static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, i long cmdpreview_ns, handle_T cmdpreview_bufnr) FUNC_ATTR_NONNULL_ALL { - char *save_shm_p = (char *)vim_strsave(p_shm); + char *save_shm_p = xstrdup(p_shm); PreviewLines lines = *preview_lines; buf_T *orig_buf = curbuf; // We keep a special-purpose buffer around, but don't assume it exists. @@ -4867,7 +4622,7 @@ static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, i if (next_linenr == orig_buf->b_ml.ml_line_count + 1) { line = ""; } else { - line = (char *)ml_get_buf(orig_buf, next_linenr, false); + line = ml_get_buf(orig_buf, next_linenr, false); line_size = strlen(line) + (size_t)col_width + 1; // Reallocate if line not long enough @@ -4935,12 +4690,12 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags) { int c; - if (vim_isIDc(*p)) { + if (vim_isIDc((uint8_t)(*p))) { // ":vimgrep pattern fname" if (s != NULL) { *s = p; } - p = (char *)skiptowhite((char_u *)p); + p = skiptowhite(p); if (s != NULL && *p != NUL) { *p++ = NUL; } @@ -4949,8 +4704,8 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags) if (s != NULL) { *s = p + 1; } - c = (char_u)(*p); - p = (char *)skip_regexp((char_u *)p + 1, c, true, NULL); + c = (uint8_t)(*p); + p = skip_regexp(p + 1, c, true); if (*p != c) { return NULL; } @@ -4986,46 +4741,72 @@ void ex_oldfiles(exarg_T *eap) if (l == NULL) { msg(_("No old files")); - } else { - msg_start(); - msg_scroll = true; - TV_LIST_ITER(l, li, { - if (got_int) { - break; - } - nr++; - const char *fname = tv_get_string(TV_LIST_ITEM_TV(li)); - if (!message_filtered((char_u *)fname)) { - msg_outnum(nr); - msg_puts(": "); - msg_outtrans((char *)tv_get_string(TV_LIST_ITEM_TV(li))); - msg_clr_eos(); - msg_putchar('\n'); - ui_flush(); // output one line at a time - os_breakcheck(); - } - }); - - // Assume "got_int" was set to truncate the listing. - got_int = false; - - // File selection prompt on ":browse oldfiles" - if (cmdmod.cmod_flags & CMOD_BROWSE) { - quit_more = false; - nr = prompt_for_number(false); - msg_starthere(); - if (nr > 0 && nr <= tv_list_len(l)) { - const char *const p = tv_list_find_str(l, (int)nr - 1); - if (p == NULL) { - return; - } - char *const s = expand_env_save((char *)p); - eap->arg = s; - eap->cmdidx = CMD_edit; - cmdmod.cmod_flags &= ~CMOD_BROWSE; - do_exedit(eap, NULL); - xfree(s); + return; + } + + msg_start(); + msg_scroll = true; + TV_LIST_ITER(l, li, { + if (got_int) { + break; + } + nr++; + const char *fname = tv_get_string(TV_LIST_ITEM_TV(li)); + if (!message_filtered((char *)fname)) { + msg_outnum(nr); + msg_puts(": "); + msg_outtrans((char *)tv_get_string(TV_LIST_ITEM_TV(li))); + msg_clr_eos(); + msg_putchar('\n'); + os_breakcheck(); + } + }); + + // Assume "got_int" was set to truncate the listing. + got_int = false; + + // File selection prompt on ":browse oldfiles" + if (cmdmod.cmod_flags & CMOD_BROWSE) { + quit_more = false; + nr = prompt_for_number(false); + msg_starthere(); + if (nr > 0 && nr <= tv_list_len(l)) { + const char *const p = tv_list_find_str(l, (int)nr - 1); + if (p == NULL) { + return; } + char *const s = expand_env_save((char *)p); + eap->arg = s; + eap->cmdidx = CMD_edit; + cmdmod.cmod_flags &= ~CMOD_BROWSE; + do_exedit(eap, NULL); + xfree(s); } } } + +void ex_trust(exarg_T *eap) +{ + const char *const p = skiptowhite(eap->arg); + char *arg1 = xmemdupz(eap->arg, (size_t)(p - eap->arg)); + const char *action = "allow"; + const char *path = skipwhite(p); + + if (strcmp(arg1, "++deny") == 0) { + action = "deny"; + } else if (strcmp(arg1, "++remove") == 0) { + action = "remove"; + } else if (*arg1 != '\0') { + semsg(e_invarg2, arg1); + goto theend; + } + + if (path[0] == '\0') { + path = NULL; + } + + nlua_trust(action, path); + +theend: + xfree(arg1); +} diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h index 3aaba9ce42..39bff3e35d 100644 --- a/src/nvim/ex_cmds.h +++ b/src/nvim/ex_cmds.h @@ -5,6 +5,7 @@ #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/os/time.h" #include "nvim/pos.h" diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 4bed1e94b9..c8b6ceab69 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -673,18 +673,6 @@ module.cmds = { func='ex_cc', }, { - command='cscope', - flags=bit.bor(EXTRA, NOTRLCOM, XFILE), - addr_type='ADDR_NONE', - func='ex_cscope', - }, - { - command='cstag', - flags=bit.bor(BANG, TRLBAR, WORD1), - addr_type='ADDR_NONE', - func='ex_cstag', - }, - { command='cunmap', flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, CTRLV, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', @@ -1117,12 +1105,6 @@ module.cmds = { func='ex_helptags', }, { - command='hardcopy', - flags=bit.bor(RANGE, COUNT, EXTRA, TRLBAR, DFLALL, BANG), - addr_type='ADDR_LINES', - func='ex_hardcopy', - }, - { command='highlight', flags=bit.bor(BANG, EXTRA, TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', @@ -1141,6 +1123,12 @@ module.cmds = { func='ex_history', }, { + command='horizontal', + flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM), + addr_type='ADDR_NONE', + func='ex_wrongmodifier', + }, + { command='insert', flags=bit.bor(BANG, RANGE, TRLBAR, CMDWIN, LOCK_OK, MODIFY), addr_type='ADDR_LINES', @@ -1399,12 +1387,6 @@ module.cmds = { func='ex_cclose', }, { - command='lcscope', - flags=bit.bor(EXTRA, NOTRLCOM, XFILE), - addr_type='ADDR_NONE', - func='ex_cscope', - }, - { command='ldo', flags=bit.bor(BANG, NEEDARG, EXTRA, NOTRLCOM, RANGE, DFLALL), addr_type='ADDR_QUICKFIX_VALID', @@ -2405,7 +2387,7 @@ module.cmds = { }, { command='scriptnames', - flags=bit.bor(BANG, RANGE, COUNT, TRLBAR, CMDWIN, LOCK_OK), + flags=bit.bor(BANG, FILES, RANGE, COUNT, TRLBAR, CMDWIN, LOCK_OK), addr_type='ADDR_OTHER', func='ex_scriptnames', }, @@ -2416,12 +2398,6 @@ module.cmds = { func='ex_scriptencoding', }, { - command='scscope', - flags=bit.bor(EXTRA, NOTRLCOM), - addr_type='ADDR_NONE', - func='ex_scscope', - }, - { command='set', flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, LOCK_OK, SBOXOK), addr_type='ADDR_NONE', @@ -2952,6 +2928,12 @@ module.cmds = { func='ex_tag', }, { + command='trust', + flags=bit.bor(EXTRA, FILE1, TRLBAR, LOCK_OK), + addr_type='ADDR_NONE', + func='ex_trust', + }, + { command='try', flags=bit.bor(TRLBAR, SBOXOK, CMDWIN, LOCK_OK), addr_type='ADDR_NONE', diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 54315a6417..d08823bc30 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -6,46 +6,45 @@ /// Some more functions for command line commands #include <assert.h> -#include <fcntl.h> #include <inttypes.h> #include <stdbool.h> +#include <stdio.h> #include <string.h> #include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" -#include "nvim/globals.h" -#include "nvim/vim.h" -#ifdef HAVE_LOCALE_H -# include <locale.h> -#endif #include "nvim/buffer.h" #include "nvim/change.h" -#include "nvim/charset.h" +#include "nvim/channel.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" +#include "nvim/macros.h" #include "nvim/mark.h" -#include "nvim/mbyte.h" +#include "nvim/memline_defs.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" #include "nvim/normal.h" -#include "nvim/ops.h" #include "nvim/option.h" -#include "nvim/os/fs_defs.h" -#include "nvim/os/shell.h" -#include "nvim/os_unix.h" +#include "nvim/os/os_defs.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" #include "nvim/runtime.h" -#include "nvim/strings.h" #include "nvim/undo.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -204,9 +203,9 @@ void dialog_changed(buf_T *buf, bool checkall) dialog_msg((char *)buff, _("Save changes to \"%s\"?"), buf->b_fname); if (checkall) { - ret = vim_dialog_yesnoallcancel(VIM_QUESTION, NULL, (char_u *)buff, 1); + ret = vim_dialog_yesnoallcancel(VIM_QUESTION, NULL, buff, 1); } else { - ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, (char_u *)buff, 1); + ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1); } if (ret == VIM_YES) { @@ -256,7 +255,7 @@ bool dialog_close_terminal(buf_T *buf) dialog_msg(buff, _("Close \"%s\"?"), (buf->b_fname != NULL) ? buf->b_fname : "?"); - int ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, (char_u *)buff, 1); + int ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1); return ret == VIM_YES; } @@ -368,7 +367,7 @@ bool check_changed_any(bool hidden, bool unload) exiting = false; // When ":confirm" used, don't give an error message. if (!(p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM))) { - // There must be a wait_return for this message, do_buffer() + // There must be a wait_return() for this message, do_buffer() // may cause a redraw. But wait_return() is a no-op when vgetc() // is busy (Quit used from window menu), then make sure we don't // cause a scroll up. @@ -543,10 +542,10 @@ void ex_listdo(exarg_T *eap) if (curwin->w_arg_idx != i || !editing_arg_idx(curwin)) { // Clear 'shm' to avoid that the file message overwrites // any output from the command. - p_shm_save = (char *)vim_strsave(p_shm); - set_option_value("shm", 0L, "", 0); + p_shm_save = xstrdup(p_shm); + set_option_value_give_err("shm", 0L, "", 0); do_argfile(eap, i); - set_option_value("shm", 0L, p_shm_save, 0); + set_option_value_give_err("shm", 0L, p_shm_save, 0); xfree(p_shm_save); } if (curwin->w_arg_idx != i) { @@ -612,10 +611,10 @@ void ex_listdo(exarg_T *eap) // Go to the next buffer. Clear 'shm' to avoid that the file // message overwrites any output from the command. - p_shm_save = (char *)vim_strsave(p_shm); - set_option_value("shm", 0L, "", 0); + p_shm_save = xstrdup(p_shm); + set_option_value_give_err("shm", 0L, "", 0); goto_buffer(eap, DOBUF_FIRST, FORWARD, next_fnum); - set_option_value("shm", 0L, p_shm_save, 0); + set_option_value_give_err("shm", 0L, p_shm_save, 0); xfree(p_shm_save); // If autocommands took us elsewhere, quit here. @@ -635,10 +634,10 @@ void ex_listdo(exarg_T *eap) // Clear 'shm' to avoid that the file message overwrites // any output from the command. - p_shm_save = (char *)vim_strsave(p_shm); - set_option_value("shm", 0L, "", 0); + p_shm_save = xstrdup(p_shm); + set_option_value_give_err("shm", 0L, "", 0); ex_cnext(eap); - set_option_value("shm", 0L, p_shm_save, 0); + set_option_value_give_err("shm", 0L, p_shm_save, 0); xfree(p_shm_save); // If jumping to the next quickfix entry fails, quit here. @@ -680,11 +679,11 @@ void ex_listdo(exarg_T *eap) // buffer was opened while Syntax autocommands were disabled, // need to trigger them now. if (buf == curbuf) { - apply_autocmds(EVENT_SYNTAX, (char *)curbuf->b_p_syn, curbuf->b_fname, true, + apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname, true, curbuf); } else { aucmd_prepbuf(&aco, buf); - apply_autocmds(EVENT_SYNTAX, (char *)buf->b_p_syn, buf->b_fname, true, buf); + apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname, true, buf); aucmd_restbuf(&aco); } @@ -706,54 +705,56 @@ void ex_compiler(exarg_T *eap) // List all compiler scripts. do_cmdline_cmd("echo globpath(&rtp, 'compiler/*.vim')"); // NOLINT do_cmdline_cmd("echo globpath(&rtp, 'compiler/*.lua')"); // NOLINT - } else { - size_t bufsize = STRLEN(eap->arg) + 14; - buf = xmalloc(bufsize); - if (eap->forceit) { - // ":compiler! {name}" sets global options - do_cmdline_cmd("command -nargs=* CompilerSet set <args>"); - } else { - // ":compiler! {name}" sets local options. - // To remain backwards compatible "current_compiler" is always - // used. A user's compiler plugin may set it, the distributed - // plugin will then skip the settings. Afterwards set - // "b:current_compiler" and restore "current_compiler". - // Explicitly prepend "g:" to make it work in a function. - old_cur_comp = (char *)get_var_value("g:current_compiler"); - if (old_cur_comp != NULL) { - old_cur_comp = xstrdup(old_cur_comp); - } - do_cmdline_cmd("command -nargs=* -keepscript CompilerSet setlocal <args>"); - } - do_unlet(S_LEN("g:current_compiler"), true); - do_unlet(S_LEN("b:current_compiler"), true); + return; + } - snprintf(buf, bufsize, "compiler/%s.vim", eap->arg); + size_t bufsize = strlen(eap->arg) + 14; + buf = xmalloc(bufsize); + + if (eap->forceit) { + // ":compiler! {name}" sets global options + do_cmdline_cmd("command -nargs=* CompilerSet set <args>"); + } else { + // ":compiler! {name}" sets local options. + // To remain backwards compatible "current_compiler" is always + // used. A user's compiler plugin may set it, the distributed + // plugin will then skip the settings. Afterwards set + // "b:current_compiler" and restore "current_compiler". + // Explicitly prepend "g:" to make it work in a function. + old_cur_comp = get_var_value("g:current_compiler"); + if (old_cur_comp != NULL) { + old_cur_comp = xstrdup(old_cur_comp); + } + do_cmdline_cmd("command -nargs=* -keepscript CompilerSet setlocal <args>"); + } + do_unlet(S_LEN("g:current_compiler"), true); + do_unlet(S_LEN("b:current_compiler"), true); + + snprintf(buf, bufsize, "compiler/%s.vim", eap->arg); + if (source_runtime(buf, DIP_ALL) == FAIL) { + // Try lua compiler + snprintf(buf, bufsize, "compiler/%s.lua", eap->arg); if (source_runtime(buf, DIP_ALL) == FAIL) { - // Try lua compiler - snprintf(buf, bufsize, "compiler/%s.lua", eap->arg); - if (source_runtime(buf, DIP_ALL) == FAIL) { - semsg(_("E666: compiler not supported: %s"), eap->arg); - } + semsg(_("E666: compiler not supported: %s"), eap->arg); } - xfree(buf); + } + xfree(buf); - do_cmdline_cmd(":delcommand CompilerSet"); + do_cmdline_cmd(":delcommand CompilerSet"); - // Set "b:current_compiler" from "current_compiler". - p = (char *)get_var_value("g:current_compiler"); - if (p != NULL) { - set_internal_string_var("b:current_compiler", p); - } + // Set "b:current_compiler" from "current_compiler". + p = get_var_value("g:current_compiler"); + if (p != NULL) { + set_internal_string_var("b:current_compiler", p); + } - // Restore "current_compiler" for ":compiler {name}". - if (!eap->forceit) { - if (old_cur_comp != NULL) { - set_internal_string_var("g:current_compiler", old_cur_comp); - xfree(old_cur_comp); - } else { - do_unlet(S_LEN("g:current_compiler"), true); - } + // Restore "current_compiler" for ":compiler {name}". + if (!eap->forceit) { + if (old_cur_comp != NULL) { + set_internal_string_var("g:current_compiler", old_cur_comp); + xfree(old_cur_comp); + } else { + do_unlet(S_LEN("g:current_compiler"), true); } } } @@ -776,324 +777,6 @@ void ex_checktime(exarg_T *eap) no_check_timestamps = save_no_check_timestamps; } -#if defined(HAVE_LOCALE_H) -# define HAVE_GET_LOCALE_VAL - -static char *get_locale_val(int what) -{ - // Obtain the locale value from the libraries. - char *loc = setlocale(what, NULL); - - return loc; -} -#endif - -/// @return true when "lang" starts with a valid language name. -/// Rejects NULL, empty string, "C", "C.UTF-8" and others. -static bool is_valid_mess_lang(char *lang) -{ - return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); -} - -/// Obtain the current messages language. Used to set the default for -/// 'helplang'. May return NULL or an empty string. -char *get_mess_lang(void) -{ - char *p; - -#ifdef HAVE_GET_LOCALE_VAL -# if defined(LC_MESSAGES) - p = get_locale_val(LC_MESSAGES); -# else - // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG - // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME - // and LC_MONETARY may be set differently for a Japanese working in the - // US. - p = get_locale_val(LC_COLLATE); -# endif -#else - p = os_getenv("LC_ALL"); - if (!is_valid_mess_lang(p)) { - p = os_getenv("LC_MESSAGES"); - if (!is_valid_mess_lang(p)) { - p = os_getenv("LANG"); - } - } -#endif - return is_valid_mess_lang(p) ? p : NULL; -} - -// Complicated #if; matches with where get_mess_env() is used below. -#ifdef HAVE_WORKING_LIBINTL -/// Get the language used for messages from the environment. -static char *get_mess_env(void) -{ - char *p; - - p = (char *)os_getenv("LC_ALL"); - if (p == NULL) { - p = (char *)os_getenv("LC_MESSAGES"); - if (p == NULL) { - p = (char *)os_getenv("LANG"); - if (p != NULL && ascii_isdigit(*p)) { - p = NULL; // ignore something like "1043" - } -# ifdef HAVE_GET_LOCALE_VAL - if (p == NULL) { - p = get_locale_val(LC_CTYPE); - } -# endif - } - } - return p; -} - -#endif - -/// Set the "v:lang" variable according to the current locale setting. -/// Also do "v:lc_time"and "v:ctype". -void set_lang_var(void) -{ - const char *loc; - -#ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_CTYPE); -#else - // setlocale() not supported: use the default value - loc = "C"; -#endif - set_vim_var_string(VV_CTYPE, loc, -1); - - // When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall - // back to LC_CTYPE if it's empty. -#ifdef HAVE_WORKING_LIBINTL - loc = get_mess_env(); -#elif defined(LC_MESSAGES) - loc = get_locale_val(LC_MESSAGES); -#else - // In Windows LC_MESSAGES is not defined fallback to LC_CTYPE - loc = get_locale_val(LC_CTYPE); -#endif - set_vim_var_string(VV_LANG, loc, -1); - -#ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_TIME); -#endif - set_vim_var_string(VV_LC_TIME, loc, -1); - -#ifdef HAVE_GET_LOCALE_VAL - loc = get_locale_val(LC_COLLATE); -#else - // setlocale() not supported: use the default value - loc = "C"; -#endif - set_vim_var_string(VV_COLLATE, loc, -1); -} - -#ifdef HAVE_WORKING_LIBINTL - -/// ":language": Set the language (locale). -/// -/// @param eap -void ex_language(exarg_T *eap) -{ - char *loc; - char *p; - char *name; - int what = LC_ALL; - char *whatstr = ""; -# ifdef LC_MESSAGES -# define VIM_LC_MESSAGES LC_MESSAGES -# else -# define VIM_LC_MESSAGES 6789 -# endif - - name = eap->arg; - - // Check for "messages {name}", "ctype {name}" or "time {name}" argument. - // Allow abbreviation, but require at least 3 characters to avoid - // confusion with a two letter language name "me" or "ct". - p = (char *)skiptowhite((char_u *)eap->arg); - if ((*p == NUL || ascii_iswhite(*p)) && p - eap->arg >= 3) { - if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) { - what = VIM_LC_MESSAGES; - name = skipwhite(p); - whatstr = "messages "; - } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) { - what = LC_CTYPE; - name = skipwhite(p); - whatstr = "ctype "; - } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) { - what = LC_TIME; - name = skipwhite(p); - whatstr = "time "; - } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) { - what = LC_COLLATE; - name = skipwhite(p); - whatstr = "collate "; - } - } - - if (*name == NUL) { - if (what == VIM_LC_MESSAGES) { - p = get_mess_env(); - } else { - p = setlocale(what, NULL); - } - if (p == NULL || *p == NUL) { - p = "Unknown"; - } - smsg(_("Current %slanguage: \"%s\""), whatstr, p); - } else { -# ifndef LC_MESSAGES - if (what == VIM_LC_MESSAGES) { - loc = ""; - } else { -# endif - loc = setlocale(what, name); -# ifdef LC_NUMERIC - // Make sure strtod() uses a decimal point, not a comma. - setlocale(LC_NUMERIC, "C"); -# endif -# ifndef LC_MESSAGES - } -# endif - if (loc == NULL) { - semsg(_("E197: Cannot set language to \"%s\""), name); - } else { -# ifdef HAVE_NL_MSG_CAT_CNTR - // Need to do this for GNU gettext, otherwise cached translations - // will be used again. - extern int _nl_msg_cat_cntr; - - _nl_msg_cat_cntr++; -# endif - // Reset $LC_ALL, otherwise it would overrule everything. - os_setenv("LC_ALL", "", 1); - - if (what != LC_TIME && what != LC_COLLATE) { - // Tell gettext() what to translate to. It apparently doesn't - // use the currently effective locale. - if (what == LC_ALL) { - os_setenv("LANG", name, 1); - - // Clear $LANGUAGE because GNU gettext uses it. - os_setenv("LANGUAGE", "", 1); - } - if (what != LC_CTYPE) { - os_setenv("LC_MESSAGES", name, 1); - set_helplang_default(name); - } - } - - // Set v:lang, v:lc_time, v:collate and v:ctype to the final result. - set_lang_var(); - maketitle(); - } - } -} - -static char **locales = NULL; // Array of all available locales - -# ifndef WIN32 -static bool did_init_locales = false; - -/// @return an array of strings for all available locales + NULL for the -/// last element or, -/// NULL in case of error. -static char **find_locales(void) -{ - garray_T locales_ga; - char *loc; - char *saveptr = NULL; - - // Find all available locales by running command "locale -a". If this - // doesn't work we won't have completion. - char *locale_a = (char *)get_cmd_output((char_u *)"locale -a", NULL, - kShellOptSilent, NULL); - if (locale_a == NULL) { - return NULL; - } - ga_init(&locales_ga, sizeof(char_u *), 20); - - // Transform locale_a string where each locale is separated by "\n" - // into an array of locale strings. - loc = os_strtok(locale_a, "\n", &saveptr); - - while (loc != NULL) { - loc = xstrdup(loc); - GA_APPEND(char *, &locales_ga, loc); - loc = os_strtok(NULL, "\n", &saveptr); - } - xfree(locale_a); - // Guarantee that .ga_data is NULL terminated - ga_grow(&locales_ga, 1); - ((char_u **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; - return locales_ga.ga_data; -} -# endif - -/// Lazy initialization of all available locales. -static void init_locales(void) -{ -# ifndef WIN32 - if (!did_init_locales) { - did_init_locales = true; - locales = find_locales(); - } -# endif -} - -# if defined(EXITFREE) -void free_locales(void) -{ - int i; - if (locales != NULL) { - for (i = 0; locales[i] != NULL; i++) { - xfree(locales[i]); - } - XFREE_CLEAR(locales); - } -} - -# endif - -/// Function given to ExpandGeneric() to obtain the possible arguments of the -/// ":language" command. -char *get_lang_arg(expand_T *xp, int idx) -{ - if (idx == 0) { - return "messages"; - } - if (idx == 1) { - return "ctype"; - } - if (idx == 2) { - return "time"; - } - if (idx == 3) { - return "collate"; - } - - init_locales(); - if (locales == NULL) { - return NULL; - } - return locales[idx - 4]; -} - -/// Function given to ExpandGeneric() to obtain the available locales. -char *get_locales(expand_T *xp, int idx) -{ - init_locales(); - if (locales == NULL) { - return NULL; - } - return locales[idx]; -} - -#endif - static void script_host_execute(char *name, exarg_T *eap) { size_t len; @@ -1166,45 +849,46 @@ void ex_drop(exarg_T *eap) // edited in a window yet. It's like ":tab all" but without closing // windows or tabs. ex_all(eap); - } else { - // ":drop file ...": Edit the first argument. Jump to an existing - // window if possible, edit in current window if the current buffer - // can be abandoned, otherwise open a new window. - buf = buflist_findnr(ARGLIST[0].ae_fnum); + return; + } - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == buf) { - goto_tabpage_win(tp, wp); - curwin->w_arg_idx = 0; - if (!bufIsChanged(curbuf)) { - const int save_ar = curbuf->b_p_ar; - - // reload the file if it is newer - curbuf->b_p_ar = 1; - buf_check_timestamp(curbuf); - curbuf->b_p_ar = save_ar; - } - return; + // ":drop file ...": Edit the first argument. Jump to an existing + // window if possible, edit in current window if the current buffer + // can be abandoned, otherwise open a new window. + buf = buflist_findnr(ARGLIST[0].ae_fnum); + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == buf) { + goto_tabpage_win(tp, wp); + curwin->w_arg_idx = 0; + if (!bufIsChanged(curbuf)) { + const int save_ar = curbuf->b_p_ar; + + // reload the file if it is newer + curbuf->b_p_ar = 1; + buf_check_timestamp(curbuf); + curbuf->b_p_ar = save_ar; } + return; } + } - // Check whether the current buffer is changed. If so, we will need - // to split the current window or data could be lost. - // Skip the check if the 'hidden' option is set, as in this case the - // buffer won't be lost. - if (!buf_hide(curbuf)) { - emsg_off++; - split = check_changed(curbuf, CCGD_AW | CCGD_EXCMD); - emsg_off--; - } + // Check whether the current buffer is changed. If so, we will need + // to split the current window or data could be lost. + // Skip the check if the 'hidden' option is set, as in this case the + // buffer won't be lost. + if (!buf_hide(curbuf)) { + emsg_off++; + split = check_changed(curbuf, CCGD_AW | CCGD_EXCMD); + emsg_off--; + } - // Fake a ":sfirst" or ":first" command edit the first argument. - if (split) { - eap->cmdidx = CMD_sfirst; - eap->cmd[0] = 's'; - } else { - eap->cmdidx = CMD_first; - } - ex_rewind(eap); + // Fake a ":sfirst" or ":first" command edit the first argument. + if (split) { + eap->cmdidx = CMD_sfirst; + eap->cmd[0] = 's'; + } else { + eap->cmdidx = CMD_first; } + ex_rewind(eap); } diff --git a/src/nvim/ex_cmds2.h b/src/nvim/ex_cmds2.h index e454a30028..3a41e105f3 100644 --- a/src/nvim/ex_cmds2.h +++ b/src/nvim/ex_cmds2.h @@ -1,8 +1,6 @@ #ifndef NVIM_EX_CMDS2_H #define NVIM_EX_CMDS2_H -#include <stdbool.h> - #include "nvim/ex_cmds_defs.h" // diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index e80e47bcff..629aaf14cf 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -6,7 +6,7 @@ #include "nvim/eval/typval.h" #include "nvim/normal.h" -#include "nvim/pos.h" // for linenr_T +#include "nvim/pos.h" #include "nvim/regexp_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -62,7 +62,7 @@ #define EX_FLAGS 0x200000u // allow flags after count in argument #define EX_LOCK_OK 0x1000000u // command can be executed when textlock is // set; when missing disallows editing another - // buffer when current buffer is locked + // buffer when curbuf->b_ro_locked is set #define EX_KEEPSCRIPT 0x4000000u // keep sctx of where command was invoked #define EX_PREVIEW 0x8000000u // allow incremental command preview #define EX_FILES (EX_XFILE | EX_EXTRA) // multiple extra files allowed @@ -188,7 +188,7 @@ struct exarg { cmdidx_T cmdidx; ///< the index for the command uint32_t argt; ///< flags for the command int skip; ///< don't execute the command, only parse it - int forceit; ///< TRUE if ! present + int forceit; ///< true if ! present int addr_count; ///< the number of addresses given linenr_T line1; ///< the first line number linenr_T line2; ///< the second line number or count @@ -196,12 +196,13 @@ struct exarg { int flags; ///< extra flags after count: EXFLAG_ char *do_ecmd_cmd; ///< +command arg to be used in edited file linenr_T do_ecmd_lnum; ///< the line number in an edited file - int append; ///< TRUE with ":w >>file" command - int usefilter; ///< TRUE with ":w !command" and ":r!command" + int append; ///< true with ":w >>file" command + int usefilter; ///< true with ":w !command" and ":r!command" int amount; ///< number of '>' or '<' for shift command int regname; ///< register name (NUL if none) int force_bin; ///< 0, FORCE_BIN or FORCE_NOBIN int read_edit; ///< ++edit argument + int mkdir_p; ///< ++p argument int force_ff; ///< ++ff= argument (first char of argument) int force_enc; ///< ++enc= argument (index in cmd[]) int bad_char; ///< BAD_KEEP, BAD_DROP or replacement byte @@ -230,13 +231,15 @@ struct expand { sctx_T xp_script_ctx; // SCTX for completion function int xp_backslash; // one of the XP_BS_ values #ifndef BACKSLASH_IN_FILENAME - int xp_shell; // TRUE for a shell command, more + int xp_shell; // true for a shell command, more // characters need to be escaped #endif int xp_numfiles; // number of files found by file name completion int xp_col; // cursor position in line char **xp_files; // list of files char *xp_line; // text being completed +#define EXPAND_BUF_LEN 256 + char xp_buf[EXPAND_BUF_LEN]; // buffer for returned match }; // values for xp_backslash diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index c2e7b96a53..5d271e4ef6 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4,58 +4,57 @@ // ex_docmd.c: functions for executing an Ex command line. #include <assert.h> +#include <ctype.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include "auto/config.h" #include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" -#include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/debugger.h" -#include "nvim/diff.h" #include "nvim/digraph.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" -#include "nvim/eval/vars.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" +#include "nvim/event/loop.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" -#include "nvim/ex_session.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" -#include "nvim/hardcopy.h" -#include "nvim/help.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" -#include "nvim/if_cscope.h" #include "nvim/input.h" #include "nvim/keycodes.h" -#include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/mapping.h" #include "nvim/mark.h" -#include "nvim/match.h" #include "nvim/mbyte.h" +#include "nvim/memfile_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/menu.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" @@ -65,28 +64,25 @@ #include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/time.h" -#include "nvim/os_unix.h" +#include "nvim/os/shell.h" #include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/shada.h" -#include "nvim/sign.h" -#include "nvim/spell.h" -#include "nvim/spellfile.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/strings.h" -#include "nvim/syntax.h" #include "nvim/tag.h" -#include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" -#include "nvim/undo_defs.h" #include "nvim/usercmd.h" -#include "nvim/version.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -94,6 +90,12 @@ static char e_ambiguous_use_of_user_defined_command[] = N_("E464: Ambiguous use of user-defined command"); static char e_not_an_editor_command[] = N_("E492: Not an editor command"); +static char e_no_source_file_name_to_substitute_for_sfile[] + = N_("E498: no :source file name to substitute for \"<sfile>\""); +static char e_no_call_stack_to_substitute_for_stack[] + = N_("E489: no call stack to substitute for \"<stack>\""); +static char e_no_script_file_name_to_substitute_for_script[] + = N_("E1274: No script file name to substitute for \"<script>\""); static int quitmore = 0; static bool ex_pressedreturn = false; @@ -127,6 +129,7 @@ struct dbg_stuff { char *vv_throwpoint; int did_emsg; int got_int; + bool did_throw; int need_rethrow; int check_cstack; except_T *current_exception; @@ -158,6 +161,7 @@ static void save_dbg_stuff(struct dbg_stuff *dsp) // Necessary for debugging an inactive ":catch", ":finally", ":endtry". dsp->did_emsg = did_emsg; did_emsg = false; dsp->got_int = got_int; got_int = false; + dsp->did_throw = did_throw; did_throw = false; dsp->need_rethrow = need_rethrow; need_rethrow = false; dsp->check_cstack = check_cstack; check_cstack = false; dsp->current_exception = current_exception; current_exception = NULL; @@ -173,6 +177,7 @@ static void restore_dbg_stuff(struct dbg_stuff *dsp) (void)v_throwpoint(dsp->vv_throwpoint); did_emsg = dsp->did_emsg; got_int = dsp->got_int; + did_throw = dsp->did_throw; need_rethrow = dsp->need_rethrow; check_cstack = dsp->check_cstack; current_exception = dsp->current_exception; @@ -243,8 +248,8 @@ void do_exmode(void) RedrawingDisabled--; no_wait_return--; - redraw_all_later(NOT_VALID); - update_screen(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); + update_screen(); need_wait_return = false; msg_scroll = save_msg_scroll; } @@ -364,7 +369,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // Get the function or script name and the address where the next breakpoint // line and the debug tick for a function or script are stored. if (getline_is_func) { - fname = (char *)func_name(real_cookie); + fname = func_name(real_cookie); breakpoint = func_breakpoint(real_cookie); dbg_tick = func_dbg_tick(real_cookie); } else if (getline_equal(fgetline, cookie, getsourceline)) { @@ -390,7 +395,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) initial_trylevel = trylevel; - current_exception = NULL; + // "did_throw" will be set to true when an exception is being thrown. + did_throw = false; // "did_emsg" will be set to true when emsg() is used, in which case we // cancel the whole command line, and any if/endif or loop. // If force_abort is set, we cancel everything. @@ -455,7 +461,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (breakpoint != NULL && dbg_tick != NULL && *dbg_tick != debug_tick) { *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, SOURCING_LNUM); + fname, SOURCING_LNUM); *dbg_tick = debug_tick; } @@ -464,10 +470,10 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // Did we encounter a breakpoint? if (breakpoint != NULL && *breakpoint != 0 && *breakpoint <= SOURCING_LNUM) { - dbg_breakpoint((char_u *)fname, SOURCING_LNUM); + dbg_breakpoint(fname, SOURCING_LNUM); // Find next breakpoint. *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, SOURCING_LNUM); + fname, SOURCING_LNUM); *dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { @@ -509,7 +515,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) cstack.cs_idx < 0 ? 0 : (cstack.cs_idx + 1) * 2, true)) == NULL) { - // Don't call wait_return for aborted command line. The NULL + // Don't call wait_return() for aborted command line. The NULL // returned for the end of a sourced file or executed function // doesn't do this. if (KeyTyped && !(flags & DOCMD_REPEAT)) { @@ -524,7 +530,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (flags & DOCMD_KEEPLINE) { xfree(repeat_cmdline); if (count == 0) { - repeat_cmdline = vim_strsave((char_u *)next_cmdline); + repeat_cmdline = xstrdup(next_cmdline); } else { repeat_cmdline = NULL; } @@ -618,7 +624,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // not to use a cs_line[] from an entry that isn't a ":while" // or ":for": It would make "current_line" invalid and can // cause a crash. - if (!did_emsg && !got_int && !current_exception + if (!did_emsg && !got_int && !did_throw && cstack.cs_idx >= 0 && (cstack.cs_flags[cstack.cs_idx] & (CSF_WHILE | CSF_FOR)) @@ -631,9 +637,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // Check for the next breakpoint at or after the ":while" // or ":for". - if (breakpoint != NULL) { - *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), - (char_u *)fname, + if (breakpoint != NULL && lines_ga.ga_len > current_line) { + *breakpoint = dbg_find_breakpoint(getline_equal(fgetline, cookie, getsourceline), fname, ((wcmd_T *)lines_ga.ga_data)[current_line].lnum - 1); *dbg_tick = debug_tick; } @@ -660,7 +665,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) current_line = 0; } - // A ":finally" makes did_emsg, got_int and current_exception pending for + // A ":finally" makes did_emsg, got_int and did_throw pending for // being restored at the ":endtry". Reset them here and set the // ACTIVE and FINALLY flags, so that the finally clause gets executed. // This includes the case where a missing ":endif", ":endwhile" or @@ -669,9 +674,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) cstack.cs_lflags &= ~CSL_HAD_FINA; report_make_pending((cstack.cs_pending[cstack.cs_idx] & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW)), - current_exception); - did_emsg = got_int = false; - current_exception = NULL; + did_throw ? current_exception : NULL); + did_emsg = got_int = did_throw = false; cstack.cs_flags[cstack.cs_idx] |= CSF_ACTIVE | CSF_FINALLY; } @@ -684,7 +688,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // exception, cancel everything. If it is left normally, reset // force_abort to get the non-EH compatible abortion behavior for // the rest of the script. - if (trylevel == 0 && !did_emsg && !got_int && !current_exception) { + if (trylevel == 0 && !did_emsg && !got_int && !did_throw) { force_abort = false; } @@ -698,7 +702,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // - didn't get an error message or lines are not typed // - there is a command after '|', inside a :if, :while, :for or :try, or // looping for ":source" command or function call. - } while (!((got_int || (did_emsg && force_abort) || current_exception) + } while (!((got_int || (did_emsg && force_abort) || did_throw) && cstack.cs_trylevel == 0) && !(did_emsg // Keep going when inside try/catch, so that the error can be @@ -718,7 +722,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) if (cstack.cs_idx >= 0) { // If a sourced file or executed function ran to its end, report the // unclosed conditional. - if (!got_int && !current_exception + if (!got_int && !did_throw && !aborting() && ((getline_equal(fgetline, cookie, getsourceline) && !source_finished(fgetline, cookie)) || (getline_equal(fgetline, cookie, get_func_line) @@ -761,52 +765,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // conditional, discard the uncaught exception, disable the conversion // of interrupts or errors to exceptions, and ensure that no more // commands are executed. - if (current_exception) { - char *p = NULL; - msglist_T *messages = NULL; - msglist_T *next; - - // If the uncaught exception is a user exception, report it as an - // error. If it is an error exception, display the saved error - // message now. For an interrupt exception, do nothing; the - // interrupt message is given elsewhere. - switch (current_exception->type) { - case ET_USER: - vim_snprintf((char *)IObuff, IOSIZE, - _("E605: Exception not caught: %s"), - current_exception->value); - p = (char *)vim_strsave(IObuff); - break; - case ET_ERROR: - messages = current_exception->messages; - current_exception->messages = NULL; - break; - case ET_INTERRUPT: - break; - } - - estack_push(ETYPE_EXCEPT, current_exception->throw_name, current_exception->throw_lnum); - current_exception->throw_name = NULL; - - discard_current_exception(); // uses IObuff if 'verbose' - suppress_errthrow = true; - force_abort = true; - msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993 - - if (messages != NULL) { - do { - next = messages->next; - emsg(messages->msg); - xfree(messages->msg); - xfree(messages); - messages = next; - } while (messages != NULL); - } else if (p != NULL) { - emsg(p); - xfree(p); - } - xfree(SOURCING_NAME); - estack_pop(); + if (did_throw) { + handle_did_throw(); } else if (got_int || (did_emsg && force_abort)) { // On an interrupt or an aborting error not converted to an exception, // disable the conversion of errors to exceptions. (Interrupts are not @@ -823,14 +783,14 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // cstack belongs to the same function or, respectively, script file, it // will have to be checked for finally clauses to be executed due to the // ":return" or ":finish". This is done in do_one_cmd(). - if (current_exception) { + if (did_throw) { need_rethrow = true; } if ((getline_equal(fgetline, cookie, getsourceline) && ex_nesting_level > source_level(real_cookie)) || (getline_equal(fgetline, cookie, get_func_line) && ex_nesting_level > func_level(real_cookie) + 1)) { - if (!current_exception) { + if (!did_throw) { check_cstack = true; } } else { @@ -844,8 +804,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) || getline_equal(fgetline, cookie, get_func_line)) && ex_nesting_level + 1 <= debug_break_level) { do_debug(getline_equal(fgetline, cookie, getsourceline) - ? (char_u *)_("End of sourced file") - : (char_u *)_("End of function")); + ? _("End of sourced file") + : _("End of function")); } } @@ -881,7 +841,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) need_wait_return = false; msg_didany = false; // don't wait when restarting edit } else if (need_wait_return) { - // The msg_start() above clears msg_didout. The wait_return we do + // The msg_start() above clears msg_didout. The wait_return() we do // here should not overwrite the command that may be shown before // doing that. msg_didout |= msg_didout_before_start; @@ -896,6 +856,57 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) return retval; } +/// Handle when "did_throw" is set after executing commands. +void handle_did_throw(void) +{ + assert(current_exception != NULL); + char *p = NULL; + msglist_T *messages = NULL; + + // If the uncaught exception is a user exception, report it as an + // error. If it is an error exception, display the saved error + // message now. For an interrupt exception, do nothing; the + // interrupt message is given elsewhere. + switch (current_exception->type) { + case ET_USER: + vim_snprintf(IObuff, IOSIZE, + _("E605: Exception not caught: %s"), + current_exception->value); + p = xstrdup(IObuff); + break; + case ET_ERROR: + messages = current_exception->messages; + current_exception->messages = NULL; + break; + case ET_INTERRUPT: + break; + } + + estack_push(ETYPE_EXCEPT, current_exception->throw_name, current_exception->throw_lnum); + current_exception->throw_name = NULL; + + discard_current_exception(); // uses IObuff if 'verbose' + suppress_errthrow = true; + force_abort = true; + msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993 + + if (messages != NULL) { + do { + msglist_T *next = messages->next; + emsg(messages->msg); + xfree(messages->msg); + xfree(messages->sfile); + xfree(messages); + messages = next; + } while (messages != NULL); + } else if (p != NULL) { + emsg(p); + xfree(p); + } + xfree(SOURCING_NAME); + estack_pop(); +} + /// Obtain a line when inside a ":while" or ":for" loop. static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) { @@ -908,7 +919,7 @@ static char *get_loop_line(int c, void *cookie, int indent, bool do_concat) char *line; // First time inside the ":while"/":for": get line normally. if (cp->getline == NULL) { - line = (char *)getcmdline(c, 0L, indent, do_concat); + line = getcmdline(c, 0L, indent, do_concat); } else { line = cp->getline(c, cp->cookie, indent, do_concat); } @@ -1050,7 +1061,7 @@ static int current_tab_nr(tabpage_T *tab) #define LAST_TAB_NR current_tab_nr(NULL) /// Figure out the address type for ":wincmd". -static void get_wincmd_addr_type(char *arg, exarg_T *eap) +static void get_wincmd_addr_type(const char *arg, exarg_T *eap) { switch (*arg) { case 'S': @@ -1290,9 +1301,9 @@ static void parse_register(exarg_T *eap) // for '=' register: accept the rest of the line as an expression if (eap->arg[-1] == '=' && eap->arg[0] != NUL) { if (!eap->skip) { - set_expr_line(vim_strsave((char_u *)eap->arg)); + set_expr_line(xstrdup(eap->arg)); } - eap->arg += STRLEN(eap->arg); + eap->arg += strlen(eap->arg); } eap->arg = skipwhite(eap->arg); } @@ -1329,6 +1340,20 @@ static int parse_count(exarg_T *eap, char **errormsg, bool validate) || ascii_iswhite(*p))) { long n = getdigits_long(&eap->arg, false, -1); eap->arg = skipwhite(eap->arg); + + if (eap->args != NULL) { + assert(eap->argc > 0 && eap->arg >= eap->args[0]); + // If eap->arg is still pointing to the first argument, just make eap->args[0] point to the + // same location. This is needed for usecases like vim.cmd.sleep('10m'). If eap->arg is + // pointing outside the first argument, shift arguments by 1. + if (eap->arg < eap->args[0] + eap->arglens[0]) { + eap->arglens[0] -= (size_t)(eap->arg - eap->args[0]); + eap->args[0] = eap->arg; + } else { + shift_cmd_args(eap); + } + } + if (n <= 0 && (eap->argt & EX_ZEROR) == 0) { if (errormsg != NULL) { *errormsg = _(e_zerocount); @@ -1361,6 +1386,13 @@ bool is_cmd_ni(cmdidx_T cmdidx) bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **errormsg) { char *after_modifier = NULL; + bool retval = false; + // parsing the command modifiers may set ex_pressedreturn + const bool save_ex_pressedreturn = ex_pressedreturn; + // parsing the command range may require moving the cursor + const pos_T save_cursor = curwin->w_cursor; + // parsing the command range may set the last search pattern + save_last_search_pattern(); // Initialize cmdinfo CLEAR_POINTER(cmdinfo); @@ -1375,13 +1407,10 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er .cookie = NULL, }; - const bool save_ex_pressedreturn = ex_pressedreturn; // Parse command modifiers if (parse_command_modifiers(eap, errormsg, &cmdinfo->cmdmod, false) == FAIL) { - ex_pressedreturn = save_ex_pressedreturn; - goto err; + goto end; } - ex_pressedreturn = save_ex_pressedreturn; after_modifier = eap->cmd; // Save location after command modifiers @@ -1394,21 +1423,21 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er char *p = find_ex_command(eap, NULL); if (p == NULL) { *errormsg = _(e_ambiguous_use_of_user_defined_command); - goto err; + goto end; } // Set command address type and parse command range set_cmd_addr_type(eap, p); eap->cmd = cmd; - if (parse_cmd_address(eap, errormsg, false) == FAIL) { - goto err; + if (parse_cmd_address(eap, errormsg, true) == FAIL) { + goto end; } // Skip colon and whitespace eap->cmd = skip_colon_white(eap->cmd, true); // Fail if command is a comment or if command doesn't exist if (*eap->cmd == NUL || *eap->cmd == '"') { - goto err; + goto end; } // Fail if command is invalid if (eap->cmdidx == CMD_SIZE) { @@ -1416,8 +1445,8 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // If the modifier was parsed OK the error must be in the following command char *cmdname = after_modifier ? after_modifier : cmdline; append_command(cmdname); - *errormsg = (char *)IObuff; - goto err; + *errormsg = IObuff; + goto end; } // Correctly set 'forceit' for commands @@ -1456,12 +1485,12 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // Fail if command doesn't support bang but is used with a bang if (!(eap->argt & EX_BANG) && eap->forceit) { *errormsg = _(e_nobang); - goto err; + goto end; } // Fail if command doesn't support a range but it is given a range if (!(eap->argt & EX_RANGE) && eap->addr_count > 0) { *errormsg = _(e_norange); - goto err; + goto end; } // Set default range for command if required if ((eap->argt & EX_DFLALL) && eap->addr_count == 0) { @@ -1471,7 +1500,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er // Parse register and count parse_register(eap); if (parse_count(eap, errormsg, false) == FAIL) { - goto err; + goto end; } // Remove leading whitespace and colon from next command @@ -1487,10 +1516,39 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er cmdinfo->magic.bar = true; } - return true; -err: - undo_cmdmod(&cmdinfo->cmdmod); - return false; + retval = true; +end: + if (!retval) { + undo_cmdmod(&cmdinfo->cmdmod); + } + ex_pressedreturn = save_ex_pressedreturn; + curwin->w_cursor = save_cursor; + restore_last_search_pattern(); + return retval; +} + +// Shift Ex-command arguments to the right. +static void shift_cmd_args(exarg_T *eap) +{ + assert(eap->args != NULL && eap->argc > 0); + + char **oldargs = eap->args; + size_t *oldarglens = eap->arglens; + + eap->argc--; + eap->args = eap->argc > 0 ? xcalloc(eap->argc, sizeof(char *)) : NULL; + eap->arglens = eap->argc > 0 ? xcalloc(eap->argc, sizeof(size_t)) : NULL; + + for (size_t i = 0; i < eap->argc; i++) { + eap->args[i] = oldargs[i + 1]; + eap->arglens[i] = oldarglens[i + 1]; + } + + // If there are no arguments, make eap->arg point to the end of string. + eap->arg = (eap->argc > 0 ? eap->args[0] : (oldargs[0] + oldarglens[0])); + + xfree(oldargs); + xfree(oldarglens); } static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview) @@ -1517,7 +1575,7 @@ static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview) || eap->cmdidx == CMD_bunload) { p = skiptowhite_esc(eap->arg); } else { - p = eap->arg + STRLEN(eap->arg); + p = eap->arg + strlen(eap->arg); while (p > eap->arg && ascii_iswhite(p[-1])) { p--; } @@ -1532,15 +1590,7 @@ static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview) eap->args[0] + eap->arglens[0], (eap->argt & EX_BUFUNL) != 0, false, false); eap->addr_count = 1; - // Shift each argument by 1 - for (size_t i = 0; i < eap->argc - 1; i++) { - eap->args[i] = eap->args[i + 1]; - } - // Make the last argument point to the NUL terminator at the end of string - eap->args[eap->argc - 1] = eap->args[eap->argc - 1] + eap->arglens[eap->argc - 1]; - eap->argc -= 1; - - eap->arg = eap->args[0]; + shift_cmd_args(eap); } if (eap->line2 < 0) { // failed return FAIL; @@ -1589,6 +1639,7 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) char *errormsg = NULL; int retv = 0; +#undef ERROR #define ERROR(msg) \ do { \ errormsg = msg; \ @@ -1639,6 +1690,11 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) (void)hasFolding(eap->line2, NULL, &eap->line2); } + // Use first argument as count when possible + if (parse_count(eap, &errormsg, true) == FAIL) { + goto end; + } + // Execute the command execute_cmd0(&retv, eap, &errormsg, preview); @@ -1660,7 +1716,7 @@ static void profile_cmd(const exarg_T *eap, cstack_T *cstack, LineGetter fgetlin && (!eap->skip || cstack->cs_idx == 0 || (cstack->cs_idx > 0 && (cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE)))) { - int skip = did_emsg || got_int || current_exception; + bool skip = did_emsg || got_int || did_throw; if (eap->cmdidx == CMD_catch) { skip = !skip && !(cstack->cs_idx >= 0 @@ -1738,6 +1794,7 @@ static bool skip_cmd(const exarg_T *eap) case CMD_filter: case CMD_help: case CMD_hide: + case CMD_horizontal: case CMD_ijump: case CMD_ilist: case CMD_isearch: @@ -1775,6 +1832,7 @@ static bool skip_cmd(const exarg_T *eap) case CMD_throw: case CMD_tilde: case CMD_topleft: + case CMD_trust: case CMD_unlet: case CMD_unlockvar: case CMD_verbose: @@ -1857,7 +1915,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter ea.skip = (did_emsg || got_int - || current_exception + || did_throw || (cstack->cs_idx >= 0 && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); @@ -1873,9 +1931,11 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter profile_cmd(&ea, cstack, fgetline, cookie); - // May go to debug mode. If this happens and the ">quit" debug command is - // used, throw an interrupt exception and skip the next command. - dbg_check_breakpoint(&ea); + if (!exiting) { + // May go to debug mode. If this happens and the ">quit" debug command is + // used, throw an interrupt exception and skip the next command. + dbg_check_breakpoint(&ea); + } if (!ea.skip && got_int) { ea.skip = true; (void)do_intthrow(cstack); @@ -1910,7 +1970,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter // If we got a line, but no command, then go to the line. // If we find a '|' or '\n' we set ea.nextcmd. if (*ea.cmd == NUL || *ea.cmd == '"' - || (ea.nextcmd = (char *)check_nextcmd((char_u *)ea.cmd)) != NULL) { + || (ea.nextcmd = check_nextcmd(ea.cmd)) != NULL) { // strange vi behaviour: // ":3" jumps to line 3 // ":3|..." prints line 3 @@ -1978,7 +2038,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter if (!(flags & DOCMD_VERBOSE)) { append_command(cmdname); } - errormsg = (char *)IObuff; + errormsg = IObuff; did_emsg_syntax = true; verify_command(cmdname); } @@ -2247,9 +2307,9 @@ doend: if (errormsg != NULL && *errormsg != NUL && !did_emsg) { if (flags & DOCMD_VERBOSE) { - if (errormsg != (char *)IObuff) { + if (errormsg != IObuff) { STRCPY(IObuff, errormsg); - errormsg = (char *)IObuff; + errormsg = IObuff; } append_command(*ea.cmdlinep); } @@ -2419,8 +2479,12 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool continue; } - // ":hide" and ":hide | cmd" are not modifiers case 'h': + if (checkforcmd(&eap->cmd, "horizontal", 3)) { + cmod->cmod_split |= WSP_HOR; + continue; + } + // ":hide" and ":hide | cmd" are not modifiers if (p != eap->cmd || !checkforcmd(&p, "hide", 3) || *p == NUL || ends_excmd(*p)) { break; @@ -2565,7 +2629,7 @@ static void apply_cmdmod(cmdmod_T *cmod) if ((cmod->cmod_flags & CMOD_NOAUTOCMD) && cmod->cmod_save_ei == NULL) { // Set 'eventignore' to "all". // First save the existing option value for restoring it later. - cmod->cmod_save_ei = (char *)vim_strsave(p_ei); + cmod->cmod_save_ei = xstrdup(p_ei); set_string_option_direct("ei", -1, "all", OPT_FREE, SID_NONE); } } @@ -2587,7 +2651,7 @@ void undo_cmdmod(cmdmod_T *cmod) if (cmod->cmod_save_ei != NULL) { // Restore 'eventignore' to the value before ":noautocmd". set_string_option_direct("ei", -1, cmod->cmod_save_ei, OPT_FREE, SID_NONE); - free_string_option((char_u *)cmod->cmod_save_ei); + free_string_option(cmod->cmod_save_ei); cmod->cmod_save_ei = NULL; } @@ -2779,7 +2843,7 @@ theend: /// @param pp start of command /// @param cmd name of command /// @param len required length -bool checkforcmd(char **pp, char *cmd, int len) +bool checkforcmd(char **pp, const char *cmd, int len) { int i; @@ -2788,7 +2852,7 @@ bool checkforcmd(char **pp, char *cmd, int len) break; } } - if (i >= len && !isalpha((*pp)[i])) { + if (i >= len && !ASCII_ISALPHA((*pp)[i])) { *pp = skipwhite(*pp + i); return true; } @@ -2800,24 +2864,24 @@ bool checkforcmd(char **pp, char *cmd, int len) /// invisible otherwise. static void append_command(char *cmd) { - size_t len = STRLEN(IObuff); + size_t len = strlen(IObuff); char *s = cmd; char *d; if (len > IOSIZE - 100) { // Not enough space, truncate and put in "...". - d = (char *)IObuff + IOSIZE - 100; - d -= utf_head_off(IObuff, (const char_u *)d); + d = IObuff + IOSIZE - 100; + d -= utf_head_off(IObuff, d); STRCPY(d, "..."); } STRCAT(IObuff, ": "); - d = (char *)IObuff + STRLEN(IObuff); - while (*s != NUL && (char_u *)d - IObuff + 5 < IOSIZE) { - if ((char_u)s[0] == 0xc2 && (char_u)s[1] == 0xa0) { + d = IObuff + strlen(IObuff); + while (*s != NUL && d - IObuff + 5 < IOSIZE) { + if ((uint8_t)s[0] == 0xc2 && (uint8_t)s[1] == 0xa0) { s += 2; STRCPY(d, "<a0>"); d += 4; - } else if ((char_u *)d - IObuff + utfc_ptr2len(s) + 1 >= IOSIZE) { + } else if (d - IObuff + utfc_ptr2len(s) + 1 >= IOSIZE) { break; } else { mb_copy_char((const char **)&s, &d); @@ -2826,6 +2890,33 @@ static void append_command(char *cmd) *d = NUL; } +/// Return true and set "*idx" if "p" points to a one letter command. +/// - The 'k' command can directly be followed by any character. +/// - The 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' +/// but :sre[wind] is another command, as are :scr[iptnames], +/// :scs[cope], :sim[alt], :sig[ns] and :sil[ent]. +static int one_letter_cmd(const char *p, cmdidx_T *idx) +{ + if (*p == 'k') { + *idx = CMD_k; + return true; + } + if (p[0] == 's' + && ((p[1] == 'c' + && (p[2] == NUL + || (p[2] != 's' && p[2] != 'r' + && (p[3] == NUL + || (p[3] != 'i' && p[4] != 'p'))))) + || p[1] == 'g' + || (p[1] == 'i' && p[2] != 'm' && p[2] != 'l' && p[2] != 'g') + || p[1] == 'I' + || (p[1] == 'r' && p[2] != 'e'))) { + *idx = CMD_substitute; + return true; + } + return false; +} + /// Find an Ex command by its name, either built-in or user. /// Start of the name can be found at eap->cmd. /// Sets eap->cmdidx and returns a pointer to char after the command name. @@ -2836,27 +2927,8 @@ char *find_ex_command(exarg_T *eap, int *full) FUNC_ATTR_NONNULL_ARG(1) { // Isolate the command and search for it in the command table. - // Exceptions: - // - the 'k' command can directly be followed by any character. - // - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' - // but :sre[wind] is another command, as are :scr[iptnames], - // :scs[cope], :sim[alt], :sig[ns] and :sil[ent]. - // - the "d" command can directly be followed by 'l' or 'p' flag. char *p = eap->cmd; - if (*p == 'k') { - eap->cmdidx = CMD_k; - p++; - } else if (p[0] == 's' - && ((p[1] == 'c' - && (p[2] == NUL - || (p[2] != 's' && p[2] != 'r' - && (p[3] == NUL - || (p[3] != 'i' && p[4] != 'p'))))) - || p[1] == 'g' - || (p[1] == 'i' && p[2] != 'm' && p[2] != 'l' && p[2] != 'g') - || p[1] == 'I' - || (p[1] == 'r' && p[2] != 'e'))) { - eap->cmdidx = CMD_substitute; + if (one_letter_cmd(p, &eap->cmdidx)) { p++; } else { while (ASCII_ISALPHA(*p)) { @@ -2870,10 +2942,11 @@ char *find_ex_command(exarg_T *eap, int *full) } // check for non-alpha command - if (p == eap->cmd && vim_strchr("@!=><&~#", *p) != NULL) { + if (p == eap->cmd && vim_strchr("@!=><&~#", (uint8_t)(*p)) != NULL) { p++; } int len = (int)(p - eap->cmd); + // The "d" command can directly be followed by 'l' or 'p' flag. if (*eap->cmd == 'd' && (p[-1] == 'l' || p[-1] == 'p')) { // Check for ":dl", ":dell", etc. to ":deletel": that's // :delete with the 'l' flag. Same for 'p'. @@ -2894,7 +2967,7 @@ char *find_ex_command(exarg_T *eap, int *full) } if (ASCII_ISLOWER(eap->cmd[0])) { - const int c1 = (char_u)eap->cmd[0]; + const int c1 = (uint8_t)eap->cmd[0]; const int c2 = len == 1 ? NUL : eap->cmd[1]; if (command_count != CMD_SIZE) { @@ -2917,7 +2990,7 @@ char *find_ex_command(exarg_T *eap, int *full) for (; (int)eap->cmdidx < CMD_SIZE; eap->cmdidx = (cmdidx_T)((int)eap->cmdidx + 1)) { - if (STRNCMP(cmdnames[(int)eap->cmdidx].cmd_name, eap->cmd, + if (strncmp(cmdnames[(int)eap->cmdidx].cmd_name, eap->cmd, (size_t)len) == 0) { if (full != NULL && cmdnames[(int)eap->cmdidx].cmd_name[len] == NUL) { @@ -3038,7 +3111,7 @@ int cmd_exists(const char *const name) } /// "fullcommand" function -void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_fullcommand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char *name = argvars[0].vval.v_string; @@ -3062,19 +3135,20 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - rettv->vval.v_string = (char *)vim_strsave(IS_USER_CMDIDX(ea.cmdidx) - ? (char_u *)get_user_command_name(ea.useridx, - ea.cmdidx) - : (char_u *)cmdnames[ea.cmdidx].cmd_name); + rettv->vval.v_string = xstrdup(IS_USER_CMDIDX(ea.cmdidx) + ? get_user_command_name(ea.useridx, ea.cmdidx) + : cmdnames[ea.cmdidx].cmd_name); } cmdidx_T excmd_get_cmdidx(const char *cmd, size_t len) { cmdidx_T idx; - for (idx = (cmdidx_T)0; (int)idx < (int)CMD_SIZE; idx = (cmdidx_T)((int)idx + 1)) { - if (strncmp(cmdnames[(int)idx].cmd_name, cmd, len) == 0) { - break; + if (!one_letter_cmd(cmd, &idx)) { + for (idx = (cmdidx_T)0; (int)idx < CMD_SIZE; idx = (cmdidx_T)((int)idx + 1)) { + if (strncmp(cmdnames[(int)idx].cmd_name, cmd, len) == 0) { + break; + } } } @@ -3098,7 +3172,7 @@ uint32_t excmd_get_argt(cmdidx_T idx) /// @return the "cmd" pointer advanced to beyond the range. char *skip_range(const char *cmd, int *ctx) { - while (vim_strchr(" \t0123456789.$%'/?-+,;\\", *cmd) != NULL) { + while (vim_strchr(" \t0123456789.$%'/?-+,;\\", (uint8_t)(*cmd)) != NULL) { if (*cmd == '\\') { if (cmd[1] == '?' || cmd[1] == '/' || cmd[1] == '&') { cmd++; @@ -3287,14 +3361,14 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int case '/': case '?': // '/' or '?' - search - c = (char_u)(*cmd++); + c = (uint8_t)(*cmd++); if (addr_type != ADDR_LINES) { addr_error(addr_type); cmd = NULL; goto error; } if (skip) { // skip "/pat/" - cmd = (char *)skip_regexp((char_u *)cmd, c, p_magic, NULL); + cmd = skip_regexp(cmd, c, magic_isset()); if (*cmd == c) { cmd++; } @@ -3323,7 +3397,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int } searchcmdlen = 0; flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG; - if (!do_search(NULL, c, c, (char_u *)cmd, 1L, flags, NULL)) { + if (!do_search(NULL, c, c, cmd, 1L, flags, NULL)) { curwin->w_cursor = pos; cmd = NULL; goto error; @@ -3360,7 +3434,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int pos.coladd = 0; if (searchit(curwin, curbuf, &pos, NULL, *cmd == '?' ? BACKWARD : FORWARD, - (char_u *)"", 1L, SEARCH_MSG, i, NULL) != FAIL) { + "", 1L, SEARCH_MSG, i, NULL) != FAIL) { lnum = pos.lnum; } else { cmd = NULL; @@ -3421,11 +3495,12 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int if (ascii_isdigit(*cmd)) { i = '+'; // "number" is same as "+number" } else { - i = (char_u)(*cmd++); + i = (uint8_t)(*cmd++); } - if (!ascii_isdigit(*cmd)) { // '+' is '+1', but '+0' is not '+1' + if (!ascii_isdigit(*cmd)) { // '+' is '+1' n = 1; } else { + // "number", "+number" or "-number" n = getdigits_int32(&cmd, false, MAXLNUM); if (n == MAXLNUM) { emsg(_(e_line_number_out_of_range)); @@ -3440,8 +3515,8 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int } else if (addr_type == ADDR_LOADED_BUFFERS || addr_type == ADDR_BUFFERS) { lnum = compute_buffer_local_count(addr_type, lnum, (i == '-') ? -1 * n : n); } else { - // Relative line addressing, need to adjust for folded lines - // now, but only do it after the first address. + // Relative line addressing: need to adjust for lines in a + // closed fold after the first address. if (addr_type == ADDR_LINES && (i == '-' || i == '+') && address_count >= 2) { (void)hasFolding(lnum, NULL, &lnum); @@ -3467,7 +3542,7 @@ error: /// Get flags from an Ex command argument. static void get_flags(exarg_T *eap) { - while (vim_strchr("lp#", *eap->arg) != NULL) { + while (vim_strchr("lp#", (uint8_t)(*eap->arg)) != NULL) { if (*eap->arg == 'l') { eap->flags |= EXFLAG_LIST; } else if (*eap->arg == 'p') { @@ -3570,6 +3645,9 @@ char *invalid_range(exarg_T *eap) assert(eap->line2 >= 0); // No error for value that is too big, will use the last entry. if (eap->line2 <= 0) { + if (eap->addr_count == 0) { + return _(e_no_errors); + } return _(e_invrange); } break; @@ -3631,8 +3709,8 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep) // Don't do it when ":vimgrep" is used for ":grep". if ((eap->cmdidx == CMD_make || eap->cmdidx == CMD_lmake || isgrep) && !grep_internal(eap->cmdidx)) { - const char *program = isgrep ? (*curbuf->b_p_gp == NUL ? (char *)p_gp : (char *)curbuf->b_p_gp) - : (*curbuf->b_p_mp == NUL ? (char *)p_mp : (char *)curbuf->b_p_mp); + const char *program = isgrep ? (*curbuf->b_p_gp == NUL ? p_gp : curbuf->b_p_gp) + : (*curbuf->b_p_mp == NUL ? p_mp : curbuf->b_p_mp); arg = skipwhite(arg); @@ -3640,13 +3718,13 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep) // Replace $* by given arguments if ((new_cmdline = strrep(program, "$*", arg)) == NULL) { // No $* in arg, build "<makeprg> <arg>" instead - new_cmdline = xmalloc(STRLEN(program) + STRLEN(arg) + 2); + new_cmdline = xmalloc(strlen(program) + strlen(arg) + 2); STRCPY(new_cmdline, program); STRCAT(new_cmdline, " "); STRCAT(new_cmdline, arg); } - msg_make((char_u *)arg); + msg_make(arg); // 'eap->cmd' is not set here, because it is not used at CMD_make xfree(*cmdlinep); @@ -3668,7 +3746,7 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) // Decide to expand wildcards *before* replacing '%', '#', etc. If // the file name contains a wildcard it should not cause expanding. // (it will be expanded anyway if there is a wildcard before replacing). - int has_wildcards = path_has_wildcard((char_u *)p); + int has_wildcards = path_has_wildcard(p); while (*p != NUL) { // Skip over `=expr`, wildcards in it are not expanded. if (p[0] == '`' && p[1] == '=') { @@ -3681,7 +3759,7 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) } // Quick check if this cannot be the start of a special string. // Also removes backslash before '%', '#' and '<'. - if (vim_strchr("%#<", *p) == NULL) { + if (vim_strchr("%#<", (uint8_t)(*p)) == NULL) { p++; continue; } @@ -3689,8 +3767,8 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) // Try to find a match at this position. size_t srclen; int escaped; - char *repl = (char *)eval_vars((char_u *)p, (char_u *)eap->arg, &srclen, &(eap->do_ecmd_lnum), - errormsgp, &escaped); + char *repl = eval_vars(p, eap->arg, &srclen, &(eap->do_ecmd_lnum), + errormsgp, &escaped, true); if (*errormsgp != NULL) { // error detected return FAIL; } @@ -3717,7 +3795,6 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) && eap->cmdidx != CMD_bang && eap->cmdidx != CMD_grep && eap->cmdidx != CMD_grepadd - && eap->cmdidx != CMD_hardcopy && eap->cmdidx != CMD_lgrep && eap->cmdidx != CMD_lgrepadd && eap->cmdidx != CMD_lmake @@ -3735,8 +3812,8 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) #endif for (l = repl; *l; l++) { - if (vim_strchr((char *)ESCAPE_CHARS, *l) != NULL) { - l = (char *)vim_strsave_escaped((char_u *)repl, ESCAPE_CHARS); + if (vim_strchr(ESCAPE_CHARS, (uint8_t)(*l)) != NULL) { + l = vim_strsave_escaped(repl, ESCAPE_CHARS); xfree(repl); repl = l; break; @@ -3751,7 +3828,7 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) && strpbrk(repl, "!") != NULL) { char *l; - l = (char *)vim_strsave_escaped((char_u *)repl, (char_u *)"!"); + l = vim_strsave_escaped(repl, "!"); xfree(repl); repl = l; } @@ -3772,14 +3849,14 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) // if there are still wildcards present. if (vim_strchr(eap->arg, '$') != NULL || vim_strchr(eap->arg, '~') != NULL) { - expand_env_esc((char_u *)eap->arg, NameBuff, MAXPATHL, true, true, NULL); + expand_env_esc(eap->arg, NameBuff, MAXPATHL, true, true, NULL); has_wildcards = path_has_wildcard(NameBuff); - p = (char *)NameBuff; + p = NameBuff; } else { p = NULL; } if (p != NULL) { - (void)repl_cmdline(eap, eap->arg, STRLEN(eap->arg), p, cmdlinep); + (void)repl_cmdline(eap, eap->arg, strlen(eap->arg), p, cmdlinep); } } @@ -3788,10 +3865,10 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) // done by ExpandOne() below. #ifdef UNIX if (!has_wildcards) { - backslash_halve((char_u *)eap->arg); + backslash_halve(eap->arg); } #else - backslash_halve((char_u *)eap->arg); + backslash_halve(eap->arg); #endif if (has_wildcards) { @@ -3803,11 +3880,11 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp) if (p_wic) { options += WILD_ICASE; } - p = (char *)ExpandOne(&xpc, (char_u *)eap->arg, NULL, options, WILD_EXPAND_FREE); + p = ExpandOne(&xpc, eap->arg, NULL, options, WILD_EXPAND_FREE); if (p == NULL) { return FAIL; } - (void)repl_cmdline(eap, eap->arg, STRLEN(eap->arg), p, cmdlinep); + (void)repl_cmdline(eap, eap->arg, strlen(eap->arg), p, cmdlinep); xfree(p); } } @@ -3825,10 +3902,10 @@ static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, ch // The new command line is build in new_cmdline[]. // First allocate it. // Careful: a "+cmd" argument may have been NUL terminated. - size_t len = STRLEN(repl); - size_t i = (size_t)(src - *cmdlinep) + STRLEN(src + srclen) + len + 3; + size_t len = strlen(repl); + size_t i = (size_t)(src - *cmdlinep) + strlen(src + srclen) + len + 3; if (eap->nextcmd != NULL) { - i += STRLEN(eap->nextcmd); // add space for next command + i += strlen(eap->nextcmd); // add space for next command } char *new_cmdline = xmalloc(i); size_t offset = (size_t)(src - *cmdlinep); @@ -3846,7 +3923,7 @@ static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, ch src = new_cmdline + i; // remember where to continue if (eap->nextcmd != NULL) { // append next command - i = STRLEN(new_cmdline) + 1; + i = strlen(new_cmdline) + 1; STRCPY(new_cmdline + i, eap->nextcmd); eap->nextcmd = new_cmdline + i; } @@ -3861,7 +3938,7 @@ static char *repl_cmdline(exarg_T *eap, char *src, size_t srclen, char *repl, ch } else { // Otherwise, argument gets shifted alongside the replaced text. // The amount of the shift is equal to the difference of the old and new string length. - eap->args[j] = new_cmdline + (eap->args[j] - *cmdlinep) + (len - srclen); + eap->args[j] = new_cmdline + ((eap->args[j] - *cmdlinep) + (ptrdiff_t)(len - srclen)); } } @@ -3913,7 +3990,7 @@ void separate_nextcmd(exarg_T *eap) STRMOVE(p - 1, p); // remove the '\' p--; } else { - eap->nextcmd = (char *)check_nextcmd((char_u *)p); + eap->nextcmd = check_nextcmd(p); *p = NUL; break; } @@ -3921,7 +3998,7 @@ void separate_nextcmd(exarg_T *eap) } if (!(eap->argt & EX_NOTRLCOM)) { // remove trailing spaces - del_trailing_spaces((char_u *)eap->arg); + del_trailing_spaces(eap->arg); } } @@ -3967,15 +4044,15 @@ char *skip_cmd_arg(char *p, int rembs) return p; } -int get_bad_opt(const char_u *p, exarg_T *eap) +int get_bad_opt(const char *p, exarg_T *eap) FUNC_ATTR_NONNULL_ALL { if (STRICMP(p, "keep") == 0) { eap->bad_char = BAD_KEEP; } else if (STRICMP(p, "drop") == 0) { eap->bad_char = BAD_DROP; - } else if (MB_BYTE2LEN(*p) == 1 && p[1] == NUL) { - eap->bad_char = *p; + } else if (MB_BYTE2LEN((uint8_t)(*p)) == 1 && p[1] == NUL) { + eap->bad_char = (uint8_t)(*p); } else { return FAIL; } @@ -3992,7 +4069,7 @@ static int getargopt(exarg_T *eap) int bad_char_idx; // ":edit ++[no]bin[ary] file" - if (STRNCMP(arg, "bin", 3) == 0 || STRNCMP(arg, "nobin", 5) == 0) { + if (strncmp(arg, "bin", 3) == 0 || strncmp(arg, "nobin", 5) == 0) { if (*arg == 'n') { arg += 2; eap->force_bin = FORCE_NOBIN; @@ -4007,26 +4084,33 @@ static int getargopt(exarg_T *eap) } // ":read ++edit file" - if (STRNCMP(arg, "edit", 4) == 0) { + if (strncmp(arg, "edit", 4) == 0) { eap->read_edit = true; eap->arg = skipwhite(arg + 4); return OK; } - if (STRNCMP(arg, "ff", 2) == 0) { + // ":write ++p foo/bar/file + if (strncmp(arg, "p", 1) == 0) { + eap->mkdir_p = true; + eap->arg = skipwhite(arg + 1); + return OK; + } + + if (strncmp(arg, "ff", 2) == 0) { arg += 2; pp = &eap->force_ff; - } else if (STRNCMP(arg, "fileformat", 10) == 0) { + } else if (strncmp(arg, "fileformat", 10) == 0) { arg += 10; pp = &eap->force_ff; - } else if (STRNCMP(arg, "enc", 3) == 0) { - if (STRNCMP(arg, "encoding", 8) == 0) { + } else if (strncmp(arg, "enc", 3) == 0) { + if (strncmp(arg, "encoding", 8) == 0) { arg += 8; } else { arg += 3; } pp = &eap->force_enc; - } else if (STRNCMP(arg, "bad", 3) == 0) { + } else if (strncmp(arg, "bad", 3) == 0) { arg += 3; pp = &bad_char_idx; } @@ -4042,10 +4126,10 @@ static int getargopt(exarg_T *eap) *arg = NUL; if (pp == &eap->force_ff) { - if (check_ff_value((char_u *)eap->cmd + eap->force_ff) == FAIL) { + if (check_ff_value(eap->cmd + eap->force_ff) == FAIL) { return FAIL; } - eap->force_ff = (char_u)eap->cmd[eap->force_ff]; + eap->force_ff = (uint8_t)eap->cmd[eap->force_ff]; } else if (pp == &eap->force_enc) { // Make 'fileencoding' lower case. for (char *p = eap->cmd + eap->force_enc; *p != NUL; p++) { @@ -4054,7 +4138,7 @@ static int getargopt(exarg_T *eap) } else { // Check ++bad= argument. Must be a single-byte character, "keep" or // "drop". - if (get_bad_opt((char_u *)eap->cmd + bad_char_idx, eap) == FAIL) { + if (get_bad_opt(eap->cmd + bad_char_idx, eap) == FAIL) { return FAIL; } } @@ -4088,9 +4172,9 @@ static int get_tabpage_arg(exarg_T *eap) tab_number = (int)getdigits(&p, false, tab_number); if (relative == 0) { - if (STRCMP(p, "$") == 0) { + if (strcmp(p, "$") == 0) { tab_number = LAST_TAB_NR; - } else if (STRCMP(p, "#") == 0) { + } else if (strcmp(p, "#") == 0) { if (valid_tabpage(lastused_tabpage)) { tab_number = tabpage_index(lastused_tabpage); } else { @@ -4157,13 +4241,12 @@ theend: static void ex_autocmd(exarg_T *eap) { - // Disallow autocommands from .exrc and .vimrc in current - // directory for security reasons. + // Disallow autocommands in secure mode. if (secure) { secure = 2; eap->errmsg = _(e_curdir); } else if (eap->cmdidx == CMD_autocmd) { - do_autocmd(eap->arg, eap->forceit); + do_autocmd(eap, eap->arg, eap->forceit); } else { do_augroup(eap->arg, eap->forceit); } @@ -4276,7 +4359,7 @@ int ends_excmd(int c) FUNC_ATTR_CONST /// @return the next command, after the first '|' or '\n' or, /// NULL if not found. -char_u *find_nextcmd(const char_u *p) +char *find_nextcmd(const char *p) { while (*p != '|' && *p != '\n') { if (*p == NUL) { @@ -4284,21 +4367,20 @@ char_u *find_nextcmd(const char_u *p) } p++; } - return (char_u *)p + 1; + return (char *)p + 1; } /// Check if *p is a separator between Ex commands, skipping over white space. /// /// @return NULL if it isn't, the following character if it is. -char_u *check_nextcmd(char_u *p) +char *check_nextcmd(char *p) { - char *s = skipwhite((char *)p); + char *s = skipwhite(p); if (*s == '|' || *s == '\n') { - return (char_u *)(s + 1); - } else { - return NULL; + return s + 1; } + return NULL; } /// - if there are more files to edit @@ -4321,14 +4403,14 @@ static int check_more(int message, bool forceit) vim_snprintf((char *)buff, DIALOG_MSG_SIZE, NGETTEXT("%d more file to edit. Quit anyway?", - "%d more files to edit. Quit anyway?", (unsigned long)n), n); - if (vim_dialog_yesno(VIM_QUESTION, NULL, (char_u *)buff, 1) == VIM_YES) { + "%d more files to edit. Quit anyway?", n), n); + if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 1) == VIM_YES) { return OK; } return FAIL; } semsg(NGETTEXT("E173: %" PRId64 " more file to edit", - "E173: %" PRId64 " more files to edit", (unsigned long)n), (int64_t)n); + "E173: %" PRId64 " more files to edit", n), (int64_t)n); quitmore = 2; // next try to quit is allowed } return FAIL; @@ -4361,7 +4443,7 @@ static void ex_colorscheme(exarg_T *eap) } else { msg("default"); } - } else if (load_colors((char_u *)eap->arg) == FAIL) { + } else if (load_colors(eap->arg) == FAIL) { semsg(_("E185: Cannot find color scheme '%s'"), eap->arg); } } @@ -4556,7 +4638,7 @@ static void ex_pclose(exarg_T *eap) void ex_win_close(int forceit, win_T *win, tabpage_T *tp) { // Never close the autocommand window. - if (win == aucmd_win) { + if (is_aucmd_win(win)) { emsg(_(e_autocmd_close)); return; } @@ -4593,23 +4675,29 @@ static void ex_tabclose(exarg_T *eap) { if (cmdwin_type != 0) { cmdwin_result = K_IGNORE; - } else if (first_tabpage->tp_next == NULL) { + return; + } + + if (first_tabpage->tp_next == NULL) { emsg(_("E784: Cannot close last tab page")); - } else { - int tab_number = get_tabpage_arg(eap); - if (eap->errmsg == NULL) { - tabpage_T *tp = find_tabpage(tab_number); - if (tp == NULL) { - beep_flush(); - return; - } - if (tp != curtab) { - tabpage_close_other(tp, eap->forceit); - return; - } else if (!text_locked() && !curbuf_locked()) { - tabpage_close(eap->forceit); - } - } + return; + } + + int tab_number = get_tabpage_arg(eap); + if (eap->errmsg != NULL) { + return; + } + + tabpage_T *tp = find_tabpage(tab_number); + if (tp == NULL) { + beep_flush(); + return; + } + if (tp != curtab) { + tabpage_close_other(tp, eap->forceit); + return; + } else if (!text_locked() && !curbuf_locked()) { + tabpage_close(eap->forceit); } } @@ -4618,32 +4706,38 @@ static void ex_tabonly(exarg_T *eap) { if (cmdwin_type != 0) { cmdwin_result = K_IGNORE; - } else if (first_tabpage->tp_next == NULL) { + return; + } + + if (first_tabpage->tp_next == NULL) { msg(_("Already only one tab page")); - } else { - int tab_number = get_tabpage_arg(eap); - if (eap->errmsg == NULL) { - goto_tabpage(tab_number); - // Repeat this up to a 1000 times, because autocommands may - // mess up the lists. - for (int done = 0; done < 1000; done++) { - FOR_ALL_TABS(tp) { - if (tp->tp_topframe != topframe) { - tabpage_close_other(tp, eap->forceit); - // if we failed to close it quit - if (valid_tabpage(tp)) { - done = 1000; - } - // start over, "tp" is now invalid - break; - } - } - assert(first_tabpage); - if (first_tabpage->tp_next == NULL) { - break; + return; + } + + int tab_number = get_tabpage_arg(eap); + if (eap->errmsg != NULL) { + return; + } + + goto_tabpage(tab_number); + // Repeat this up to a 1000 times, because autocommands may + // mess up the lists. + for (int done = 0; done < 1000; done++) { + FOR_ALL_TABS(tp) { + if (tp->tp_topframe != topframe) { + tabpage_close_other(tp, eap->forceit); + // if we failed to close it quit + if (valid_tabpage(tp)) { + done = 1000; } + // start over, "tp" is now invalid + break; } } + assert(first_tabpage); + if (first_tabpage->tp_next == NULL) { + break; + } } } @@ -4670,7 +4764,6 @@ void tabpage_close(int forceit) void tabpage_close_other(tabpage_T *tp, int forceit) { int done = 0; - int h = tabline_height(); char prev_idx[NUMBUFLEN]; // Limit to 1000 windows, autocommands may add a window while we close @@ -4686,11 +4779,6 @@ void tabpage_close_other(tabpage_T *tp, int forceit) break; } } - - redraw_tabline = true; - if (h != tabline_height()) { - win_new_screen_rows(); - } } /// ":only". @@ -4703,9 +4791,8 @@ static void ex_only(exarg_T *eap) for (wp = firstwin; --wnr > 0;) { if (wp->w_next == NULL) { break; - } else { - wp = wp->w_next; } + wp = wp->w_next; } } else { wp = curwin; @@ -4719,25 +4806,27 @@ static void ex_only(exarg_T *eap) static void ex_hide(exarg_T *eap) { // ":hide" or ":hide | cmd": hide current window - if (!eap->skip) { - if (eap->addr_count == 0) { - win_close(curwin, false, eap->forceit); // don't free buffer - } else { - int winnr = 0; - win_T *win = NULL; + if (eap->skip) { + return; + } - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - winnr++; - if (winnr == eap->line2) { - win = wp; - break; - } - } - if (win == NULL) { - win = lastwin; + if (eap->addr_count == 0) { + win_close(curwin, false, eap->forceit); // don't free buffer + } else { + int winnr = 0; + win_T *win = NULL; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + winnr++; + if (winnr == eap->line2) { + win = wp; + break; } - win_close(win, false, eap->forceit); } + if (win == NULL) { + win = lastwin; + } + win_close(win, false, eap->forceit); } } @@ -4812,7 +4901,6 @@ static void ex_print(exarg_T *eap) if (++eap->line1 > eap->line2) { break; } - ui_flush(); // show one line at a time } setpcmark(); // put cursor at last line @@ -4888,8 +4976,8 @@ void ex_splitview(exarg_T *eap) } if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) { - fname = (char *)find_file_in_path((char_u *)eap->arg, STRLEN(eap->arg), - FNAME_MESS, true, (char_u *)curbuf->b_ffname); + fname = find_file_in_path(eap->arg, strlen(eap->arg), + FNAME_MESS, true, curbuf->b_ffname); if (fname == NULL) { goto theend; } @@ -4899,7 +4987,7 @@ void ex_splitview(exarg_T *eap) // Either open new tab page or split the window. if (use_tab) { if (win_new_tabpage(cmdmod.cmod_tab != 0 ? cmdmod.cmod_tab : eap->addr_count == 0 - ? 0 : (int)eap->line2 + 1, (char_u *)eap->arg) != FAIL) { + ? 0 : (int)eap->line2 + 1, eap->arg) != FAIL) { do_exedit(eap, old_curwin); apply_autocmds(EVENT_TABNEWENTERED, NULL, NULL, false, curbuf); @@ -5012,9 +5100,8 @@ static void ex_tabs(exarg_T *eap) } msg_putchar('\n'); - vim_snprintf((char *)IObuff, IOSIZE, _("Tab page %d"), tabcount++); + vim_snprintf(IObuff, IOSIZE, _("Tab page %d"), tabcount++); msg_outtrans_attr(IObuff, HL_ATTR(HLF_T)); - ui_flush(); // output one line at a time os_breakcheck(); FOR_ALL_WINDOWS_IN_TAB(wp, tp) { @@ -5028,12 +5115,11 @@ static void ex_tabs(exarg_T *eap) msg_putchar(bufIsChanged(wp->w_buffer) ? '+' : ' '); msg_putchar(' '); if (buf_spname(wp->w_buffer) != NULL) { - STRLCPY(IObuff, buf_spname(wp->w_buffer), IOSIZE); + xstrlcpy(IObuff, buf_spname(wp->w_buffer), IOSIZE); } else { - home_replace(wp->w_buffer, wp->w_buffer->b_fname, (char *)IObuff, IOSIZE, true); + home_replace(wp->w_buffer, wp->w_buffer->b_fname, IObuff, IOSIZE, true); } - msg_outtrans((char *)IObuff); - ui_flush(); // output one line at a time + msg_outtrans(IObuff); os_breakcheck(); } } @@ -5044,7 +5130,7 @@ static void ex_tabs(exarg_T *eap) static void ex_mode(exarg_T *eap) { if (*eap->arg == NUL) { - must_redraw = CLEAR; + must_redraw = UPD_CLEAR; ex_redraw(eap); } else { emsg(_(e_screenmode)); @@ -5083,23 +5169,25 @@ static void ex_resize(exarg_T *eap) /// ":find [+command] <file>" command. static void ex_find(exarg_T *eap) { - char *fname = (char *)find_file_in_path((char_u *)eap->arg, STRLEN(eap->arg), - FNAME_MESS, true, (char_u *)curbuf->b_ffname); + char *fname = find_file_in_path(eap->arg, strlen(eap->arg), + FNAME_MESS, true, curbuf->b_ffname); if (eap->addr_count > 0) { // Repeat finding the file "count" times. This matters when it // appears several times in the path. linenr_T count = eap->line2; while (fname != NULL && --count > 0) { xfree(fname); - fname = (char *)find_file_in_path(NULL, 0, FNAME_MESS, false, (char_u *)curbuf->b_ffname); + fname = find_file_in_path(NULL, 0, FNAME_MESS, false, curbuf->b_ffname); } } - if (fname != NULL) { - eap->arg = fname; - do_exedit(eap, NULL); - xfree(fname); + if (fname == NULL) { + return; } + + eap->arg = fname; + do_exedit(eap, NULL); + xfree(fname); } /// ":edit", ":badd", ":balt", ":visual". @@ -5136,7 +5224,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) no_wait_return = 0; need_wait_return = false; msg_scroll = 0; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); pending_exmode_active = true; normal_enter(false, true); @@ -5161,9 +5249,9 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) old_curwin == NULL ? curwin : NULL); } else if ((eap->cmdidx != CMD_split && eap->cmdidx != CMD_vsplit) || *eap->arg != NUL) { - // Can't edit another file when "curbuf->b_ro_lockec" is set. Only ":edit" - // can bring us here, others are stopped earlier. - if (*eap->arg != NUL && curbuf_locked()) { + // Can't edit another file when "textlock" or "curbuf->b_ro_locked" is set. + // Only ":edit" or ":script" can bring us here, others are stopped earlier. + if (*eap->arg != NUL && text_or_buf_locked()) { return; } n = readonlymode; @@ -5251,7 +5339,7 @@ static void ex_swapname(exarg_T *eap) if (curbuf->b_ml.ml_mfp == NULL || curbuf->b_ml.ml_mfp->mf_fname == NULL) { msg(_("No swap file")); } else { - msg((char *)curbuf->b_ml.ml_mfp->mf_fname); + msg(curbuf->b_ml.ml_mfp->mf_fname); } } @@ -5298,7 +5386,7 @@ static void ex_syncbind(exarg_T *eap) scrolldown(-y, true); } curwin->w_scbind_pos = topline; - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); cursor_correct(); curwin->w_redr_status = true; } @@ -5324,50 +5412,51 @@ static void ex_read(exarg_T *eap) if (eap->usefilter) { // :r!cmd do_bang(1, eap, false, false, true); - } else { - if (u_save(eap->line2, (linenr_T)(eap->line2 + 1)) == FAIL) { + return; + } + + if (u_save(eap->line2, (linenr_T)(eap->line2 + 1)) == FAIL) { + return; + } + + int i; + if (*eap->arg == NUL) { + if (check_fname() == FAIL) { // check for no file name return; } - int i; - - if (*eap->arg == NUL) { - if (check_fname() == FAIL) { // check for no file name - return; - } - i = readfile(curbuf->b_ffname, curbuf->b_fname, - eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false); - } else { - if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL) { - (void)setaltfname(eap->arg, eap->arg, (linenr_T)1); - } - i = readfile(eap->arg, NULL, - eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false); + i = readfile(curbuf->b_ffname, curbuf->b_fname, + eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false); + } else { + if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL) { + (void)setaltfname(eap->arg, eap->arg, (linenr_T)1); + } + i = readfile(eap->arg, NULL, + eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false); + } + if (i != OK) { + if (!aborting()) { + semsg(_(e_notopen), eap->arg); } - if (i != OK) { - if (!aborting()) { - semsg(_(e_notopen), eap->arg); + } else { + if (empty && exmode_active) { + // Delete the empty line that remains. Historically ex does + // this but vi doesn't. + linenr_T lnum; + if (eap->line2 == 0) { + lnum = curbuf->b_ml.ml_line_count; + } else { + lnum = 1; } - } else { - if (empty && exmode_active) { - // Delete the empty line that remains. Historically ex does - // this but vi doesn't. - linenr_T lnum; - if (eap->line2 == 0) { - lnum = curbuf->b_ml.ml_line_count; - } else { - lnum = 1; - } - if (*ml_get(lnum) == NUL && u_savedel(lnum, 1L) == OK) { - ml_delete(lnum, false); - if (curwin->w_cursor.lnum > 1 - && curwin->w_cursor.lnum >= lnum) { - curwin->w_cursor.lnum--; - } - deleted_lines_mark(lnum, 1L); + if (*ml_get(lnum) == NUL && u_savedel(lnum, 1L) == OK) { + ml_delete(lnum, false); + if (curwin->w_cursor.lnum > 1 + && curwin->w_cursor.lnum >= lnum) { + curwin->w_cursor.lnum--; } + deleted_lines_mark(lnum, 1L); } - redraw_curbuf_later(VALID); } + redraw_curbuf_later(UPD_VALID); } } @@ -5419,7 +5508,7 @@ static void post_chdir(CdScope scope, bool trigger_dirchanged) } char cwd[MAXPATHL]; - if (os_dirname((char_u *)cwd, MAXPATHL) != OK) { + if (os_dirname(cwd, MAXPATHL) != OK) { return; } switch (scope) { @@ -5457,7 +5546,7 @@ bool changedir_func(char *new_dir, CdScope scope) char *pdir = NULL; // ":cd -": Change to previous directory - if (STRCMP(new_dir, "-") == 0) { + if (strcmp(new_dir, "-") == 0) { pdir = get_prevdir(scope); if (pdir == NULL) { emsg(_("E186: No previous directory")); @@ -5467,7 +5556,7 @@ bool changedir_func(char *new_dir, CdScope scope) } if (os_dirname(NameBuff, MAXPATHL) == OK) { - pdir = (char *)vim_strsave(NameBuff); + pdir = xstrdup(NameBuff); } else { pdir = NULL; } @@ -5480,14 +5569,14 @@ bool changedir_func(char *new_dir, CdScope scope) if (*new_dir == NUL && p_cdh) { #endif // Use NameBuff for home directory name. - expand_env((char_u *)"$HOME", NameBuff, MAXPATHL); - new_dir = (char *)NameBuff; + expand_env("$HOME", NameBuff, MAXPATHL); + new_dir = NameBuff; } bool dir_differs = pdir == NULL || pathcmp(pdir, new_dir, -1) != 0; if (dir_differs) { do_autocmd_dirchanged(new_dir, scope, kCdCauseManual, true); - if (vim_chdir((char_u *)new_dir) != 0) { + if (vim_chdir(new_dir) != 0) { emsg(_(e_failed)); xfree(pdir); return false; @@ -5521,27 +5610,27 @@ void ex_cd(exarg_T *eap) // for non-UNIX ":cd" means: print current directory unless 'cdhome' is set if (*new_dir == NUL && !p_cdh) { ex_pwd(NULL); - } else + return; + } #endif - { - CdScope scope = kCdScopeGlobal; - switch (eap->cmdidx) { - case CMD_tcd: - case CMD_tchdir: - scope = kCdScopeTabpage; - break; - case CMD_lcd: - case CMD_lchdir: - scope = kCdScopeWindow; - break; - default: - break; - } - if (changedir_func(new_dir, scope)) { - // Echo the new current directory if the command was typed. - if (KeyTyped || p_verbose >= 5) { - ex_pwd(eap); - } + + CdScope scope = kCdScopeGlobal; + switch (eap->cmdidx) { + case CMD_tcd: + case CMD_tchdir: + scope = kCdScopeTabpage; + break; + case CMD_lcd: + case CMD_lchdir: + scope = kCdScopeWindow; + break; + default: + break; + } + if (changedir_func(new_dir, scope)) { + // Echo the new current directory if the command was typed. + if (KeyTyped || p_verbose >= 5) { + ex_pwd(eap); } } } @@ -5562,9 +5651,9 @@ static void ex_pwd(exarg_T *eap) } else if (curtab->tp_localdir != NULL) { context = "tabpage"; } - smsg("[%s] %s", context, (char *)NameBuff); + smsg("[%s] %s", context, NameBuff); } else { - msg((char *)NameBuff); + msg(NameBuff); } } else { emsg(_("E187: Unknown")); @@ -5599,15 +5688,11 @@ static void ex_sleep(exarg_T *eap) do_sleep(len); } -/// Sleep for "msec" milliseconds, but keep checking for a CTRL-C every second. +/// Sleep for "msec" milliseconds, but return early on CTRL-C. void do_sleep(long msec) { ui_flush(); // flush before waiting - for (long left = msec; !got_int && left > 0; left -= 1000L) { - int next = left > 1000l ? 1000 : (int)left; - LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, (int)next, got_int); - os_breakcheck(); - } + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, msec, got_int); // If CTRL-C was typed to interrupt the sleep, drop the CTRL-C from the // input buffer, otherwise a following call to input() fails. @@ -5653,7 +5738,7 @@ static void ex_wincmd(exarg_T *eap) p = eap->arg + 1; } - eap->nextcmd = (char *)check_nextcmd((char_u *)p); + eap->nextcmd = check_nextcmd(p); p = skipwhite(p); if (*p != NUL && *p != '"' && eap->nextcmd == NULL) { emsg(_(e_invarg)); @@ -5769,21 +5854,21 @@ void ex_may_print(exarg_T *eap) /// ":smagic" and ":snomagic". static void ex_submagic(exarg_T *eap) { - int magic_save = p_magic; + const optmagic_T saved = magic_overruled; - p_magic = (eap->cmdidx == CMD_smagic); + magic_overruled = eap->cmdidx == CMD_smagic ? OPTION_MAGIC_ON : OPTION_MAGIC_OFF; ex_substitute(eap); - p_magic = magic_save; + magic_overruled = saved; } /// ":smagic" and ":snomagic" preview callback. static int ex_submagic_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr) { - int magic_save = p_magic; + const optmagic_T saved = magic_overruled; - p_magic = (eap->cmdidx == CMD_smagic); + magic_overruled = eap->cmdidx == CMD_smagic ? OPTION_MAGIC_ON : OPTION_MAGIC_OFF; int retv = ex_substitute_preview(eap, cmdpreview_ns, cmdpreview_bufnr); - p_magic = magic_save; + magic_overruled = saved; return retv; } @@ -5824,20 +5909,21 @@ static void ex_at(exarg_T *eap) // Put the register in the typeahead buffer with the "silent" flag. if (do_execreg(c, true, vim_strchr(p_cpo, CPO_EXECBUF) != NULL, true) == FAIL) { beep_flush(); - } else { - bool save_efr = exec_from_reg; + return; + } - exec_from_reg = true; + const bool save_efr = exec_from_reg; - // Execute from the typeahead buffer. - // Continue until the stuff buffer is empty and all added characters - // have been consumed. - while (!stuff_empty() || typebuf.tb_len > prev_len) { - (void)do_cmdline(NULL, getexline, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE); - } + exec_from_reg = true; - exec_from_reg = save_efr; + // Execute from the typeahead buffer. + // Continue until the stuff buffer is empty and all added characters + // have been consumed. + while (!stuff_empty() || typebuf.tb_len > prev_len) { + (void)do_cmdline(NULL, getexline, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE); } + + exec_from_reg = save_efr; } /// ":!". @@ -5851,7 +5937,7 @@ static void ex_undo(exarg_T *eap) { if (eap->addr_count != 1) { if (eap->forceit) { - u_undo_and_forget(1); // :undo! + u_undo_and_forget(1, true); // :undo! } else { u_undo(1); // :undo } @@ -5878,7 +5964,7 @@ static void ex_undo(exarg_T *eap) emsg(_(e_undobang_cannot_redo_or_move_branch)); return; } - u_undo_and_forget(count); + u_undo_and_forget(count, true); } else { // :undo 123 undo_time(step, false, false, true); } @@ -5886,18 +5972,18 @@ static void ex_undo(exarg_T *eap) static void ex_wundo(exarg_T *eap) { - char hash[UNDO_HASH_SIZE]; + uint8_t hash[UNDO_HASH_SIZE]; - u_compute_hash(curbuf, (char_u *)hash); - u_write_undo(eap->arg, eap->forceit, curbuf, (char_u *)hash); + u_compute_hash(curbuf, hash); + u_write_undo(eap->arg, eap->forceit, curbuf, hash); } static void ex_rundo(exarg_T *eap) { - char hash[UNDO_HASH_SIZE]; + uint8_t hash[UNDO_HASH_SIZE]; - u_compute_hash(curbuf, (char_u *)hash); - u_read_undo(eap->arg, (char_u *)hash, NULL); + u_compute_hash(curbuf, hash); + u_read_undo(eap->arg, hash, NULL); } /// ":redo". @@ -5916,7 +6002,7 @@ static void ex_later(exarg_T *eap) if (*p == NUL) { count = 1; - } else if (isdigit(*p)) { + } else if (isdigit((uint8_t)(*p))) { count = getdigits_long(&p, false, 0); switch (*p) { case 's': @@ -5967,14 +6053,14 @@ static void ex_redir(exarg_T *eap) return; } - redir_fd = open_exfile((char_u *)fname, eap->forceit, mode); + redir_fd = open_exfile(fname, eap->forceit, mode); xfree(fname); } else if (*arg == '@') { // redirect to a register a-z (resp. A-Z for appending) close_redir(); arg++; if (valid_yank_reg(*arg, true) && *arg != '_') { - redir_reg = (char_u)(*arg++); + redir_reg = (uint8_t)(*arg++); if (*arg == '>' && arg[1] == '>') { // append arg += 2; } else { @@ -5985,7 +6071,7 @@ static void ex_redir(exarg_T *eap) // Make register empty when not using @A-@Z and the // command is valid. if (*arg == NUL && !isupper(redir_reg)) { - write_reg_contents(redir_reg, (char_u *)"", 0, false); + write_reg_contents(redir_reg, "", 0, false); } } } @@ -6037,11 +6123,12 @@ static void ex_redraw(exarg_T *eap) validate_cursor(); update_topline(curwin); if (eap->forceit) { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); redraw_cmdline = true; + } else if (VIsual_active) { + redraw_curbuf_later(UPD_INVERTED); } - update_screen(eap->forceit ? NOT_VALID - : VIsual_active ? INVERTED : 0); + update_screen(); if (need_maketitle) { maketitle(); } @@ -6067,14 +6154,22 @@ static void ex_redrawstatus(exarg_T *eap) int r = RedrawingDisabled; int p = p_lz; - RedrawingDisabled = 0; - p_lz = false; if (eap->forceit) { status_redraw_all(); } else { status_redraw_curbuf(); } - update_screen(VIsual_active ? INVERTED : 0); + + RedrawingDisabled = 0; + p_lz = false; + if (State & MODE_CMDLINE) { + redraw_statuslines(); + } else { + if (VIsual_active) { + redraw_curbuf_later(UPD_INVERTED); + } + update_screen(); + } RedrawingDisabled = r; p_lz = p; ui_flush(); @@ -6131,7 +6226,7 @@ int vim_mkdir_emsg(const char *const name, const int prot) /// @param mode "w" for create new file or "a" for append /// /// @return file descriptor, or NULL on failure. -FILE *open_exfile(char_u *fname, int forceit, char *mode) +FILE *open_exfile(char *fname, int forceit, char *mode) { #ifdef UNIX // with Unix it is possible to open a directory @@ -6146,7 +6241,7 @@ FILE *open_exfile(char_u *fname, int forceit, char *mode) } FILE *fd; - if ((fd = os_fopen((char *)fname, mode)) == NULL) { + if ((fd = os_fopen(fname, mode)) == NULL) { semsg(_("E190: Cannot open \"%s\" for writing"), fname); } @@ -6158,17 +6253,21 @@ static void ex_mark(exarg_T *eap) { if (*eap->arg == NUL) { // No argument? emsg(_(e_argreq)); - } else if (eap->arg[1] != NUL) { // more than one character? + return; + } + + if (eap->arg[1] != NUL) { // more than one character? semsg(_(e_trailing_arg), eap->arg); - } else { - pos_T pos = curwin->w_cursor; // save curwin->w_cursor - curwin->w_cursor.lnum = eap->line2; - beginline(BL_WHITE | BL_FIX); - if (setmark(*eap->arg) == FAIL) { // set mark - emsg(_("E191: Argument must be a letter or forward/backward quote")); - } - curwin->w_cursor = pos; // restore curwin->w_cursor + return; + } + + pos_T pos = curwin->w_cursor; // save curwin->w_cursor + curwin->w_cursor.lnum = eap->line2; + beginline(BL_WHITE | BL_FIX); + if (setmark(*eap->arg) == FAIL) { // set mark + emsg(_("E191: Argument must be a letter or forward/backward quote")); } + curwin->w_cursor = pos; // restore curwin->w_cursor } /// Update w_topline, w_leftcol and the cursor position. @@ -6270,7 +6369,7 @@ static void ex_normal(exarg_T *eap) } } if (len > 0) { - arg = xmalloc(STRLEN(eap->arg) + (size_t)len + 1); + arg = xmalloc(strlen(eap->arg) + (size_t)len + 1); len = 0; for (p = eap->arg; *p != NUL; p++) { arg[len++] = *p; @@ -6299,7 +6398,7 @@ static void ex_normal(exarg_T *eap) check_cursor_moved(curwin); } - exec_normal_cmd((char_u *)(arg != NULL ? arg : eap->arg), + exec_normal_cmd((arg != NULL ? arg : eap->arg), eap->forceit ? REMAP_NONE : REMAP_YES, false); } while (eap->addr_count > 0 && eap->line1 <= eap->line2 && !got_int); } @@ -6363,10 +6462,10 @@ static void ex_stopinsert(exarg_T *eap) /// Execute normal mode command "cmd". /// "remap" can be REMAP_NONE or REMAP_YES. -void exec_normal_cmd(char_u *cmd, int remap, bool silent) +void exec_normal_cmd(char *cmd, int remap, bool silent) { // Stuff the argument into the typeahead buffer. - ins_typebuf((char *)cmd, remap, 0, true, silent); + ins_typebuf(cmd, remap, 0, true, silent); exec_normal(false); } @@ -6435,7 +6534,7 @@ static void ex_findpat(exarg_T *eap) if (*eap->arg == '/') { // Match regexp, not just whole words whole = false; eap->arg++; - char *p = (char *)skip_regexp((char_u *)eap->arg, '/', p_magic, NULL); + char *p = skip_regexp(eap->arg, '/', magic_isset()); if (*p) { *p++ = NUL; p = skipwhite(p); @@ -6444,12 +6543,12 @@ static void ex_findpat(exarg_T *eap) if (!ends_excmd(*p)) { eap->errmsg = ex_errmsg(e_trailing_arg, p); } else { - eap->nextcmd = (char *)check_nextcmd((char_u *)p); + eap->nextcmd = check_nextcmd(p); } } } if (!eap->skip) { - find_pattern_in_path((char_u *)eap->arg, 0, STRLEN(eap->arg), whole, !eap->forceit, + find_pattern_in_path(eap->arg, 0, strlen(eap->arg), whole, !eap->forceit, *eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY, n, action, eap->line1, eap->line2); } @@ -6477,7 +6576,7 @@ static void ex_pedit(exarg_T *eap) if (curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were validate_cursor(); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); win_enter(curwin_save, true); } g_do_tagpreview = 0; @@ -6500,7 +6599,7 @@ static void ex_tag(exarg_T *eap) ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name); } -static void ex_tag_cmd(exarg_T *eap, char *name) +static void ex_tag_cmd(exarg_T *eap, const char *name) { int cmd; @@ -6529,10 +6628,6 @@ static void ex_tag_cmd(exarg_T *eap, char *name) cmd = DT_LAST; // ":tlast" break; default: // ":tag" - if (p_cst && *eap->arg != NUL) { - ex_cstag(eap); - return; - } cmd = DT_TAG; break; } @@ -6541,7 +6636,7 @@ static void ex_tag_cmd(exarg_T *eap, char *name) cmd = DT_LTAG; } - do_tag((char_u *)eap->arg, cmd, eap->addr_count > 0 ? (int)eap->line2 : 1, + do_tag(eap->arg, cmd, eap->addr_count > 0 ? (int)eap->line2 : 1, eap->forceit, true); } @@ -6555,6 +6650,7 @@ enum { SPEC_SFILE, SPEC_SLNUM, SPEC_STACK, + SPEC_SCRIPT, SPEC_AFILE, SPEC_ABUF, SPEC_AMATCH, @@ -6566,7 +6662,7 @@ enum { /// Check "str" for starting with a special cmdline variable. /// If found return one of the SPEC_ values and set "*usedlen" to the length of /// the variable. Otherwise return -1 and "*usedlen" is unchanged. -ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) +ssize_t find_cmdline_var(const char *src, size_t *usedlen) FUNC_ATTR_NONNULL_ALL { static char *(spec_str[]) = { @@ -6579,6 +6675,7 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) [SPEC_SFILE] = "<sfile>", // ":so" file name [SPEC_SLNUM] = "<slnum>", // ":so" file line number [SPEC_STACK] = "<stack>", // call stack + [SPEC_SCRIPT] = "<script>", // script file name [SPEC_AFILE] = "<afile>", // autocommand file name [SPEC_ABUF] = "<abuf>", // autocommand buffer number [SPEC_AMATCH] = "<amatch>", // autocommand match name @@ -6588,8 +6685,8 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) }; for (size_t i = 0; i < ARRAY_SIZE(spec_str); i++) { - size_t len = STRLEN(spec_str[i]); - if (STRNCMP(src, spec_str[i], len) == 0) { + size_t len = strlen(spec_str[i]); + if (strncmp(src, spec_str[i], len) == 0) { *usedlen = len; assert(i <= SSIZE_MAX); return (ssize_t)i; @@ -6600,34 +6697,36 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) /// Evaluate cmdline variables. /// -/// change '%' to curbuf->b_ffname -/// '#' to curwin->w_alt_fnum -/// '<cword>' to word under the cursor -/// '<cWORD>' to WORD under the cursor -/// '<cexpr>' to C-expression under the cursor -/// '<cfile>' to path name under the cursor -/// '<sfile>' to sourced file name -/// '<stack>' to call stack -/// '<slnum>' to sourced file line number -/// '<afile>' to file name for autocommand -/// '<abuf>' to buffer number for autocommand -/// '<amatch>' to matching name for autocommand +/// change "%" to curbuf->b_ffname +/// "#" to curwin->w_alt_fnum +/// "<cword>" to word under the cursor +/// "<cWORD>" to WORD under the cursor +/// "<cexpr>" to C-expression under the cursor +/// "<cfile>" to path name under the cursor +/// "<sfile>" to sourced file name +/// "<stack>" to call stack +/// "<script>" to current script name +/// "<slnum>" to sourced file line number +/// "<afile>" to file name for autocommand +/// "<abuf>" to buffer number for autocommand +/// "<amatch>" to matching name for autocommand /// /// When an error is detected, "errormsg" is set to a non-NULL pointer (may be /// "" for error without a message) and NULL is returned. /// -/// @param src pointer into commandline -/// @param srcstart beginning of valid memory for src -/// @param usedlen characters after src that are used -/// @param lnump line number for :e command, or NULL -/// @param errormsg pointer to error message -/// @param escaped return value has escaped white space (can be NULL) +/// @param src pointer into commandline +/// @param srcstart beginning of valid memory for src +/// @param usedlen characters after src that are used +/// @param lnump line number for :e command, or NULL +/// @param errormsg pointer to error message +/// @param escaped return value has escaped white space (can be NULL) +/// @param empty_is_error empty result is considered an error /// /// @return an allocated string if a valid match was found. /// Returns NULL if no match was found. "usedlen" then still contains the /// number of characters to skip. -char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnump, char **errormsg, - int *escaped) +char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnump, char **errormsg, + int *escaped, bool empty_is_error) { char *result; char *resultbuf = NULL; @@ -6653,7 +6752,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum // Note: In "\\%" the % is also not recognized! if (src > srcstart && src[-1] == '\\') { *usedlen = 0; - STRMOVE(src - 1, src); // remove backslash + STRMOVE(src - 1, (char *)src); // remove backslash return NULL; } @@ -6686,7 +6785,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum valid = 0; // Must have ":p:h" to be valid } else { result = curbuf->b_fname; - tilde_file = STRCMP(result, "~") == 0; + tilde_file = strcmp(result, "~") == 0; } break; @@ -6701,16 +6800,16 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum skip_mod = true; break; } - char *s = (char *)src + 1; + char *s = src + 1; if (*s == '<') { // "#<99" uses v:oldfiles. s++; } int i = getdigits_int(&s, false, 0); - if ((char_u *)s == src + 2 && src[1] == '-') { + if (s == src + 2 && src[1] == '-') { // just a minus sign, don't skip over it s--; } - *usedlen = (size_t)((char_u *)s - src); // length of what we expand + *usedlen = (size_t)(s - src); // length of what we expand if (src[1] == '<' && i != 0) { if (*usedlen < 2) { @@ -6740,13 +6839,13 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum valid = 0; // Must have ":p:h" to be valid } else { result = buf->b_fname; - tilde_file = STRCMP(result, "~") == 0; + tilde_file = strcmp(result, "~") == 0; } } break; case SPEC_CFILE: // file name under cursor - result = (char *)file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1L, NULL); + result = file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1L, NULL); if (result == NULL) { *errormsg = ""; return NULL; @@ -6756,14 +6855,14 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum case SPEC_AFILE: // file name for autocommand if (autocmd_fname != NULL - && !path_is_absolute((char_u *)autocmd_fname) + && !path_is_absolute(autocmd_fname) // For CmdlineEnter and related events, <afile> is not a path! #9348 && !strequal("/", autocmd_fname)) { // Still need to turn the fname into a full path. It was // postponed to avoid a delay when <afile> is not used. result = FullName_save(autocmd_fname, false); // Copy into `autocmd_fname`, don't reassign it. #8165 - STRLCPY(autocmd_fname, result, MAXPATHL); + xstrlcpy(autocmd_fname, result, MAXPATHL); xfree(result); } result = autocmd_fname; @@ -6771,7 +6870,7 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum *errormsg = _("E495: no autocommand file name to substitute for \"<afile>\""); return NULL; } - result = (char *)path_try_shorten_fname((char_u *)result); + result = path_try_shorten_fname(result); break; case SPEC_ABUF: // buffer number for autocommand @@ -6792,12 +6891,25 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum break; case SPEC_SFILE: // file name for ":so" command + result = estack_sfile(ESTACK_SFILE); + if (result == NULL) { + *errormsg = _(e_no_source_file_name_to_substitute_for_sfile); + return NULL; + } + resultbuf = result; // remember allocated string + break; case SPEC_STACK: // call stack - result = estack_sfile(spec_idx == SPEC_SFILE ? ESTACK_SFILE : ESTACK_STACK); + result = estack_sfile(ESTACK_STACK); if (result == NULL) { - *errormsg = spec_idx == SPEC_SFILE - ? _("E498: no :source file name to substitute for \"<sfile>\"") - : _("E489: no call stack to substitute for \"<stack>\""); + *errormsg = _(e_no_call_stack_to_substitute_for_stack); + return NULL; + } + resultbuf = result; // remember allocated string + break; + case SPEC_SCRIPT: // script file name + result = estack_sfile(ESTACK_SCRIPT); + if (result == NULL) { + *errormsg = _(e_no_script_file_name_to_substitute_for_script); return NULL; } resultbuf = result; // remember allocated string @@ -6840,17 +6952,17 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum } // Length of new string. - resultlen = STRLEN(result); + resultlen = strlen(result); // Remove the file name extension. if (src[*usedlen] == '<') { (*usedlen)++; char *s; - if ((s = (char *)STRRCHR(result, '.')) != NULL + if ((s = strrchr(result, '.')) != NULL && s >= path_tail(result)) { resultlen = (size_t)(s - result); } } else if (!skip_mod) { - valid |= modify_fname((char *)src, tilde_file, usedlen, &result, + valid |= modify_fname(src, tilde_file, usedlen, &result, &resultbuf, &resultlen); if (result == NULL) { *errormsg = ""; @@ -6860,18 +6972,20 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum } if (resultlen == 0 || valid != VALID_HEAD + VALID_PATH) { - if (valid != VALID_HEAD + VALID_PATH) { - // xgettext:no-c-format - *errormsg = _("E499: Empty file name for '%' or '#', only works with \":p:h\""); - } else { - *errormsg = _("E500: Evaluates to an empty string"); + if (empty_is_error) { + if (valid != VALID_HEAD + VALID_PATH) { + // xgettext:no-c-format + *errormsg = _("E499: Empty file name for '%' or '#', only works with \":p:h\""); + } else { + *errormsg = _("E500: Evaluates to an empty string"); + } } result = NULL; } else { result = xstrnsave(result, resultlen); } xfree(resultbuf); - return (char_u *)result; + return result; } /// Expand the <sfile> string in "arg". @@ -6882,13 +6996,13 @@ char *expand_sfile(char *arg) char *result = xstrdup(arg); for (char *p = result; *p;) { - if (STRNCMP(p, "<sfile>", 7) != 0) { + if (strncmp(p, "<sfile>", 7) != 0) { p++; } else { // replace "<sfile>" with the sourced file name, and do ":" stuff size_t srclen; char *errormsg; - char *repl = (char *)eval_vars((char_u *)p, (char_u *)result, &srclen, NULL, &errormsg, NULL); + char *repl = eval_vars(p, result, &srclen, NULL, &errormsg, NULL, true); if (errormsg != NULL) { if (*errormsg) { emsg(errormsg); @@ -6900,11 +7014,11 @@ char *expand_sfile(char *arg) p += srclen; continue; } - size_t len = STRLEN(result) - srclen + STRLEN(repl) + 1; + size_t len = strlen(result) - srclen + strlen(repl) + 1; char *newres = xmalloc(len); memmove(newres, result, (size_t)(p - result)); STRCPY(newres + (p - result), repl); - len = STRLEN(newres); + len = strlen(newres); STRCAT(newres, p + srclen); xfree(repl); xfree(result); @@ -6919,16 +7033,16 @@ char *expand_sfile(char *arg) /// ":rshada" and ":wshada". static void ex_shada(exarg_T *eap) { - char *save_shada = (char *)p_shada; + char *save_shada = p_shada; if (*p_shada == NUL) { - p_shada = (char_u *)"'100"; + p_shada = "'100"; } if (eap->cmdidx == CMD_rviminfo || eap->cmdidx == CMD_rshada) { (void)shada_read_everything(eap->arg, eap->forceit, false); } else { shada_write_file(eap->arg, eap->forceit); } - p_shada = (char_u *)save_shada; + p_shada = save_shada; } /// Make a dialog message in "buff[DIALOG_MSG_SIZE]". @@ -6944,16 +7058,16 @@ void dialog_msg(char *buff, char *format, char *fname) /// ":behave {mswin,xterm}" static void ex_behave(exarg_T *eap) { - if (STRCMP(eap->arg, "mswin") == 0) { - set_option_value("selection", 0L, "exclusive", 0); - set_option_value("selectmode", 0L, "mouse,key", 0); - set_option_value("mousemodel", 0L, "popup", 0); - set_option_value("keymodel", 0L, "startsel,stopsel", 0); - } else if (STRCMP(eap->arg, "xterm") == 0) { - set_option_value("selection", 0L, "inclusive", 0); - set_option_value("selectmode", 0L, "", 0); - set_option_value("mousemodel", 0L, "extend", 0); - set_option_value("keymodel", 0L, "", 0); + if (strcmp(eap->arg, "mswin") == 0) { + set_option_value_give_err("selection", 0L, "exclusive", 0); + set_option_value_give_err("selectmode", 0L, "mouse,key", 0); + set_option_value_give_err("mousemodel", 0L, "popup", 0); + set_option_value_give_err("keymodel", 0L, "startsel,stopsel", 0); + } else if (strcmp(eap->arg, "xterm") == 0) { + set_option_value_give_err("selection", 0L, "inclusive", 0); + set_option_value_give_err("selectmode", 0L, "", 0); + set_option_value_give_err("mousemodel", 0L, "extend", 0); + set_option_value_give_err("keymodel", 0L, "", 0); } else { semsg(_(e_invarg2), eap->arg); } @@ -6987,19 +7101,19 @@ static void ex_filetype(exarg_T *eap) // Accept "plugin" and "indent" in any order. for (;;) { - if (STRNCMP(arg, "plugin", 6) == 0) { + if (strncmp(arg, "plugin", 6) == 0) { plugin = true; arg = skipwhite(arg + 6); continue; } - if (STRNCMP(arg, "indent", 6) == 0) { + if (strncmp(arg, "indent", 6) == 0) { indent = true; arg = skipwhite(arg + 6); continue; } break; } - if (STRCMP(arg, "on") == 0 || STRCMP(arg, "detect") == 0) { + if (strcmp(arg, "on") == 0 || strcmp(arg, "detect") == 0) { if (*arg == 'o' || !filetype_detect) { source_runtime(FILETYPE_FILE, DIP_ALL); filetype_detect = kTrue; @@ -7016,7 +7130,7 @@ static void ex_filetype(exarg_T *eap) (void)do_doautocmd("filetypedetect BufRead", true, NULL); do_modelines(0); } - } else if (STRCMP(arg, "off") == 0) { + } else if (strcmp(arg, "off") == 0) { if (plugin || indent) { if (plugin) { source_runtime(FTPLUGOF_FILE, DIP_ALL); @@ -7066,17 +7180,18 @@ void filetype_maybe_enable(void) /// ":setfiletype [FALLBACK] {name}" static void ex_setfiletype(exarg_T *eap) { - if (!did_filetype) { - char *arg = eap->arg; + if (did_filetype) { + return; + } - if (STRNCMP(arg, "FALLBACK ", 9) == 0) { - arg += 9; - } + char *arg = eap->arg; + if (strncmp(arg, "FALLBACK ", 9) == 0) { + arg += 9; + } - set_option_value("filetype", 0L, arg, OPT_LOCAL); - if (arg != eap->arg) { - did_filetype = false; - } + set_option_value_give_err("filetype", 0L, arg, OPT_LOCAL); + if (arg != eap->arg) { + did_filetype = false; } } @@ -7099,7 +7214,7 @@ void set_no_hlsearch(bool flag) static void ex_nohlsearch(exarg_T *eap) { set_no_hlsearch(true); - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } static void ex_fold(exarg_T *eap) @@ -7158,7 +7273,7 @@ static void ex_terminal(exarg_T *eap) char ex_cmd[1024]; if (*eap->arg != NUL) { // Run {cmd} in 'shell'. - char *name = (char *)vim_strsave_escaped((char_u *)eap->arg, (char_u *)"\"\\"); + char *name = vim_strsave_escaped(eap->arg, "\"\\"); snprintf(ex_cmd, sizeof(ex_cmd), ":enew%s | call termopen(\"%s\")", eap->forceit ? "!" : "", name); @@ -7191,7 +7306,7 @@ static void ex_terminal(exarg_T *eap) void verify_command(char *cmd) { - if (strcmp("smile", cmd)) { + if (strcmp("smile", cmd) != 0) { return; // acceptable non-existing command } msg(" #xxn` #xnxx` ,+x@##@Mz;` .xxx" diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h index 283501cf76..19dd9e96ca 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -1,6 +1,9 @@ #ifndef NVIM_EX_DOCMD_H #define NVIM_EX_DOCMD_H +#include <stdbool.h> + +#include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/globals.h" diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 69d509abb7..f76e60f6c5 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -1,8 +1,6 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -// TODO(ZyX-I): move to eval/executor - /// @file ex_eval.c /// /// Functions for Ex command line for the +eval feature. @@ -10,19 +8,30 @@ #include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/debugger.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" +#include "nvim/ex_eval_defs.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/option_defs.h" +#include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -45,20 +54,19 @@ // there or even outside the try conditional. Try conditionals may be nested. // Configuration whether an exception is thrown on error or interrupt. When -// the preprocessor macros below evaluate to FALSE, an error (did_emsg) or +// the preprocessor macros below evaluate to false, an error (did_emsg) or // interrupt (got_int) under an active try conditional terminates the script // after the non-active finally clauses of all active try conditionals have been // executed. Otherwise, errors and/or interrupts are converted into catchable -// exceptions, which terminate the script only if not caught. For user -// exceptions, only current_exception is set. (Note: got_int can be set -// asynchronously afterwards by a SIGINT, so current_exception && got_int is not +// exceptions (did_throw additionally set), which terminate the script only if +// not caught. For user exceptions, only did_throw is set. (Note: got_int can +// be set asynchronously afterwards by a SIGINT, so did_throw && got_int is not // a reliant test that the exception currently being thrown is an interrupt // exception. Similarly, did_emsg can be set afterwards on an error in an -// (unskipped) conditional command inside an inactive conditional, so -// current_exception && did_emsg is not a reliant test that the exception -// currently being thrown is an error exception.) - The macros can be defined -// as expressions checking for a variable that is allowed to be changed during -// execution of a script. +// (unskipped) conditional command inside an inactive conditional, so did_throw +// && did_emsg is not a reliant test that the exception currently being thrown +// is an error exception.) - The macros can be defined as expressions checking +// for a variable that is allowed to be changed during execution of a script. // Values used for the Vim release. #define THROW_ON_ERROR true @@ -71,7 +79,7 @@ #define CHECK_SKIP \ (did_emsg \ || got_int \ - || current_exception \ + || did_throw \ || (cstack->cs_idx > 0 \ && !(cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE))) @@ -80,16 +88,14 @@ static void discard_pending_return(typval_T *p) tv_free(p); } -/* - * When several errors appear in a row, setting "force_abort" is delayed until - * the failing command returned. "cause_abort" is set to TRUE meanwhile, in - * order to indicate that situation. This is useful when "force_abort" was set - * during execution of a function call from an expression: the aborting of the - * expression evaluation is done without producing any error messages, but all - * error messages on parsing errors during the expression evaluation are given - * (even if a try conditional is active). - */ -static int cause_abort = FALSE; +// When several errors appear in a row, setting "force_abort" is delayed until +// the failing command returned. "cause_abort" is set to true meanwhile, in +// order to indicate that situation. This is useful when "force_abort" was set +// during execution of a function call from an expression: the aborting of the +// expression evaluation is done without producing any error messages, but all +// error messages on parsing errors during the expression evaluation are given +// (even if a try conditional is active). +static int cause_abort = false; /// @return true when immediately aborting on error, or when an interrupt /// occurred or an exception was thrown but not caught. @@ -104,7 +110,7 @@ static int cause_abort = FALSE; /// value. "got_int" is also set by calling interrupt(). int aborting(void) { - return (did_emsg && force_abort) || got_int || current_exception; + return (did_emsg && force_abort) || got_int || did_throw; } /// The value of "force_abort" is temporarily reset by the first emsg() call @@ -114,11 +120,11 @@ int aborting(void) void update_force_abort(void) { if (cause_abort) { - force_abort = TRUE; + force_abort = true; } } -/// @return TRUE if a command with a subcommand resulting in "retcode" should +/// @return true if a command with a subcommand resulting in "retcode" should /// abort the script processing. Can be used to suppress an autocommand after /// execution of a failing subcommand as long as the error message has not been /// displayed and actually caused the abortion. @@ -127,7 +133,7 @@ int should_abort(int retcode) return (retcode == FAIL && trylevel != 0 && !emsg_silent) || aborting(); } -/// @return TRUE if a function with the "abort" flag should not be considered +/// @return true if a function with the "abort" flag should not be considered /// ended on an error. This means that parsing commands is continued in order /// to find finally clauses to be executed, and that some errors in skipped /// commands are still reported. @@ -154,70 +160,58 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) msglist_T *elem; msglist_T **plist; - /* - * Do nothing when displaying the interrupt message or reporting an - * uncaught exception (which has already been discarded then) at the top - * level. Also when no exception can be thrown. The message will be - * displayed by emsg(). - */ + // Do nothing when displaying the interrupt message or reporting an + // uncaught exception (which has already been discarded then) at the top + // level. Also when no exception can be thrown. The message will be + // displayed by emsg(). if (suppress_errthrow) { return false; } - /* - * If emsg() has not been called previously, temporarily reset - * "force_abort" until the throw point for error messages has been - * reached. This ensures that aborting() returns the same value for all - * errors that appear in the same command. This means particularly that - * for parsing errors during expression evaluation emsg() will be called - * multiply, even when the expression is evaluated from a finally clause - * that was activated due to an aborting error, interrupt, or exception. - */ + // If emsg() has not been called previously, temporarily reset + // "force_abort" until the throw point for error messages has been + // reached. This ensures that aborting() returns the same value for all + // errors that appear in the same command. This means particularly that + // for parsing errors during expression evaluation emsg() will be called + // multiply, even when the expression is evaluated from a finally clause + // that was activated due to an aborting error, interrupt, or exception. if (!did_emsg) { cause_abort = force_abort; - force_abort = FALSE; - } - - /* - * If no try conditional is active and no exception is being thrown and - * there has not been an error in a try conditional or a throw so far, do - * nothing (for compatibility of non-EH scripts). The message will then - * be displayed by emsg(). When ":silent!" was used and we are not - * currently throwing an exception, do nothing. The message text will - * then be stored to v:errmsg by emsg() without displaying it. - */ - if (((trylevel == 0 && !cause_abort) || emsg_silent) && !current_exception) { + force_abort = false; + } + + // If no try conditional is active and no exception is being thrown and + // there has not been an error in a try conditional or a throw so far, do + // nothing (for compatibility of non-EH scripts). The message will then + // be displayed by emsg(). When ":silent!" was used and we are not + // currently throwing an exception, do nothing. The message text will + // then be stored to v:errmsg by emsg() without displaying it. + if (((trylevel == 0 && !cause_abort) || emsg_silent) && !did_throw) { return false; } - /* - * Ignore an interrupt message when inside a try conditional or when an - * exception is being thrown or when an error in a try conditional or - * throw has been detected previously. This is important in order that an - * interrupt exception is catchable by the innermost try conditional and - * not replaced by an interrupt message error exception. - */ + // Ignore an interrupt message when inside a try conditional or when an + // exception is being thrown or when an error in a try conditional or + // throw has been detected previously. This is important in order that an + // interrupt exception is catchable by the innermost try conditional and + // not replaced by an interrupt message error exception. if (mesg == _(e_interr)) { *ignore = true; return true; } - /* - * Ensure that all commands in nested function calls and sourced files - * are aborted immediately. - */ - cause_abort = TRUE; - - /* - * When an exception is being thrown, some commands (like conditionals) are - * not skipped. Errors in those commands may affect what of the subsequent - * commands are regarded part of catch and finally clauses. Catching the - * exception would then cause execution of commands not intended by the - * user, who wouldn't even get aware of the problem. Therefore, discard the - * exception currently being thrown to prevent it from being caught. Just - * execute finally clauses and terminate. - */ - if (current_exception) { + // Ensure that all commands in nested function calls and sourced files + // are aborted immediately. + cause_abort = true; + + // When an exception is being thrown, some commands (like conditionals) are + // not skipped. Errors in those commands may affect what of the subsequent + // commands are regarded part of catch and finally clauses. Catching the + // exception would then cause execution of commands not intended by the + // user, who wouldn't even get aware of the problem. Therefore, discard the + // exception currently being thrown to prevent it from being caught. Just + // execute finally clauses and terminate. + if (did_throw) { // When discarding an interrupt exception, reset got_int to prevent the // same interrupt being converted to an exception again and discarding // the error exception we are about to throw here. @@ -229,25 +223,21 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) #ifdef THROW_TEST if (!THROW_ON_ERROR) { - /* - * Print error message immediately without searching for a matching - * catch clause; just finally clauses are executed before the script - * is terminated. - */ + // Print error message immediately without searching for a matching + // catch clause; just finally clauses are executed before the script + // is terminated. return false; } else // NOLINT(readability/braces) #endif { - /* - * Prepare the throw of an error exception, so that everything will - * be aborted (except for executing finally clauses), until the error - * exception is caught; if still uncaught at the top level, the error - * message will be displayed and the script processing terminated - * then. - This function has no access to the conditional stack. - * Thus, the actual throw is made after the failing command has - * returned. - Throw only the first of several errors in a row, except - * a severe error is following. - */ + // Prepare the throw of an error exception, so that everything will + // be aborted (except for executing finally clauses), until the error + // exception is caught; if still uncaught at the top level, the error + // message will be displayed and the script processing terminated + // then. - This function has no access to the conditional stack. + // Thus, the actual throw is made after the failing command has + // returned. - Throw only the first of several errors in a row, except + // a severe error is following. if (msg_list != NULL) { plist = msg_list; while (*plist != NULL) { @@ -264,7 +254,7 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore) // Skip the extra "Vim " prefix for message "E458". tmsg = elem->msg; - if (STRNCMP(tmsg, "Vim E", 5) == 0 + if (strncmp(tmsg, "Vim E", 5) == 0 && ascii_isdigit(tmsg[5]) && ascii_isdigit(tmsg[6]) && ascii_isdigit(tmsg[7]) @@ -313,13 +303,11 @@ void free_global_msglist(void) /// has returned (see do_one_cmd()). void do_errthrow(cstack_T *cstack, char *cmdname) { - /* - * Ensure that all commands in nested function calls and sourced files - * are aborted immediately. - */ + // Ensure that all commands in nested function calls and sourced files + // are aborted immediately. if (cause_abort) { - cause_abort = FALSE; - force_abort = TRUE; + cause_abort = false; + force_abort = true; } // If no exception is to be thrown or the conversion should be done after @@ -334,7 +322,7 @@ void do_errthrow(cstack_T *cstack, char *cmdname) if (cstack != NULL) { do_throw(cstack); } else { - need_rethrow = TRUE; + need_rethrow = true; } } *msg_list = NULL; @@ -343,13 +331,13 @@ void do_errthrow(cstack_T *cstack, char *cmdname) /// do_intthrow(): Replace the current exception by an interrupt or interrupt /// exception if appropriate. /// -/// @return TRUE if the current exception is discarded or, -/// FALSE otherwise. +/// @return true if the current exception is discarded or, +/// false otherwise. int do_intthrow(cstack_T *cstack) { // If no interrupt occurred or no try conditional is active and no exception // is being thrown, do nothing (for compatibility of non-EH scripts). - if (!got_int || (trylevel == 0 && !current_exception)) { + if (!got_int || (trylevel == 0 && !did_throw)) { return false; } @@ -358,7 +346,7 @@ int do_intthrow(cstack_T *cstack) // The interrupt aborts everything except for executing finally clauses. // Discard any user or error or interrupt exception currently being // thrown. - if (current_exception) { + if (did_throw) { discard_current_exception(); } } else { @@ -369,7 +357,7 @@ int do_intthrow(cstack_T *cstack) // will be terminated then. - If an interrupt exception is already // being thrown, do nothing. - if (current_exception) { + if (did_throw) { if (current_exception->type == ET_INTERRUPT) { return false; } @@ -397,13 +385,13 @@ char *get_exception_string(void *value, except_type_T type, char *cmdname, int * *should_free = true; mesg = ((msglist_T *)value)->throw_msg; if (cmdname != NULL && *cmdname != NUL) { - size_t cmdlen = STRLEN(cmdname); - ret = xstrnsave("Vim(", 4 + cmdlen + 2 + STRLEN(mesg)); + size_t cmdlen = strlen(cmdname); + ret = xstrnsave("Vim(", 4 + cmdlen + 2 + strlen(mesg)); STRCPY(&ret[4], cmdname); STRCPY(&ret[4 + cmdlen], "):"); val = ret + 4 + cmdlen + 2; } else { - ret = xstrnsave("Vim:", 4 + STRLEN(mesg)); + ret = xstrnsave("Vim:", 4 + strlen(mesg)); val = ret + 4; } @@ -431,7 +419,7 @@ char *get_exception_string(void *value, except_type_T type, char *cmdname, int * STRCAT(val, p); p[-2] = NUL; - snprintf(val + STRLEN(p), strlen(" (%s)"), " (%s)", &mesg[1]); + snprintf(val + strlen(p), strlen(" (%s)"), " (%s)", &mesg[1]); p[-2] = '"'; } break; @@ -456,15 +444,13 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) except_T *excp; int should_free; - /* - * Disallow faking Interrupt or error exceptions as user exceptions. They - * would be treated differently from real interrupt or error exceptions - * when no active try block is found, see do_cmdline(). - */ + // Disallow faking Interrupt or error exceptions as user exceptions. They + // would be treated differently from real interrupt or error exceptions + // when no active try block is found, see do_cmdline(). if (type == ET_USER) { - if (STRNCMP((char_u *)value, "Vim", 3) == 0 - && (((char_u *)value)[3] == NUL || ((char_u *)value)[3] == ':' - || ((char_u *)value)[3] == '(')) { + if (strncmp(value, "Vim", 3) == 0 + && (((char *)value)[3] == NUL || ((char *)value)[3] == ':' + || ((char *)value)[3] == '(')) { emsg(_("E608: Cannot :throw exceptions with 'Vim' prefix")); goto fail; } @@ -501,13 +487,13 @@ static int throw_exception(void *value, except_type_T type, char *cmdname) int save_msg_silent = msg_silent; if (debug_break_level > 0) { - msg_silent = FALSE; // display messages + msg_silent = false; // display messages } else { verbose_enter(); } no_wait_return++; if (debug_break_level > 0 || *p_vfile == NUL) { - msg_scroll = TRUE; // always scroll up, don't overwrite + msg_scroll = true; // always scroll up, don't overwrite } smsg(_("Exception thrown: %s"), excp->value); msg_puts("\n"); // don't overwrite this either @@ -552,19 +538,17 @@ static void discard_exception(except_T *excp, bool was_finished) if (p_verbose >= 13 || debug_break_level > 0) { int save_msg_silent = msg_silent; - saved_IObuff = (char *)vim_strsave(IObuff); + saved_IObuff = xstrdup(IObuff); if (debug_break_level > 0) { - msg_silent = FALSE; // display messages + msg_silent = false; // display messages } else { verbose_enter(); } no_wait_return++; if (debug_break_level > 0 || *p_vfile == NUL) { - msg_scroll = TRUE; // always scroll up, don't overwrite + msg_scroll = true; // always scroll up, don't overwrite } - smsg(was_finished ? _("Exception finished: %s") - : _("Exception discarded: %s"), - excp->value); + smsg(was_finished ? _("Exception finished: %s") : _("Exception discarded: %s"), excp->value); msg_puts("\n"); // don't overwrite this either if (debug_break_level > 0 || *p_vfile == NUL) { cmdline_row = msg_row; @@ -575,7 +559,7 @@ static void discard_exception(except_T *excp, bool was_finished) } else { verbose_leave(); } - STRLCPY(IObuff, saved_IObuff, IOSIZE); + xstrlcpy(IObuff, saved_IObuff, IOSIZE); xfree(saved_IObuff); } if (excp->type != ET_INTERRUPT) { @@ -596,6 +580,7 @@ void discard_current_exception(void) } // Note: all globals manipulated here should be saved/restored in // try_enter/try_leave. + did_throw = false; need_rethrow = false; } @@ -607,12 +592,12 @@ static void catch_exception(except_T *excp) set_vim_var_string(VV_EXCEPTION, excp->value, -1); if (*excp->throw_name != NUL) { if (excp->throw_lnum != 0) { - vim_snprintf((char *)IObuff, IOSIZE, _("%s, line %" PRId64), + vim_snprintf(IObuff, IOSIZE, _("%s, line %" PRId64), excp->throw_name, (int64_t)excp->throw_lnum); } else { - vim_snprintf((char *)IObuff, IOSIZE, "%s", excp->throw_name); + vim_snprintf(IObuff, IOSIZE, "%s", excp->throw_name); } - set_vim_var_string(VV_THROWPOINT, (char *)IObuff, -1); + set_vim_var_string(VV_THROWPOINT, IObuff, -1); } else { // throw_name not set on an exception from a command that was typed. set_vim_var_string(VV_THROWPOINT, NULL, -1); @@ -622,13 +607,13 @@ static void catch_exception(except_T *excp) int save_msg_silent = msg_silent; if (debug_break_level > 0) { - msg_silent = FALSE; // display messages + msg_silent = false; // display messages } else { verbose_enter(); } no_wait_return++; if (debug_break_level > 0 || *p_vfile == NUL) { - msg_scroll = TRUE; // always scroll up, don't overwrite + msg_scroll = true; // always scroll up, don't overwrite } smsg(_("Exception caught: %s"), excp->value); msg_puts("\n"); // don't overwrite this either @@ -656,14 +641,14 @@ static void finish_exception(except_T *excp) set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1); if (*caught_stack->throw_name != NUL) { if (caught_stack->throw_lnum != 0) { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("%s, line %" PRId64), caught_stack->throw_name, (int64_t)caught_stack->throw_lnum); } else { - vim_snprintf((char *)IObuff, IOSIZE, "%s", + vim_snprintf(IObuff, IOSIZE, "%s", caught_stack->throw_name); } - set_vim_var_string(VV_THROWPOINT, (char *)IObuff, -1); + set_vim_var_string(VV_THROWPOINT, IObuff, -1); } else { // throw_name not set on an exception from a command that was // typed. @@ -678,9 +663,7 @@ static void finish_exception(except_T *excp) discard_exception(excp, true); } -/* - * Flags specifying the message displayed by report_pending. - */ +// Flags specifying the message displayed by report_pending. #define RP_MAKE 0 #define RP_RESUME 1 #define RP_DISCARD 2 @@ -726,14 +709,14 @@ static void report_pending(int action, int pending, void *value) break; case CSTP_RETURN: // ":return" command producing value, allocated - s = (char *)get_return_cmd(value); + s = get_return_cmd(value); break; default: if (pending & CSTP_THROW) { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, mesg, _("Exception")); - mesg = (char *)concat_str(IObuff, (char_u *)": %s"); + mesg = concat_str(IObuff, ": %s"); s = ((except_T *)value)->value; } else if ((pending & CSTP_ERROR) && (pending & CSTP_INTERRUPT)) { s = _("Error and interrupt"); @@ -746,7 +729,7 @@ static void report_pending(int action, int pending, void *value) save_msg_silent = msg_silent; if (debug_break_level > 0) { - msg_silent = FALSE; // display messages + msg_silent = false; // display messages } no_wait_return++; msg_scroll = true; // always scroll up, don't overwrite @@ -830,7 +813,7 @@ void ex_if(exarg_T *eap) if (cstack->cs_idx == CSTACK_LEN - 1) { eap->errmsg = _("E579: :if nesting too deep"); } else { - ++cstack->cs_idx; + cstack->cs_idx++; cstack->cs_flags[cstack->cs_idx] = 0; skip = CHECK_SKIP; @@ -870,14 +853,14 @@ void ex_endif(exarg_T *eap) (void)do_intthrow(eap->cstack); } - --eap->cstack->cs_idx; + eap->cstack->cs_idx--; } } /// Handle ":else" and ":elseif". void ex_else(exarg_T *eap) { - int result; + bool result = false; cstack_T *const cstack = eap->cstack; bool skip = CHECK_SKIP; @@ -925,13 +908,20 @@ void ex_else(exarg_T *eap) if (eap->cmdidx == CMD_elseif) { bool error; - result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); + // When skipping we ignore most errors, but a missing expression is + // wrong, perhaps it should have been "else". + // A double quote here is the start of a string, not a comment. + if (skip && *eap->arg != '"' && ends_excmd(*eap->arg)) { + semsg(_(e_invexpr2), eap->arg); + } else { + result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); + } + // When throwing error exceptions, we want to throw always the first // of several errors in a row. This is what actually happens when // a conditional error was detected above and there is another failure // when parsing the expression. Since the skip flag is set in this // case, the parsing error will be ignored by emsg(). - if (!skip && !error) { if (result) { cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE | CSF_TRUE; @@ -958,14 +948,12 @@ void ex_while(exarg_T *eap) if (cstack->cs_idx == CSTACK_LEN - 1) { eap->errmsg = _("E585: :while/:for nesting too deep"); } else { - /* - * The loop flag is set when we have jumped back from the matching - * ":endwhile" or ":endfor". When not set, need to initialise this - * cstack entry. - */ + // The loop flag is set when we have jumped back from the matching + // ":endwhile" or ":endfor". When not set, need to initialise this + // cstack entry. if ((cstack->cs_lflags & CSL_HAD_LOOP) == 0) { - ++cstack->cs_idx; - ++cstack->cs_looplevel; + cstack->cs_idx++; + cstack->cs_looplevel++; cstack->cs_line[cstack->cs_idx] = -1; } cstack->cs_flags[cstack->cs_idx] = @@ -973,21 +961,17 @@ void ex_while(exarg_T *eap) skip = CHECK_SKIP; if (eap->cmdidx == CMD_while) { - /* - * ":while bool-expr" - */ + // ":while bool-expr" result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); } else { void *fi; - /* - * ":for var in list-expr" - */ + // ":for var in list-expr" if ((cstack->cs_lflags & CSL_HAD_LOOP) != 0) { // Jumping here from a ":continue" or ":endfor": use the // previously evaluated list. fi = cstack->cs_forinfo[cstack->cs_idx]; - error = FALSE; + error = false; } else { // Evaluate the argument and get the info in a structure. fi = eval_for_line(eap->arg, &error, &eap->nextcmd, skip); @@ -998,7 +982,7 @@ void ex_while(exarg_T *eap) if (!error && fi != NULL && !skip) { result = next_for_item(fi, eap->arg); } else { - result = FALSE; + result = false; } if (!result) { @@ -1007,11 +991,9 @@ void ex_while(exarg_T *eap) } } - /* - * If this cstack entry was just initialised and is active, set the - * loop flag, so do_cmdline() will set the line number in cs_line[]. - * If executing the command a second time, clear the loop flag. - */ + // If this cstack entry was just initialised and is active, set the + // loop flag, so do_cmdline() will set the line number in cs_line[]. + // If executing the command a second time, clear the loop flag. if (!skip && !error && result) { cstack->cs_flags[cstack->cs_idx] |= (CSF_ACTIVE | CSF_TRUE); cstack->cs_lflags ^= CSL_HAD_LOOP; @@ -1046,10 +1028,8 @@ void ex_continue(exarg_T *eap) if (cstack->cs_flags[idx] & (CSF_WHILE | CSF_FOR)) { rewind_conditionals(cstack, idx, CSF_TRY, &cstack->cs_trylevel); - /* - * Set CSL_HAD_CONT, so do_cmdline() will jump back to the - * matching ":while". - */ + // Set CSL_HAD_CONT, so do_cmdline() will jump back to the + // matching ":while". cstack->cs_lflags |= CSL_HAD_CONT; // let do_cmdline() handle it } else { // If a try conditional not in its finally clause is reached first, @@ -1118,8 +1098,8 @@ void ex_endwhile(exarg_T *eap) eap->errmsg = _(e_endtry); } // Try to find the matching ":while" and report what's missing. - for (idx = cstack->cs_idx; idx > 0; --idx) { - fl = cstack->cs_flags[idx]; + for (idx = cstack->cs_idx; idx > 0; idx--) { + fl = cstack->cs_flags[idx]; if ((fl & CSF_TRY) && !(fl & CSF_FINALLY)) { // Give up at a try conditional not in its finally clause. // Ignore the ":endwhile"/":endfor". @@ -1131,7 +1111,7 @@ void ex_endwhile(exarg_T *eap) } } // Cleanup and rewind all contained (and unclosed) conditionals. - (void)cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, FALSE); + (void)cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, false); rewind_conditionals(cstack, idx, CSF_TRY, &cstack->cs_trylevel); } else if (cstack->cs_flags[cstack->cs_idx] & CSF_TRUE && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE) @@ -1147,10 +1127,8 @@ void ex_endwhile(exarg_T *eap) (void)do_intthrow(cstack); } - /* - * Set loop flag, so do_cmdline() will jump back to the matching - * ":while" or ":for". - */ + // Set loop flag, so do_cmdline() will jump back to the matching + // ":while" or ":for". cstack->cs_lflags |= CSL_HAD_ENDLOOP; } } @@ -1172,7 +1150,7 @@ void ex_throw(exarg_T *eap) // On error or when an exception is thrown during argument evaluation, do // not throw. if (!eap->skip && value != NULL) { - if (throw_exception((char_u *)value, ET_USER, NULL) == FAIL) { + if (throw_exception(value, ET_USER, NULL) == FAIL) { xfree(value); } else { do_throw(eap->cstack); @@ -1186,7 +1164,7 @@ void ex_throw(exarg_T *eap) void do_throw(cstack_T *cstack) { int idx; - int inactivate_try = FALSE; + int inactivate_try = false; // // Cleanup and deactivate up to the next surrounding try conditional that @@ -1199,31 +1177,29 @@ void do_throw(cstack_T *cstack) // #ifndef THROW_ON_ERROR_TRUE if (did_emsg && !THROW_ON_ERROR) { - inactivate_try = TRUE; - did_emsg = FALSE; + inactivate_try = true; + did_emsg = false; } #endif #ifndef THROW_ON_INTERRUPT_TRUE if (got_int && !THROW_ON_INTERRUPT) { - inactivate_try = TRUE; - got_int = FALSE; + inactivate_try = true; + got_int = false; } #endif idx = cleanup_conditionals(cstack, 0, inactivate_try); if (idx >= 0) { - /* - * If this try conditional is active and we are before its first - * ":catch", set THROWN so that the ":catch" commands will check - * whether the exception matches. When the exception came from any of - * the catch clauses, it will be made pending at the ":finally" (if - * present) and rethrown at the ":endtry". This will also happen if - * the try conditional is inactive. This is the case when we are - * throwing an exception due to an error or interrupt on the way from - * a preceding ":continue", ":break", ":return", ":finish", error or - * interrupt (not converted to an exception) to the finally clause or - * from a preceding throw of a user or error or interrupt exception to - * the matching catch clause or the finally clause. - */ + // If this try conditional is active and we are before its first + // ":catch", set THROWN so that the ":catch" commands will check + // whether the exception matches. When the exception came from any of + // the catch clauses, it will be made pending at the ":finally" (if + // present) and rethrown at the ":endtry". This will also happen if + // the try conditional is inactive. This is the case when we are + // throwing an exception due to an error or interrupt on the way from + // a preceding ":continue", ":break", ":return", ":finish", error or + // interrupt (not converted to an exception) to the finally clause or + // from a preceding throw of a user or error or interrupt exception to + // the matching catch clause or the finally clause. if (!(cstack->cs_flags[idx] & CSF_CAUGHT)) { if (cstack->cs_flags[idx] & CSF_ACTIVE) { cstack->cs_flags[idx] |= CSF_THROWN; @@ -1237,6 +1213,8 @@ void do_throw(cstack_T *cstack) cstack->cs_flags[idx] &= ~CSF_ACTIVE; cstack->cs_exception[idx] = current_exception; } + + did_throw = true; } /// Handle ":try" @@ -1248,8 +1226,8 @@ void ex_try(exarg_T *eap) if (cstack->cs_idx == CSTACK_LEN - 1) { eap->errmsg = _("E601: :try nesting too deep"); } else { - ++cstack->cs_idx; - ++cstack->cs_trylevel; + cstack->cs_idx++; + cstack->cs_trylevel++; cstack->cs_flags[cstack->cs_idx] = CSF_TRY; cstack->cs_pending[cstack->cs_idx] = CSTP_NONE; @@ -1261,22 +1239,20 @@ void ex_try(exarg_T *eap) // that the finally clause needs to be executed. cstack->cs_flags[cstack->cs_idx] |= CSF_ACTIVE | CSF_TRUE; - /* - * ":silent!", even when used in a try conditional, disables - * displaying of error messages and conversion of errors to - * exceptions. When the silent commands again open a try - * conditional, save "emsg_silent" and reset it so that errors are - * again converted to exceptions. The value is restored when that - * try conditional is left. If it is left normally, the commands - * following the ":endtry" are again silent. If it is left by - * a ":continue", ":break", ":return", or ":finish", the commands - * executed next are again silent. If it is left due to an - * aborting error, an interrupt, or an exception, restoring - * "emsg_silent" does not matter since we are already in the - * aborting state and/or the exception has already been thrown. - * The effect is then just freeing the memory that was allocated - * to save the value. - */ + // ":silent!", even when used in a try conditional, disables + // displaying of error messages and conversion of errors to + // exceptions. When the silent commands again open a try + // conditional, save "emsg_silent" and reset it so that errors are + // again converted to exceptions. The value is restored when that + // try conditional is left. If it is left normally, the commands + // following the ":endtry" are again silent. If it is left by + // a ":continue", ":break", ":return", or ":finish", the commands + // executed next are again silent. If it is left due to an + // aborting error, an interrupt, or an exception, restoring + // "emsg_silent" does not matter since we are already in the + // aborting state and/or the exception has already been thrown. + // The effect is then just freeing the memory that was allocated + // to save the value. if (emsg_silent) { eslist_T *elem = xmalloc(sizeof(*elem)); elem->saved_emsg_silent = emsg_silent; @@ -1314,7 +1290,7 @@ void ex_catch(exarg_T *eap) eap->errmsg = get_end_emsg(cstack); skip = true; } - for (idx = cstack->cs_idx; idx > 0; --idx) { + for (idx = cstack->cs_idx; idx > 0; idx--) { if (cstack->cs_flags[idx] & CSF_TRY) { break; } @@ -1333,27 +1309,26 @@ void ex_catch(exarg_T *eap) if (ends_excmd(*eap->arg)) { // no argument, catch all errors pat = ".*"; end = NULL; - eap->nextcmd = (char *)find_nextcmd((char_u *)eap->arg); + eap->nextcmd = find_nextcmd(eap->arg); } else { pat = eap->arg + 1; - end = (char *)skip_regexp((char_u *)pat, *eap->arg, true, NULL); + end = skip_regexp_err(pat, *eap->arg, true); + if (end == NULL) { + give_up = true; + } } if (!give_up) { - /* - * Don't do something when no exception has been thrown or when the - * corresponding try block never got active (because of an inactive - * surrounding conditional or after an error or interrupt or throw). - */ - if (!current_exception || !(cstack->cs_flags[idx] & CSF_TRUE)) { + // Don't do something when no exception has been thrown or when the + // corresponding try block never got active (because of an inactive + // surrounding conditional or after an error or interrupt or throw). + if (!did_throw || !(cstack->cs_flags[idx] & CSF_TRUE)) { skip = true; } - /* - * Check for a match only if an exception is thrown but not caught by - * a previous ":catch". An exception that has replaced a discarded - * exception is not checked (THROWN is not set then). - */ + // Check for a match only if an exception is thrown but not caught by + // a previous ":catch". An exception that has replaced a discarded + // exception is not checked (THROWN is not set then). if (!skip && (cstack->cs_flags[idx] & CSF_THROWN) && !(cstack->cs_flags[idx] & CSF_CAUGHT)) { if (end != NULL && *end != NUL && !ends_excmd(*skipwhite(end + 1))) { @@ -1376,7 +1351,7 @@ void ex_catch(exarg_T *eap) *end = NUL; } save_cpo = p_cpo; - p_cpo = ""; + p_cpo = empty_option; // Disable error messages, it will make current exception // invalid emsg_off++; @@ -1397,8 +1372,7 @@ void ex_catch(exarg_T *eap) // prev_got_int = got_int; got_int = false; - caught = vim_regexec_nl(®match, (char_u *)current_exception->value, - (colnr_T)0); + caught = vim_regexec_nl(®match, current_exception->value, (colnr_T)0); got_int |= prev_got_int; vim_regfree(regmatch.regprog); } @@ -1406,10 +1380,10 @@ void ex_catch(exarg_T *eap) } if (caught) { - // Make this ":catch" clause active and reset did_emsg and got_int. - // Put the exception on the caught stack. + // Make this ":catch" clause active and reset did_emsg, got_int, + // and did_throw. Put the exception on the caught stack. cstack->cs_flags[idx] |= CSF_ACTIVE | CSF_CAUGHT; - did_emsg = got_int = false; + did_emsg = got_int = did_throw = false; catch_exception((except_T *)cstack->cs_exception[idx]); // It's mandatory that the current exception is stored in the cstack // so that it can be discarded at the next ":catch", ":finally", or @@ -1419,27 +1393,21 @@ void ex_catch(exarg_T *eap) if (cstack->cs_exception[cstack->cs_idx] != current_exception) { internal_error("ex_catch()"); } - // Discarding current_exceptions happens based on what is stored in - // cstack->cs_exception, *all* calls to discard_current_exception() are - // (and must be) guarded by current_exception check. - current_exception = NULL; } else { - /* - * If there is a preceding catch clause and it caught the exception, - * finish the exception now. This happens also after errors except - * when this ":catch" was after the ":finally" or not within - * a ":try". Make the try conditional inactive so that the - * following catch clauses are skipped. On an error or interrupt - * after the preceding try block or catch clause was left by - * a ":continue", ":break", ":return", or ":finish", discard the - * pending action. - */ - cleanup_conditionals(cstack, CSF_TRY, TRUE); + // If there is a preceding catch clause and it caught the exception, + // finish the exception now. This happens also after errors except + // when this ":catch" was after the ":finally" or not within + // a ":try". Make the try conditional inactive so that the + // following catch clauses are skipped. On an error or interrupt + // after the preceding try block or catch clause was left by + // a ":continue", ":break", ":return", or ":finish", discard the + // pending action. + cleanup_conditionals(cstack, CSF_TRY, true); } } if (end != NULL) { - eap->nextcmd = (char *)find_nextcmd((char_u *)end); + eap->nextcmd = find_nextcmd(end); } } @@ -1447,121 +1415,111 @@ void ex_catch(exarg_T *eap) void ex_finally(exarg_T *eap) { int idx; - int skip = FALSE; + int skip = false; int pending = CSTP_NONE; cstack_T *const cstack = eap->cstack; - if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { - eap->errmsg = _("E606: :finally without :try"); - } else { - if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { - eap->errmsg = get_end_emsg(cstack); - for (idx = cstack->cs_idx - 1; idx > 0; --idx) { - if (cstack->cs_flags[idx] & CSF_TRY) { - break; - } - } - // Make this error pending, so that the commands in the following - // finally clause can be executed. This overrules also a pending - // ":continue", ":break", ":return", or ":finish". - pending = CSTP_ERROR; - } else { - idx = cstack->cs_idx; + for (idx = cstack->cs_idx; idx >= 0; idx--) { + if (cstack->cs_flags[idx] & CSF_TRY) { + break; } + } + if (cstack->cs_trylevel <= 0 || idx < 0) { + eap->errmsg = _("E606: :finally without :try"); + return; + } - if (cstack->cs_flags[idx] & CSF_FINALLY) { - // Give up for a multiple ":finally" and ignore it. - eap->errmsg = _("E607: multiple :finally"); - return; - } - rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, - &cstack->cs_looplevel); + if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { + eap->errmsg = get_end_emsg(cstack); + // Make this error pending, so that the commands in the following + // finally clause can be executed. This overrules also a pending + // ":continue", ":break", ":return", or ":finish". + pending = CSTP_ERROR; + } - /* - * Don't do something when the corresponding try block never got active - * (because of an inactive surrounding conditional or after an error or - * interrupt or throw) or for a ":finally" without ":try" or a multiple - * ":finally". After every other error (did_emsg or the conditional - * errors detected above) or after an interrupt (got_int) or an - * exception (current_exception), the finally clause must be executed. - */ - skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); + if (cstack->cs_flags[idx] & CSF_FINALLY) { + // Give up for a multiple ":finally" and ignore it. + eap->errmsg = _("E607: multiple :finally"); + return; + } + rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, + &cstack->cs_looplevel); + + // Don't do something when the corresponding try block never got active + // (because of an inactive surrounding conditional or after an error or + // interrupt or throw) or for a ":finally" without ":try" or a multiple + // ":finally". After every other error (did_emsg or the conditional + // errors detected above) or after an interrupt (got_int) or an + // exception (did_throw), the finally clause must be executed. + skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); + + if (!skip) { + // When debugging or a breakpoint was encountered, display the + // debug prompt (if not already done). The user then knows that the + // finally clause is executed. + if (dbg_check_skipped(eap)) { + // Handle a ">quit" debug command as if an interrupt had + // occurred before the ":finally". That is, discard the + // original exception and replace it by an interrupt + // exception. + (void)do_intthrow(cstack); + } - if (!skip) { - // When debugging or a breakpoint was encountered, display the - // debug prompt (if not already done). The user then knows that the - // finally clause is executed. - if (dbg_check_skipped(eap)) { - // Handle a ">quit" debug command as if an interrupt had - // occurred before the ":finally". That is, discard the - // original exception and replace it by an interrupt - // exception. - (void)do_intthrow(cstack); + // If there is a preceding catch clause and it caught the exception, + // finish the exception now. This happens also after errors except + // when this is a multiple ":finally" or one not within a ":try". + // After an error or interrupt, this also discards a pending + // ":continue", ":break", ":finish", or ":return" from the preceding + // try block or catch clause. + cleanup_conditionals(cstack, CSF_TRY, false); + + // Make did_emsg, got_int, did_throw pending. If set, they overrule + // a pending ":continue", ":break", ":return", or ":finish". Then + // we have particularly to discard a pending return value (as done + // by the call to cleanup_conditionals() above when did_emsg or + // got_int is set). The pending values are restored by the + // ":endtry", except if there is a new error, interrupt, exception, + // ":continue", ":break", ":return", or ":finish" in the following + // finally clause. A missing ":endwhile", ":endfor" or ":endif" + // detected here is treated as if did_emsg and did_throw had + // already been set, respectively in case that the error is not + // converted to an exception, did_throw had already been unset. + // We must not set did_emsg here since that would suppress the + // error message. + if (pending == CSTP_ERROR || did_emsg || got_int || did_throw) { + if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN) { + report_discard_pending(CSTP_RETURN, + cstack->cs_rettv[cstack->cs_idx]); + discard_pending_return(cstack->cs_rettv[cstack->cs_idx]); } - - /* - * If there is a preceding catch clause and it caught the exception, - * finish the exception now. This happens also after errors except - * when this is a multiple ":finally" or one not within a ":try". - * After an error or interrupt, this also discards a pending - * ":continue", ":break", ":finish", or ":return" from the preceding - * try block or catch clause. - */ - cleanup_conditionals(cstack, CSF_TRY, FALSE); - - /* - * Make did_emsg, got_int, current_exception pending. If set, they - * overrule a pending ":continue", ":break", ":return", or ":finish". - * Then we have particularly to discard a pending return value (as done - * by the call to cleanup_conditionals() above when did_emsg or - * got_int is set). The pending values are restored by the - * ":endtry", except if there is a new error, interrupt, exception, - * ":continue", ":break", ":return", or ":finish" in the following - * finally clause. A missing ":endwhile", ":endfor" or ":endif" - * detected here is treated as if did_emsg and current_exception had - * already been set, respectively in case that the error is not - * converted to an exception, current_exception had already been unset. - * We must not set did_emsg here since that would suppress the - * error message. - */ - if (pending == CSTP_ERROR || did_emsg || got_int || current_exception) { - if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN) { - report_discard_pending(CSTP_RETURN, - cstack->cs_rettv[cstack->cs_idx]); - discard_pending_return(cstack->cs_rettv[cstack->cs_idx]); - } - if (pending == CSTP_ERROR && !did_emsg) { - pending |= (THROW_ON_ERROR ? CSTP_THROW : 0); - } else { - pending |= (current_exception ? CSTP_THROW : 0); - } - pending |= did_emsg ? CSTP_ERROR : 0; - pending |= got_int ? CSTP_INTERRUPT : 0; - assert(pending >= CHAR_MIN && pending <= CHAR_MAX); - cstack->cs_pending[cstack->cs_idx] = (char)pending; - - // It's mandatory that the current exception is stored in the - // cstack so that it can be rethrown at the ":endtry" or be - // discarded if the finally clause is left by a ":continue", - // ":break", ":return", ":finish", error, interrupt, or another - // exception. When emsg() is called for a missing ":endif" or - // a missing ":endwhile"/":endfor" detected here, the - // exception will be discarded. - if (current_exception - && cstack->cs_exception[cstack->cs_idx] != current_exception) { - internal_error("ex_finally()"); - } + if (pending == CSTP_ERROR && !did_emsg) { + pending |= (THROW_ON_ERROR ? CSTP_THROW : 0); + } else { + pending |= (did_throw ? CSTP_THROW : 0); + } + pending |= did_emsg ? CSTP_ERROR : 0; + pending |= got_int ? CSTP_INTERRUPT : 0; + assert(pending >= CHAR_MIN && pending <= CHAR_MAX); + cstack->cs_pending[cstack->cs_idx] = (char)pending; + + // It's mandatory that the current exception is stored in the + // cstack so that it can be rethrown at the ":endtry" or be + // discarded if the finally clause is left by a ":continue", + // ":break", ":return", ":finish", error, interrupt, or another + // exception. When emsg() is called for a missing ":endif" or + // a missing ":endwhile"/":endfor" detected here, the + // exception will be discarded. + if (did_throw && cstack->cs_exception[cstack->cs_idx] != current_exception) { + internal_error("ex_finally()"); } - - /* - * Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg, - * got_int, and current_exception and make the finally clause active. - * This will happen after emsg() has been called for a missing - * ":endif" or a missing ":endwhile"/":endfor" detected here, so - * that the following finally clause will be executed even then. - */ - cstack->cs_lflags |= CSL_HAD_FINA; } + + // Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg, + // got_int, and did_throw and make the finally clause active. + // This will happen after emsg() has been called for a missing + // ":endif" or a missing ":endwhile"/":endfor" detected here, so + // that the following finally clause will be executed even then. + cstack->cs_lflags |= CSL_HAD_FINA; } } @@ -1574,189 +1532,180 @@ void ex_endtry(exarg_T *eap) void *rettv = NULL; cstack_T *const cstack = eap->cstack; - if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { + for (idx = cstack->cs_idx; idx >= 0; idx--) { + if (cstack->cs_flags[idx] & CSF_TRY) { + break; + } + } + if (cstack->cs_trylevel <= 0 || idx < 0) { eap->errmsg = _("E602: :endtry without :try"); - } else { - // Don't do something after an error, interrupt or throw in the try - // block, catch clause, or finally clause preceding this ":endtry" or - // when an error or interrupt occurred after a ":continue", ":break", - // ":return", or ":finish" in a try block or catch clause preceding this - // ":endtry" or when the try block never got active (because of an - // inactive surrounding conditional or after an error or interrupt or - // throw) or when there is a surrounding conditional and it has been - // made inactive by a ":continue", ":break", ":return", or ":finish" in - // the finally clause. The latter case need not be tested since then - // anything pending has already been discarded. - bool skip = did_emsg || got_int || current_exception - || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); + return; + } - if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { - eap->errmsg = get_end_emsg(cstack); + // Don't do something after an error, interrupt or throw in the try + // block, catch clause, or finally clause preceding this ":endtry" or + // when an error or interrupt occurred after a ":continue", ":break", + // ":return", or ":finish" in a try block or catch clause preceding this + // ":endtry" or when the try block never got active (because of an + // inactive surrounding conditional or after an error or interrupt or + // throw) or when there is a surrounding conditional and it has been + // made inactive by a ":continue", ":break", ":return", or ":finish" in + // the finally clause. The latter case need not be tested since then + // anything pending has already been discarded. + bool skip = did_emsg || got_int || did_throw || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); - // Find the matching ":try" and report what's missing. - idx = cstack->cs_idx; - do { - idx--; - } while (idx > 0 && !(cstack->cs_flags[idx] & CSF_TRY)); - rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, - &cstack->cs_looplevel); - skip = true; + if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { + eap->errmsg = get_end_emsg(cstack); - /* - * If an exception is being thrown, discard it to prevent it from - * being rethrown at the end of this function. It would be - * discarded by the error message, anyway. Resets current_exception. - * This does not affect the script termination due to the error - * since "trylevel" is decremented after emsg() has been called. - */ - if (current_exception) { - discard_current_exception(); - } + // Find the matching ":try" and report what's missing. + rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, + &cstack->cs_looplevel); + skip = true; - // report eap->errmsg, also when there already was an error - did_emsg = false; - } else { - idx = cstack->cs_idx; - - /* - * If we stopped with the exception currently being thrown at this - * try conditional since we didn't know that it doesn't have - * a finally clause, we need to rethrow it after closing the try - * conditional. - */ - if (current_exception - && (cstack->cs_flags[idx] & CSF_TRUE) - && !(cstack->cs_flags[idx] & CSF_FINALLY)) { - rethrow = true; - } + // If an exception is being thrown, discard it to prevent it from + // being rethrown at the end of this function. It would be + // discarded by the error message, anyway. Resets did_throw. + // This does not affect the script termination due to the error + // since "trylevel" is decremented after emsg() has been called. + if (did_throw) { + discard_current_exception(); } - // If there was no finally clause, show the user when debugging or - // a breakpoint was encountered that the end of the try conditional has - // been reached: display the debug prompt (if not already done). Do - // this on normal control flow or when an exception was thrown, but not - // on an interrupt or error not converted to an exception or when - // a ":break", ":continue", ":return", or ":finish" is pending. These - // actions are carried out immediately. - if ((rethrow || (!skip - && !(cstack->cs_flags[idx] & CSF_FINALLY) - && !cstack->cs_pending[idx])) - && dbg_check_skipped(eap)) { - // Handle a ">quit" debug command as if an interrupt had occurred - // before the ":endtry". That is, throw an interrupt exception and - // set "skip" and "rethrow". - if (got_int) { - skip = true; - (void)do_intthrow(cstack); - // The do_intthrow() call may have reset current_exception or - // cstack->cs_pending[idx]. - rethrow = false; - if (current_exception && !(cstack->cs_flags[idx] & CSF_FINALLY)) { - rethrow = true; - } + // report eap->errmsg, also when there already was an error + did_emsg = false; + } else { + idx = cstack->cs_idx; + + // If we stopped with the exception currently being thrown at this + // try conditional since we didn't know that it doesn't have + // a finally clause, we need to rethrow it after closing the try + // conditional. + if (did_throw + && (cstack->cs_flags[idx] & CSF_TRUE) + && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + rethrow = true; + } + } + + // If there was no finally clause, show the user when debugging or + // a breakpoint was encountered that the end of the try conditional has + // been reached: display the debug prompt (if not already done). Do + // this on normal control flow or when an exception was thrown, but not + // on an interrupt or error not converted to an exception or when + // a ":break", ":continue", ":return", or ":finish" is pending. These + // actions are carried out immediately. + if ((rethrow || (!skip + && !(cstack->cs_flags[idx] & CSF_FINALLY) + && !cstack->cs_pending[idx])) + && dbg_check_skipped(eap)) { + // Handle a ">quit" debug command as if an interrupt had occurred + // before the ":endtry". That is, throw an interrupt exception and + // set "skip" and "rethrow". + if (got_int) { + skip = true; + (void)do_intthrow(cstack); + // The do_intthrow() call may have reset did_throw or + // cstack->cs_pending[idx]. + rethrow = false; + if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + rethrow = true; } } + } - /* - * If a ":return" is pending, we need to resume it after closing the - * try conditional; remember the return value. If there was a finally - * clause making an exception pending, we need to rethrow it. Make it - * the exception currently being thrown. - */ - if (!skip) { - pending = cstack->cs_pending[idx]; - cstack->cs_pending[idx] = CSTP_NONE; - if (pending == CSTP_RETURN) { - rettv = cstack->cs_rettv[idx]; - } else if (pending & CSTP_THROW) { - current_exception = cstack->cs_exception[idx]; - } + // If a ":return" is pending, we need to resume it after closing the + // try conditional; remember the return value. If there was a finally + // clause making an exception pending, we need to rethrow it. Make it + // the exception currently being thrown. + if (!skip) { + pending = cstack->cs_pending[idx]; + cstack->cs_pending[idx] = CSTP_NONE; + if (pending == CSTP_RETURN) { + rettv = cstack->cs_rettv[idx]; + } else if (pending & CSTP_THROW) { + current_exception = cstack->cs_exception[idx]; } + } - /* - * Discard anything pending on an error, interrupt, or throw in the - * finally clause. If there was no ":finally", discard a pending - * ":continue", ":break", ":return", or ":finish" if an error or - * interrupt occurred afterwards, but before the ":endtry" was reached. - * If an exception was caught by the last of the catch clauses and there - * was no finally clause, finish the exception now. This happens also - * after errors except when this ":endtry" is not within a ":try". - * Restore "emsg_silent" if it has been reset by this try conditional. - */ - (void)cleanup_conditionals(cstack, CSF_TRY | CSF_SILENT, TRUE); + // Discard anything pending on an error, interrupt, or throw in the + // finally clause. If there was no ":finally", discard a pending + // ":continue", ":break", ":return", or ":finish" if an error or + // interrupt occurred afterwards, but before the ":endtry" was reached. + // If an exception was caught by the last of the catch clauses and there + // was no finally clause, finish the exception now. This happens also + // after errors except when this ":endtry" is not within a ":try". + // Restore "emsg_silent" if it has been reset by this try conditional. + (void)cleanup_conditionals(cstack, CSF_TRY | CSF_SILENT, true); - if (cstack->cs_idx >= 0 && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { - cstack->cs_idx--; - } - cstack->cs_trylevel--; + if (cstack->cs_idx >= 0 && (cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { + cstack->cs_idx--; + } + cstack->cs_trylevel--; - if (!skip) { - report_resume_pending(pending, - (pending == CSTP_RETURN) ? rettv : - (pending & CSTP_THROW) ? (void *)current_exception : NULL); - switch (pending) { - case CSTP_NONE: - break; + if (!skip) { + report_resume_pending(pending, + (pending == CSTP_RETURN) ? rettv : + (pending & CSTP_THROW) ? (void *)current_exception : NULL); + switch (pending) { + case CSTP_NONE: + break; - // Reactivate a pending ":continue", ":break", ":return", - // ":finish" from the try block or a catch clause of this try - // conditional. This is skipped, if there was an error in an - // (unskipped) conditional command or an interrupt afterwards - // or if the finally clause is present and executed a new error, - // interrupt, throw, ":continue", ":break", ":return", or - // ":finish". - case CSTP_CONTINUE: - ex_continue(eap); - break; - case CSTP_BREAK: - ex_break(eap); - break; - case CSTP_RETURN: - do_return(eap, FALSE, FALSE, rettv); - break; - case CSTP_FINISH: - do_finish(eap, FALSE); - break; + // Reactivate a pending ":continue", ":break", ":return", + // ":finish" from the try block or a catch clause of this try + // conditional. This is skipped, if there was an error in an + // (unskipped) conditional command or an interrupt afterwards + // or if the finally clause is present and executed a new error, + // interrupt, throw, ":continue", ":break", ":return", or + // ":finish". + case CSTP_CONTINUE: + ex_continue(eap); + break; + case CSTP_BREAK: + ex_break(eap); + break; + case CSTP_RETURN: + do_return(eap, false, false, rettv); + break; + case CSTP_FINISH: + do_finish(eap, false); + break; - // When the finally clause was entered due to an error, - // interrupt or throw (as opposed to a ":continue", ":break", - // ":return", or ":finish"), restore the pending values of - // did_emsg, got_int, and current_exception. This is skipped, if there - // was a new error, interrupt, throw, ":continue", ":break", - // ":return", or ":finish". in the finally clause. - default: - if (pending & CSTP_ERROR) { - did_emsg = true; - } - if (pending & CSTP_INTERRUPT) { - got_int = true; - } - if (pending & CSTP_THROW) { - rethrow = true; - } - break; + // When the finally clause was entered due to an error, + // interrupt or throw (as opposed to a ":continue", ":break", + // ":return", or ":finish"), restore the pending values of + // did_emsg, got_int, and did_throw. This is skipped, if there + // was a new error, interrupt, throw, ":continue", ":break", + // ":return", or ":finish". in the finally clause. + default: + if (pending & CSTP_ERROR) { + did_emsg = true; + } + if (pending & CSTP_INTERRUPT) { + got_int = true; } + if (pending & CSTP_THROW) { + rethrow = true; + } + break; } + } - if (rethrow) { - // Rethrow the current exception (within this cstack). - do_throw(cstack); - } + if (rethrow) { + // Rethrow the current exception (within this cstack). + do_throw(cstack); } } -/* - * enter_cleanup() and leave_cleanup() - * - * Functions to be called before/after invoking a sequence of autocommands for - * cleanup for a failed command. (Failure means here that a call to emsg() - * has been made, an interrupt occurred, or there is an uncaught exception - * from a previous autocommand execution of the same command.) - * - * Call enter_cleanup() with a pointer to a cleanup_T and pass the same - * pointer to leave_cleanup(). The cleanup_T structure stores the pending - * error/interrupt/exception state. - */ +// enter_cleanup() and leave_cleanup() +// +// Functions to be called before/after invoking a sequence of autocommands for +// cleanup for a failed command. (Failure means here that a call to emsg() +// has been made, an interrupt occurred, or there is an uncaught exception +// from a previous autocommand execution of the same command.) +// +// Call enter_cleanup() with a pointer to a cleanup_T and pass the same +// pointer to leave_cleanup(). The cleanup_T structure stores the pending +// error/interrupt/exception state. /// This function works a bit like ex_finally() except that there was not /// actually an extra try block around the part that failed and an error or @@ -1768,35 +1717,32 @@ void enter_cleanup(cleanup_T *csp) { int pending = CSTP_NONE; - /* - * Postpone did_emsg, got_int, current_exception. The pending values will be - * restored by leave_cleanup() except if there was an aborting error, - * interrupt, or uncaught exception after this function ends. - */ - if (did_emsg || got_int || current_exception || need_rethrow) { + // Postpone did_emsg, got_int, did_throw. The pending values will be + // restored by leave_cleanup() except if there was an aborting error, + // interrupt, or uncaught exception after this function ends. + if (did_emsg || got_int || did_throw || need_rethrow) { csp->pending = (did_emsg ? CSTP_ERROR : 0) | (got_int ? CSTP_INTERRUPT : 0) - | (current_exception ? CSTP_THROW : 0) + | (did_throw ? CSTP_THROW : 0) | (need_rethrow ? CSTP_THROW : 0); - // If we are currently throwing an exception, save it as well. On an error - // not yet converted to an exception, update "force_abort" and reset - // "cause_abort" (as do_errthrow() would do). This is needed for the - // do_cmdline() call that is going to be made for autocommand execution. We - // need not save *msg_list because there is an extra instance for every call - // of do_cmdline(), anyway. - if (current_exception || need_rethrow) { + // If we are currently throwing an exception (did_throw), save it as + // well. On an error not yet converted to an exception, update + // "force_abort" and reset "cause_abort" (as do_errthrow() would do). + // This is needed for the do_cmdline() call that is going to be made + // for autocommand execution. We need not save *msg_list because + // there is an extra instance for every call of do_cmdline(), anyway. + if (did_throw || need_rethrow) { csp->exception = current_exception; current_exception = NULL; } else { csp->exception = NULL; if (did_emsg) { force_abort |= cause_abort; - cause_abort = FALSE; + cause_abort = false; } } - did_emsg = got_int = need_rethrow = false; - current_exception = NULL; + did_emsg = got_int = did_throw = need_rethrow = false; // Report if required by the 'verbose' option or when debugging. report_make_pending(pending, csp->exception); @@ -1844,18 +1790,14 @@ void leave_cleanup(cleanup_T *csp) if (msg_list != NULL) { free_global_msglist(); } - } - /* - * If there was no new error, interrupt, or throw between the calls - * to enter_cleanup() and leave_cleanup(), restore the pending - * error/interrupt/exception state. - */ - else { - /* - * If there was an exception being thrown when enter_cleanup() was - * called, we need to rethrow it. Make it the exception currently - * being thrown. - */ + } else { + // If there was no new error, interrupt, or throw between the calls + // to enter_cleanup() and leave_cleanup(), restore the pending + // error/interrupt/exception state. + + // If there was an exception being thrown when enter_cleanup() was + // called, we need to rethrow it. Make it the exception currently + // being thrown. if (pending & CSTP_THROW) { current_exception = csp->exception; } else if (pending & CSTP_ERROR) { @@ -1863,10 +1805,10 @@ void leave_cleanup(cleanup_T *csp) // enter_cleanup() was called, let "cause_abort" take the part of // "force_abort" (as done by cause_errthrow()). cause_abort = force_abort; - force_abort = FALSE; + force_abort = false; } - // Restore the pending values of did_emsg, got_int, and current_exception. + // Restore the pending values of did_emsg, got_int, and did_throw. if (pending & CSTP_ERROR) { did_emsg = true; } @@ -1874,7 +1816,7 @@ void leave_cleanup(cleanup_T *csp) got_int = true; } if (pending & CSTP_THROW) { - need_rethrow = true; // current_exception will be set by do_one_cmd() + need_rethrow = true; // did_throw will be set by do_one_cmd() } // Report if required by the 'verbose' option or when debugging. @@ -1895,7 +1837,7 @@ void leave_cleanup(cleanup_T *csp) /// inactive itself (a try conditional not in its finally /// clause possibly find before is always made inactive). /// -/// If "inclusive" is TRUE and "searched_cond" is CSF_TRY|CSF_SILENT, the saved +/// If "inclusive" is true and "searched_cond" is CSF_TRY|CSF_SILENT, the saved /// former value of "emsg_silent", if reset when the try conditional finally /// reached was entered, is restored (used by ex_endtry()). This is normally /// done only when such a try conditional is left. @@ -1904,16 +1846,14 @@ void leave_cleanup(cleanup_T *csp) int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) { int idx; - int stop = FALSE; + int stop = false; - for (idx = cstack->cs_idx; idx >= 0; --idx) { + for (idx = cstack->cs_idx; idx >= 0; idx--) { if (cstack->cs_flags[idx] & CSF_TRY) { - /* - * Discard anything pending in a finally clause and continue the - * search. There may also be a pending ":continue", ":break", - * ":return", or ":finish" before the finally clause. We must not - * discard it, unless an error or interrupt occurred afterwards. - */ + // Discard anything pending in a finally clause and continue the + // search. There may also be a pending ":continue", ":break", + // ":return", or ":finish" before the finally clause. We must not + // discard it, unless an error or interrupt occurred afterwards. if (did_emsg || got_int || (cstack->cs_flags[idx] & CSF_FINALLY)) { switch (cstack->cs_pending[idx]) { case CSTP_NONE: @@ -1949,11 +1889,9 @@ int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) } } - /* - * Stop at a try conditional not in its finally clause. If this try - * conditional is in an active catch clause, finish the caught - * exception. - */ + // Stop at a try conditional not in its finally clause. If this try + // conditional is in an active catch clause, finish the caught + // exception. if (!(cstack->cs_flags[idx] & CSF_FINALLY)) { if ((cstack->cs_flags[idx] & CSF_ACTIVE) && (cstack->cs_flags[idx] & CSF_CAUGHT) && !(cstack->cs_flags[idx] & CSF_FINISHED)) { @@ -1968,31 +1906,29 @@ int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) if (searched_cond == 0 && !inclusive) { break; } - stop = TRUE; + stop = true; } } } // Stop on the searched conditional type (even when the surrounding // conditional is not active or something has been made pending). - // If "inclusive" is TRUE and "searched_cond" is CSF_TRY|CSF_SILENT, + // If "inclusive" is true and "searched_cond" is CSF_TRY|CSF_SILENT, // check first whether "emsg_silent" needs to be restored. if (cstack->cs_flags[idx] & searched_cond) { if (!inclusive) { break; } - stop = TRUE; + stop = true; } cstack->cs_flags[idx] &= ~CSF_ACTIVE; if (stop && searched_cond != (CSF_TRY | CSF_SILENT)) { break; } - /* - * When leaving a try conditional that reset "emsg_silent" on its - * entry after saving the original value, restore that value here and - * free the memory used to store it. - */ + // When leaving a try conditional that reset "emsg_silent" on its + // entry after saving the original value, restore that value here and + // free the memory used to store it. if ((cstack->cs_flags[idx] & CSF_TRY) && (cstack->cs_flags[idx] & CSF_SILENT)) { eslist_T *elem; @@ -2031,12 +1967,12 @@ void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_lev { while (cstack->cs_idx > idx) { if (cstack->cs_flags[cstack->cs_idx] & cond_type) { - --*cond_level; + (*cond_level)--; } if (cstack->cs_flags[cstack->cs_idx] & CSF_FOR) { free_for_info(cstack->cs_forinfo[cstack->cs_idx]); } - --cstack->cs_idx; + cstack->cs_idx--; } } @@ -2046,7 +1982,7 @@ void ex_endfunction(exarg_T *eap) emsg(_("E193: :endfunction not inside a function")); } -/// @return TRUE if the string "p" looks like a ":while" or ":for" command. +/// @return true if the string "p" looks like a ":while" or ":for" command. int has_loop_cmd(char *p) { int len; @@ -2064,7 +2000,7 @@ int has_loop_cmd(char *p) } if ((p[0] == 'w' && p[1] == 'h') || (p[0] == 'f' && p[1] == 'o' && p[2] == 'r')) { - return TRUE; + return true; } - return FALSE; + return false; } diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h index 9e3ac5e9c1..d3053ae0d4 100644 --- a/src/nvim/ex_eval.h +++ b/src/nvim/ex_eval.h @@ -1,7 +1,7 @@ #ifndef NVIM_EX_EVAL_H #define NVIM_EX_EVAL_H -#include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_eval_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/ex_eval_defs.h b/src/nvim/ex_eval_defs.h index 9da0c9ad12..6b3c426722 100644 --- a/src/nvim/ex_eval_defs.h +++ b/src/nvim/ex_eval_defs.h @@ -1,7 +1,7 @@ #ifndef NVIM_EX_EVAL_DEFS_H #define NVIM_EX_EVAL_DEFS_H -#include "nvim/pos.h" // for linenr_T +#include "nvim/pos.h" /// There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if" /// was used. diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index a0bcccb5be..76c3680742 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1,50 +1,49 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * ex_getln.c: Functions for entering and editing an Ex command line. - */ +// ex_getln.c: Functions for entering and editing an Ex command line. #include <assert.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> +#include <time.h> +#include "klib/kvec.h" #include "nvim/api/extmark.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/arabic.h" #include "nvim/ascii.h" -#include "nvim/assert.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/cmdhist.h" #include "nvim/cursor.h" -#include "nvim/cursor_shape.h" #include "nvim/digraph.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/event/loop.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" -#include "nvim/fileio.h" -#include "nvim/func_attr.h" +#include "nvim/extmark.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/grid.h" -#include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" -#include "nvim/indent.h" #include "nvim/keycodes.h" -#include "nvim/lib/kvec.h" -#include "nvim/log.h" -#include "nvim/main.h" +#include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -53,15 +52,18 @@ #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" +#include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/optionstr.h" #include "nvim/os/input.h" -#include "nvim/os/time.h" +#include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/regexp.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -96,7 +98,7 @@ typedef struct { pos_T match_end; bool did_incsearch; bool incsearch_postponed; - int magic_save; + optmagic_T magic_overruled_save; } incsearch_state_T; typedef struct command_line_state { @@ -105,9 +107,9 @@ typedef struct command_line_state { long count; int indent; int c; - int gotesc; // TRUE when <ESC> just typed - int do_abbr; // when TRUE check for abbr. - char_u *lookfor; // string to match + int gotesc; // true when <ESC> just typed + int do_abbr; // when true check for abbr. + char *lookfor; // string to match int hiscnt; // current history line in use int save_hiscnt; // history line before attempting // to jump to next match @@ -115,10 +117,9 @@ typedef struct command_line_state { incsearch_state_T is_state; int did_wild_list; // did wild_list() recently int wim_index; // index in wim_flags[] - int res; int save_msg_scroll; int save_State; // remember State when called - char_u *save_p_icm; + char *save_p_icm; int some_key_typed; // one of the keys was typed // mouse drag and release events are ignored, unless they are // preceded with a mouse down event @@ -138,6 +139,7 @@ typedef struct cmdpreview_win_info { typedef struct cmdpreview_buf_info { buf_T *buf; + bool save_b_u_synced; time_t save_b_u_time_cur; long save_b_u_seq_cur; u_header_T *save_b_u_newhead; @@ -154,6 +156,14 @@ typedef struct cmdpreview_info { garray_T save_view; } CpInfo; +/// Return value when handling keys in command-line mode. +enum { + CMDLINE_NOT_CHANGED = 1, + CMDLINE_CHANGED = 2, + GOTO_NORMAL_MODE = 3, + PROCESS_NEXT_KEY = 4, +}; + /// The current cmdline_info. It is initialized in getcmdline() and after that /// used by other functions. When invoking getcmdline() recursively it needs /// to be saved with save_cmdline() and restored with restore_cmdline(). @@ -170,6 +180,8 @@ static Array cmdline_block = ARRAY_DICT_INIT; /// user interrupting highlight function to not interrupt command-line. static bool getln_interrupted_highlight = false; +static int cedit_key = -1; ///< key value of 'cedit' option + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_getln.c.generated.h" #endif @@ -206,7 +218,7 @@ static void init_incsearch_state(incsearch_state_T *s) s->match_start = curwin->w_cursor; s->did_incsearch = false; s->incsearch_postponed = false; - s->magic_save = p_magic; + s->magic_overruled_save = magic_overruled; clearpos(&s->match_end); s->save_cursor = curwin->w_cursor; // may be restored later s->search_start = curwin->w_cursor; @@ -229,6 +241,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s pos_T save_cursor; bool use_last_pat; bool retval = false; + magic_T magic = 0; *skiplen = 0; *patlen = ccline.cmdlen; @@ -253,7 +266,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s exarg_T ea = { .line1 = 1, .line2 = 1, - .cmd = (char *)ccline.cmdbuff, + .cmd = ccline.cmdbuff, .addr_type = ADDR_LINES, }; @@ -261,7 +274,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s parse_command_modifiers(&ea, &dummy, &dummy_cmdmod, true); cmd = skip_range(ea.cmd, NULL); - if (vim_strchr("sgvl", *cmd) == NULL) { + if (vim_strchr("sgvl", (uint8_t)(*cmd)) == NULL) { goto theend; } @@ -271,16 +284,16 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s goto theend; } - if (STRNCMP(cmd, "substitute", p - cmd) == 0 - || STRNCMP(cmd, "smagic", p - cmd) == 0 - || STRNCMP(cmd, "snomagic", MAX(p - cmd, 3)) == 0 - || STRNCMP(cmd, "vglobal", p - cmd) == 0) { + if (strncmp(cmd, "substitute", (size_t)(p - cmd)) == 0 + || strncmp(cmd, "smagic", (size_t)(p - cmd)) == 0 + || strncmp(cmd, "snomagic", (size_t)MAX(p - cmd, 3)) == 0 + || strncmp(cmd, "vglobal", (size_t)(p - cmd)) == 0) { if (*cmd == 's' && cmd[1] == 'm') { - p_magic = true; + magic_overruled = OPTION_MAGIC_ON; } else if (*cmd == 's' && cmd[1] == 'n') { - p_magic = false; + magic_overruled = OPTION_MAGIC_OFF; } - } else if (STRNCMP(cmd, "sort", MAX(p - cmd, 3)) == 0) { + } else if (strncmp(cmd, "sort", (size_t)MAX(p - cmd, 3)) == 0) { // skip over ! and flags if (*p == '!') { p = skipwhite(p + 1); @@ -291,11 +304,11 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s if (*p == NUL) { goto theend; } - } else if (STRNCMP(cmd, "vimgrep", MAX(p - cmd, 3)) == 0 - || STRNCMP(cmd, "vimgrepadd", MAX(p - cmd, 8)) == 0 - || STRNCMP(cmd, "lvimgrep", MAX(p - cmd, 2)) == 0 - || STRNCMP(cmd, "lvimgrepadd", MAX(p - cmd, 9)) == 0 - || STRNCMP(cmd, "global", p - cmd) == 0) { + } else if (strncmp(cmd, "vimgrep", (size_t)MAX(p - cmd, 3)) == 0 + || strncmp(cmd, "vimgrepadd", (size_t)MAX(p - cmd, 8)) == 0 + || strncmp(cmd, "lvimgrep", (size_t)MAX(p - cmd, 2)) == 0 + || strncmp(cmd, "lvimgrepadd", (size_t)MAX(p - cmd, 9)) == 0 + || strncmp(cmd, "global", (size_t)(p - cmd)) == 0) { // skip over "!/". if (*p == '!') { p++; @@ -311,9 +324,9 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s } p = skipwhite(p); - delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++; + delim = (delim_optional && vim_isIDc((uint8_t)(*p))) ? ' ' : *p++; *search_delim = delim; - end = (char *)skip_regexp((char_u *)p, delim, p_magic, NULL); + end = skip_regexp_ex(p, delim, magic_isset(), NULL, NULL, &magic); use_last_pat = end == p && *end == delim; if (end == p && !use_last_pat) { @@ -323,10 +336,8 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s // Don't do 'hlsearch' highlighting if the pattern matches everything. if (!use_last_pat) { char c = *end; - int empty; - *end = NUL; - empty = empty_pattern((char_u *)p); + bool empty = empty_pattern_magic(p, strlen(p), magic); *end = c; if (empty) { goto theend; @@ -334,7 +345,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s } // found a non-empty pattern or // - *skiplen = (int)((char_u *)p - ccline.cmdbuff); + *skiplen = (int)(p - ccline.cmdbuff); *patlen = (int)(end - p); // parse the address range @@ -369,8 +380,8 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat pos_T end_pos; proftime_T tm; int skiplen, patlen; - char_u next_char; - char_u use_last_pat; + char next_char; + bool use_last_pat; int search_delim; // Parsing range may already set the last search pattern. @@ -415,7 +426,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat if (patlen == 0 && !use_last_pat) { found = 0; set_no_hlsearch(true); // turn off previous highlight - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } else { int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK; ui_busy_start(); @@ -481,14 +492,14 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat } else { end_pos = curwin->w_cursor; // shutup gcc 4 } - // + // Disable 'hlsearch' highlighting if the pattern matches // everything. Avoids a flash when typing "foo\|". if (!use_last_pat) { next_char = ccline.cmdbuff[skiplen + patlen]; ccline.cmdbuff[skiplen + patlen] = NUL; - if (empty_pattern(ccline.cmdbuff) && !no_hlsearch) { - redraw_all_later(SOME_VALID); + if (empty_pattern(ccline.cmdbuff + skiplen, search_delim) && !no_hlsearch) { + redraw_all_later(UPD_SOME_VALID); set_no_hlsearch(true); } ccline.cmdbuff[skiplen + patlen] = next_char; @@ -500,7 +511,8 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat curwin->w_redr_status = true; } - update_screen(SOME_VALID); + redraw_later(curwin, UPD_SOME_VALID); + update_screen(); highlight_match = false; restore_last_search_pattern(); @@ -550,7 +562,7 @@ static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s) *c = mb_tolower(*c); } if (*c == search_delim - || vim_strchr((p_magic ? "\\~^$.*[" : "\\^$"), *c) != NULL) { + || vim_strchr((magic_isset() ? "\\~^$.*[" : "\\^$"), *c) != NULL) { // put a backslash before special characters stuffcharReadbuff(*c); *c = '\\'; @@ -563,60 +575,74 @@ static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s) static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool call_update_screen) { - if (s->did_incsearch) { - s->did_incsearch = false; - if (gotesc) { + if (!s->did_incsearch) { + return; + } + + s->did_incsearch = false; + if (gotesc) { + curwin->w_cursor = s->save_cursor; + } else { + if (!equalpos(s->save_cursor, s->search_start)) { + // put the '" mark at the original position curwin->w_cursor = s->save_cursor; - } else { - if (!equalpos(s->save_cursor, s->search_start)) { - // put the '" mark at the original position - curwin->w_cursor = s->save_cursor; - setpcmark(); - } - curwin->w_cursor = s->search_start; // -V519 + setpcmark(); } - restore_viewstate(curwin, &s->old_viewstate); - highlight_match = false; + curwin->w_cursor = s->search_start; // -V519 + } + restore_viewstate(curwin, &s->old_viewstate); + highlight_match = false; - // by default search all lines - search_first_line = 0; - search_last_line = MAXLNUM; + // by default search all lines + search_first_line = 0; + search_last_line = MAXLNUM; - p_magic = s->magic_save; + magic_overruled = s->magic_overruled_save; - validate_cursor(); // needed for TAB - redraw_all_later(SOME_VALID); - if (call_update_screen) { - update_screen(SOME_VALID); - } + validate_cursor(); // needed for TAB + redraw_all_later(UPD_SOME_VALID); + if (call_update_screen) { + update_screen(); } } -/// Internal entry point for cmdline mode. -/// -/// @param count only used for incremental search -/// @param indent indent for inside conditionals -/// @param init_ccline clear ccline first -static uint8_t *command_line_enter(int firstc, long count, int indent, bool init_ccline) +/// Initialize the current command-line info. +static void init_ccline(int firstc, int indent) { - const bool cmdheight0 = !ui_has_messages(); + ccline.overstrike = false; // always start in insert mode - if (cmdheight0) { - const long save_so = lastwin->w_p_so; + assert(indent >= 0); - // If cmdheight is 0, cmdheight must be set to 1 when we enter the - // command line. Set "made_cmdheight_nonzero" and reset 'scrolloff' to - // avoid scrolling the last window. - made_cmdheight_nonzero = true; + // set some variables for redrawcmd() + ccline.cmdfirstc = (firstc == '@' ? 0 : firstc); + ccline.cmdindent = (firstc > 0 ? indent : 0); - lastwin->w_p_so = 0; - set_option_value("ch", 1L, NULL, 0); - update_screen(VALID); // redraw the screen NOW + // alloc initial ccline.cmdbuff + alloc_cmdbuff(indent + 50); + ccline.cmdlen = ccline.cmdpos = 0; + ccline.cmdbuff[0] = NUL; - made_cmdheight_nonzero = false; - lastwin->w_p_so = save_so; + ccline.last_colors = (ColoredCmdline){ .cmdbuff = NULL, + .colors = KV_INITIAL_VALUE }; + sb_text_start_cmdline(); + + // autoindent for :insert and :append + if (firstc <= 0) { + memset(ccline.cmdbuff, ' ', (size_t)indent); + ccline.cmdbuff[indent] = NUL; + ccline.cmdpos = indent; + ccline.cmdspos = indent; + ccline.cmdlen = indent; } +} +/// Internal entry point for cmdline mode. +/// +/// @param count only used for incremental search +/// @param indent indent for inside conditionals +/// @param clear_ccline clear ccline first +static uint8_t *command_line_enter(int firstc, long count, int indent, bool clear_ccline) +{ // can be invoked recursively, identify each level static int cmdline_level = 0; cmdline_level++; @@ -632,20 +658,20 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init .ignore_drag_release = true, }; CommandLineState *s = &state; - s->save_p_icm = vim_strsave(p_icm); + s->save_p_icm = xstrdup(p_icm); init_incsearch_state(&s->is_state); CmdlineInfo save_ccline; bool did_save_ccline = false; if (ccline.cmdbuff != NULL) { - // Currently ccline can never be in use if init_ccline is false. + // Currently ccline can never be in use if clear_ccline is false. // Some changes will be needed if this is no longer the case. - assert(init_ccline); + assert(clear_ccline); // Being called recursively. Since ccline is global, we need to save // the current buffer and restore it when returning. save_cmdline(&save_ccline); did_save_ccline = true; - } else if (init_ccline) { + } else if (clear_ccline) { CLEAR_FIELD(ccline); } @@ -659,33 +685,9 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init cmd_hkmap = 0; } + init_ccline(s->firstc, s->indent); ccline.prompt_id = last_prompt_id++; ccline.level = cmdline_level; - ccline.overstrike = false; // always start in insert mode - - assert(indent >= 0); - - // set some variables for redrawcmd() - ccline.cmdfirstc = (s->firstc == '@' ? 0 : s->firstc); - ccline.cmdindent = (s->firstc > 0 ? s->indent : 0); - - // alloc initial ccline.cmdbuff - alloc_cmdbuff(indent + 50); - ccline.cmdlen = ccline.cmdpos = 0; - ccline.cmdbuff[0] = NUL; - - ccline.last_colors = (ColoredCmdline){ .cmdbuff = NULL, - .colors = KV_INITIAL_VALUE }; - sb_text_start_cmdline(); - - // autoindent for :insert and :append - if (s->firstc <= 0) { - memset(ccline.cmdbuff, ' ', (size_t)s->indent); - ccline.cmdbuff[s->indent] = NUL; - ccline.cmdpos = s->indent; - ccline.cmdspos = s->indent; - ccline.cmdlen = s->indent; - } if (cmdline_level == 50) { // Somehow got into a loop recursively calling getcmdline(), bail out. @@ -722,8 +724,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init if (ccline.input_fn) { s->xpc.xp_context = ccline.xp_context; - s->xpc.xp_pattern = (char *)ccline.cmdbuff; - s->xpc.xp_arg = (char *)ccline.xp_arg; + s->xpc.xp_pattern = ccline.cmdbuff; + s->xpc.xp_arg = ccline.xp_arg; } // Avoid scrolling when called by a recursive do_cmdline(), e.g. when @@ -751,13 +753,14 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init TryState tstate; Error err = ERROR_INIT; bool tl_ret = true; - save_v_event_T save_v_event; - dict_T *dict = get_v_event(&save_v_event); char firstcbuf[2]; firstcbuf[0] = (char)(firstc > 0 ? firstc : '-'); firstcbuf[1] = 0; if (has_event(EVENT_CMDLINEENTER)) { + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); + // set v:event to a dictionary with information about the commandline tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf); tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level); @@ -770,7 +773,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init tl_ret = try_leave(&tstate, &err); if (!tl_ret && ERROR_SET(&err)) { msg_putchar('\n'); - msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + msg_scroll = true; + msg_puts_attr(err.msg, HL_ATTR(HLF_E)|MSG_HIST); api_clear_error(&err); redrawcmd(); } @@ -791,11 +795,11 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init // Redraw the statusline in case it uses the current mode using the mode() // function. - if (!cmd_silent && msg_scrolled == 0) { + if (!cmd_silent) { bool found_one = false; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (*p_stl != NUL || *wp->w_p_stl != NUL) { + if (*p_stl != NUL || *wp->w_p_stl != NUL || *p_wbr != NUL || *wp->w_p_wbr != NUL) { wp->w_redr_status = true; found_one = true; } @@ -819,6 +823,9 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init state_enter(&s->state); if (has_event(EVENT_CMDLINELEAVE)) { + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); + tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf); tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level); tv_dict_set_keys_readonly(dict); @@ -852,7 +859,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init s->histype == HIST_SEARCH ? s->firstc : NUL); if (s->firstc == ':') { xfree(new_last_cmdline); - new_last_cmdline = vim_strsave(ccline.cmdbuff); + new_last_cmdline = xstrdup(ccline.cmdbuff); } } @@ -865,12 +872,18 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init // If the line is too long, clear it, so ruler and shown command do // not get printed in the middle of it. msg_check(); + if (p_ch == 0 && !ui_has(kUIMessages)) { + if (must_redraw < UPD_VALID) { + must_redraw = UPD_VALID; + } + } msg_scroll = s->save_msg_scroll; redir_off = false; if (!tl_ret && ERROR_SET(&err)) { msg_putchar('\n'); - msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + emsg(err.msg); + did_emsg = false; api_clear_error(&err); } @@ -879,11 +892,11 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init need_wait_return = false; } - set_string_option_direct("icm", -1, (char *)s->save_p_icm, OPT_FREE, SID_NONE); + set_string_option_direct("icm", -1, s->save_p_icm, OPT_FREE, SID_NONE); State = s->save_State; if (cmdpreview != save_cmdpreview) { cmdpreview = save_cmdpreview; // restore preview state - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } may_trigger_modechanged(); setmouse(); @@ -895,7 +908,7 @@ theend: xfree(ccline.last_colors.cmdbuff); kv_destroy(ccline.last_colors.colors); - char_u *p = ccline.cmdbuff; + char *p = ccline.cmdbuff; if (ui_has(kUICmdline)) { ui_call_cmdline_hide(ccline.level); @@ -910,18 +923,7 @@ theend: ccline.cmdbuff = NULL; } - if (cmdheight0) { - made_cmdheight_nonzero = true; - - // Restore cmdheight - set_option_value("ch", 0L, NULL, 0); - // Redraw is needed for command line completion - redraw_all_later(CLEAR); - - made_cmdheight_nonzero = false; - } - - return p; + return (uint8_t *)p; } static int command_line_check(VimState *state) @@ -940,6 +942,191 @@ static int command_line_check(VimState *state) return 1; } +/// Handle CTRL-\ pressed in Command-line mode: +/// - CTRL-\ CTRL-N or CTRL-\ CTRL-G goes to Normal mode. +/// - CTRL-\ e prompts for an expression. +static int command_line_handle_ctrl_bsl(CommandLineState *s) +{ + no_mapping++; + allow_keys++; + s->c = plain_vgetc(); + no_mapping--; + allow_keys--; + + // CTRL-\ e doesn't work when obtaining an expression, unless it + // is in a mapping. + if (s->c != Ctrl_N + && s->c != Ctrl_G + && (s->c != 'e' + || (ccline.cmdfirstc == '=' && KeyTyped) + || cmdline_star > 0)) { + vungetc(s->c); + return PROCESS_NEXT_KEY; + } + + if (s->c == 'e') { + // Replace the command line with the result of an expression. + // This will call getcmdline() recursively in get_expr_register(). + if (ccline.cmdpos == ccline.cmdlen) { + new_cmdpos = 99999; // keep it at the end + } else { + new_cmdpos = ccline.cmdpos; + } + + s->c = get_expr_register(); + if (s->c == '=') { + // Evaluate the expression. Set "textlock" to avoid nasty things + // like going to another buffer. + textlock++; + char *p = get_expr_line(); + textlock--; + + if (p != NULL) { + int len = (int)strlen(p); + realloc_cmdbuff(len + 1); + ccline.cmdlen = len; + STRCPY(ccline.cmdbuff, p); + xfree(p); + + // Restore the cursor or use the position set with + // set_cmdline_pos(). + if (new_cmdpos > ccline.cmdlen) { + ccline.cmdpos = ccline.cmdlen; + } else { + ccline.cmdpos = new_cmdpos; + } + + KeyTyped = false; // Don't do p_wc completion. + redrawcmd(); + return CMDLINE_CHANGED; + } + } + beep_flush(); + got_int = false; // don't abandon the command line + did_emsg = false; + emsg_on_display = false; + redrawcmd(); + return CMDLINE_NOT_CHANGED; + } + + s->gotesc = true; // will free ccline.cmdbuff after putting it in history + return GOTO_NORMAL_MODE; +} + +/// Completion for 'wildchar' or 'wildcharm' key. +/// - hitting <ESC> twice means: abandon command line. +/// - wildcard expansion is only done when the 'wildchar' key is really +/// typed, not when it comes from a macro +/// @return CMDLINE_CHANGED if command line is changed or CMDLINE_NOT_CHANGED. +static int command_line_wildchar_complete(CommandLineState *s) +{ + int res; + int options = WILD_NO_BEEP; + if (wim_flags[s->wim_index] & WIM_BUFLASTUSED) { + options |= WILD_BUFLASTUSED; + } + if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice + // if 'wildmode' contains "list" may still need to list + if (s->xpc.xp_numfiles > 1 + && !s->did_wild_list + && ((wim_flags[s->wim_index] & WIM_LIST) + || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0))) { + (void)showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + redrawcmd(); + s->did_wild_list = true; + } + + if (wim_flags[s->wim_index] & WIM_LONGEST) { + res = nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); + } else if (wim_flags[s->wim_index] & WIM_FULL) { + res = nextwild(&s->xpc, WILD_NEXT, options, s->firstc != '@'); + } else { + res = OK; // don't insert 'wildchar' now + } + } else { // typed p_wc first time + s->wim_index = 0; + int j = ccline.cmdpos; + + // if 'wildmode' first contains "longest", get longest + // common part + if (wim_flags[0] & WIM_LONGEST) { + res = nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); + } else { + res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, s->firstc != '@'); + } + + // if interrupted while completing, behave like it failed + if (got_int) { + (void)vpeekc(); // remove <C-C> from input stream + got_int = false; // don't abandon the command line + (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); + s->xpc.xp_context = EXPAND_NOTHING; + return CMDLINE_CHANGED; + } + + // when more than one match, and 'wildmode' first contains + // "list", or no change and 'wildmode' contains "longest,list", + // list all matches + if (res == OK && s->xpc.xp_numfiles > 1) { + // a "longest" that didn't do anything is skipped (but not + // "list:longest") + if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) { + s->wim_index = 1; + } + if ((wim_flags[s->wim_index] & WIM_LIST) + || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0)) { + if (!(wim_flags[0] & WIM_LONGEST)) { + int p_wmnu_save = p_wmnu; + p_wmnu = 0; + // remove match + nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); + p_wmnu = p_wmnu_save; + } + + (void)showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + redrawcmd(); + s->did_wild_list = true; + + if (wim_flags[s->wim_index] & WIM_LONGEST) { + nextwild(&s->xpc, WILD_LONGEST, options, s->firstc != '@'); + } else if (wim_flags[s->wim_index] & WIM_FULL) { + nextwild(&s->xpc, WILD_NEXT, options, s->firstc != '@'); + } + } else { + vim_beep(BO_WILD); + } + } else if (s->xpc.xp_numfiles == -1) { + s->xpc.xp_context = EXPAND_NOTHING; + } + } + + if (s->wim_index < 3) { + s->wim_index++; + } + + if (s->c == ESC) { + s->gotesc = true; + } + + return (res == OK) ? CMDLINE_CHANGED : CMDLINE_NOT_CHANGED; +} + +static void command_line_end_wildmenu(CommandLineState *s) +{ + if (cmdline_pum_active()) { + cmdline_pum_remove(); + } + if (s->xpc.xp_numfiles != -1) { + (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); + } + s->did_wild_list = false; + if (!p_wmnu || (s->c != K_UP && s->c != K_DOWN)) { + s->xpc.xp_context = EXPAND_NOTHING; + } + s->wim_index = 0; + wildmenu_cleanup(&ccline); +} + static int command_line_execute(VimState *state, int key) { if (key == K_IGNORE || key == K_NOP) { @@ -958,6 +1145,19 @@ static int command_line_execute(VimState *state, int key) map_execute_lua(); } + // nvim_select_popupmenu_item() can be called from the handling of + // K_EVENT, K_COMMAND, or K_LUA. + if (pum_want.active) { + if (cmdline_pum_active()) { + nextwild(&s->xpc, WILD_PUM_WANT, 0, s->firstc != '@'); + if (pum_want.finish) { + nextwild(&s->xpc, WILD_APPLY, WILD_NO_BEEP, s->firstc != '@'); + command_line_end_wildmenu(s); + } + } + pum_want.active = false; + } + if (!cmdline_was_last_drawn) { redrawcmdline(); } @@ -1026,100 +1226,49 @@ static int command_line_execute(VimState *state, int key) } if (cmdline_pum_active() || s->did_wild_list) { + // Ctrl-Y: Accept the current selection and close the popup menu. + // Ctrl-E: cancel the cmdline popup menu and return the original text. if (s->c == Ctrl_E || s->c == Ctrl_Y) { const int wild_type = (s->c == Ctrl_E) ? WILD_CANCEL : WILD_APPLY; - s->res = nextwild(&s->xpc, wild_type, WILD_NO_BEEP, s->firstc != '@'); + (void)nextwild(&s->xpc, wild_type, WILD_NO_BEEP, s->firstc != '@'); s->c = Ctrl_E; } } + // The wildmenu is cleared if the pressed key is not used for + // navigating the wild menu (i.e. the key is not 'wildchar' or + // 'wildcharm' or Ctrl-N or Ctrl-P or Ctrl-A or Ctrl-L). + // If the popup menu is displayed, then PageDown and PageUp keys are + // also used to navigate the menu. + bool end_wildmenu = (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_Z + && s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A + && s->c != Ctrl_L); + end_wildmenu = end_wildmenu && (!cmdline_pum_active() + || (s->c != K_PAGEDOWN && s->c != K_PAGEUP + && s->c != K_KPAGEDOWN && s->c != K_KPAGEUP)); + // free expanded names when finished walking through matches - if (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_Z - && s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A - && s->c != Ctrl_L) { - if (cmdline_pum_active()) { - cmdline_pum_remove(); - } - if (s->xpc.xp_numfiles != -1) { - (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); - } - s->did_wild_list = false; - if (!p_wmnu || (s->c != K_UP && s->c != K_DOWN)) { - s->xpc.xp_context = EXPAND_NOTHING; - } - s->wim_index = 0; - wildmenu_cleanup(&ccline); + if (end_wildmenu) { + command_line_end_wildmenu(s); } if (p_wmnu) { s->c = wildmenu_process_key(&ccline, s->c, &s->xpc); } - // CTRL-\ CTRL-N goes to Normal mode, CTRL-\ e prompts for an expression. + // CTRL-\ CTRL-N or CTRL-\ CTRL-G goes to Normal mode, + // CTRL-\ e prompts for an expression. if (s->c == Ctrl_BSL) { - no_mapping++; - allow_keys++; - s->c = plain_vgetc(); - no_mapping--; - allow_keys--; - // CTRL-\ e doesn't work when obtaining an expression, unless it - // is in a mapping. - if (s->c != Ctrl_N - && s->c != Ctrl_G - && (s->c != 'e' - || (ccline.cmdfirstc == '=' && KeyTyped) - || cmdline_star > 0)) { - vungetc(s->c); - s->c = Ctrl_BSL; - } else if (s->c == 'e') { - char_u *p = NULL; - int len; - - // Replace the command line with the result of an expression. - // Need to save and restore the current command line, to be - // able to enter a new one... - if (ccline.cmdpos == ccline.cmdlen) { - new_cmdpos = 99999; // keep it at the end - } else { - new_cmdpos = ccline.cmdpos; - } - - s->c = get_expr_register(); - if (s->c == '=') { - textlock++; - p = get_expr_line(); - textlock--; - - if (p != NULL) { - len = (int)STRLEN(p); - realloc_cmdbuff(len + 1); - ccline.cmdlen = len; - STRCPY(ccline.cmdbuff, p); - xfree(p); - - // Restore the cursor or use the position set with - // set_cmdline_pos(). - if (new_cmdpos > ccline.cmdlen) { - ccline.cmdpos = ccline.cmdlen; - } else { - ccline.cmdpos = new_cmdpos; - } - - KeyTyped = false; // Don't do p_wc completion. - redrawcmd(); - return command_line_changed(s); - } - } - beep_flush(); - got_int = false; // don't abandon the command line - did_emsg = false; - emsg_on_display = false; - redrawcmd(); + switch (command_line_handle_ctrl_bsl(s)) { + case CMDLINE_CHANGED: + return command_line_changed(s); + case CMDLINE_NOT_CHANGED: return command_line_not_changed(s); - } else { - s->gotesc = true; // will free ccline.cmdbuff after putting it - // in history - return 0; // back to Normal mode + case GOTO_NORMAL_MODE: + return 0; // back to cmd mode + default: + s->c = Ctrl_BSL; // backslash key not processed by + // command_line_handle_ctrl_bsl() } } @@ -1167,107 +1316,8 @@ static int command_line_execute(VimState *state, int key) } // Completion for 'wildchar' or 'wildcharm' key. - // - hitting <ESC> twice means: abandon command line. - // - wildcard expansion is only done when the 'wildchar' key is really - // typed, not when it comes from a macro - if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm - || s->c == Ctrl_Z) { - int options = WILD_NO_BEEP; - if (wim_flags[s->wim_index] & WIM_BUFLASTUSED) { - options |= WILD_BUFLASTUSED; - } - if (s->xpc.xp_numfiles > 0) { // typed p_wc at least twice - // if 'wildmode' contains "list" may still need to list - if (s->xpc.xp_numfiles > 1 - && !s->did_wild_list - && ((wim_flags[s->wim_index] & WIM_LIST) - || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0))) { - (void)showmatches(&s->xpc, p_wmnu - && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); - redrawcmd(); - s->did_wild_list = true; - } - - if (wim_flags[s->wim_index] & WIM_LONGEST) { - s->res = nextwild(&s->xpc, WILD_LONGEST, options, - s->firstc != '@'); - } else if (wim_flags[s->wim_index] & WIM_FULL) { - s->res = nextwild(&s->xpc, WILD_NEXT, options, - s->firstc != '@'); - } else { - s->res = OK; // don't insert 'wildchar' now - } - } else { // typed p_wc first time - s->wim_index = 0; - int j = ccline.cmdpos; - - // if 'wildmode' first contains "longest", get longest - // common part - if (wim_flags[0] & WIM_LONGEST) { - s->res = nextwild(&s->xpc, WILD_LONGEST, options, - s->firstc != '@'); - } else { - s->res = nextwild(&s->xpc, WILD_EXPAND_KEEP, options, - s->firstc != '@'); - } - - // if interrupted while completing, behave like it failed - if (got_int) { - (void)vpeekc(); // remove <C-C> from input stream - got_int = false; // don't abandon the command line - (void)ExpandOne(&s->xpc, NULL, NULL, 0, WILD_FREE); - s->xpc.xp_context = EXPAND_NOTHING; - return command_line_changed(s); - } - - // when more than one match, and 'wildmode' first contains - // "list", or no change and 'wildmode' contains "longest,list", - // list all matches - if (s->res == OK && s->xpc.xp_numfiles > 1) { - // a "longest" that didn't do anything is skipped (but not - // "list:longest") - if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) { - s->wim_index = 1; - } - if ((wim_flags[s->wim_index] & WIM_LIST) - || (p_wmnu && (wim_flags[s->wim_index] & WIM_FULL) != 0)) { - if (!(wim_flags[0] & WIM_LONGEST)) { - int p_wmnu_save = p_wmnu; - p_wmnu = 0; - // remove match - nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); - p_wmnu = p_wmnu_save; - } - - (void)showmatches(&s->xpc, p_wmnu - && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); - redrawcmd(); - s->did_wild_list = true; - - if (wim_flags[s->wim_index] & WIM_LONGEST) { - nextwild(&s->xpc, WILD_LONGEST, options, - s->firstc != '@'); - } else if (wim_flags[s->wim_index] & WIM_FULL) { - nextwild(&s->xpc, WILD_NEXT, options, - s->firstc != '@'); - } - } else { - vim_beep(BO_WILD); - } - } else if (s->xpc.xp_numfiles == -1) { - s->xpc.xp_context = EXPAND_NOTHING; - } - } - - if (s->wim_index < 3) { - ++s->wim_index; - } - - if (s->c == ESC) { - s->gotesc = true; - } - - if (s->res == OK) { + if ((s->c == p_wc && !s->gotesc && KeyTyped) || s->c == p_wcm || s->c == Ctrl_Z) { + if (command_line_wildchar_complete(s) == CMDLINE_CHANGED) { return command_line_changed(s); } } @@ -1277,8 +1327,11 @@ static int command_line_execute(VimState *state, int key) // <S-Tab> goes to last match, in a clumsy way if (s->c == K_S_TAB && KeyTyped) { if (nextwild(&s->xpc, WILD_EXPAND_KEEP, 0, s->firstc != '@') == OK) { - // Trigger the popup menu when wildoptions=pum - showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + if (s->xpc.xp_numfiles > 1 + && ((!s->did_wild_list && (wim_flags[s->wim_index] & WIM_LIST)) || p_wmnu)) { + // Trigger the popup menu when wildoptions=pum + showmatches(&s->xpc, p_wmnu && ((wim_flags[s->wim_index] & WIM_LIST) == 0)); + } nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); nextwild(&s->xpc, WILD_PREV, 0, s->firstc != '@'); return command_line_changed(s); @@ -1321,9 +1374,9 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_ ui_flush(); pos_T t; - char_u *pat; + char *pat; int search_flags = SEARCH_NOOF; - char_u save; + char save; if (search_delim == ccline.cmdbuff[skiplen]) { pat = last_search_pattern(); @@ -1332,7 +1385,7 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_ return FAIL; } skiplen = 0; - patlen = (int)STRLEN(pat); + patlen = (int)strlen(pat); } else { pat = ccline.cmdbuff + skiplen; } @@ -1395,7 +1448,8 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_ validate_cursor(); highlight_match = true; save_viewstate(curwin, &s->old_viewstate); - update_screen(NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); + update_screen(); highlight_match = false; redrawcmdline(); curwin->w_cursor = s->match_end; @@ -1406,9 +1460,202 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_ return FAIL; } +/// Handle backspace, delete and CTRL-W keys in the command-line mode. +static int command_line_erase_chars(CommandLineState *s) +{ + if (s->c == K_KDEL) { + s->c = K_DEL; + } + + // Delete current character is the same as backspace on next + // character, except at end of line + if (s->c == K_DEL && ccline.cmdpos != ccline.cmdlen) { + ccline.cmdpos++; + } + + if (s->c == K_DEL) { + ccline.cmdpos += mb_off_next(ccline.cmdbuff, + ccline.cmdbuff + ccline.cmdpos); + } + + if (ccline.cmdpos > 0) { + char *p; + + int j = ccline.cmdpos; + p = mb_prevptr(ccline.cmdbuff, ccline.cmdbuff + j); + + if (s->c == Ctrl_W) { + while (p > ccline.cmdbuff && ascii_isspace(*p)) { + p = mb_prevptr(ccline.cmdbuff, p); + } + + int i = mb_get_class(p); + while (p > ccline.cmdbuff && mb_get_class(p) == i) { + p = mb_prevptr(ccline.cmdbuff, p); + } + + if (mb_get_class(p) != i) { + p += utfc_ptr2len(p); + } + } + + ccline.cmdpos = (int)(p - ccline.cmdbuff); + ccline.cmdlen -= j - ccline.cmdpos; + int i = ccline.cmdpos; + + while (i < ccline.cmdlen) { + ccline.cmdbuff[i++] = ccline.cmdbuff[j++]; + } + + // Truncate at the end, required for multi-byte chars. + ccline.cmdbuff[ccline.cmdlen] = NUL; + if (ccline.cmdlen == 0) { + s->is_state.search_start = s->is_state.save_cursor; + // save view settings, so that the screen won't be restored at the + // wrong position + s->is_state.old_viewstate = s->is_state.init_viewstate; + } + redrawcmd(); + } else if (ccline.cmdlen == 0 && s->c != Ctrl_W + && ccline.cmdprompt == NULL && s->indent == 0) { + // In ex and debug mode it doesn't make sense to return. + if (exmode_active || ccline.cmdfirstc == '>') { + return CMDLINE_NOT_CHANGED; + } + + XFREE_CLEAR(ccline.cmdbuff); // no commandline to return + if (!cmd_silent && !ui_has(kUICmdline)) { + if (cmdmsg_rl) { + msg_col = Columns; + } else { + msg_col = 0; + } + msg_putchar(' '); // delete ':' + } + s->is_state.search_start = s->is_state.save_cursor; + redraw_cmdline = true; + return GOTO_NORMAL_MODE; + } + return CMDLINE_CHANGED; +} + +/// Handle the CTRL-^ key in the command-line mode and toggle the use of the +/// language :lmap mappings and/or Input Method. +static void command_line_toggle_langmap(CommandLineState *s) +{ + if (map_to_exists_mode("", MODE_LANGMAP, false)) { + // ":lmap" mappings exists, toggle use of mappings. + State ^= MODE_LANGMAP; + if (s->b_im_ptr != NULL) { + if (State & MODE_LANGMAP) { + *s->b_im_ptr = B_IMODE_LMAP; + } else { + *s->b_im_ptr = B_IMODE_NONE; + } + } + } + + if (s->b_im_ptr != NULL) { + if (s->b_im_ptr == &curbuf->b_p_iminsert) { + set_iminsert_global(curbuf); + } else { + set_imsearch_global(curbuf); + } + } + ui_cursor_shape(); // may show different cursor shape + // Show/unshow value of 'keymap' in status lines later. + status_redraw_curbuf(); +} + +/// Handle the CTRL-R key in the command-line mode and insert the contents of a +/// numbered or named register. +static int command_line_insert_reg(CommandLineState *s) +{ + const int save_new_cmdpos = new_cmdpos; + + putcmdline('"', true); + no_mapping++; + allow_keys++; + int i = s->c = plain_vgetc(); // CTRL-R <char> + if (i == Ctrl_O) { + i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R + } + + if (i == Ctrl_R) { + s->c = plain_vgetc(); // CTRL-R CTRL-R <char> + } + no_mapping--; + allow_keys--; + // Insert the result of an expression. + new_cmdpos = -1; + if (s->c == '=') { + if (ccline.cmdfirstc == '=' // can't do this recursively + || cmdline_star > 0) { // or when typing a password + beep_flush(); + s->c = ESC; + } else { + s->c = get_expr_register(); + } + } + + if (s->c != ESC) { // use ESC to cancel inserting register + cmdline_paste(s->c, i == Ctrl_R, false); + + // When there was a serious error abort getting the + // command line. + if (aborting()) { + s->gotesc = true; // will free ccline.cmdbuff after + // putting it in history + return GOTO_NORMAL_MODE; + } + KeyTyped = false; // Don't do p_wc completion. + if (new_cmdpos >= 0) { + // set_cmdline_pos() was used + if (new_cmdpos > ccline.cmdlen) { + ccline.cmdpos = ccline.cmdlen; + } else { + ccline.cmdpos = new_cmdpos; + } + } + } + new_cmdpos = save_new_cmdpos; + + // remove the double quote + ccline.special_char = NUL; + redrawcmd(); + + // The text has been stuffed, the command line didn't change yet. + return CMDLINE_NOT_CHANGED; +} + +/// Handle the Left and Right mouse clicks in the command-line mode. +static void command_line_left_right_mouse(CommandLineState *s) +{ + if (s->c == K_LEFTRELEASE || s->c == K_RIGHTRELEASE) { + s->ignore_drag_release = true; + } else { + s->ignore_drag_release = false; + } + + ccline.cmdspos = cmd_startcol(); + for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen; + ccline.cmdpos++) { + int cells = cmdline_charsize(ccline.cmdpos); + if (mouse_row <= cmdline_row + ccline.cmdspos / Columns + && mouse_col < ccline.cmdspos % Columns + cells) { + break; + } + + // Count ">" for double-wide char that doesn't fit. + correct_screencol(ccline.cmdpos, cells, &ccline.cmdspos); + ccline.cmdpos += utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos) - 1; + ccline.cmdspos += cells; + } +} + static void command_line_next_histidx(CommandLineState *s, bool next_match) { - int j = (int)STRLEN(s->lookfor); + int j = (int)strlen(s->lookfor); for (;;) { // one step backwards if (!next_match) { @@ -1451,96 +1698,118 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match) if ((s->c != K_UP && s->c != K_DOWN) || s->hiscnt == s->save_hiscnt - || STRNCMP(get_histentry(s->histype)[s->hiscnt].hisstr, + || strncmp(get_histentry(s->histype)[s->hiscnt].hisstr, s->lookfor, (size_t)j) == 0) { break; } } } -static int command_line_handle_key(CommandLineState *s) +/// Handle the Up, Down, Page Up, Page down, CTRL-N and CTRL-P key in the +/// command-line mode. +static int command_line_browse_history(CommandLineState *s) { - // Big switch for a typed command line character. - switch (s->c) { - case K_BS: - case Ctrl_H: - case K_DEL: - case K_KDEL: - case Ctrl_W: - if (s->c == K_KDEL) { - s->c = K_DEL; - } + if (s->histype == HIST_INVALID || get_hislen() == 0 || s->firstc == NUL) { + // no history + return CMDLINE_NOT_CHANGED; + } - // delete current character is the same as backspace on next - // character, except at end of line - if (s->c == K_DEL && ccline.cmdpos != ccline.cmdlen) { - ++ccline.cmdpos; - } + s->save_hiscnt = s->hiscnt; - if (s->c == K_DEL) { - ccline.cmdpos += mb_off_next(ccline.cmdbuff, - ccline.cmdbuff + ccline.cmdpos); - } + // save current command string so it can be restored later + if (s->lookfor == NULL) { + s->lookfor = xstrdup(ccline.cmdbuff); + s->lookfor[ccline.cmdpos] = NUL; + } - if (ccline.cmdpos > 0) { - char_u *p; + bool next_match = (s->c == K_DOWN || s->c == K_S_DOWN || s->c == Ctrl_N + || s->c == K_PAGEDOWN || s->c == K_KPAGEDOWN); + command_line_next_histidx(s, next_match); - int j = ccline.cmdpos; - p = mb_prevptr(ccline.cmdbuff, ccline.cmdbuff + j); + if (s->hiscnt != s->save_hiscnt) { + // jumped to other entry + char *p; + int len = 0; + int old_firstc; - if (s->c == Ctrl_W) { - while (p > ccline.cmdbuff && ascii_isspace(*p)) { - p = mb_prevptr(ccline.cmdbuff, p); - } + XFREE_CLEAR(ccline.cmdbuff); + s->xpc.xp_context = EXPAND_NOTHING; + if (s->hiscnt == get_hislen()) { + p = s->lookfor; // back to the old one + } else { + p = get_histentry(s->histype)[s->hiscnt].hisstr; + } + + if (s->histype == HIST_SEARCH + && p != s->lookfor + && (old_firstc = (uint8_t)p[strlen(p) + 1]) != s->firstc) { + // Correct for the separator character used when + // adding the history entry vs the one used now. + // First loop: count length. + // Second loop: copy the characters. + for (int i = 0; i <= 1; i++) { + len = 0; + for (int j = 0; p[j] != NUL; j++) { + // Replace old sep with new sep, unless it is + // escaped. + if (p[j] == old_firstc + && (j == 0 || p[j - 1] != '\\')) { + if (i > 0) { + ccline.cmdbuff[len] = (char)s->firstc; + } + } else { + // Escape new sep, unless it is already + // escaped. + if (p[j] == s->firstc + && (j == 0 || p[j - 1] != '\\')) { + if (i > 0) { + ccline.cmdbuff[len] = '\\'; + } + len++; + } - int i = mb_get_class(p); - while (p > ccline.cmdbuff && mb_get_class(p) == i) { - p = mb_prevptr(ccline.cmdbuff, p); + if (i > 0) { + ccline.cmdbuff[len] = p[j]; + } + } + len++; } - if (mb_get_class(p) != i) { - p += utfc_ptr2len((char *)p); + if (i == 0) { + alloc_cmdbuff(len); } } + ccline.cmdbuff[len] = NUL; + } else { + alloc_cmdbuff((int)strlen(p)); + STRCPY(ccline.cmdbuff, p); + } - ccline.cmdpos = (int)(p - ccline.cmdbuff); - ccline.cmdlen -= j - ccline.cmdpos; - int i = ccline.cmdpos; - - while (i < ccline.cmdlen) { - ccline.cmdbuff[i++] = ccline.cmdbuff[j++]; - } - - // Truncate at the end, required for multi-byte chars. - ccline.cmdbuff[ccline.cmdlen] = NUL; - if (ccline.cmdlen == 0) { - s->is_state.search_start = s->is_state.save_cursor; - // save view settings, so that the screen won't be restored at the - // wrong position - s->is_state.old_viewstate = s->is_state.init_viewstate; - } - redrawcmd(); - } else if (ccline.cmdlen == 0 && s->c != Ctrl_W - && ccline.cmdprompt == NULL && s->indent == 0) { - // In ex and debug mode it doesn't make sense to return. - if (exmode_active || ccline.cmdfirstc == '>') { - return command_line_not_changed(s); - } + ccline.cmdpos = ccline.cmdlen = (int)strlen(ccline.cmdbuff); + redrawcmd(); + return CMDLINE_CHANGED; + } + beep_flush(); + return CMDLINE_NOT_CHANGED; +} - XFREE_CLEAR(ccline.cmdbuff); // no commandline to return - if (!cmd_silent && !ui_has(kUICmdline)) { - if (cmdmsg_rl) { - msg_col = Columns; - } else { - msg_col = 0; - } - msg_putchar(' '); // delete ':' - } - s->is_state.search_start = s->is_state.save_cursor; - redraw_cmdline = true; - return 0; // back to cmd mode +static int command_line_handle_key(CommandLineState *s) +{ + // Big switch for a typed command line character. + switch (s->c) { + case K_BS: + case Ctrl_H: + case K_DEL: + case K_KDEL: + case Ctrl_W: + switch (command_line_erase_chars(s)) { + case CMDLINE_NOT_CHANGED: + return command_line_not_changed(s); + case GOTO_NORMAL_MODE: + return 0; // back to cmd mode + default: + return command_line_changed(s); } - return command_line_changed(s); case K_INS: case K_KINS: @@ -1550,28 +1819,7 @@ static int command_line_handle_key(CommandLineState *s) return command_line_not_changed(s); case Ctrl_HAT: - if (map_to_exists_mode("", MODE_LANGMAP, false)) { - // ":lmap" mappings exists, toggle use of mappings. - State ^= MODE_LANGMAP; - if (s->b_im_ptr != NULL) { - if (State & MODE_LANGMAP) { - *s->b_im_ptr = B_IMODE_LMAP; - } else { - *s->b_im_ptr = B_IMODE_NONE; - } - } - } - - if (s->b_im_ptr != NULL) { - if (s->b_im_ptr == &curbuf->b_p_iminsert) { - set_iminsert_global(); - } else { - set_imsearch_global(); - } - } - ui_cursor_shape(); // may show different cursor shape - // Show/unshow value of 'keymap' in status lines later. - status_redraw_curbuf(); + command_line_toggle_langmap(s); return command_line_not_changed(s); case Ctrl_U: { @@ -1607,58 +1855,15 @@ static int command_line_handle_key(CommandLineState *s) // putting it in history return 0; // back to cmd mode - case Ctrl_R: { // insert register - putcmdline('"', true); - no_mapping++; - allow_keys++; - int i = s->c = plain_vgetc(); // CTRL-R <char> - if (i == Ctrl_O) { - i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R - } - - if (i == Ctrl_R) { - s->c = plain_vgetc(); // CTRL-R CTRL-R <char> - } - no_mapping--; - allow_keys--; - // Insert the result of an expression. - // Need to save the current command line, to be able to enter - // a new one... - new_cmdpos = -1; - if (s->c == '=') { - if (ccline.cmdfirstc == '=' // can't do this recursively - || cmdline_star > 0) { // or when typing a password - beep_flush(); - s->c = ESC; - } else { - s->c = get_expr_register(); - } - } - - if (s->c != ESC) { // use ESC to cancel inserting register - cmdline_paste(s->c, i == Ctrl_R, false); - - // When there was a serious error abort getting the - // command line. - if (aborting()) { - s->gotesc = true; // will free ccline.cmdbuff after - // putting it in history - return 0; // back to cmd mode - } - KeyTyped = false; // Don't do p_wc completion. - if (new_cmdpos >= 0) { - // set_cmdline_pos() was used - if (new_cmdpos > ccline.cmdlen) { - ccline.cmdpos = ccline.cmdlen; - } else { - ccline.cmdpos = new_cmdpos; - } - } + case Ctrl_R: // insert register + switch (command_line_insert_reg(s)) { + case CMDLINE_NOT_CHANGED: + return command_line_not_changed(s); + case GOTO_NORMAL_MODE: + return 0; // back to cmd mode + default: + return command_line_changed(s); } - ccline.special_char = NUL; - redrawcmd(); - return command_line_changed(s); - } case Ctrl_D: if (showmatches(&s->xpc, false) == EXPAND_NOTHING) { @@ -1683,7 +1888,7 @@ static int command_line_handle_key(CommandLineState *s) } ccline.cmdspos += cells; - ccline.cmdpos += utfc_ptr2len((char *)ccline.cmdbuff + ccline.cmdpos); + ccline.cmdpos += utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos); } while ((s->c == K_S_RIGHT || s->c == K_C_RIGHT || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) && ccline.cmdbuff[ccline.cmdpos] != ' '); @@ -1739,26 +1944,7 @@ static int command_line_handle_key(CommandLineState *s) FALLTHROUGH; case K_LEFTMOUSE: case K_RIGHTMOUSE: - if (s->c == K_LEFTRELEASE || s->c == K_RIGHTRELEASE) { - s->ignore_drag_release = true; - } else { - s->ignore_drag_release = false; - } - - ccline.cmdspos = cmd_startcol(); - for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen; - ccline.cmdpos++) { - int cells = cmdline_charsize(ccline.cmdpos); - if (mouse_row <= cmdline_row + ccline.cmdspos / Columns - && mouse_col < ccline.cmdspos % Columns + cells) { - break; - } - - // Count ">" for double-wide char that doesn't fit. - correct_screencol(ccline.cmdpos, cells, &ccline.cmdspos); - ccline.cmdpos += utfc_ptr2len((char *)ccline.cmdbuff + ccline.cmdpos) - 1; - ccline.cmdspos += cells; - } + command_line_left_right_mouse(s); return command_line_not_changed(s); // Mouse scroll wheel: ignored here @@ -1798,13 +1984,17 @@ static int command_line_handle_key(CommandLineState *s) return command_line_not_changed(s); case Ctrl_A: // all matches - if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL) { - break; - } if (cmdline_pum_active()) { + // As Ctrl-A completes all the matches, close the popup + // menu (if present) cmdline_pum_cleanup(&ccline); - s->xpc.xp_context = EXPAND_NOTHING; } + + if (nextwild(&s->xpc, WILD_ALL, 0, s->firstc != '@') == FAIL) { + break; + } + s->xpc.xp_context = EXPAND_NOTHING; + s->did_wild_list = false; return command_line_changed(s); case Ctrl_L: @@ -1821,8 +2011,8 @@ static int command_line_handle_key(CommandLineState *s) case Ctrl_N: // next match case Ctrl_P: // previous match if (s->xpc.xp_numfiles > 0) { - if (nextwild(&s->xpc, (s->c == Ctrl_P) ? WILD_PREV : WILD_NEXT, - 0, s->firstc != '@') == FAIL) { + const int wild_type = (s->c == Ctrl_P) ? WILD_PREV : WILD_NEXT; + if (nextwild(&s->xpc, wild_type, 0, s->firstc != '@') == FAIL) { break; } return command_line_not_changed(s); @@ -1837,88 +2027,27 @@ static int command_line_handle_key(CommandLineState *s) case K_KPAGEUP: case K_PAGEDOWN: case K_KPAGEDOWN: - if (s->histype == HIST_INVALID || get_hislen() == 0 || s->firstc == NUL) { - // no history - return command_line_not_changed(s); - } - - s->save_hiscnt = s->hiscnt; - - // save current command string so it can be restored later - if (s->lookfor == NULL) { - s->lookfor = vim_strsave(ccline.cmdbuff); - s->lookfor[ccline.cmdpos] = NUL; - } - - bool next_match = (s->c == K_DOWN || s->c == K_S_DOWN || s->c == Ctrl_N - || s->c == K_PAGEDOWN || s->c == K_KPAGEDOWN); - command_line_next_histidx(s, next_match); - - if (s->hiscnt != s->save_hiscnt) { - // jumped to other entry - char_u *p; - int len = 0; - int old_firstc; - - XFREE_CLEAR(ccline.cmdbuff); - s->xpc.xp_context = EXPAND_NOTHING; - if (s->hiscnt == get_hislen()) { - p = s->lookfor; // back to the old one - } else { - p = get_histentry(s->histype)[s->hiscnt].hisstr; + if (cmdline_pum_active() + && (s->c == K_PAGEUP || s->c == K_PAGEDOWN + || s->c == K_KPAGEUP || s->c == K_KPAGEDOWN)) { + // If the popup menu is displayed, then PageUp and PageDown + // are used to scroll the menu. + const int wild_type = + (s->c == K_PAGEDOWN || s->c == K_KPAGEDOWN) ? WILD_PAGEDOWN : WILD_PAGEUP; + if (nextwild(&s->xpc, wild_type, 0, s->firstc != '@') == FAIL) { + break; } - - if (s->histype == HIST_SEARCH - && p != s->lookfor - && (old_firstc = p[STRLEN(p) + 1]) != s->firstc) { - // Correct for the separator character used when - // adding the history entry vs the one used now. - // First loop: count length. - // Second loop: copy the characters. - for (int i = 0; i <= 1; i++) { - len = 0; - for (int j = 0; p[j] != NUL; j++) { - // Replace old sep with new sep, unless it is - // escaped. - if (p[j] == old_firstc - && (j == 0 || p[j - 1] != '\\')) { - if (i > 0) { - ccline.cmdbuff[len] = (char_u)s->firstc; - } - } else { - // Escape new sep, unless it is already - // escaped. - if (p[j] == s->firstc - && (j == 0 || p[j - 1] != '\\')) { - if (i > 0) { - ccline.cmdbuff[len] = '\\'; - } - len++; - } - - if (i > 0) { - ccline.cmdbuff[len] = p[j]; - } - } - len++; - } - - if (i == 0) { - alloc_cmdbuff(len); - } - } - ccline.cmdbuff[len] = NUL; - } else { - alloc_cmdbuff((int)STRLEN(p)); - STRCPY(ccline.cmdbuff, p); + return command_line_not_changed(s); + } else { + switch (command_line_browse_history(s)) { + case CMDLINE_CHANGED: + return command_line_changed(s); + case GOTO_NORMAL_MODE: + return 0; + default: + return command_line_not_changed(s); } - - ccline.cmdpos = ccline.cmdlen = (int)STRLEN(ccline.cmdbuff); - redrawcmd(); - return command_line_changed(s); } - beep_flush(); - return command_line_not_changed(s); case Ctrl_G: // next match case Ctrl_T: // previous match @@ -1994,9 +2123,9 @@ static int command_line_handle_key(CommandLineState *s) // put the character in the command line if (IS_SPECIAL(s->c) || mod_mask != 0) { - put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true); + put_on_cmdline((char *)get_special_key_name(s->c, mod_mask), -1, true); } else { - int j = utf_char2bytes(s->c, (char *)IObuff); + int j = utf_char2bytes(s->c, IObuff); IObuff[j] = NUL; // exclude composing chars put_on_cmdline(IObuff, j, true); } @@ -2018,16 +2147,35 @@ static int command_line_not_changed(CommandLineState *s) /// Guess that the pattern matches everything. Only finds specific cases, such /// as a trailing \|, which can happen while typing a pattern. -static int empty_pattern(char_u *p) +static bool empty_pattern(char *p, int delim) { - size_t n = STRLEN(p); + size_t n = strlen(p); + magic_T magic_val = MAGIC_ON; - // remove trailing \v and the like - while (n >= 2 && p[n - 2] == '\\' - && vim_strchr("mMvVcCZ", p[n - 1]) != NULL) { - n -= 2; + if (n > 0) { + (void)skip_regexp_ex(p, delim, magic_isset(), NULL, NULL, &magic_val); + } else { + return true; } - return n == 0 || (n >= 2 && p[n - 2] == '\\' && p[n - 1] == '|'); + + return empty_pattern_magic(p, n, magic_val); +} + +static bool empty_pattern_magic(char *p, size_t len, magic_T magic_val) +{ + // remove trailing \v and the like + while (len >= 2 && p[len - 2] == '\\' + && vim_strchr("mMvVcCZ", (uint8_t)p[len - 1]) != NULL) { + len -= 2; + } + + // true, if the pattern is empty, or the pattern ends with \| and magic is + // set (or it ends with '|' and very magic is set) + return len == 0 || (len > 1 + && ((p[len - 2] == '\\' + && p[len - 1] == '|' && magic_val == MAGIC_ON) + || (p[len - 2] != '\\' + && p[len - 1] == '|' && magic_val == MAGIC_ALL))); } handle_T cmdpreview_get_bufnr(void) @@ -2147,6 +2295,7 @@ static void cmdpreview_prepare(CpInfo *cpinfo) CpBufInfo cp_bufinfo; cp_bufinfo.buf = buf; + cp_bufinfo.save_b_u_synced = buf->b_u_synced; cp_bufinfo.save_b_u_time_cur = buf->b_u_time_cur; cp_bufinfo.save_b_u_seq_cur = buf->b_u_seq_cur; cp_bufinfo.save_b_u_newhead = buf->b_u_newhead; @@ -2184,6 +2333,8 @@ static void cmdpreview_prepare(CpInfo *cpinfo) cmdmod.cmod_split = 0; // Disable :leftabove/botright modifiers cmdmod.cmod_tab = 0; // Disable :tab modifier cmdmod.cmod_flags |= CMOD_NOSWAPFILE; // Disable swap for preview buffer + + u_sync(true); } // Restore the state of buffers and windows before command preview. @@ -2206,7 +2357,7 @@ static void cmdpreview_restore_state(CpInfo *cpinfo) aco_save_T aco; aucmd_prepbuf(&aco, buf); // Undo invisibly. This also moves the cursor! - if (!u_undo_and_forget(count)) { + if (!u_undo_and_forget(count, false)) { abort(); } aucmd_restbuf(&aco); @@ -2216,6 +2367,11 @@ static void cmdpreview_restore_state(CpInfo *cpinfo) buf->b_u_newhead = cp_bufinfo.save_b_u_newhead; buf->b_u_time_cur = cp_bufinfo.save_b_u_time_cur; } + + if (buf->b_u_curhead == NULL) { + buf->b_u_synced = cp_bufinfo.save_b_u_synced; + } + if (cp_bufinfo.save_changedtick != buf_get_changedtick(buf)) { buf_set_changedtick(buf, cp_bufinfo.save_changedtick); } @@ -2272,7 +2428,7 @@ static bool cmdpreview_may_show(CommandLineState *s) CmdParseInfo cmdinfo; // Copy the command line so we can modify it. int cmdpreview_type = 0; - char *cmdline = xstrdup((char *)ccline.cmdbuff); + char *cmdline = xstrdup(ccline.cmdbuff); char *errormsg = NULL; emsg_off++; // Block errors when parsing the command line, and don't update v:errmsg if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) { @@ -2343,7 +2499,7 @@ static bool cmdpreview_may_show(CommandLineState *s) if (cmdpreview_type != 0) { int save_rd = RedrawingDisabled; RedrawingDisabled = 0; - update_screen(SOME_VALID); + update_screen(); RedrawingDisabled = save_rd; } @@ -2364,9 +2520,9 @@ end: return cmdpreview_type != 0; } -static int command_line_changed(CommandLineState *s) +/// Trigger CmdlineChanged autocommands. +static void do_autocmd_cmdlinechanged(int firstc) { - // Trigger CmdlineChanged autocommands. if (has_event(EVENT_CMDLINECHANGED)) { TryState tstate; Error err = ERROR_INIT; @@ -2374,7 +2530,7 @@ static int command_line_changed(CommandLineState *s) dict_T *dict = get_v_event(&save_v_event); char firstcbuf[2]; - firstcbuf[0] = (char)(s->firstc > 0 ? s->firstc : '-'); + firstcbuf[0] = (char)firstc; firstcbuf[1] = 0; // set v:event to a dictionary with information about the commandline @@ -2389,12 +2545,20 @@ static int command_line_changed(CommandLineState *s) bool tl_ret = try_leave(&tstate, &err); if (!tl_ret && ERROR_SET(&err)) { msg_putchar('\n'); - msg_printf_attr(HL_ATTR(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + msg_scroll = true; + msg_puts_attr(err.msg, HL_ATTR(HLF_E)|MSG_HIST); api_clear_error(&err); redrawcmd(); } } +} + +static int command_line_changed(CommandLineState *s) +{ + // Trigger CmdlineChanged autocommands. + do_autocmd_cmdlinechanged(s->firstc > 0 ? s->firstc : '-'); + const bool prev_cmdpreview = cmdpreview; if (s->firstc == ':' && current_sctx.sc_sid == 0 // only if interactive && *p_icm != NUL // 'inccommand' is set @@ -2403,10 +2567,13 @@ static int command_line_changed(CommandLineState *s) && !vpeekc_any() && cmdpreview_may_show(s)) { // 'inccommand' preview has been shown. - } else if (cmdpreview) { - cmdpreview = false; - update_screen(SOME_VALID); // Clear 'inccommand' preview. } else { + cmdpreview = false; + if (prev_cmdpreview) { + // TODO(bfredl): add an immediate redraw flag for cmdline mode which will trigger + // at next wait-for-input + update_screen(); // Clear 'inccommand' preview. + } if (s->xpc.xp_context == EXPAND_NOTHING && (KeyTyped || vpeekc() == NUL)) { may_do_incsearch_highlighting(s->firstc, s->count, &s->is_state); } @@ -2458,9 +2625,9 @@ static void abandon_cmdline(void) /// /// @param count only used for incremental search /// @param indent indent for inside conditionals -char_u *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_UNUSED) +char *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_UNUSED) { - return command_line_enter(firstc, count, indent, true); + return (char *)command_line_enter(firstc, count, indent, true); } /// Get a command line with a prompt @@ -2493,10 +2660,10 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int at CLEAR_FIELD(ccline); } ccline.prompt_id = last_prompt_id++; - ccline.cmdprompt = (char_u *)prompt; + ccline.cmdprompt = (char *)prompt; ccline.cmdattr = attr; ccline.xp_context = xp_context; - ccline.xp_arg = (char_u *)xp_arg; + ccline.xp_arg = (char *)xp_arg; ccline.input_fn = (firstc == '@'); ccline.highlight_callback = highlight_callback; @@ -2520,10 +2687,56 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int at return ret; } -// Return current cmdline prompt -char_u *get_cmdprompt(void) +/// Read the 'wildmode' option, fill wim_flags[]. +int check_opt_wim(void) { - return ccline.cmdprompt; + char_u new_wim_flags[4]; + int i; + int idx = 0; + + for (i = 0; i < 4; i++) { + new_wim_flags[i] = 0; + } + + for (char *p = p_wim; *p; p++) { + for (i = 0; ASCII_ISALPHA(p[i]); i++) {} + if (p[i] != NUL && p[i] != ',' && p[i] != ':') { + return FAIL; + } + if (i == 7 && strncmp(p, "longest", 7) == 0) { + new_wim_flags[idx] |= WIM_LONGEST; + } else if (i == 4 && strncmp(p, "full", 4) == 0) { + new_wim_flags[idx] |= WIM_FULL; + } else if (i == 4 && strncmp(p, "list", 4) == 0) { + new_wim_flags[idx] |= WIM_LIST; + } else if (i == 8 && strncmp(p, "lastused", 8) == 0) { + new_wim_flags[idx] |= WIM_BUFLASTUSED; + } else { + return FAIL; + } + p += i; + if (*p == NUL) { + break; + } + if (*p == ',') { + if (idx == 3) { + return FAIL; + } + idx++; + } + } + + // fill remaining entries with last flag + while (idx < 3) { + new_wim_flags[idx + 1] = new_wim_flags[idx]; + idx++; + } + + // only when there are no errors, wim_flags[] is changed + for (i = 0; i < 4; i++) { + wim_flags[i] = new_wim_flags[i]; + } + return OK; } /// Return true when the text must not be changed and we can't switch to @@ -2536,10 +2749,8 @@ bool text_locked(void) return textlock != 0; } -/* - * Give an error message for a command that isn't allowed while the cmdline - * window is open or editing the cmdline in another way. - */ +// Give an error message for a command that isn't allowed while the cmdline +// window is open or editing the cmdline in another way. void text_locked_msg(void) { emsg(_(get_text_locked_msg())); @@ -2592,7 +2803,7 @@ static int cmdline_charsize(int idx) if (cmdline_star > 0) { // showing '*', always 1 position return 1; } - return ptr2cells((char *)ccline.cmdbuff + idx); + return ptr2cells(ccline.cmdbuff + idx); } /// Compute the offset of the cursor on the command line for the prompt and @@ -2618,7 +2829,7 @@ int cmd_screencol(int bytepos) } for (int i = 0; i < ccline.cmdlen && i < bytepos; - i += utfc_ptr2len((char *)ccline.cmdbuff + i)) { + i += utfc_ptr2len(ccline.cmdbuff + i)) { int c = cmdline_charsize(i); // Count ">" for double-wide multi-byte char that doesn't fit. correct_screencol(i, c, &col); @@ -2637,8 +2848,8 @@ int cmd_screencol(int bytepos) /// character that doesn't fit, so that a ">" must be displayed. static void correct_screencol(int idx, int cells, int *col) { - if (utfc_ptr2len((char *)ccline.cmdbuff + idx) > 1 - && utf_ptr2cells((char *)ccline.cmdbuff + idx) > 1 + if (utfc_ptr2len(ccline.cmdbuff + idx) > 1 + && utf_ptr2cells(ccline.cmdbuff + idx) > 1 && (*col) % Columns + cells > Columns) { (*col)++; } @@ -2655,7 +2866,7 @@ char *getexline(int c, void *cookie, int indent, bool do_concat) (void)vgetc(); } - return (char *)getcmdline(c, 1L, indent, do_concat); + return getcmdline(c, 1L, indent, do_concat); } bool cmdline_overstrike(void) @@ -2671,15 +2882,11 @@ bool cmdline_at_end(void) return (ccline.cmdpos >= ccline.cmdlen); } -/* - * Allocate a new command line buffer. - * Assigns the new buffer to ccline.cmdbuff and ccline.cmdbufflen. - */ +// Allocate a new command line buffer. +// Assigns the new buffer to ccline.cmdbuff and ccline.cmdbufflen. static void alloc_cmdbuff(int len) { - /* - * give some extra space to avoid having to allocate all the time - */ + // give some extra space to avoid having to allocate all the time if (len < 80) { len = 100; } else { @@ -2697,7 +2904,7 @@ void realloc_cmdbuff(int len) return; // no need to resize } - char_u *p = ccline.cmdbuff; + char *p = ccline.cmdbuff; alloc_cmdbuff(len); // will get some more // There isn't always a NUL after the command, but it may need to be // there, thus copy up to the NUL and add a NUL. @@ -2709,12 +2916,12 @@ void realloc_cmdbuff(int len) && ccline.xpc->xp_pattern != NULL && ccline.xpc->xp_context != EXPAND_NOTHING && ccline.xpc->xp_context != EXPAND_UNSUCCESSFUL) { - int i = (int)((char_u *)ccline.xpc->xp_pattern - p); + int i = (int)(ccline.xpc->xp_pattern - p); // If xp_pattern points inside the old cmdbuff it needs to be adjusted // to point into the newly allocated memory. if (i >= 0 && i <= ccline.cmdlen) { - ccline.xpc->xp_pattern = (char *)ccline.cmdbuff + i; + ccline.xpc->xp_pattern = ccline.cmdbuff + i; } } } @@ -2744,7 +2951,7 @@ static void color_expr_cmdline(const CmdlineInfo *const colored_ccline, ParserLine parser_lines[] = { { .data = (const char *)colored_ccline->cmdbuff, - .size = STRLEN(colored_ccline->cmdbuff), + .size = strlen(colored_ccline->cmdbuff), .allocated = false, }, { NULL, 0, false }, @@ -2822,7 +3029,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) // Check whether result of the previous call is still valid. if (ccline_colors->prompt_id == colored_ccline->prompt_id && ccline_colors->cmdbuff != NULL - && STRCMP(ccline_colors->cmdbuff, colored_ccline->cmdbuff) == 0) { + && strcmp(ccline_colors->cmdbuff, colored_ccline->cmdbuff) == 0) { return ret; } @@ -2837,7 +3044,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) bool arg_allocated = false; typval_T arg = { .v_type = VAR_STRING, - .vval.v_string = (char *)colored_ccline->cmdbuff, + .vval.v_string = colored_ccline->cmdbuff, }; typval_T tv = { .v_type = VAR_UNKNOWN }; @@ -3021,10 +3228,8 @@ color_cmdline_error: #undef PRINT_ERRMSG } -/* - * Draw part of the cmdline at the current cursor position. But draw stars - * when cmdline_star is TRUE. - */ +// Draw part of the cmdline at the current cursor position. But draw stars +// when cmdline_star is true. static void draw_cmdline(int start, int len) { if (!color_cmdline(&ccline)) { @@ -3040,13 +3245,13 @@ static void draw_cmdline(int start, int len) if (cmdline_star > 0) { for (int i = 0; i < len; i++) { msg_putchar('*'); - i += utfc_ptr2len((char *)ccline.cmdbuff + start + i) - 1; + i += utfc_ptr2len(ccline.cmdbuff + start + i) - 1; } } else if (p_arshape && !p_tbidi && len > 0) { bool do_arabicshape = false; int mb_l; for (int i = start; i < start + len; i += mb_l) { - char_u *p = ccline.cmdbuff + i; + char *p = ccline.cmdbuff + i; int u8cc[MAX_MCO]; int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); mb_l = utfc_ptr2len_len(p, start + len - i); @@ -3073,7 +3278,7 @@ static void draw_cmdline(int start, int len) } int newlen = 0; - if (utf_iscomposing(utf_ptr2char((char *)ccline.cmdbuff + start))) { + if (utf_iscomposing(utf_ptr2char(ccline.cmdbuff + start))) { // Prepend a space to draw the leading composing char on. arshape_buf[0] = ' '; newlen = 1; @@ -3082,7 +3287,7 @@ static void draw_cmdline(int start, int len) int prev_c = 0; int prev_c1 = 0; for (int i = start; i < start + len; i += mb_l) { - char_u *p = ccline.cmdbuff + i; + char *p = ccline.cmdbuff + i; int u8cc[MAX_MCO]; int u8c = utfc_ptr2char_len(p, u8cc, start + len - i); mb_l = utfc_ptr2len_len(p, start + len - i); @@ -3099,7 +3304,7 @@ static void draw_cmdline(int start, int len) if (i + mb_l >= start + len) { nc = NUL; } else { - nc = utf_ptr2char((char *)p + mb_l); + nc = utf_ptr2char(p + mb_l); } } else { // Displaying from left to right. @@ -3131,7 +3336,7 @@ static void draw_cmdline(int start, int len) } } - msg_outtrans_len((char_u *)arshape_buf, newlen); + msg_outtrans_len(arshape_buf, newlen); } else { draw_cmdline_no_arabicshape: if (kv_size(ccline.last_colors.colors)) { @@ -3154,12 +3359,11 @@ draw_cmdline_no_arabicshape: static void ui_ext_cmdline_show(CmdlineInfo *line) { Arena arena = ARENA_EMPTY; - arena_start(&arena, &ui_ext_fixblk); Array content; if (cmdline_star) { content = arena_array(&arena, 1); size_t len = 0; - for (char_u *p = ccline.cmdbuff; *p; MB_PTR_ADV(p)) { + for (char *p = ccline.cmdbuff; *p; MB_PTR_ADV(p)) { len++; } char *buf = arena_alloc(&arena, len, false); @@ -3190,7 +3394,7 @@ static void ui_ext_cmdline_show(CmdlineInfo *line) char charbuf[2] = { (char)line->cmdfirstc, 0 }; ui_call_cmdline_show(content, line->cmdpos, cstr_as_string(charbuf), - cstr_as_string((char *)(line->cmdprompt)), + cstr_as_string((line->cmdprompt)), line->cmdindent, line->level); if (line->special_char) { @@ -3199,7 +3403,7 @@ static void ui_ext_cmdline_show(CmdlineInfo *line) line->special_shift, line->level); } - arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); + arena_mem_free(arena_finish(&arena)); } void ui_ext_cmdline_block_append(size_t indent, const char *line) @@ -3276,11 +3480,9 @@ void cmdline_ui_flush(void) } } -/* - * Put a character on the command line. Shifts the following text to the - * right when "shift" is TRUE. Used for CTRL-V, CTRL-K, etc. - * "c" must be printable (fit in one display cell)! - */ +// Put a character on the command line. Shifts the following text to the +// right when "shift" is true. Used for CTRL-V, CTRL-K, etc. +// "c" must be printable (fit in one display cell)! void putcmdline(char c, int shift) { if (cmd_silent) { @@ -3304,7 +3506,7 @@ void putcmdline(char c, int shift) ui_cursor_shape(); } -/// Undo a putcmdline(c, FALSE). +/// Undo a putcmdline(c, false). void unputcmdline(void) { if (cmd_silent) { @@ -3314,7 +3516,7 @@ void unputcmdline(void) if (ccline.cmdlen == ccline.cmdpos && !ui_has(kUICmdline)) { msg_putchar(' '); } else { - draw_cmdline(ccline.cmdpos, utfc_ptr2len((char *)ccline.cmdbuff + ccline.cmdpos)); + draw_cmdline(ccline.cmdpos, utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos)); } msg_no_more = false; cursorcmd(); @@ -3322,22 +3524,20 @@ void unputcmdline(void) ui_cursor_shape(); } -/* - * Put the given string, of the given length, onto the command line. - * If len is -1, then STRLEN() is used to calculate the length. - * If 'redraw' is TRUE then the new part of the command line, and the remaining - * part will be redrawn, otherwise it will not. If this function is called - * twice in a row, then 'redraw' should be FALSE and redrawcmd() should be - * called afterwards. - */ -void put_on_cmdline(char_u *str, int len, int redraw) +// Put the given string, of the given length, onto the command line. +// If len is -1, then strlen() is used to calculate the length. +// If 'redraw' is true then the new part of the command line, and the remaining +// part will be redrawn, otherwise it will not. If this function is called +// twice in a row, then 'redraw' should be false and redrawcmd() should be +// called afterwards. +void put_on_cmdline(char *str, int len, int redraw) { int i; int m; int c; if (len < 0) { - len = (int)STRLEN(str); + len = (int)strlen(str); } realloc_cmdbuff(ccline.cmdlen + len + 1); @@ -3350,13 +3550,13 @@ void put_on_cmdline(char_u *str, int len, int redraw) } else { // Count nr of characters in the new string. m = 0; - for (i = 0; i < len; i += utfc_ptr2len((char *)str + i)) { + for (i = 0; i < len; i += utfc_ptr2len(str + i)) { m++; } // Count nr of bytes in cmdline that are overwritten by these // characters. for (i = ccline.cmdpos; i < ccline.cmdlen && m > 0; - i += utfc_ptr2len((char *)ccline.cmdbuff + i)) { + i += utfc_ptr2len(ccline.cmdbuff + i)) { m--; } if (i < ccline.cmdlen) { @@ -3374,17 +3574,17 @@ void put_on_cmdline(char_u *str, int len, int redraw) // When the inserted text starts with a composing character, // backup to the character before it. There could be two of them. i = 0; - c = utf_ptr2char((char *)ccline.cmdbuff + ccline.cmdpos); + c = utf_ptr2char(ccline.cmdbuff + ccline.cmdpos); while (ccline.cmdpos > 0 && utf_iscomposing(c)) { i = utf_head_off(ccline.cmdbuff, ccline.cmdbuff + ccline.cmdpos - 1) + 1; ccline.cmdpos -= i; len += i; - c = utf_ptr2char((char *)ccline.cmdbuff + ccline.cmdpos); + c = utf_ptr2char(ccline.cmdbuff + ccline.cmdpos); } if (i == 0 && ccline.cmdpos > 0 && arabic_maycombine(c)) { // Check the previous character for Arabic combining pair. i = utf_head_off(ccline.cmdbuff, ccline.cmdbuff + ccline.cmdpos - 1) + 1; - if (arabic_combine(utf_ptr2char((char *)ccline.cmdbuff + ccline.cmdpos - i), c)) { + if (arabic_combine(utf_ptr2char(ccline.cmdbuff + ccline.cmdpos - i), c)) { ccline.cmdpos -= i; len += i; } else { @@ -3393,7 +3593,7 @@ void put_on_cmdline(char_u *str, int len, int redraw) } if (i != 0) { // Also backup the cursor position. - i = ptr2cells((char *)ccline.cmdbuff + ccline.cmdpos); + i = ptr2cells(ccline.cmdbuff + ccline.cmdpos); ccline.cmdspos -= i; msg_col -= i; if (msg_col < 0) { @@ -3404,7 +3604,7 @@ void put_on_cmdline(char_u *str, int len, int redraw) } if (redraw && !cmd_silent) { - msg_no_more = TRUE; + msg_no_more = true; i = cmdline_row; cursorcmd(); draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); @@ -3412,7 +3612,7 @@ void put_on_cmdline(char_u *str, int len, int redraw) if (cmdline_row != i || ccline.overstrike) { msg_clr_eos(); } - msg_no_more = FALSE; + msg_no_more = false; } if (KeyTyped) { m = Columns * Rows; @@ -3432,7 +3632,7 @@ void put_on_cmdline(char_u *str, int len, int redraw) if (ccline.cmdspos + c < m) { ccline.cmdspos += c; } - c = utfc_ptr2len((char *)ccline.cmdbuff + ccline.cmdpos) - 1; + c = utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos) - 1; if (c > len - i - 1) { c = len - i - 1; } @@ -3476,7 +3676,7 @@ static void restore_cmdline(CmdlineInfo *ccp) static bool cmdline_paste(int regname, bool literally, bool remcr) { char *arg; - char_u *p; + char *p; bool allocated; // check for valid regname; also accept special characters for CTRL-R in @@ -3508,21 +3708,21 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) // When 'incsearch' is set and CTRL-R CTRL-W used: skip the duplicate // part of the word. - p = (char_u *)arg; + p = arg; if (p_is && regname == Ctrl_W) { - char_u *w; + char *w; int len; // Locate start of last word in the cmd buffer. for (w = ccline.cmdbuff + ccline.cmdpos; w > ccline.cmdbuff;) { len = utf_head_off(ccline.cmdbuff, w - 1) + 1; - if (!vim_iswordc(utf_ptr2char((char *)w - len))) { + if (!vim_iswordc(utf_ptr2char(w - len))) { break; } w -= len; } len = (int)((ccline.cmdbuff + ccline.cmdpos) - w); - if (p_ic ? STRNICMP(w, arg, len) == 0 : STRNCMP(w, arg, len) == 0) { + if (p_ic ? STRNICMP(w, arg, len) == 0 : strncmp(w, arg, (size_t)len) == 0) { p += len; } } @@ -3537,25 +3737,23 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) return cmdline_paste_reg(regname, literally, remcr); } -/* - * Put a string on the command line. - * When "literally" is TRUE, insert literally. - * When "literally" is FALSE, insert as typed, but don't leave the command - * line. - */ -void cmdline_paste_str(char_u *s, int literally) +// Put a string on the command line. +// When "literally" is true, insert literally. +// When "literally" is false, insert as typed, but don't leave the command +// line. +void cmdline_paste_str(char *s, int literally) { int c, cv; if (literally) { - put_on_cmdline(s, -1, TRUE); + put_on_cmdline(s, -1, true); } else { while (*s != NUL) { - cv = *s; + cv = (uint8_t)(*s); if (cv == Ctrl_V && s[1]) { s++; } - c = mb_cptr2char_adv((const char_u **)&s); + c = mb_cptr2char_adv((const char **)&s); if (cv == Ctrl_V || c == ESC || c == Ctrl_C || c == CAR || c == NL || c == Ctrl_L || (c == Ctrl_BSL && *s == Ctrl_N)) { @@ -3596,7 +3794,7 @@ static void redrawcmdprompt(void) msg_putchar(ccline.cmdfirstc); } if (ccline.cmdprompt != NULL) { - msg_puts_attr((const char *)ccline.cmdprompt, ccline.cmdattr); + msg_puts_attr(ccline.cmdprompt, ccline.cmdattr); ccline.cmdindent = msg_col + (msg_row - cmdline_row) * Columns; // do the reverse of cmd_startcol() if (ccline.cmdfirstc != NUL) { @@ -3609,9 +3807,7 @@ static void redrawcmdprompt(void) } } -/* - * Redraw what is currently on the command line. - */ +// Redraw what is currently on the command line. void redrawcmd(void) { if (cmd_silent) { @@ -3637,7 +3833,7 @@ void redrawcmd(void) redrawcmdprompt(); // Don't use more prompt, truncate the cmdline if it doesn't fit. - msg_no_more = TRUE; + msg_no_more = true; draw_cmdline(0, ccline.cmdlen); msg_clr_eos(); msg_no_more = false; @@ -3648,11 +3844,9 @@ void redrawcmd(void) putcmdline(ccline.special_char, ccline.special_shift); } - /* - * An emsg() before may have set msg_scroll. This is used in normal mode, - * in cmdline mode we can reset them now. - */ - msg_scroll = FALSE; // next message overwrites cmdline + // An emsg() before may have set msg_scroll. This is used in normal mode, + // in cmdline mode we can reset them now. + msg_scroll = false; // next message overwrites cmdline // Typing ':' at the more prompt may set skip_redraw. We don't want this // in cmdline mode. @@ -3670,6 +3864,9 @@ void compute_cmdrow(void) cmdline_row = wp->w_winrow + wp->w_height + wp->w_hsep_height + wp->w_status_height + global_stl_height(); } + if (cmdline_row == Rows && p_ch > 0) { + cmdline_row--; + } lines_left = cmdline_row; } @@ -3728,12 +3925,10 @@ void gotocmdline(bool clr) cmd_cursor_goto(cmdline_row, 0); } -/* - * Check the word in front of the cursor for an abbreviation. - * Called when the non-id character "c" has been entered. - * When an abbreviation is recognized it is removed from the text with - * backspaces and the replacement string is inserted, followed by "c". - */ +// Check the word in front of the cursor for an abbreviation. +// Called when the non-id character "c" has been entered. +// When an abbreviation is recognized it is removed from the text with +// backspaces and the replacement string is inserted, followed by "c". static int ccheck_abbr(int c) { int spos = 0; @@ -3774,34 +3969,31 @@ char *vim_strsave_fnameescape(const char *const fname, const int what) { #ifdef BACKSLASH_IN_FILENAME # define PATH_ESC_CHARS " \t\n*?[{`%#'\"|!<" -# define BUFFER_ESC_CHARS ((char_u *)" \t\n*?[`%#'\"|!<") - char_u buf[sizeof(PATH_ESC_CHARS)]; +# define BUFFER_ESC_CHARS (" \t\n*?[`%#'\"|!<") + char buf[sizeof(PATH_ESC_CHARS)]; int j = 0; // Don't escape '[', '{' and '!' if they are in 'isfname' and for the // ":buffer" command. for (const char *p = what == VSE_BUFFER ? BUFFER_ESC_CHARS : PATH_ESC_CHARS; *p != NUL; p++) { - if ((*p != '[' && *p != '{' && *p != '!') || !vim_isfilec(*p)) { + if ((*p != '[' && *p != '{' && *p != '!') || !vim_isfilec((uint8_t)(*p))) { buf[j++] = *p; } } buf[j] = NUL; - char *p = (char *)vim_strsave_escaped((const char_u *)fname, - (const char_u *)buf); + char *p = vim_strsave_escaped(fname, buf); #else -# define PATH_ESC_CHARS ((char_u *)" \t\n*?[{`$\\%#'\"|!<") -# define SHELL_ESC_CHARS ((char_u *)" \t\n*?[{`$\\%#'\"|!<>();&") -# define BUFFER_ESC_CHARS ((char_u *)" \t\n*?[`$\\%#'\"|!<") - char *p = - (char *)vim_strsave_escaped((const char_u *)fname, - what == VSE_SHELL ? SHELL_ESC_CHARS - : what == VSE_BUFFER ? BUFFER_ESC_CHARS : PATH_ESC_CHARS); +# define PATH_ESC_CHARS " \t\n*?[{`$\\%#'\"|!<" +# define SHELL_ESC_CHARS " \t\n*?[{`$\\%#'\"|!<>();&" +# define BUFFER_ESC_CHARS " \t\n*?[`$\\%#'\"|!<" + char *p = vim_strsave_escaped(fname, + what == VSE_SHELL ? SHELL_ESC_CHARS : what == + VSE_BUFFER ? BUFFER_ESC_CHARS : PATH_ESC_CHARS); if (what == VSE_SHELL && csh_like_shell()) { // For csh and similar shells need to put two backslashes before '!'. // One is taken by Vim, one by the shell. - char *s = (char *)vim_strsave_escaped((const char_u *)p, - (const char_u *)"!"); + char *s = vim_strsave_escaped(p, "!"); xfree(p); p = s; } @@ -3819,16 +4011,16 @@ char *vim_strsave_fnameescape(const char *const fname, const int what) /// Put a backslash before the file name in "pp", which is in allocated memory. void escape_fname(char **pp) { - char_u *p = xmalloc(STRLEN(*pp) + 2); + char *p = xmalloc(strlen(*pp) + 2); p[0] = '\\'; STRCPY(p + 1, *pp); xfree(*pp); - *pp = (char *)p; + *pp = p; } /// For each file name in files[num_files]: /// If 'orig_pat' starts with "~/", replace the home directory with "~". -void tilde_replace(char_u *orig_pat, int num_files, char **files) +void tilde_replace(char *orig_pat, int num_files, char **files) { char *p; @@ -3867,76 +4059,127 @@ static CmdlineInfo *get_ccline_ptr(void) } } -/// Get the current command-line completion type. -char_u *get_cmdline_completion(void) +/// Get the current command-line type. +/// Returns ':' or '/' or '?' or '@' or '>' or '-' +/// Only works when the command line is being edited. +/// Returns NUL when something is wrong. +static int get_cmdline_type(void) +{ + CmdlineInfo *p = get_ccline_ptr(); + + if (p == NULL) { + return NUL; + } + if (p->cmdfirstc == NUL) { + return (p->input_fn) ? '@' : '-'; + } + return p->cmdfirstc; +} + +/// Get the current command line in allocated memory. +/// Only works when the command line is being edited. +/// +/// @return NULL when something is wrong. +static char *get_cmdline_str(void) { if (cmdline_star > 0) { return NULL; } CmdlineInfo *p = get_ccline_ptr(); - if (p != NULL && p->xpc != NULL) { - set_expand_context(p->xpc); - char *cmd_compl = get_user_cmd_complete(p->xpc, p->xpc->xp_context); - if (cmd_compl != NULL) { - return vim_strsave((char_u *)cmd_compl); - } + if (p == NULL) { + return NULL; } - - return NULL; + return xstrnsave(p->cmdbuff, (size_t)p->cmdlen); } -/* - * Get the current command line in allocated memory. - * Only works when the command line is being edited. - * Returns NULL when something is wrong. - */ -char_u *get_cmdline_str(void) +/// Get the current command-line completion type. +static char *get_cmdline_completion(void) { if (cmdline_star > 0) { return NULL; } CmdlineInfo *p = get_ccline_ptr(); - if (p == NULL) { + if (p == NULL || p->xpc == NULL) { return NULL; } - return vim_strnsave(p->cmdbuff, (size_t)p->cmdlen); + + set_expand_context(p->xpc); + char *cmd_compl = get_user_cmd_complete(p->xpc, p->xpc->xp_context); + if (cmd_compl != NULL) { + return xstrdup(cmd_compl); + } + + return NULL; +} + +/// "getcmdcompltype()" function +void f_getcmdcompltype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = get_cmdline_completion(); +} + +/// "getcmdline()" function +void f_getcmdline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = get_cmdline_str(); } -/* - * Get the current command line position, counted in bytes. - * Zero is the first position. - * Only works when the command line is being edited. - * Returns -1 when something is wrong. - */ -int get_cmdline_pos(void) +/// "getcmdpos()" function +void f_getcmdpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { CmdlineInfo *p = get_ccline_ptr(); + rettv->vval.v_number = p != NULL ? p->cmdpos + 1 : 0; +} - if (p == NULL) { - return -1; - } - return p->cmdpos; +/// "getcmdscreenpos()" function +void f_getcmdscreenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + CmdlineInfo *p = get_ccline_ptr(); + rettv->vval.v_number = p != NULL ? p->cmdspos + 1 : 0; } -/// Get the command line cursor screen position. -int get_cmdline_screen_pos(void) +/// "getcmdtype()" function +void f_getcmdtype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xmallocz(1); + rettv->vval.v_string[0] = (char)get_cmdline_type(); +} + +/// Set the command line str to "str". +/// @return 1 when failed, 0 when OK. +static int set_cmdline_str(const char *str, int pos) { CmdlineInfo *p = get_ccline_ptr(); if (p == NULL) { - return -1; + return 1; } - return p->cmdspos; + + int len = (int)strlen(str); + realloc_cmdbuff(len + 1); + p->cmdlen = len; + STRCPY(p->cmdbuff, str); + + p->cmdpos = pos < 0 || pos > p->cmdlen ? p->cmdlen : pos; + new_cmdpos = p->cmdpos; + + redrawcmd(); + + // Trigger CmdlineChanged autocommands. + do_autocmd_cmdlinechanged(get_cmdline_type()); + + return 0; } -/* - * Set the command line byte position to "pos". Zero is the first position. - * Only works when the command line is being edited. - * Returns 1 when failed, 0 when OK. - */ -int set_cmdline_pos(int pos) +/// Set the command line byte position to "pos". Zero is the first position. +/// Only works when the command line is being edited. +/// @return 1 when failed, 0 when OK. +static int set_cmdline_pos(int pos) { CmdlineInfo *p = get_ccline_ptr(); @@ -3954,23 +4197,39 @@ int set_cmdline_pos(int pos) return 0; } -/* - * Get the current command-line type. - * Returns ':' or '/' or '?' or '@' or '>' or '-' - * Only works when the command line is being edited. - * Returns NUL when something is wrong. - */ -int get_cmdline_type(void) +/// "setcmdline()" function +void f_setcmdline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - CmdlineInfo *p = get_ccline_ptr(); + if (tv_check_for_string_arg(argvars, 0) == FAIL + || tv_check_for_opt_number_arg(argvars, 1) == FAIL) { + return; + } - if (p == NULL) { - return NUL; + int pos = -1; + if (argvars[1].v_type != VAR_UNKNOWN) { + bool error = false; + + pos = (int)tv_get_number_chk(&argvars[1], &error) - 1; + if (error) { + return; + } + if (pos < 0) { + emsg(_(e_positive)); + return; + } } - if (p->cmdfirstc == NUL) { - return (p->input_fn) ? '@' : '-'; + + rettv->vval.v_number = set_cmdline_str(argvars[0].vval.v_string, pos); +} + +/// "setcmdpos()" function +void f_setcmdpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + const int pos = (int)tv_get_number(&argvars[0]) - 1; + + if (pos >= 0) { + rettv->vval.v_number = set_cmdline_pos(pos); } - return p->cmdfirstc; } /// Return the first character of the current command line. @@ -3995,7 +4254,7 @@ int get_list_range(char **str, int *num1, int *num2) *str = skipwhite((*str)); if (**str == '-' || ascii_isdigit(**str)) { // parse "from" part of range - vim_str2nr((char_u *)(*str), NULL, &len, 0, &num, NULL, 0, false); + vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false); *str += len; *num1 = (int)num; first = true; @@ -4003,7 +4262,7 @@ int get_list_range(char **str, int *num1, int *num2) *str = skipwhite((*str)); if (**str == ',') { // parse "to" part of range *str = skipwhite((*str) + 1); - vim_str2nr((char_u *)(*str), NULL, &len, 0, &num, NULL, 0, false); + vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false); if (len > 0) { *num2 = (int)num; *str = skipwhite((*str) + len); @@ -4021,6 +4280,24 @@ void cmdline_init(void) CLEAR_FIELD(ccline); } +/// Check value of 'cedit' and set cedit_key. +/// Returns NULL if value is OK, error message otherwise. +char *check_cedit(void) +{ + int n; + + if (*p_cedit == NUL) { + cedit_key = -1; + } else { + n = string_to_key(p_cedit); + if (vim_isprintc(n)) { + return e_invarg; + } + cedit_key = n; + } + return NULL; +} + /// Open a window on the current command line and history. Allow editing in /// the window. Returns when the window is closed. /// Returns: @@ -4068,17 +4345,27 @@ static int open_cmdwin(void) ga_clear(&winsizes); return K_IGNORE; } + // Don't let quitting the More prompt make this fail. + got_int = false; + + // Set "cmdwin_type" before any autocommands may mess things up. cmdwin_type = get_cmdline_type(); cmdwin_level = ccline.level; // Create empty command-line buffer. - buf_open_scratch(0, _("[Command Line]")); + if (buf_open_scratch(0, _("[Command Line]")) == FAIL) { + // Some autocommand messed it up? + win_close(curwin, true, false); + ga_clear(&winsizes); + cmdwin_type = 0; + return Ctrl_C; + } // Command-line buffer has bufhidden=wipe, unlike a true "scratch" buffer. - set_option_value("bh", 0L, "wipe", OPT_LOCAL); - curwin->w_p_rl = cmdmsg_rl; - cmdmsg_rl = false; + set_option_value_give_err("bh", 0L, "wipe", OPT_LOCAL); curbuf->b_p_ma = true; curwin->w_p_fen = false; + curwin->w_p_rl = cmdmsg_rl; + cmdmsg_rl = false; // Don't allow switching to another buffer. curbuf->b_ro_locked++; @@ -4092,7 +4379,7 @@ static int open_cmdwin(void) add_map("<Tab>", "<C-X><C-V>", MODE_INSERT, true); add_map("<Tab>", "a<C-X><C-V>", MODE_NORMAL, true); } - set_option_value("ft", 0L, "vim", OPT_LOCAL); + set_option_value_give_err("ft", 0L, "vim", OPT_LOCAL); } curbuf->b_ro_locked--; @@ -4111,7 +4398,7 @@ static int open_cmdwin(void) i = 0; } if (get_histentry(histtype)[i].hisstr != NULL) { - ml_append(lnum++, (char *)get_histentry(histtype)[i].hisstr, (colnr_T)0, false); + ml_append(lnum++, get_histentry(histtype)[i].hisstr, (colnr_T)0, false); } } while (i != *get_hisidx(histtype)); } @@ -4119,7 +4406,7 @@ static int open_cmdwin(void) // Replace the empty last line with the current command-line and put the // cursor there. - ml_replace(curbuf->b_ml.ml_line_count, (char *)ccline.cmdbuff, true); + ml_replace(curbuf->b_ml.ml_line_count, ccline.cmdbuff, true); curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; curwin->w_cursor.col = ccline.cmdpos; changed_line_abv_curs(); @@ -4128,7 +4415,7 @@ static int open_cmdwin(void) ccline.redraw_state = kCmdRedrawNone; ui_call_cmdline_hide(ccline.level); } - redraw_later(curwin, SOME_VALID); + redraw_later(curwin, UPD_SOME_VALID); // No Ex mode here! exmode_active = false; @@ -4151,9 +4438,7 @@ static int open_cmdwin(void) RedrawingDisabled = 0; int save_count = save_batch_count(); - /* - * Call the main loop until <CR> or CTRL-C is typed. - */ + // Call the main loop until <CR> or CTRL-C is typed. normal_enter(true, false); RedrawingDisabled = i; @@ -4189,7 +4474,7 @@ static int open_cmdwin(void) if (histtype == HIST_CMD) { // Execute the command directly. - ccline.cmdbuff = (char_u *)xstrdup(p); + ccline.cmdbuff = xstrdup(p); cmdwin_result = CAR; } else { // First need to cancel what we were doing. @@ -4203,16 +4488,16 @@ static int open_cmdwin(void) // and don't modify the cmd window. ccline.cmdbuff = NULL; } else { - ccline.cmdbuff = vim_strsave(get_cursor_line_ptr()); + ccline.cmdbuff = xstrdup(get_cursor_line_ptr()); } if (ccline.cmdbuff == NULL) { - ccline.cmdbuff = vim_strsave((char_u *)""); + ccline.cmdbuff = xstrdup(""); ccline.cmdlen = 0; ccline.cmdbufflen = 1; ccline.cmdpos = 0; cmdwin_result = Ctrl_C; } else { - ccline.cmdlen = (int)STRLEN(ccline.cmdbuff); + ccline.cmdlen = (int)strlen(ccline.cmdbuff); ccline.cmdbufflen = ccline.cmdlen + 1; ccline.cmdpos = curwin->w_cursor.col; if (ccline.cmdpos > ccline.cmdlen) { @@ -4229,6 +4514,7 @@ static int open_cmdwin(void) // First go back to the original window. wp = curwin; set_bufref(&bufref, curbuf); + skip_win_fix_cursor = true; win_goto(old_curwin); // win_goto() may trigger an autocommand that already closes the @@ -4245,6 +4531,7 @@ static int open_cmdwin(void) // Restore window sizes. win_size_restore(&winsizes); + skip_win_fix_cursor = false; } ga_clear(&winsizes); @@ -4285,7 +4572,7 @@ char *script_get(exarg_T *const eap, size_t *const lenp) const char *const cmd = (const char *)eap->arg; if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) { - *lenp = STRLEN(eap->arg); + *lenp = strlen(eap->arg); return eap->skip ? NULL : xmemdupz(eap->arg, *lenp); } diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h index 5e3ad20a31..61ac4b69c5 100644 --- a/src/nvim/ex_getln.h +++ b/src/nvim/ex_getln.h @@ -1,10 +1,16 @@ #ifndef NVIM_EX_GETLN_H #define NVIM_EX_GETLN_H +#include <stdbool.h> + +#include "klib/kvec.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/types.h" +struct cmdline_info; + /// Command-line colors: one chunk /// /// Defines a region which has the same highlighting. @@ -41,21 +47,21 @@ typedef enum { /// structure. typedef struct cmdline_info CmdlineInfo; struct cmdline_info { - char_u *cmdbuff; ///< pointer to command line buffer + char *cmdbuff; ///< pointer to command line buffer int cmdbufflen; ///< length of cmdbuff int cmdlen; ///< number of chars in command line int cmdpos; ///< current cursor position int cmdspos; ///< cursor column on screen int cmdfirstc; ///< ':', '/', '?', '=', '>' or NUL int cmdindent; ///< number of spaces before cmdline - char_u *cmdprompt; ///< message in front of cmdline + char *cmdprompt; ///< message in front of cmdline int cmdattr; ///< attributes for prompt int overstrike; ///< Typing mode on the command line. Shared by ///< getcmdline() and put_on_cmdline(). expand_T *xpc; ///< struct being used for expansion, xp_pattern ///< may point into cmdbuff int xp_context; ///< type of expansion - char_u *xp_arg; ///< user-defined expansion arg + char *xp_arg; ///< user-defined expansion arg int input_fn; ///< when true Invoked for input() function unsigned prompt_id; ///< Prompt number, used to disable coloring on errors. Callback highlight_callback; ///< Callback used for coloring user input. diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index 9495ae1c4b..3de5e1db52 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -9,31 +9,34 @@ #include <assert.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> -#include <stdlib.h> +#include <stdio.h> #include <string.h> #include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" -#include "nvim/cursor.h" -#include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/globals.h" -#include "nvim/keycodes.h" +#include "nvim/macros.h" #include "nvim/mapping.h" -#include "nvim/move.h" +#include "nvim/mark_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/option.h" -#include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/time.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/runtime.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -109,40 +112,43 @@ static int ses_win_rec(FILE *fd, frame_T *fr) frame_T *frc; int count = 0; - if (fr->fr_layout != FR_LEAF) { - // Find first frame that's not skipped and then create a window for - // each following one (first frame is already there). - frc = ses_skipframe(fr->fr_child); - if (frc != NULL) { - while ((frc = ses_skipframe(frc->fr_next)) != NULL) { - // Make window as big as possible so that we have lots of room - // to split. - if (fprintf(fd, "%s%s", - "wincmd _ | wincmd |\n", - (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) { - return FAIL; - } - count++; + if (fr->fr_layout == FR_LEAF) { + return OK; + } + + // Find first frame that's not skipped and then create a window for + // each following one (first frame is already there). + frc = ses_skipframe(fr->fr_child); + if (frc != NULL) { + while ((frc = ses_skipframe(frc->fr_next)) != NULL) { + // Make window as big as possible so that we have lots of room + // to split. + if (fprintf(fd, "%s%s", + "wincmd _ | wincmd |\n", + (fr->fr_layout == FR_COL ? "split\n" : "vsplit\n")) < 0) { + return FAIL; } + count++; } + } - // Go back to the first window. - if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL - ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) { - return FAIL; - } + // Go back to the first window. + if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL + ? "%dwincmd k\n" : "%dwincmd h\n", count) < 0)) { + return FAIL; + } - // Recursively create frames/windows in each window of this column or row. - frc = ses_skipframe(fr->fr_child); - while (frc != NULL) { - ses_win_rec(fd, frc); - frc = ses_skipframe(frc->fr_next); - // Go to next window. - if (frc != NULL && put_line(fd, "wincmd w") == FAIL) { - return FAIL; - } + // Recursively create frames/windows in each window of this column or row. + frc = ses_skipframe(fr->fr_child); + while (frc != NULL) { + ses_win_rec(fd, frc); + frc = ses_skipframe(frc->fr_next); + // Go to next window. + if (frc != NULL && put_line(fd, "wincmd w") == FAIL) { + return FAIL; } } + return OK; } @@ -211,22 +217,22 @@ static int ses_do_win(win_T *wp) /// @returns FAIL if writing fails. static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, unsigned *flagp) { - char_u *buf = NULL; - char_u *s; + char *buf = NULL; + char *s; if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) { return FAIL; } for (int i = 0; i < gap->ga_len; i++) { // NULL file names are skipped (only happens when out of memory). - s = (char_u *)alist_name(&((aentry_T *)gap->ga_data)[i]); + s = alist_name(&((aentry_T *)gap->ga_data)[i]); if (s != NULL) { if (fullname) { buf = xmalloc(MAXPATHL); - (void)vim_FullName((char *)s, (char *)buf, MAXPATHL, false); + (void)vim_FullName(s, buf, MAXPATHL, false); s = buf; } - char *fname_esc = ses_escape_fname((char *)s, flagp); + char *fname_esc = ses_escape_fname(s, flagp); if (fprintf(fd, "$argadd %s\n", fname_esc) < 0) { xfree(fname_esc); xfree(buf); @@ -240,7 +246,7 @@ static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, unsigne } /// @return the buffer name for `buf`. -static char *ses_get_fname(buf_T *buf, unsigned *flagp) +static char *ses_get_fname(buf_T *buf, const unsigned *flagp) { // Use the short file name if the current directory is known at the time // the session file will be sourced. @@ -264,7 +270,7 @@ static char *ses_get_fname(buf_T *buf, unsigned *flagp) static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) { char *name = ses_get_fname(buf, flagp); - if (ses_put_fname(fd, (char_u *)name, flagp) == FAIL + if (ses_put_fname(fd, name, flagp) == FAIL || (add_eol && fprintf(fd, "\n") < 0)) { return FAIL; } @@ -299,9 +305,9 @@ static char *ses_escape_fname(char *name, unsigned *flagp) /// characters. /// /// @return FAIL if writing fails. -static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) +static int ses_put_fname(FILE *fd, char *name, unsigned *flagp) { - char *p = ses_escape_fname((char *)name, flagp); + char *p = ses_escape_fname(name, flagp); bool retval = fputs(p, fd) < 0 ? FAIL : OK; xfree(p); return retval; @@ -359,7 +365,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr // Then ":help" will re-use both the buffer and the window and set // the options, even when "options" is not in 'sessionoptions'. if (0 < wp->w_tagstackidx && wp->w_tagstackidx <= wp->w_tagstacklen) { - curtag = (char *)wp->w_tagstack[wp->w_tagstackidx - 1].tagname; + curtag = wp->w_tagstack[wp->w_tagstackidx - 1].tagname; } if (put_line(fd, "enew | setl bt=help") == FAIL @@ -523,7 +529,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr if (wp->w_localdir != NULL && (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) { if (fputs("lcd ", fd) < 0 - || ses_put_fname(fd, (char_u *)wp->w_localdir, flagp) == FAIL + || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL || fprintf(fd, "\n") < 0) { return FAIL; } @@ -542,7 +548,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr /// @param fd File descriptor to write to /// /// @return FAIL on error, OK otherwise. -static int makeopens(FILE *fd, char_u *dirnow) +static int makeopens(FILE *fd, char *dirnow) { int only_save_windows = true; int nr; @@ -581,7 +587,7 @@ static int makeopens(FILE *fd, char_u *dirnow) if (ssop_flags & SSOP_SESDIR) { PUTLINE_FAIL("exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')"); } else if (ssop_flags & SSOP_CURDIR) { - sname = home_replace_save(NULL, globaldir != NULL ? globaldir : (char *)dirnow); + sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); char *fname_esc = ses_escape_fname(sname, &ssop_flags); if (fprintf(fd, "cd %s\n", fname_esc) < 0) { xfree(fname_esc); @@ -828,7 +834,7 @@ static int makeopens(FILE *fd, char_u *dirnow) // Take care of tab-local working directories if applicable if (tp->tp_localdir) { if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0 - || ses_put_fname(fd, (char_u *)tp->tp_localdir, &ssop_flags) == FAIL + || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL || fputs(" | endif\n", fd) < 0) { return FAIL; } @@ -903,12 +909,14 @@ static int makeopens(FILE *fd, char_u *dirnow) void ex_loadview(exarg_T *eap) { char *fname = get_view_file(*eap->arg); - if (fname != NULL) { - if (do_source(fname, false, DOSO_NONE) == FAIL) { - semsg(_(e_notopen), fname); - } - xfree(fname); + if (fname == NULL) { + return; + } + + if (do_source(fname, false, DOSO_NONE) == FAIL) { + semsg(_(e_notopen), fname); } + xfree(fname); } /// ":mkexrc", ":mkvimrc", ":mkview", ":mksession". @@ -960,7 +968,7 @@ void ex_mkrc(exarg_T *eap) vim_mkdir_emsg((const char *)p_vdir, 0755); } - fd = open_exfile((char_u *)fname, eap->forceit, WRITEBIN); + fd = open_exfile(fname, eap->forceit, WRITEBIN); if (fd != NULL) { if (eap->cmdidx == CMD_mkview) { flagp = &vop_flags; @@ -997,14 +1005,14 @@ void ex_mkrc(exarg_T *eap) failed = true; } if (eap->cmdidx == CMD_mksession) { - char_u *dirnow; // current directory + char *dirnow; // current directory dirnow = xmalloc(MAXPATHL); // // Change to session file's dir. // if (os_dirname(dirnow, MAXPATHL) == FAIL - || os_chdir((char *)dirnow) != 0) { + || os_chdir(dirnow) != 0) { *dirnow = NUL; } if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { @@ -1024,7 +1032,7 @@ void ex_mkrc(exarg_T *eap) if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) || ((ssop_flags & SSOP_CURDIR) && globaldir != NULL))) { - if (os_chdir((char *)dirnow) != 0) { + if (os_chdir(dirnow) != 0) { emsg(_(e_prev_dir)); } shorten_fnames(true); @@ -1095,7 +1103,7 @@ static char *get_view_file(int c) len++; } } - char *retval = xmalloc(strlen(sname) + len + STRLEN(p_vdir) + 9); + char *retval = xmalloc(strlen(sname) + len + strlen(p_vdir) + 9); STRCPY(retval, p_vdir); add_pathsep(retval); char *s = retval + strlen(retval); diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 1639f72990..3e059bcc6c 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -29,20 +29,21 @@ // code for redrawing the line with the deleted decoration. #include <assert.h> +#include <sys/types.h> -#include "nvim/api/extmark.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" -#include "nvim/charset.h" #include "nvim/decoration.h" #include "nvim/extmark.h" +#include "nvim/extmark_defs.h" #include "nvim/globals.h" -#include "nvim/lib/kbtree.h" #include "nvim/map.h" +#include "nvim/marktree.h" #include "nvim/memline.h" +#include "nvim/memory.h" #include "nvim/pos.h" #include "nvim/undo.h" -#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "extmark.c.generated.h" @@ -70,7 +71,8 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col || kv_size(decor->virt_lines) || decor->conceal || decor_has_sign(decor) - || decor->ui_watched) { + || decor->ui_watched + || decor->spell != kNone) { decor_full = true; decor = xmemdup(decor, sizeof *decor); } @@ -111,6 +113,7 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col marktree_revise(buf->b_marktree, itr, decor_level, old_mark); goto revised; } + decor_remove(buf, old_mark.pos.row, old_mark.pos.row, old_mark.decor_full); marktree_del_itr(buf->b_marktree, itr, false); } } else { @@ -510,11 +513,11 @@ void extmark_adjust(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount, bcount_t old_byte = 0, new_byte = 0; int old_row, new_row; if (amount == MAXLNUM) { - old_row = (int)(line2 - line1 + 1); + old_row = line2 - line1 + 1; // TODO(bfredl): ej kasta? old_byte = (bcount_t)buf->deleted_bytes2; - new_row = (int)(amount_after + old_row); + new_row = amount_after + old_row; } else { // A region is either deleted (amount == MAXLNUM) or // added (line2 == MAXLNUM). The only other case is :move diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index c144504076..657e99a938 100644 --- a/src/nvim/extmark.h +++ b/src/nvim/extmark.h @@ -1,11 +1,18 @@ #ifndef NVIM_EXTMARK_H #define NVIM_EXTMARK_H +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include "klib/kvec.h" #include "nvim/buffer_defs.h" #include "nvim/decoration.h" #include "nvim/extmark_defs.h" +#include "nvim/macros.h" #include "nvim/marktree.h" #include "nvim/pos.h" +#include "nvim/types.h" EXTERN int extmark_splice_pending INIT(= 0); diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h index 5570b5c71e..51b60dcf6d 100644 --- a/src/nvim/extmark_defs.h +++ b/src/nvim/extmark_defs.h @@ -1,7 +1,7 @@ #ifndef NVIM_EXTMARK_DEFS_H #define NVIM_EXTMARK_DEFS_H -#include "nvim/lib/kvec.h" +#include "klib/kvec.h" #include "nvim/types.h" typedef struct { @@ -9,6 +9,8 @@ typedef struct { int hl_id; } VirtTextChunk; +typedef kvec_t(VirtTextChunk) VirtText; + typedef struct undo_object ExtmarkUndoObject; typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 2d09e7aa71..e236f23895 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -47,41 +47,43 @@ #include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/ascii.h" #include "nvim/autocmd.h" -#include "nvim/charset.h" +#include "nvim/buffer_defs.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/file_search.h" -#include "nvim/fileio.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" #include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/strings.h" -#include "nvim/tag.h" +#include "nvim/types.h" #include "nvim/vim.h" #include "nvim/window.h" -static char_u *ff_expand_buffer = NULL; // used for expanding filenames +static char *ff_expand_buffer = NULL; // used for expanding filenames -/* - * type for the directory search stack - */ +// type for the directory search stack typedef struct ff_stack { struct ff_stack *ffs_prev; - /* the fix part (no wildcards) and the part containing the wildcards - * of the search path - */ - char_u *ffs_fix_path; - char_u *ffs_wc_path; + // the fix part (no wildcards) and the part containing the wildcards + // of the search path + char *ffs_fix_path; + char *ffs_wc_path; // files/dirs found in the above directory, matched by the first wildcard // of wc_part @@ -89,98 +91,87 @@ typedef struct ff_stack { int ffs_filearray_size; int ffs_filearray_cur; // needed for partly handled dirs - /* to store status of partly handled directories - * 0: we work on this directory for the first time - * 1: this directory was partly searched in an earlier step - */ + // to store status of partly handled directories + // 0: we work on this directory for the first time + // 1: this directory was partly searched in an earlier step int ffs_stage; - /* How deep are we in the directory tree? - * Counts backward from value of level parameter to vim_findfile_init - */ + // How deep are we in the directory tree? + // Counts backward from value of level parameter to vim_findfile_init int ffs_level; // Did we already expand '**' to an empty string? int ffs_star_star_empty; } ff_stack_T; -/* - * type for already visited directories or files. - */ +// type for already visited directories or files. typedef struct ff_visited { struct ff_visited *ffv_next; - /* Visited directories are different if the wildcard string are - * different. So we have to save it. - */ - char_u *ffv_wc_path; + // Visited directories are different if the wildcard string are + // different. So we have to save it. + char *ffv_wc_path; + // use FileID for comparison (needed because of links), else use filename. bool file_id_valid; FileID file_id; - /* The memory for this struct is allocated according to the length of - * ffv_fname. - */ - char_u ffv_fname[1]; // actually longer + // The memory for this struct is allocated according to the length of + // ffv_fname. + char ffv_fname[1]; // actually longer } ff_visited_T; -/* - * We might have to manage several visited lists during a search. - * This is especially needed for the tags option. If tags is set to: - * "./++/tags,./++/TAGS,++/tags" (replace + with *) - * So we have to do 3 searches: - * 1) search from the current files directory downward for the file "tags" - * 2) search from the current files directory downward for the file "TAGS" - * 3) search from Vims current directory downwards for the file "tags" - * As you can see, the first and the third search are for the same file, so for - * the third search we can use the visited list of the first search. For the - * second search we must start from an empty visited list. - * The struct ff_visited_list_hdr is used to manage a linked list of already - * visited lists. - */ +// We might have to manage several visited lists during a search. +// This is especially needed for the tags option. If tags is set to: +// "./++/tags,./++/TAGS,++/tags" (replace + with *) +// So we have to do 3 searches: +// 1) search from the current files directory downward for the file "tags" +// 2) search from the current files directory downward for the file "TAGS" +// 3) search from Vims current directory downwards for the file "tags" +// As you can see, the first and the third search are for the same file, so for +// the third search we can use the visited list of the first search. For the +// second search we must start from an empty visited list. +// The struct ff_visited_list_hdr is used to manage a linked list of already +// visited lists. typedef struct ff_visited_list_hdr { struct ff_visited_list_hdr *ffvl_next; // the filename the attached visited list is for - char_u *ffvl_filename; + char *ffvl_filename; ff_visited_T *ffvl_visited_list; } ff_visited_list_hdr_T; -/* - * '**' can be expanded to several directory levels. - * Set the default maximum depth. - */ -#define FF_MAX_STAR_STAR_EXPAND ((char_u)30) - -/* - * The search context: - * ffsc_stack_ptr: the stack for the dirs to search - * ffsc_visited_list: the currently active visited list - * ffsc_dir_visited_list: the currently active visited list for search dirs - * ffsc_visited_lists_list: the list of all visited lists - * ffsc_dir_visited_lists_list: the list of all visited lists for search dirs - * ffsc_file_to_search: the file to search for - * ffsc_start_dir: the starting directory, if search path was relative - * ffsc_fix_path: the fix part of the given path (without wildcards) - * Needed for upward search. - * ffsc_wc_path: the part of the given path containing wildcards - * ffsc_level: how many levels of dirs to search downwards - * ffsc_stopdirs_v: array of stop directories for upward search - * ffsc_find_what: FINDFILE_BOTH, FINDFILE_DIR or FINDFILE_FILE - * ffsc_tagfile: searching for tags file, don't use 'suffixesadd' - */ +// '**' can be expanded to several directory levels. +// Set the default maximum depth. +#define FF_MAX_STAR_STAR_EXPAND 30 + +// The search context: +// ffsc_stack_ptr: the stack for the dirs to search +// ffsc_visited_list: the currently active visited list +// ffsc_dir_visited_list: the currently active visited list for search dirs +// ffsc_visited_lists_list: the list of all visited lists +// ffsc_dir_visited_lists_list: the list of all visited lists for search dirs +// ffsc_file_to_search: the file to search for +// ffsc_start_dir: the starting directory, if search path was relative +// ffsc_fix_path: the fix part of the given path (without wildcards) +// Needed for upward search. +// ffsc_wc_path: the part of the given path containing wildcards +// ffsc_level: how many levels of dirs to search downwards +// ffsc_stopdirs_v: array of stop directories for upward search +// ffsc_find_what: FINDFILE_BOTH, FINDFILE_DIR or FINDFILE_FILE +// ffsc_tagfile: searching for tags file, don't use 'suffixesadd' typedef struct ff_search_ctx_T { ff_stack_T *ffsc_stack_ptr; ff_visited_list_hdr_T *ffsc_visited_list; ff_visited_list_hdr_T *ffsc_dir_visited_list; ff_visited_list_hdr_T *ffsc_visited_lists_list; ff_visited_list_hdr_T *ffsc_dir_visited_lists_list; - char_u *ffsc_file_to_search; - char_u *ffsc_start_dir; - char_u *ffsc_fix_path; - char_u *ffsc_wc_path; + char *ffsc_file_to_search; + char *ffsc_start_dir; + char *ffsc_fix_path; + char *ffsc_wc_path; int ffsc_level; - char_u **ffsc_stopdirs_v; + char **ffsc_stopdirs_v; int ffsc_find_what; int ffsc_tagfile; } ff_search_ctx_T; @@ -191,7 +182,7 @@ typedef struct ff_search_ctx_T { # include "file_search.c.generated.h" #endif -static char_u e_pathtoolong[] = N_("E854: path too long for completion"); +static char e_pathtoolong[] = N_("E854: path too long for completion"); /// Initialization routine for vim_findfile(). /// @@ -221,8 +212,8 @@ static char_u e_pathtoolong[] = N_("E854: path too long for completion"); /// /// Upward search is only done on the starting dir. /// -/// If 'free_visited' is TRUE the list of already visited files/directories is -/// cleared. Set this to FALSE if you just want to search from another +/// If 'free_visited' is true the list of already visited files/directories is +/// cleared. Set this to false if you just want to search from another /// directory, but want to be sure that no directory from a previous search is /// searched again. This is useful if you search for a file at different places. /// The list of visited files/dirs can also be cleared with the function @@ -246,17 +237,15 @@ static char_u e_pathtoolong[] = N_("E854: path too long for completion"); /// /// @param tagfile expanding names of tags files /// @param rel_fname file name to use for "." -void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int level, - int free_visited, int find_what, void *search_ctx_arg, int tagfile, - char_u *rel_fname) +void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, int free_visited, + int find_what, void *search_ctx_arg, int tagfile, char *rel_fname) { - char_u *wc_part; + char *wc_part; ff_stack_T *sptr; ff_search_ctx_T *search_ctx; - /* If a search context is given by the caller, reuse it, else allocate a - * new one. - */ + // If a search context is given by the caller, reuse it, else allocate a + // new one. if (search_ctx_arg != NULL) { search_ctx = search_ctx_arg; } else { @@ -269,12 +258,12 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le ff_clear(search_ctx); // clear visited list if wanted - if (free_visited == TRUE) { + if (free_visited == true) { vim_findfile_free_visited(search_ctx); } else { - /* Reuse old visited lists. Get the visited list for the given - * filename. If no list for the current filename exists, creates a new - * one. */ + // Reuse old visited lists. Get the visited list for the given + // filename. If no list for the current filename exists, creates a new + // one. search_ctx->ffsc_visited_list = ff_get_visited_list(filename, &search_ctx->ffsc_visited_lists_list); if (search_ctx->ffsc_visited_list == NULL) { @@ -291,20 +280,20 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le ff_expand_buffer = xmalloc(MAXPATHL); } - /* Store information on starting dir now if path is relative. - * If path is absolute, we do that later. */ + // Store information on starting dir now if path is relative. + // If path is absolute, we do that later. if (path[0] == '.' && (vim_ispathsep(path[1]) || path[1] == NUL) && (!tagfile || vim_strchr(p_cpo, CPO_DOTTAG) == NULL) && rel_fname != NULL) { - size_t len = (size_t)((char_u *)path_tail((char *)rel_fname) - rel_fname); + size_t len = (size_t)(path_tail(rel_fname) - rel_fname); if (!vim_isAbsName(rel_fname) && len + 1 < MAXPATHL) { // Make the start dir an absolute path name. - STRLCPY(ff_expand_buffer, rel_fname, len + 1); - search_ctx->ffsc_start_dir = (char_u *)FullName_save((char *)ff_expand_buffer, FALSE); + xstrlcpy(ff_expand_buffer, rel_fname, len + 1); + search_ctx->ffsc_start_dir = FullName_save(ff_expand_buffer, false); } else { - search_ctx->ffsc_start_dir = vim_strnsave(rel_fname, len); + search_ctx->ffsc_start_dir = xstrnsave(rel_fname, len); } if (*++path != NUL) { path++; @@ -313,28 +302,26 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le #ifdef BACKSLASH_IN_FILENAME // "c:dir" needs "c:" to be expanded, otherwise use current dir if (*path != NUL && path[1] == ':') { - char_u drive[3]; + char drive[3]; drive[0] = path[0]; drive[1] = ':'; drive[2] = NUL; - if (vim_FullName((const char *)drive, (char *)ff_expand_buffer, MAXPATHL, - true) - == FAIL) { + if (vim_FullName(drive, ff_expand_buffer, MAXPATHL, true) == FAIL) { goto error_return; } path += 2; - } else + } else // NOLINT(readability/braces) #endif if (os_dirname(ff_expand_buffer, MAXPATHL) == FAIL) { goto error_return; } - search_ctx->ffsc_start_dir = vim_strsave(ff_expand_buffer); + search_ctx->ffsc_start_dir = xstrdup(ff_expand_buffer); #ifdef BACKSLASH_IN_FILENAME - /* A path that starts with "/dir" is relative to the drive, not to the - * directory (but not for "//machine/dir"). Only use the drive name. */ + // A path that starts with "/dir" is relative to the drive, not to the + // directory (but not for "//machine/dir"). Only use the drive name. if ((*path == '/' || *path == '\\') && path[1] != path[0] && search_ctx->ffsc_start_dir[1] == ':') { @@ -343,45 +330,39 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le #endif } - /* - * If stopdirs are given, split them into an array of pointers. - * If this fails (mem allocation), there is no upward search at all or a - * stop directory is not recognized -> continue silently. - * If stopdirs just contains a ";" or is empty, - * search_ctx->ffsc_stopdirs_v will only contain a NULL pointer. This - * is handled as unlimited upward search. See function - * ff_path_in_stoplist() for details. - */ + // If stopdirs are given, split them into an array of pointers. + // If this fails (mem allocation), there is no upward search at all or a + // stop directory is not recognized -> continue silently. + // If stopdirs just contains a ";" or is empty, + // search_ctx->ffsc_stopdirs_v will only contain a NULL pointer. This + // is handled as unlimited upward search. See function + // ff_path_in_stoplist() for details. if (stopdirs != NULL) { - char_u *walker = stopdirs; + char *walker = stopdirs; while (*walker == ';') { walker++; } size_t dircount = 1; - search_ctx->ffsc_stopdirs_v = xmalloc(sizeof(char_u *)); + search_ctx->ffsc_stopdirs_v = xmalloc(sizeof(char *)); do { - char_u *helper; + char *helper; void *ptr; helper = walker; ptr = xrealloc(search_ctx->ffsc_stopdirs_v, - (dircount + 1) * sizeof(char_u *)); + (dircount + 1) * sizeof(char *)); search_ctx->ffsc_stopdirs_v = ptr; - walker = (char_u *)vim_strchr((char *)walker, ';'); + walker = vim_strchr(walker, ';'); if (walker) { assert(walker - helper >= 0); - search_ctx->ffsc_stopdirs_v[dircount - 1] = - vim_strnsave(helper, (size_t)(walker - helper)); + search_ctx->ffsc_stopdirs_v[dircount - 1] = xstrnsave(helper, (size_t)(walker - helper)); walker++; } else { - /* this might be "", which means ascent till top - * of directory tree. - */ - search_ctx->ffsc_stopdirs_v[dircount - 1] = - vim_strsave(helper); + // this might be "", which means ascent till top of directory tree. + search_ctx->ffsc_stopdirs_v[dircount - 1] = xstrdup(helper); } dircount++; @@ -391,11 +372,10 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le search_ctx->ffsc_level = level; - /* split into: - * -fix path - * -wildcard_stuff (might be NULL) - */ - wc_part = (char_u *)vim_strchr((char *)path, '*'); + // split into: + // -fix path + // -wildcard_stuff (might be NULL) + wc_part = vim_strchr(path, '*'); if (wc_part != NULL) { int64_t llevel; int len; @@ -403,37 +383,35 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le // save the fix part of the path assert(wc_part - path >= 0); - search_ctx->ffsc_fix_path = vim_strnsave(path, (size_t)(wc_part - path)); - - /* - * copy wc_path and add restricts to the '**' wildcard. - * The octet after a '**' is used as a (binary) counter. - * So '**3' is transposed to '**^C' ('^C' is ASCII value 3) - * or '**76' is transposed to '**N'( 'N' is ASCII value 76). - * If no restrict is given after '**' the default is used. - * Due to this technique the path looks awful if you print it as a - * string. - */ + search_ctx->ffsc_fix_path = xstrnsave(path, (size_t)(wc_part - path)); + + // copy wc_path and add restricts to the '**' wildcard. + // The octet after a '**' is used as a (binary) counter. + // So '**3' is transposed to '**^C' ('^C' is ASCII value 3) + // or '**76' is transposed to '**N'( 'N' is ASCII value 76). + // If no restrict is given after '**' the default is used. + // Due to this technique the path looks awful if you print it as a + // string. len = 0; while (*wc_part != NUL) { if (len + 5 >= MAXPATHL) { emsg(_(e_pathtoolong)); break; } - if (STRNCMP(wc_part, "**", 2) == 0) { + if (strncmp(wc_part, "**", 2) == 0) { ff_expand_buffer[len++] = *wc_part++; ff_expand_buffer[len++] = *wc_part++; - llevel = strtol((char *)wc_part, &errpt, 10); - if ((char_u *)errpt != wc_part && llevel > 0 && llevel < 255) { - ff_expand_buffer[len++] = (char_u)llevel; - } else if ((char_u *)errpt != wc_part && llevel == 0) { + llevel = strtol(wc_part, &errpt, 10); + if (errpt != wc_part && llevel > 0 && llevel < 255) { + ff_expand_buffer[len++] = (char)llevel; + } else if (errpt != wc_part && llevel == 0) { // restrict is 0 -> remove already added '**' len -= 2; } else { ff_expand_buffer[len++] = FF_MAX_STAR_STAR_EXPAND; } - wc_part = (char_u *)errpt; + wc_part = errpt; if (*wc_part != NUL && !vim_ispathsep(*wc_part)) { semsg(_( "E343: Invalid path: '**[number]' must be at the end of the path or be followed by '%s'."), @@ -445,60 +423,59 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le } } ff_expand_buffer[len] = NUL; - search_ctx->ffsc_wc_path = vim_strsave(ff_expand_buffer); + search_ctx->ffsc_wc_path = xstrdup(ff_expand_buffer); } else { - search_ctx->ffsc_fix_path = vim_strsave(path); + search_ctx->ffsc_fix_path = xstrdup(path); } if (search_ctx->ffsc_start_dir == NULL) { - /* store the fix part as startdir. - * This is needed if the parameter path is fully qualified. - */ - search_ctx->ffsc_start_dir = vim_strsave(search_ctx->ffsc_fix_path); + // store the fix part as startdir. + // This is needed if the parameter path is fully qualified. + search_ctx->ffsc_start_dir = xstrdup(search_ctx->ffsc_fix_path); search_ctx->ffsc_fix_path[0] = NUL; } // create an absolute path - if (STRLEN(search_ctx->ffsc_start_dir) - + STRLEN(search_ctx->ffsc_fix_path) + 3 >= MAXPATHL) { + if (strlen(search_ctx->ffsc_start_dir) + + strlen(search_ctx->ffsc_fix_path) + 3 >= MAXPATHL) { emsg(_(e_pathtoolong)); goto error_return; } STRCPY(ff_expand_buffer, search_ctx->ffsc_start_dir); - add_pathsep((char *)ff_expand_buffer); + add_pathsep(ff_expand_buffer); { - size_t eb_len = STRLEN(ff_expand_buffer); - char_u *buf = xmalloc(eb_len + STRLEN(search_ctx->ffsc_fix_path) + 1); + size_t eb_len = strlen(ff_expand_buffer); + char *buf = xmalloc(eb_len + strlen(search_ctx->ffsc_fix_path) + 1); STRCPY(buf, ff_expand_buffer); STRCPY(buf + eb_len, search_ctx->ffsc_fix_path); if (os_isdir(buf)) { STRCAT(ff_expand_buffer, search_ctx->ffsc_fix_path); - add_pathsep((char *)ff_expand_buffer); + add_pathsep(ff_expand_buffer); } else { - char_u *p = (char_u *)path_tail((char *)search_ctx->ffsc_fix_path); - char_u *wc_path = NULL; - char_u *temp = NULL; + char *p = path_tail(search_ctx->ffsc_fix_path); + char *wc_path = NULL; + char *temp = NULL; int len = 0; if (p > search_ctx->ffsc_fix_path) { // do not add '..' to the path and start upwards searching len = (int)(p - search_ctx->ffsc_fix_path) - 1; - if ((len >= 2 && STRNCMP(search_ctx->ffsc_fix_path, "..", 2) == 0) + if ((len >= 2 && strncmp(search_ctx->ffsc_fix_path, "..", 2) == 0) && (len == 2 || search_ctx->ffsc_fix_path[2] == PATHSEP)) { xfree(buf); goto error_return; } - STRLCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, eb_len + (size_t)len + 1); - add_pathsep((char *)ff_expand_buffer); + xstrlcat(ff_expand_buffer, search_ctx->ffsc_fix_path, eb_len + (size_t)len + 1); + add_pathsep(ff_expand_buffer); } else { - len = (int)STRLEN(search_ctx->ffsc_fix_path); + len = (int)strlen(search_ctx->ffsc_fix_path); } if (search_ctx->ffsc_wc_path != NULL) { - wc_path = vim_strsave(search_ctx->ffsc_wc_path); - temp = xmalloc(STRLEN(search_ctx->ffsc_wc_path) - + STRLEN(search_ctx->ffsc_fix_path + len) + wc_path = xstrdup(search_ctx->ffsc_wc_path); + temp = xmalloc(strlen(search_ctx->ffsc_wc_path) + + strlen(search_ctx->ffsc_fix_path + len) + 1); STRCPY(temp, search_ctx->ffsc_fix_path + len); STRCAT(temp, search_ctx->ffsc_wc_path); @@ -510,33 +487,29 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le xfree(buf); } - sptr = ff_create_stack_element(ff_expand_buffer, - search_ctx->ffsc_wc_path, - level, 0); + sptr = ff_create_stack_element(ff_expand_buffer, search_ctx->ffsc_wc_path, level, 0); ff_push(search_ctx, sptr); - search_ctx->ffsc_file_to_search = vim_strsave(filename); + search_ctx->ffsc_file_to_search = xstrdup(filename); return search_ctx; error_return: - /* - * We clear the search context now! - * Even when the caller gave us a (perhaps valid) context we free it here, - * as we might have already destroyed it. - */ + // We clear the search context now! + // Even when the caller gave us a (perhaps valid) context we free it here, + // as we might have already destroyed it. vim_findfile_cleanup(search_ctx); return NULL; } /// @return the stopdir string. Check that ';' is not escaped. -char_u *vim_findfile_stopdir(char_u *buf) +char *vim_findfile_stopdir(char *buf) { - char_u *r_ptr = buf; + char *r_ptr = buf; while (*r_ptr != NUL && *r_ptr != ';') { if (r_ptr[0] == '\\' && r_ptr[1] == ';') { - /* Overwrite the escape char, - * use STRLEN(r_ptr) to move the trailing '\0'. */ + // Overwrite the escape char, + // use strlen(r_ptr) to move the trailing '\0'. STRMOVE(r_ptr, r_ptr + 1); r_ptr++; } @@ -576,14 +549,14 @@ void vim_findfile_cleanup(void *ctx) /// /// @return a pointer to an allocated file name or, /// NULL if nothing found. -char_u *vim_findfile(void *search_ctx_arg) +char *vim_findfile(void *search_ctx_arg) { - char_u *file_path; - char_u *rest_of_wildcards; - char_u *path_end = NULL; + char *file_path; + char *rest_of_wildcards; + char *path_end = NULL; ff_stack_T *stackp = NULL; size_t len; - char_u *p; + char *p; char *suf; ff_search_ctx_T *search_ctx; @@ -593,16 +566,13 @@ char_u *vim_findfile(void *search_ctx_arg) search_ctx = (ff_search_ctx_T *)search_ctx_arg; - /* - * filepath is used as buffer for various actions and as the storage to - * return a found filename. - */ + // filepath is used as buffer for various actions and as the storage to + // return a found filename. file_path = xmalloc(MAXPATHL); // store the end of the start dir -- needed for upward search if (search_ctx->ffsc_start_dir != NULL) { - path_end = &search_ctx->ffsc_start_dir[ - STRLEN(search_ctx->ffsc_start_dir)]; + path_end = &search_ctx->ffsc_start_dir[strlen(search_ctx->ffsc_start_dir)]; } // upward search loop @@ -621,31 +591,26 @@ char_u *vim_findfile(void *search_ctx_arg) break; } - /* - * TODO: decide if we leave this test in - * - * GOOD: don't search a directory(-tree) twice. - * BAD: - check linked list for every new directory entered. - * - check for double files also done below - * - * Here we check if we already searched this directory. - * We already searched a directory if: - * 1) The directory is the same. - * 2) We would use the same wildcard string. - * - * Good if you have links on same directory via several ways - * or you have selfreferences in directories (e.g. SuSE Linux 6.3: - * /etc/rc.d/init.d is linked to /etc/rc.d -> endless loop) - * - * This check is only needed for directories we work on for the - * first time (hence stackp->ff_filearray == NULL) - */ + // TODO(vim): decide if we leave this test in + // + // GOOD: don't search a directory(-tree) twice. + // BAD: - check linked list for every new directory entered. + // - check for double files also done below + // + // Here we check if we already searched this directory. + // We already searched a directory if: + // 1) The directory is the same. + // 2) We would use the same wildcard string. + // + // Good if you have links on same directory via several ways + // or you have selfreferences in directories (e.g. SuSE Linux 6.3: + // /etc/rc.d/init.d is linked to /etc/rc.d -> endless loop) + // + // This check is only needed for directories we work on for the + // first time (hence stackp->ff_filearray == NULL) if (stackp->ffs_filearray == NULL - && ff_check_visited(&search_ctx->ffsc_dir_visited_list - ->ffvl_visited_list, - stackp->ffs_fix_path, - stackp->ffs_wc_path - ) == FAIL) { + && ff_check_visited(&search_ctx->ffsc_dir_visited_list->ffvl_visited_list, + stackp->ffs_fix_path, stackp->ffs_wc_path) == FAIL) { #ifdef FF_VERBOSE if (p_verbose >= 5) { verbose_enter_scroll(); @@ -657,16 +622,15 @@ char_u *vim_findfile(void *search_ctx_arg) #endif ff_free_stack_element(stackp); continue; - } #ifdef FF_VERBOSE - else if (p_verbose >= 5) { + } else if (p_verbose >= 5) { verbose_enter_scroll(); smsg("Searching: %s (%s)", stackp->ffs_fix_path, stackp->ffs_wc_path); msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); - } #endif + } // check depth if (stackp->ffs_level <= 0) { @@ -676,50 +640,46 @@ char_u *vim_findfile(void *search_ctx_arg) file_path[0] = NUL; - /* - * If no filearray till now expand wildcards - * The function expand_wildcards() can handle an array of paths - * and all possible expands are returned in one array. We use this - * to handle the expansion of '**' into an empty string. - */ + // If no filearray till now expand wildcards + // The function expand_wildcards() can handle an array of paths + // and all possible expands are returned in one array. We use this + // to handle the expansion of '**' into an empty string. if (stackp->ffs_filearray == NULL) { char *dirptrs[2]; - /* we use filepath to build the path expand_wildcards() should - * expand. - */ - dirptrs[0] = (char *)file_path; + // we use filepath to build the path expand_wildcards() should expand. + dirptrs[0] = file_path; dirptrs[1] = NULL; // if we have a start dir copy it in if (!vim_isAbsName(stackp->ffs_fix_path) && search_ctx->ffsc_start_dir) { - if (STRLEN(search_ctx->ffsc_start_dir) + 1 >= MAXPATHL) { + if (strlen(search_ctx->ffsc_start_dir) + 1 >= MAXPATHL) { ff_free_stack_element(stackp); goto fail; } STRCPY(file_path, search_ctx->ffsc_start_dir); - if (!add_pathsep((char *)file_path)) { + if (!add_pathsep(file_path)) { ff_free_stack_element(stackp); goto fail; } } // append the fix part of the search path - if (STRLEN(file_path) + STRLEN(stackp->ffs_fix_path) + 1 >= MAXPATHL) { + if (strlen(file_path) + strlen(stackp->ffs_fix_path) + 1 >= MAXPATHL) { ff_free_stack_element(stackp); goto fail; } STRCAT(file_path, stackp->ffs_fix_path); - if (!add_pathsep((char *)file_path)) { + if (!add_pathsep(file_path)) { ff_free_stack_element(stackp); goto fail; } rest_of_wildcards = stackp->ffs_wc_path; if (*rest_of_wildcards != NUL) { - len = STRLEN(file_path); - if (STRNCMP(rest_of_wildcards, "**", 2) == 0) { + len = strlen(file_path); + if (strncmp(rest_of_wildcards, "**", 2) == 0) { // pointer to the restrict byte // The restrict byte is not a character! p = rest_of_wildcards + 2; @@ -743,17 +703,15 @@ char_u *vim_findfile(void *search_ctx_arg) if (stackp->ffs_star_star_empty == 0) { // if not done before, expand '**' to empty stackp->ffs_star_star_empty = 1; - dirptrs[1] = (char *)stackp->ffs_fix_path; + dirptrs[1] = stackp->ffs_fix_path; } } - /* - * Here we copy until the next path separator or the end of - * the path. If we stop at a path separator, there is - * still something else left. This is handled below by - * pushing every directory returned from expand_wildcards() - * on the stack again for further search. - */ + // Here we copy until the next path separator or the end of + // the path. If we stop at a path separator, there is + // still something else left. This is handled below by + // pushing every directory returned from expand_wildcards() + // on the stack again for further search. while (*rest_of_wildcards && !vim_ispathsep(*rest_of_wildcards)) { if (len + 1 >= MAXPATHL) { @@ -769,18 +727,16 @@ char_u *vim_findfile(void *search_ctx_arg) } } - /* - * Expand wildcards like "*" and "$VAR". - * If the path is a URL don't try this. - */ + // Expand wildcards like "*" and "$VAR". + // If the path is a URL don't try this. if (path_with_url(dirptrs[0])) { stackp->ffs_filearray = xmalloc(sizeof(char *)); stackp->ffs_filearray[0] = xstrdup(dirptrs[0]); stackp->ffs_filearray_size = 1; } else { - /* Add EW_NOTWILD because the expanded path may contain - * wildcard characters that are to be taken literally. - * This is a bit of a hack. */ + // Add EW_NOTWILD because the expanded path may contain + // wildcard characters that are to be taken literally. + // This is a bit of a hack. expand_wildcards((dirptrs[1] == NULL) ? 1 : 2, dirptrs, &stackp->ffs_filearray_size, &stackp->ffs_filearray, @@ -790,66 +746,55 @@ char_u *vim_findfile(void *search_ctx_arg) stackp->ffs_filearray_cur = 0; stackp->ffs_stage = 0; } else { - rest_of_wildcards = &stackp->ffs_wc_path[ - STRLEN(stackp->ffs_wc_path)]; + rest_of_wildcards = &stackp->ffs_wc_path[strlen(stackp->ffs_wc_path)]; } if (stackp->ffs_stage == 0) { // this is the first time we work on this directory if (*rest_of_wildcards == NUL) { - /* - * We don't have further wildcards to expand, so we have to - * check for the final file now. - */ + // We don't have further wildcards to expand, so we have to + // check for the final file now. for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) { if (!path_with_url(stackp->ffs_filearray[i]) - && !os_isdir((char_u *)stackp->ffs_filearray[i])) { + && !os_isdir(stackp->ffs_filearray[i])) { continue; // not a directory } // prepare the filename to be checked for existence below - if (STRLEN(stackp->ffs_filearray[i]) + 1 - + STRLEN(search_ctx->ffsc_file_to_search) >= MAXPATHL) { + if (strlen(stackp->ffs_filearray[i]) + 1 + + strlen(search_ctx->ffsc_file_to_search) >= MAXPATHL) { ff_free_stack_element(stackp); goto fail; } STRCPY(file_path, stackp->ffs_filearray[i]); - if (!add_pathsep((char *)file_path)) { + if (!add_pathsep(file_path)) { ff_free_stack_element(stackp); goto fail; } STRCAT(file_path, search_ctx->ffsc_file_to_search); - /* - * Try without extra suffix and then with suffixes - * from 'suffixesadd'. - */ - len = STRLEN(file_path); + // Try without extra suffix and then with suffixes + // from 'suffixesadd'. + len = strlen(file_path); if (search_ctx->ffsc_tagfile) { suf = ""; } else { - suf = (char *)curbuf->b_p_sua; + suf = curbuf->b_p_sua; } for (;;) { // if file exists and we didn't already find it - if ((path_with_url((char *)file_path) + if ((path_with_url(file_path) || (os_path_exists(file_path) - && (search_ctx->ffsc_find_what - == FINDFILE_BOTH - || ((search_ctx->ffsc_find_what - == FINDFILE_DIR) + && (search_ctx->ffsc_find_what == FINDFILE_BOTH + || ((search_ctx->ffsc_find_what == FINDFILE_DIR) == os_isdir(file_path))))) #ifndef FF_VERBOSE && (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list, - file_path, - (char_u *)"" - ) == OK) + file_path, "") == OK) #endif ) { #ifdef FF_VERBOSE if (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list, - file_path, - (char_u *)"" - ) == FAIL) { + file_path, (char_u *)"") == FAIL) { if (p_verbose >= 5) { verbose_enter_scroll(); smsg("Already: %s", file_path); @@ -865,13 +810,11 @@ char_u *vim_findfile(void *search_ctx_arg) stackp->ffs_filearray_cur = i + 1; ff_push(search_ctx, stackp); - if (!path_with_url((char *)file_path)) { + if (!path_with_url(file_path)) { simplify_filename(file_path); } - if (os_dirname(ff_expand_buffer, MAXPATHL) - == OK) { - p = path_shorten_fname(file_path, - ff_expand_buffer); + if (os_dirname(ff_expand_buffer, MAXPATHL) == OK) { + p = path_shorten_fname(file_path, ff_expand_buffer); if (p != NULL) { STRMOVE(file_path, p); } @@ -892,17 +835,17 @@ char_u *vim_findfile(void *search_ctx_arg) break; } assert(MAXPATHL >= len); - copy_option_part(&suf, (char *)file_path + len, MAXPATHL - len, ","); + copy_option_part(&suf, file_path + len, MAXPATHL - len, ","); } } } else { // still wildcards left, push the directories for further search for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) { - if (!os_isdir((char_u *)stackp->ffs_filearray[i])) { + if (!os_isdir(stackp->ffs_filearray[i])) { continue; // not a directory } ff_push(search_ctx, - ff_create_stack_element((char_u *)stackp->ffs_filearray[i], + ff_create_stack_element(stackp->ffs_filearray[i], rest_of_wildcards, stackp->ffs_level - 1, 0)); } @@ -911,22 +854,20 @@ char_u *vim_findfile(void *search_ctx_arg) stackp->ffs_stage = 1; } - /* - * if wildcards contains '**' we have to descent till we reach the - * leaves of the directory tree. - */ - if (STRNCMP(stackp->ffs_wc_path, "**", 2) == 0) { + // if wildcards contains '**' we have to descent till we reach the + // leaves of the directory tree. + if (strncmp(stackp->ffs_wc_path, "**", 2) == 0) { for (int i = stackp->ffs_filearray_cur; i < stackp->ffs_filearray_size; i++) { - if (FNAMECMP(stackp->ffs_filearray[i], - stackp->ffs_fix_path) == 0) { + if (path_fnamecmp(stackp->ffs_filearray[i], + stackp->ffs_fix_path) == 0) { continue; // don't repush same directory } - if (!os_isdir((char_u *)stackp->ffs_filearray[i])) { + if (!os_isdir(stackp->ffs_filearray[i])) { continue; // not a directory } ff_push(search_ctx, - ff_create_stack_element((char_u *)stackp->ffs_filearray[i], + ff_create_stack_element(stackp->ffs_filearray[i], stackp->ffs_wc_path, stackp->ffs_level - 1, 1)); } } @@ -935,9 +876,8 @@ char_u *vim_findfile(void *search_ctx_arg) ff_free_stack_element(stackp); } - /* If we reached this, we didn't find anything downwards. - * Let's check if we should do an upward search. - */ + // If we reached this, we didn't find anything downwards. + // Let's check if we should do an upward search. if (search_ctx->ffsc_start_dir && search_ctx->ffsc_stopdirs_v != NULL && !got_int) { ff_stack_T *sptr; @@ -945,17 +885,15 @@ char_u *vim_findfile(void *search_ctx_arg) // is the last starting directory in the stop list? if (ff_path_in_stoplist(search_ctx->ffsc_start_dir, (int)(path_end - search_ctx->ffsc_start_dir), - search_ctx->ffsc_stopdirs_v) == TRUE) { + search_ctx->ffsc_stopdirs_v) == true) { break; } // cut of last dir - while (path_end > search_ctx->ffsc_start_dir - && vim_ispathsep(*path_end)) { + while (path_end > search_ctx->ffsc_start_dir && vim_ispathsep(*path_end)) { path_end--; } - while (path_end > search_ctx->ffsc_start_dir - && !vim_ispathsep(path_end[-1])) { + while (path_end > search_ctx->ffsc_start_dir && !vim_ispathsep(path_end[-1])) { path_end--; } *path_end = 0; @@ -965,12 +903,12 @@ char_u *vim_findfile(void *search_ctx_arg) break; } - if (STRLEN(search_ctx->ffsc_start_dir) + 1 - + STRLEN(search_ctx->ffsc_fix_path) >= MAXPATHL) { + if (strlen(search_ctx->ffsc_start_dir) + 1 + + strlen(search_ctx->ffsc_fix_path) >= MAXPATHL) { goto fail; } STRCPY(file_path, search_ctx->ffsc_start_dir); - if (!add_pathsep((char *)file_path)) { + if (!add_pathsep(file_path)) { goto fail; } STRCAT(file_path, search_ctx->ffsc_fix_path); @@ -1034,7 +972,7 @@ static void ff_free_visited_list(ff_visited_T *vl) /// @return the already visited list for the given filename. If none is found it /// allocates a new one. -static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, +static ff_visited_list_hdr_T *ff_get_visited_list(char *filename, ff_visited_list_hdr_T **list_headp) { ff_visited_list_hdr_T *retptr = NULL; @@ -1043,7 +981,7 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, if (*list_headp != NULL) { retptr = *list_headp; while (retptr != NULL) { - if (FNAMECMP(filename, retptr->ffvl_filename) == 0) { + if (path_fnamecmp(filename, retptr->ffvl_filename) == 0) { #ifdef FF_VERBOSE if (p_verbose >= 5) { verbose_enter_scroll(); @@ -1067,13 +1005,11 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, } #endif - /* - * if we reach this we didn't find a list and we have to allocate new list - */ + // if we reach this we didn't find a list and we have to allocate new list retptr = xmalloc(sizeof(*retptr)); retptr->ffvl_visited_list = NULL; - retptr->ffvl_filename = vim_strsave(filename); + retptr->ffvl_filename = xstrdup(filename); retptr->ffvl_next = *list_headp; *list_headp = retptr; @@ -1087,7 +1023,7 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, /// - char by char comparison is OK /// - the only differences are in the counters behind a '**', so /// '**\20' is equal to '**\24' -static bool ff_wc_equal(char_u *s1, char_u *s2) +static bool ff_wc_equal(char *s1, char *s2) { int i, j; int c1 = NUL; @@ -1104,8 +1040,8 @@ static bool ff_wc_equal(char_u *s1, char_u *s2) } for (i = 0, j = 0; s1[i] != NUL && s2[j] != NUL;) { - c1 = utf_ptr2char((char *)s1 + i); - c2 = utf_ptr2char((char *)s2 + j); + c1 = utf_ptr2char(s1 + i); + c2 = utf_ptr2char(s2 + j); if ((p_fic ? mb_tolower(c1) != mb_tolower(c2) : c1 != c2) && (prev1 != '*' || prev2 != '*')) { @@ -1114,8 +1050,8 @@ static bool ff_wc_equal(char_u *s1, char_u *s2) prev2 = prev1; prev1 = c1; - i += utfc_ptr2len((char *)s1 + i); - j += utfc_ptr2len((char *)s2 + j); + i += utfc_ptr2len(s1 + i); + j += utfc_ptr2len(s2 + j); } return s1[i] == s2[j]; } @@ -1124,7 +1060,7 @@ static bool ff_wc_equal(char_u *s1, char_u *s2) /// /// @return FAIL if the given file/dir is already in the list or, /// OK if it is newly added -static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u *wc_path) +static int ff_check_visited(ff_visited_T **visited_list, char *fname, char *wc_path) { ff_visited_T *vp; bool url = false; @@ -1132,19 +1068,19 @@ static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u * FileID file_id; // For a URL we only compare the name, otherwise we compare the // device/inode. - if (path_with_url((char *)fname)) { - STRLCPY(ff_expand_buffer, fname, MAXPATHL); + if (path_with_url(fname)) { + xstrlcpy(ff_expand_buffer, fname, MAXPATHL); url = true; } else { ff_expand_buffer[0] = NUL; - if (!os_fileid((char *)fname, &file_id)) { + if (!os_fileid(fname, &file_id)) { return FAIL; } } // check against list of already visited files for (vp = *visited_list; vp != NULL; vp = vp->ffv_next) { - if ((url && FNAMECMP(vp->ffv_fname, ff_expand_buffer) == 0) + if ((url && path_fnamecmp(vp->ffv_fname, ff_expand_buffer) == 0) || (!url && vp->file_id_valid && os_fileid_equal(&(vp->file_id), &file_id))) { // are the wildcard parts equal @@ -1155,10 +1091,8 @@ static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u * } } - /* - * New file/dir. Add it to the list of visited files/dirs. - */ - vp = xmalloc(sizeof(ff_visited_T) + STRLEN(ff_expand_buffer)); + // New file/dir. Add it to the list of visited files/dirs. + vp = xmalloc(sizeof(ff_visited_T) + strlen(ff_expand_buffer)); if (!url) { vp->file_id_valid = true; @@ -1170,7 +1104,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u * } if (wc_path != NULL) { - vp->ffv_wc_path = vim_strsave(wc_path); + vp->ffv_wc_path = xstrdup(wc_path); } else { vp->ffv_wc_path = NULL; } @@ -1182,7 +1116,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u * } /// create stack element from given path pieces -static ff_stack_T *ff_create_stack_element(char_u *fix_part, char_u *wc_part, int level, +static ff_stack_T *ff_create_stack_element(char *fix_part, char *wc_part, int level, int star_star_empty) { ff_stack_T *new = xmalloc(sizeof(ff_stack_T)); @@ -1197,14 +1131,14 @@ static ff_stack_T *ff_create_stack_element(char_u *fix_part, char_u *wc_part, in // the following saves NULL pointer checks in vim_findfile if (fix_part == NULL) { - fix_part = (char_u *)""; + fix_part = ""; } - new->ffs_fix_path = vim_strsave(fix_part); + new->ffs_fix_path = xstrdup(fix_part); if (wc_part == NULL) { - wc_part = (char_u *)""; + wc_part = ""; } - new->ffs_wc_path = vim_strsave(wc_part); + new->ffs_wc_path = xstrdup(wc_part); return new; } @@ -1212,12 +1146,14 @@ static ff_stack_T *ff_create_stack_element(char_u *fix_part, char_u *wc_part, in /// Push a dir on the directory stack. static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr) { - /* check for NULL pointer, not to return an error to the user, but - * to prevent a crash */ - if (stack_ptr != NULL) { - stack_ptr->ffs_prev = search_ctx->ffsc_stack_ptr; - search_ctx->ffsc_stack_ptr = stack_ptr; + // check for NULL pointer, not to return an error to the user, but + // to prevent a crash + if (stack_ptr == NULL) { + return; } + + stack_ptr->ffs_prev = search_ctx->ffsc_stack_ptr; + search_ctx->ffsc_stack_ptr = stack_ptr; } /// Pop a dir from the directory stack. @@ -1289,8 +1225,8 @@ static void ff_clear(ff_search_ctx_T *search_ctx) /// check if the given path is in the stopdirs /// -/// @return TRUE if yes else FALSE -static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) +/// @return true if yes else false +static int ff_path_in_stoplist(char *path, int path_len, char **stopdirs_v) { int i = 0; @@ -1301,32 +1237,31 @@ static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) // if no path consider it as match if (path_len == 0) { - return TRUE; + return true; } for (i = 0; stopdirs_v[i] != NULL; i++) { - if ((int)STRLEN(stopdirs_v[i]) > path_len) { - /* match for parent directory. So '/home' also matches - * '/home/rks'. Check for PATHSEP in stopdirs_v[i], else - * '/home/r' would also match '/home/rks' - */ - if (FNAMENCMP(stopdirs_v[i], path, path_len) == 0 + if ((int)strlen(stopdirs_v[i]) > path_len) { + // match for parent directory. So '/home' also matches + // '/home/rks'. Check for PATHSEP in stopdirs_v[i], else + // '/home/r' would also match '/home/rks' + if (path_fnamencmp(stopdirs_v[i], path, (size_t)path_len) == 0 && vim_ispathsep(stopdirs_v[i][path_len])) { - return TRUE; + return true; } } else { - if (FNAMECMP(stopdirs_v[i], path) == 0) { + if (path_fnamecmp(stopdirs_v[i], path) == 0) { return true; } } } - return FALSE; + return false; } /// Find the file name "ptr[len]" in the path. Also finds directory names. /// -/// On the first call set the parameter 'first' to TRUE to initialize -/// the search. For repeating calls to FALSE. +/// On the first call set the parameter 'first' to true to initialize +/// the search. For repeating calls to false. /// /// Repeating calls will return other files called 'ptr[len]' from the path. /// @@ -1350,7 +1285,7 @@ static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) /// @param rel_fname file name searching relative to /// /// @return an allocated string for the file name. NULL for error. -char_u *find_file_in_path(char_u *ptr, size_t len, int options, int first, char_u *rel_fname) +char *find_file_in_path(char *ptr, size_t len, int options, int first, char *rel_fname) { return find_file_in_path_option(ptr, len, options, first, (*curbuf->b_p_path == NUL @@ -1359,7 +1294,7 @@ char_u *find_file_in_path(char_u *ptr, size_t len, int options, int first, char_ FINDFILE_BOTH, rel_fname, curbuf->b_p_sua); } -static char_u *ff_file_to_find = NULL; +static char *ff_file_to_find = NULL; static void *fdip_search_ctx = NULL; #if defined(EXITFREE) @@ -1385,10 +1320,10 @@ void free_findfile(void) /// @param rel_fname file name searching relative to /// /// @return an allocated string for the file name. NULL for error. -char_u *find_directory_in_path(char_u *ptr, size_t len, int options, char_u *rel_fname) +char *find_directory_in_path(char *ptr, size_t len, int options, char *rel_fname) { - return find_file_in_path_option(ptr, len, options, TRUE, p_cdpath, - FINDFILE_DIR, rel_fname, (char_u *)""); + return find_file_in_path_option(ptr, len, options, true, p_cdpath, + FINDFILE_DIR, rel_fname, ""); } /// @param ptr file name @@ -1398,14 +1333,13 @@ char_u *find_directory_in_path(char_u *ptr, size_t len, int options, char_u *rel /// @param find_what FINDFILE_FILE, _DIR or _BOTH /// @param rel_fname file name we are looking relative to. /// @param suffixes list of suffixes, 'suffixesadd' option -char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first, - char_u *path_option, int find_what, char_u *rel_fname, - char_u *suffixes) +char *find_file_in_path_option(char *ptr, size_t len, int options, int first, char *path_option, + int find_what, char *rel_fname, char *suffixes) { static char *dir; static int did_findfile_init = false; - char_u save_char; - char_u *file_name = NULL; + char save_char; + char *file_name = NULL; char *buf = NULL; int rel_to_curdir; @@ -1426,12 +1360,12 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first ptr[len] = save_char; xfree(ff_file_to_find); - ff_file_to_find = vim_strsave(NameBuff); + ff_file_to_find = xstrdup(NameBuff); if (options & FNAME_UNESC) { // Change all "\ " to " ". - for (ptr = ff_file_to_find; *ptr != NUL; ++ptr) { + for (ptr = ff_file_to_find; *ptr != NUL; ptr++) { if (ptr[0] == '\\' && ptr[1] == ' ') { - memmove(ptr, ptr + 1, STRLEN(ptr)); + memmove(ptr, ptr + 1, strlen(ptr)); } } } @@ -1446,71 +1380,65 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first if (vim_isAbsName(ff_file_to_find) // "..", "../path", "." and "./path": don't use the path_option || rel_to_curdir -#if defined(WIN32) +#if defined(MSWIN) // handle "\tmp" as absolute path || vim_ispathsep(ff_file_to_find[0]) // handle "c:name" as absolute path || (ff_file_to_find[0] != NUL && ff_file_to_find[1] == ':') #endif ) { - /* - * Absolute path, no need to use "path_option". - * If this is not a first call, return NULL. We already returned a - * filename on the first call. - */ - if (first == TRUE) { - if (path_with_url((char *)ff_file_to_find)) { - file_name = vim_strsave(ff_file_to_find); + // Absolute path, no need to use "path_option". + // If this is not a first call, return NULL. We already returned a + // filename on the first call. + if (first == true) { + if (path_with_url(ff_file_to_find)) { + file_name = xstrdup(ff_file_to_find); goto theend; } - /* When FNAME_REL flag given first use the directory of the file. - * Otherwise or when this fails use the current directory. */ - for (int run = 1; run <= 2; ++run) { - size_t l = STRLEN(ff_file_to_find); + // When FNAME_REL flag given first use the directory of the file. + // Otherwise or when this fails use the current directory. + for (int run = 1; run <= 2; run++) { + size_t l = strlen(ff_file_to_find); if (run == 1 && rel_to_curdir && (options & FNAME_REL) && rel_fname != NULL - && STRLEN(rel_fname) + l < MAXPATHL) { + && strlen(rel_fname) + l < MAXPATHL) { STRCPY(NameBuff, rel_fname); - STRCPY(path_tail((char *)NameBuff), ff_file_to_find); - l = STRLEN(NameBuff); + STRCPY(path_tail(NameBuff), ff_file_to_find); + l = strlen(NameBuff); } else { STRCPY(NameBuff, ff_file_to_find); run = 2; } - /* When the file doesn't exist, try adding parts of - * 'suffixesadd'. */ - buf = (char *)suffixes; + // When the file doesn't exist, try adding parts of 'suffixesadd'. + buf = suffixes; for (;;) { - if ( - (os_path_exists(NameBuff) + if ((os_path_exists(NameBuff) && (find_what == FINDFILE_BOTH || ((find_what == FINDFILE_DIR) == os_isdir(NameBuff))))) { - file_name = vim_strsave(NameBuff); + file_name = xstrdup(NameBuff); goto theend; } if (*buf == NUL) { break; } assert(MAXPATHL >= l); - copy_option_part(&buf, (char *)NameBuff + l, MAXPATHL - l, ","); + copy_option_part(&buf, NameBuff + l, MAXPATHL - l, ","); } } } } else { - /* - * Loop over all paths in the 'path' or 'cdpath' option. - * When "first" is set, first setup to the start of the option. - * Otherwise continue to find the next match. - */ - if (first == TRUE) { + // Loop over all paths in the 'path' or 'cdpath' option. + // When "first" is set, first setup to the start of the option. + // Otherwise continue to find the next match. + if (first == true) { // vim_findfile_free_visited can handle a possible NULL pointer vim_findfile_free_visited(fdip_search_ctx); - dir = (char *)path_option; + dir = path_option; did_findfile_init = false; } @@ -1521,13 +1449,12 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first break; } - did_findfile_init = FALSE; + did_findfile_init = false; } else { - char_u *r_ptr; + char *r_ptr; if (dir == NULL || *dir == NUL) { - /* We searched all paths of the option, now we can - * free the search context. */ + // We searched all paths of the option, now we can free the search context. vim_findfile_cleanup(fdip_search_ctx); fdip_search_ctx = NULL; break; @@ -1540,19 +1467,19 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first copy_option_part(&dir, buf, MAXPATHL, " ,"); // get the stopdir string - r_ptr = vim_findfile_stopdir((char_u *)buf); - fdip_search_ctx = vim_findfile_init((char_u *)buf, ff_file_to_find, + r_ptr = vim_findfile_stopdir(buf); + fdip_search_ctx = vim_findfile_init(buf, ff_file_to_find, r_ptr, 100, false, find_what, fdip_search_ctx, false, rel_fname); if (fdip_search_ctx != NULL) { - did_findfile_init = TRUE; + did_findfile_init = true; } xfree(buf); } } } if (file_name == NULL && (options & FNAME_MESS)) { - if (first == TRUE) { + if (first == true) { if (find_what == FINDFILE_DIR) { semsg(_("E344: Can't find directory \"%s\" in cdpath"), ff_file_to_find); @@ -1651,14 +1578,14 @@ int vim_chdirfile(char *fname, CdCause cause) { char dir[MAXPATHL]; - STRLCPY(dir, fname, MAXPATHL); + xstrlcpy(dir, fname, MAXPATHL); *path_tail_with_sep(dir) = NUL; if (os_dirname(NameBuff, sizeof(NameBuff)) != OK) { NameBuff[0] = NUL; } - if (pathcmp(dir, (char *)NameBuff, -1) == 0) { + if (pathcmp(dir, NameBuff, -1) == 0) { // nothing to do return OK; } @@ -1679,10 +1606,10 @@ int vim_chdirfile(char *fname, CdCause cause) } /// Change directory to "new_dir". Search 'cdpath' for relative directory names. -int vim_chdir(char_u *new_dir) +int vim_chdir(char *new_dir) { - char *dir_name = (char *)find_directory_in_path(new_dir, STRLEN(new_dir), - FNAME_MESS, (char_u *)curbuf->b_ffname); + char *dir_name = find_directory_in_path(new_dir, strlen(new_dir), + FNAME_MESS, curbuf->b_ffname); if (dir_name == NULL) { return -1; } diff --git a/src/nvim/file_search.h b/src/nvim/file_search.h index 4d4e723922..b69a6fa154 100644 --- a/src/nvim/file_search.h +++ b/src/nvim/file_search.h @@ -1,10 +1,10 @@ #ifndef NVIM_FILE_SEARCH_H #define NVIM_FILE_SEARCH_H -#include <stdlib.h> // for size_t +#include <stdlib.h> -#include "nvim/globals.h" // for CdScope -#include "nvim/types.h" // for char_u +#include "nvim/globals.h" +#include "nvim/types.h" // Flags for find_file_*() functions. #define FINDFILE_FILE 0 // only files diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 5dfcbb0668..9da9d6199e 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -6,62 +6,72 @@ #include <assert.h> #include <errno.h> #include <fcntl.h> +#include <iconv.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> #include <string.h> +#include <sys/stat.h> +#include <uv.h> -#include "nvim/api/private/helpers.h" +#include "auto/config.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" -#include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/eval/typval.h" -#include "nvim/eval/userfunc.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/fileio.h" #include "nvim/fold.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" -#include "nvim/hashtab.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/iconv.h" #include "nvim/input.h" +#include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/mbyte.h" #include "nvim/memfile.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" -#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/os_defs.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/quickfix.h" +#include "nvim/pos.h" #include "nvim/regexp.h" -#include "nvim/search.h" +#include "nvim/screen.h" #include "nvim/sha256.h" #include "nvim/shada.h" -#include "nvim/state.h" #include "nvim/strings.h" #include "nvim/types.h" #include "nvim/ui.h" -#include "nvim/ui_compositor.h" #include "nvim/undo.h" #include "nvim/vim.h" -#include "nvim/window.h" + +#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD) +# include <dirent.h> +# include <sys/file.h> +#endif + +#ifdef OPEN_CHR_FILES +# include "nvim/charset.h" +#endif #define BUFSIZE 8192 // size of normal write buffer #define SMBUFSIZE 256 // size of emergency write buffer @@ -72,30 +82,30 @@ #endif #define HAS_BW_FLAGS -#define FIO_LATIN1 0x01 // convert Latin1 -#define FIO_UTF8 0x02 // convert UTF-8 -#define FIO_UCS2 0x04 // convert UCS-2 -#define FIO_UCS4 0x08 // convert UCS-4 -#define FIO_UTF16 0x10 // convert UTF-16 -#define FIO_ENDIAN_L 0x80 // little endian -#define FIO_NOCONVERT 0x2000 // skip encoding conversion -#define FIO_UCSBOM 0x4000 // check for BOM at start of file -#define FIO_ALL (-1) // allow all formats - -/* When converting, a read() or write() may leave some bytes to be converted - * for the next call. The value is guessed... */ +enum { + FIO_LATIN1 = 0x01, // convert Latin1 + FIO_UTF8 = 0x02, // convert UTF-8 + FIO_UCS2 = 0x04, // convert UCS-2 + FIO_UCS4 = 0x08, // convert UCS-4 + FIO_UTF16 = 0x10, // convert UTF-16 + FIO_ENDIAN_L = 0x80, // little endian + FIO_NOCONVERT = 0x2000, // skip encoding conversion + FIO_UCSBOM = 0x4000, // check for BOM at start of file + FIO_ALL = -1, // allow all formats +}; + +// When converting, a read() or write() may leave some bytes to be converted +// for the next call. The value is guessed... #define CONV_RESTLEN 30 -/* We have to guess how much a sequence of bytes may expand when converting - * with iconv() to be able to allocate a buffer. */ +// We have to guess how much a sequence of bytes may expand when converting +// with iconv() to be able to allocate a buffer. #define ICONV_MULT 8 -/* - * Structure to pass arguments from buf_write() to buf_write_bytes(). - */ +// Structure to pass arguments from buf_write() to buf_write_bytes(). struct bw_info { int bw_fd; // file descriptor - char_u *bw_buf; // buffer with data to be written + char *bw_buf; // buffer with data to be written int bw_len; // length of data #ifdef HAS_BW_FLAGS int bw_flags; // FIO_ flags @@ -103,14 +113,12 @@ struct bw_info { char_u bw_rest[CONV_RESTLEN]; // not converted bytes int bw_restlen; // nr of bytes in bw_rest[] int bw_first; // first write call - char_u *bw_conv_buf; // buffer for writing converted chars + char *bw_conv_buf; // buffer for writing converted chars size_t bw_conv_buflen; // size of bw_conv_buf int bw_conv_error; // set for conversion error linenr_T bw_conv_error_lnum; // first line with error or zero linenr_T bw_start_lnum; // line number at start of buffer -#ifdef HAVE_ICONV iconv_t bw_iconv_fd; // descriptor for iconv() or -1 -#endif }; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -118,23 +126,25 @@ struct bw_info { #endif static char *e_auchangedbuf = N_("E812: Autocommands changed buffer or buffer name"); +static char e_no_matching_autocommands_for_buftype_str_buffer[] + = N_("E676: No matching autocommands for buftype=%s buffer"); -void filemess(buf_T *buf, char_u *name, char_u *s, int attr) +void filemess(buf_T *buf, char *name, char *s, int attr) { int msg_scroll_save; if (msg_silent != 0) { return; } - add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)name); + add_quoted_fname(IObuff, IOSIZE - 100, buf, (const char *)name); // Avoid an over-long translation to cause trouble. - STRLCAT(IObuff, s, IOSIZE); + xstrlcat(IObuff, s, IOSIZE); // For the first message may have to start a new line. // For further ones overwrite the previous one, reset msg_scroll before // calling filemess(). msg_scroll_save = msg_scroll; if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) { - msg_scroll = FALSE; + msg_scroll = false; } if (!msg_scroll) { // wait a bit when overwriting an error msg check_for_delay(false); @@ -143,7 +153,7 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) msg_scroll = msg_scroll_save; msg_scrolled_ign = true; // may truncate the message to avoid a hit-return prompt - msg_outtrans_attr(msg_may_trunc(FALSE, IObuff), attr); + msg_outtrans_attr(msg_may_trunc(false, IObuff), attr); msg_clr_eos(); ui_flush(); msg_scrolled_ign = false; @@ -167,6 +177,7 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) /// READ_STDIN read from stdin instead of a file /// READ_BUFFER read from curbuf instead of a file (converting after reading /// stdin) +/// READ_NOFILE do not read a file, only trigger BufReadCmd /// READ_DUMMY read into a dummy buffer (to check if file contents changed) /// READ_KEEP_UNDO don't clear undo info or read it from a file /// READ_FIFO read from fifo/socket instead of a file @@ -235,11 +246,9 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, char *fenc_next = NULL; // next item in 'fencs' or NULL bool advance_fenc = false; long real_size = 0; -#ifdef HAVE_ICONV iconv_t iconv_fd = (iconv_t)-1; // descriptor for iconv() or -1 bool did_iconv = false; // true when iconv() failed and trying // 'charconvert' next -#endif bool converted = false; // true if conversion done bool notconverted = false; // true if conversion wanted but it wasn't possible char conv_rest[CONV_RESTLEN]; @@ -256,18 +265,16 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, curbuf->b_no_eol_lnum = 0; // in case it was set by the previous read - /* - * If there is no file name yet, use the one for the read file. - * BF_NOTEDITED is set to reflect this. - * Don't do this for a read from a filter. - * Only do this when 'cpoptions' contains the 'f' flag. - */ + // If there is no file name yet, use the one for the read file. + // BF_NOTEDITED is set to reflect this. + // Don't do this for a read from a filter. + // Only do this when 'cpoptions' contains the 'f' flag. if (curbuf->b_ffname == NULL && !filtering && fname != NULL && vim_strchr(p_cpo, CPO_FNAMER) != NULL && !(flags & READ_DUMMY)) { - if (set_rw_fname((char_u *)fname, (char_u *)sfname) == FAIL) { + if (set_rw_fname(fname, sfname) == FAIL) { return FAIL; } } @@ -334,20 +341,26 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } curbuf->b_op_start = orig_start; + + if (flags & READ_NOFILE) { + // Return NOTDONE instead of FAIL so that BufEnter can be triggered + // and other operations don't fail. + return NOTDONE; + } } if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) { - msg_scroll = FALSE; // overwrite previous file message + msg_scroll = false; // overwrite previous file message } else { - msg_scroll = TRUE; // don't overwrite previous file message + msg_scroll = true; // don't overwrite previous file message } // If the name is too long we might crash further on, quit here. if (fname != NULL && *fname != NUL) { - size_t namelen = STRLEN(fname); + size_t namelen = strlen(fname); // If the name is too long we might crash further on, quit here. if (namelen >= MAXPATHL) { - filemess(curbuf, (char_u *)fname, (char_u *)_("Illegal file name"), 0); + filemess(curbuf, fname, _("Illegal file name"), 0); msg_end(); msg_scroll = msg_save; return FAIL; @@ -358,7 +371,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, // swap file may destroy it! Reported on MS-DOS and Win 95. if (after_pathsep(fname, fname + namelen)) { if (!silent) { - filemess(curbuf, (char_u *)fname, (char_u *)_(msg_is_a_directory), 0); + filemess(curbuf, fname, _(msg_is_a_directory), 0); } msg_end(); msg_scroll = msg_save; @@ -380,10 +393,10 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, ) { if (S_ISDIR(perm)) { if (!silent) { - filemess(curbuf, (char_u *)fname, (char_u *)_(msg_is_a_directory), 0); + filemess(curbuf, fname, _(msg_is_a_directory), 0); } } else { - filemess(curbuf, (char_u *)fname, (char_u *)_("is not a file"), 0); + filemess(curbuf, fname, _("is not a file"), 0); } msg_end(); msg_scroll = msg_save; @@ -394,15 +407,13 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, // Set default or forced 'fileformat' and 'binary'. set_file_options(set_options, eap); - /* - * When opening a new file we take the readonly flag from the file. - * Default is r/w, can be set to r/o below. - * Don't reset it when in readonly mode - * Only set/reset b_p_ro when BF_CHECK_RO is set. - */ + // When opening a new file we take the readonly flag from the file. + // Default is r/w, can be set to r/o below. + // Don't reset it when in readonly mode + // Only set/reset b_p_ro when BF_CHECK_RO is set. check_readonly = (newfile && (curbuf->b_flags & BF_CHECK_RO)); if (check_readonly && !readonlymode) { - curbuf->b_p_ro = FALSE; + curbuf->b_p_ro = false; } if (newfile && !read_stdin && !read_buffer && !read_fifo) { @@ -412,17 +423,15 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, curbuf->b_mtime_read = curbuf->b_mtime; curbuf->b_mtime_read_ns = curbuf->b_mtime_ns; #ifdef UNIX - /* - * Use the protection bits of the original file for the swap file. - * This makes it possible for others to read the name of the - * edited file from the swapfile, but only if they can read the - * edited file. - * Remove the "write" and "execute" bits for group and others - * (they must not write the swapfile). - * Add the "read" and "write" bits for the user, otherwise we may - * not be able to write to the file ourselves. - * Setting the bits is done below, after creating the swap file. - */ + // Use the protection bits of the original file for the swap file. + // This makes it possible for others to read the name of the + // edited file from the swapfile, but only if they can read the + // edited file. + // Remove the "write" and "execute" bits for group and others + // (they must not write the swapfile). + // Add the "read" and "write" bits for the user, otherwise we may + // not be able to write to the file ourselves. + // Setting the bits is done below, after creating the swap file. swap_mode = ((int)file_info.stat.st_mode & 0644) | 0600; #endif } else { @@ -475,10 +484,10 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } } if (!silent) { - if (dir_of_file_exists((char_u *)fname)) { - filemess(curbuf, (char_u *)sfname, (char_u *)new_file_message(), 0); + if (dir_of_file_exists(fname)) { + filemess(curbuf, sfname, new_file_message(), 0); } else { - filemess(curbuf, (char_u *)sfname, (char_u *)_("[New DIRECTORY]"), 0); + filemess(curbuf, sfname, _("[New DIRECTORY]"), 0); } } // Even though this is a new file, it might have been @@ -497,39 +506,38 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, return FAIL; } return OK; // a new file is not an error - } else { - filemess(curbuf, (char_u *)sfname, (char_u *)((fd == UV_EFBIG) ? _("[File too big]") : + } + filemess(curbuf, sfname, ((fd == UV_EFBIG) ? _("[File too big]") : #if defined(UNIX) && defined(EOVERFLOW) - // libuv only returns -errno - // in Unix and in Windows - // open() does not set - // EOVERFLOW - (fd == -EOVERFLOW) ? _("[File too big]") : + // libuv only returns -errno + // in Unix and in Windows + // open() does not set + // EOVERFLOW + (fd == -EOVERFLOW) ? _("[File too big]") : #endif - _("[Permission Denied]")), 0); - curbuf->b_p_ro = true; // must use "w!" now - } + _("[Permission Denied]")), 0); + curbuf->b_p_ro = true; // must use "w!" now return FAIL; } - /* - * Only set the 'ro' flag for readonly files the first time they are - * loaded. Help files always get readonly mode - */ + // Only set the 'ro' flag for readonly files the first time they are + // loaded. Help files always get readonly mode if ((check_readonly && file_readonly) || curbuf->b_help) { - curbuf->b_p_ro = TRUE; + curbuf->b_p_ro = true; } if (set_options) { // Don't change 'eol' if reading from buffer as it will already be // correctly set when reading stdin. if (!read_buffer) { - curbuf->b_p_eol = TRUE; - curbuf->b_start_eol = TRUE; + curbuf->b_p_eof = false; + curbuf->b_start_eof = false; + curbuf->b_p_eol = true; + curbuf->b_start_eol = true; } - curbuf->b_p_bomb = FALSE; - curbuf->b_start_bomb = FALSE; + curbuf->b_p_bomb = false; + curbuf->b_start_bomb = false; } // Create a swap file now, so that other Vims are warned that we are @@ -583,7 +591,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, return FAIL; } - ++no_wait_return; // don't wait for return yet + no_wait_return++; // don't wait for return yet // Set '[ mark to the line above where the lines go (line 1 if zero). orig_start = curbuf->b_op_start; @@ -635,16 +643,14 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, if (aborting()) { // autocmds may abort script processing no_wait_return--; msg_scroll = msg_save; - curbuf->b_p_ro = TRUE; // must use "w!" now + curbuf->b_p_ro = true; // must use "w!" now return FAIL; } - /* - * Don't allow the autocommands to change the current buffer. - * Try to re-open the file. - * - * Don't allow the autocommands to change the buffer name either - * (cd for example) if it invalidates fname or sfname. - */ + // Don't allow the autocommands to change the current buffer. + // Try to re-open the file. + // + // Don't allow the autocommands to change the buffer name either + // (cd for example) if it invalidates fname or sfname. if (!read_stdin && (curbuf != old_curbuf || (using_b_ffname && (old_b_ffname != curbuf->b_ffname)) || (using_b_fname && (old_b_fname != curbuf->b_fname)) @@ -656,7 +662,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, } else { emsg(_("E201: *ReadPre autocommands must not change current buffer")); } - curbuf->b_p_ro = TRUE; // must use "w!" now + curbuf->b_p_ro = true; // must use "w!" now return FAIL; } } @@ -666,16 +672,14 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, if (!recoverymode && !filtering && !(flags & READ_DUMMY) && !silent) { if (!read_stdin && !read_buffer) { - filemess(curbuf, (char_u *)sfname, (char_u *)"", 0); + filemess(curbuf, sfname, "", 0); } } - msg_scroll = FALSE; // overwrite the file message + msg_scroll = false; // overwrite the file message - /* - * Set linecnt now, before the "retry" caused by a wrong guess for - * fileformat, and after the autocommands, which may change them. - */ + // Set linecnt now, before the "retry" caused by a wrong guess for + // fileformat, and after the autocommands, which may change them. linecnt = curbuf->b_ml.ml_line_count; // "++bad=" argument. @@ -688,11 +692,9 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, curbuf->b_bad_char = 0; } - /* - * Decide which 'encoding' to use or use first. - */ + // Decide which 'encoding' to use or use first. if (eap != NULL && eap->force_enc != 0) { - fenc = (char *)enc_canonize((char_u *)eap->cmd + eap->force_enc); + fenc = enc_canonize(eap->cmd + eap->force_enc); fenc_alloced = true; keep_dest_enc = true; } else if (curbuf->b_p_bin) { @@ -708,32 +710,30 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip, fenc_alloced = false; } else if (*p_fencs == NUL) { - fenc = (char *)curbuf->b_p_fenc; // use format from buffer + fenc = curbuf->b_p_fenc; // use format from buffer fenc_alloced = false; } else { - fenc_next = (char *)p_fencs; // try items in 'fileencodings' - fenc = (char *)next_fenc(&fenc_next, &fenc_alloced); - } - - /* - * Jump back here to retry reading the file in different ways. - * Reasons to retry: - * - encoding conversion failed: try another one from "fenc_next" - * - BOM detected and fenc was set, need to setup conversion - * - "fileformat" check failed: try another - * - * Variables set for special retry actions: - * "file_rewind" Rewind the file to start reading it again. - * "advance_fenc" Advance "fenc" using "fenc_next". - * "skip_read" Re-use already read bytes (BOM detected). - * "did_iconv" iconv() conversion failed, try 'charconvert'. - * "keep_fileformat" Don't reset "fileformat". - * - * Other status indicators: - * "tmpname" When != NULL did conversion with 'charconvert'. - * Output file has to be deleted afterwards. - * "iconv_fd" When != -1 did conversion with iconv(). - */ + fenc_next = p_fencs; // try items in 'fileencodings' + fenc = next_fenc(&fenc_next, &fenc_alloced); + } + + // Jump back here to retry reading the file in different ways. + // Reasons to retry: + // - encoding conversion failed: try another one from "fenc_next" + // - BOM detected and fenc was set, need to setup conversion + // - "fileformat" check failed: try another + // + // Variables set for special retry actions: + // "file_rewind" Rewind the file to start reading it again. + // "advance_fenc" Advance "fenc" using "fenc_next". + // "skip_read" Re-use already read bytes (BOM detected). + // "did_iconv" iconv() conversion failed, try 'charconvert'. + // "keep_fileformat" Don't reset "fileformat". + // + // Other status indicators: + // "tmpname" When != NULL did conversion with 'charconvert'. + // Output file has to be deleted afterwards. + // "iconv_fd" When != -1 did conversion with iconv(). retry: if (file_rewind) { @@ -751,22 +751,20 @@ retry: } file_rewind = false; if (set_options) { - curbuf->b_p_bomb = FALSE; - curbuf->b_start_bomb = FALSE; + curbuf->b_p_bomb = false; + curbuf->b_start_bomb = false; } conv_error = 0; } - /* - * When retrying with another "fenc" and the first time "fileformat" - * will be reset. - */ + // When retrying with another "fenc" and the first time "fileformat" + // will be reset. if (keep_fileformat) { keep_fileformat = false; } else { if (eap != NULL && eap->force_ff != 0) { fileformat = get_fileformat_force(curbuf, eap); - try_unix = try_dos = try_mac = FALSE; + try_unix = try_dos = try_mac = false; } else if (curbuf->b_p_bin) { fileformat = EOL_UNIX; // binary: use Unix format } else if (*p_ffs == @@ -777,18 +775,14 @@ retry: } } -#ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { // aborted conversion with iconv(), close the descriptor iconv_close(iconv_fd); iconv_fd = (iconv_t)-1; } -#endif if (advance_fenc) { - /* - * Try the next entry in 'fileencodings'. - */ + // Try the next entry in 'fileencodings'. advance_fenc = false; if (eap != NULL && eap->force_enc != 0) { @@ -806,7 +800,7 @@ retry: xfree(fenc); } if (fenc_next != NULL) { - fenc = (char *)next_fenc(&fenc_next, &fenc_alloced); + fenc = next_fenc(&fenc_next, &fenc_alloced); } else { fenc = ""; fenc_alloced = false; @@ -818,16 +812,14 @@ retry: } } - /* - * Conversion may be required when the encoding of the file is different - * from 'encoding' or 'encoding' is UTF-16, UCS-2 or UCS-4. - */ + // Conversion may be required when the encoding of the file is different + // from 'encoding' or 'encoding' is UTF-16, UCS-2 or UCS-4. fio_flags = 0; - converted = need_conversion((char_u *)fenc); + converted = need_conversion(fenc); if (converted) { // "ucs-bom" means we need to check the first bytes of the file // for a BOM. - if (STRCMP(fenc, ENC_UCSBOM) == 0) { + if (strcmp(fenc, ENC_UCSBOM) == 0) { fio_flags = FIO_UCSBOM; } else { // Check if UCS-2/4 or Latin1 to UTF-8 conversion needs to be @@ -837,34 +829,24 @@ retry: // appears not to handle this correctly. This works just like // conversion to UTF-8 except how the resulting character is put in // the buffer. - fio_flags = get_fio_flags((char_u *)fenc); + fio_flags = get_fio_flags(fenc); } -#ifdef HAVE_ICONV // Try using iconv() if we can't convert internally. if (fio_flags == 0 && !did_iconv) { - iconv_fd = (iconv_t)my_iconv_open((char_u *)"utf-8", (char_u *)fenc); + iconv_fd = (iconv_t)my_iconv_open("utf-8", fenc); } -#endif - /* - * Use the 'charconvert' expression when conversion is required - * and we can't do it internally or with iconv(). - */ + // Use the 'charconvert' expression when conversion is required + // and we can't do it internally or with iconv(). if (fio_flags == 0 && !read_stdin && !read_buffer && *p_ccv != NUL - && !read_fifo -#ifdef HAVE_ICONV - && iconv_fd == (iconv_t)-1 -#endif - ) { -#ifdef HAVE_ICONV + && !read_fifo && iconv_fd == (iconv_t)-1) { did_iconv = false; -#endif // Skip conversion when it's already done (retry for wrong // "fileformat"). if (tmpname == NULL) { - tmpname = (char *)readfile_charconvert((char_u *)fname, (char_u *)fenc, &fd); + tmpname = readfile_charconvert(fname, fenc, &fd); if (tmpname == NULL) { // Conversion failed. Try another one. advance_fenc = true; @@ -878,11 +860,7 @@ retry: } } } else { - if (fio_flags == 0 -#ifdef HAVE_ICONV - && iconv_fd == (iconv_t)-1 -#endif - ) { + if (fio_flags == 0 && iconv_fd == (iconv_t)-1) { // Conversion wanted but we can't. // Try the next conversion in 'fileencodings' advance_fenc = true; @@ -892,7 +870,7 @@ retry: } // Set "can_retry" when it's possible to rewind the file and try with - // another "fenc" value. It's FALSE when no other "fenc" to try, reading + // another "fenc" value. It's false when no other "fenc" to try, reading // stdin or fixed at a specific encoding. can_retry = (*fenc != NUL && !read_stdin && !keep_dest_enc && !read_fifo); @@ -915,12 +893,10 @@ retry: } while (!error && !got_int) { - /* - * We allocate as much space for the file as we can get, plus - * space for the old line plus room for one terminating NUL. - * The amount is limited by the fact that read() only can read - * up to max_unsigned characters (and other things). - */ + // We allocate as much space for the file as we can get, plus + // space for the old line plus room for one terminating NUL. + // The amount is limited by the fact that read() only can read + // up to max_unsigned characters (and other things). { if (!skip_read) { // Use buffer >= 64K. Add linerest to double the size if the @@ -967,12 +943,9 @@ retry: // ucs-4 to utf-8: 4 bytes become up to 6 bytes, size must be // multiple of 4 real_size = (int)size; -#ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { size = size / ICONV_MULT; - } else { -#endif - if (fio_flags & FIO_LATIN1) { + } else if (fio_flags & FIO_LATIN1) { size = size / 2; } else if (fio_flags & (FIO_UCS2 | FIO_UTF16)) { size = (size * 2 / 3) & ~1; @@ -981,9 +954,7 @@ retry: } else if (fio_flags == FIO_UCSBOM) { size = size / ICONV_MULT; // worst case } -#ifdef HAVE_ICONV - } -#endif + if (conv_restlen > 0) { // Insert unconverted bytes from previous line. memmove(ptr, conv_rest, (size_t)conv_restlen); // -V614 @@ -992,10 +963,8 @@ retry: } if (read_buffer) { - /* - * Read bytes from curbuf. Used for converting text read - * from stdin. - */ + // Read bytes from curbuf. Used for converting text read + // from stdin. if (read_buf_lnum > from) { size = 0; } else { @@ -1004,14 +973,14 @@ retry: tlen = 0; for (;;) { - p = ml_get(read_buf_lnum) + read_buf_col; - n = (int)STRLEN(p); + p = (char_u *)ml_get(read_buf_lnum) + read_buf_col; + n = (int)strlen((char *)p); if ((int)tlen + n + 1 > size) { // Filled up to "size", append partial line. // Change NL to NUL to reverse the effect done // below. n = (int)(size - tlen); - for (ni = 0; ni < n; ++ni) { + for (ni = 0; ni < n; ni++) { if (p[ni] == NL) { ptr[tlen++] = NUL; } else { @@ -1045,9 +1014,7 @@ retry: } } } else { - /* - * Read bytes from the file. - */ + // Read bytes from the file. size = read_eintr(fd, ptr, (size_t)size); } @@ -1055,17 +1022,11 @@ retry: if (size < 0) { // read error error = true; } else if (conv_restlen > 0) { - /* - * Reached end-of-file but some trailing bytes could - * not be converted. Truncated file? - */ + // Reached end-of-file but some trailing bytes could + // not be converted. Truncated file? // When we did a conversion report an error. - if (fio_flags != 0 -#ifdef HAVE_ICONV - || iconv_fd != (iconv_t)-1 -#endif - ) { + if (fio_flags != 0 || iconv_fd != (iconv_t)-1) { if (can_retry) { goto rewind_retry; } @@ -1073,9 +1034,8 @@ retry: conv_error = curbuf->b_ml.ml_line_count - linecnt + 1; } - } - // Remember the first linenr with an illegal byte - else if (illegal_byte == 0) { + } else if (illegal_byte == 0) { + // Remember the first linenr with an illegal byte illegal_byte = curbuf->b_ml.ml_line_count - linecnt + 1; } @@ -1087,23 +1047,17 @@ retry: // character if we were converting; if we weren't, // leave the UTF8 checking code to do it, as it // works slightly differently. - if (bad_char_behavior != BAD_KEEP && (fio_flags != 0 -#ifdef HAVE_ICONV - || iconv_fd != (iconv_t)-1 -#endif - )) { + if (bad_char_behavior != BAD_KEEP && (fio_flags != 0 || iconv_fd != (iconv_t)-1)) { while (conv_restlen > 0) { *(--ptr) = (char)bad_char_behavior; conv_restlen--; } } fio_flags = 0; // don't convert this -#ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { iconv_close(iconv_fd); iconv_fd = (iconv_t)-1; } -#endif } } } @@ -1111,26 +1065,24 @@ retry: skip_read = false; - /* - * At start of file: Check for BOM. - * Also check for a BOM for other Unicode encodings, but not after - * converting with 'charconvert' or when a BOM has already been - * found. - */ + // At start of file: Check for BOM. + // Also check for a BOM for other Unicode encodings, but not after + // converting with 'charconvert' or when a BOM has already been + // found. if ((filesize == 0) && (fio_flags == FIO_UCSBOM || (!curbuf->b_p_bomb && tmpname == NULL && (*fenc == 'u' || *fenc == NUL)))) { - char_u *ccname; + char *ccname; int blen = 0; // no BOM detection in a short file or in binary mode if (size < 2 || curbuf->b_p_bin) { ccname = NULL; } else { - ccname = check_for_bom((char_u *)ptr, size, &blen, - fio_flags == FIO_UCSBOM ? FIO_ALL : get_fio_flags((char_u *)fenc)); + ccname = check_for_bom(ptr, size, &blen, + fio_flags == FIO_UCSBOM ? FIO_ALL : get_fio_flags(fenc)); } if (ccname != NULL) { // Remove BOM from the text @@ -1138,8 +1090,8 @@ retry: size -= blen; memmove(ptr, ptr + blen, (size_t)size); if (set_options) { - curbuf->b_p_bomb = TRUE; - curbuf->b_start_bomb = TRUE; + curbuf->b_p_bomb = true; + curbuf->b_start_bomb = true; } } @@ -1152,7 +1104,7 @@ retry: if (fenc_alloced) { xfree(fenc); } - fenc = (char *)ccname; + fenc = ccname; fenc_alloced = false; } // retry reading without getting new bytes or rewinding @@ -1165,14 +1117,11 @@ retry: ptr -= conv_restlen; size += conv_restlen; conv_restlen = 0; - /* - * Break here for a read error or end-of-file. - */ + // Break here for a read error or end-of-file. if (size <= 0) { break; } -#ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { // Attempt conversion of the read bytes to 'encoding' using iconv(). const char *fromp = ptr; @@ -1181,11 +1130,9 @@ retry: char *top = ptr; size_t to_size = (size_t)(real_size - size); - /* - * If there is conversion error or not enough room try using - * another conversion. Except for when there is no - * alternative (help files). - */ + // If there is conversion error or not enough room try using + // another conversion. Except for when there is no + // alternative (help files). while ((iconv(iconv_fd, (void *)&fromp, &from_size, &top, &to_size) == (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL) @@ -1194,7 +1141,7 @@ retry: goto rewind_retry; } if (conv_error == 0) { - conv_error = readfile_linenr(linecnt, (char_u *)ptr, (char_u *)top); + conv_error = readfile_linenr(linecnt, ptr, top); } // Deal with a bad byte and continue with the next. @@ -1212,7 +1159,7 @@ retry: if (from_size > 0) { // Some remaining characters, keep them for the next // round. - memmove(conv_rest, (char_u *)fromp, from_size); + memmove(conv_rest, fromp, from_size); conv_restlen = (int)from_size; } @@ -1221,7 +1168,6 @@ retry: memmove(line_start, buffer, (size_t)linerest); size = (top - ptr); } -#endif if (fio_flags != 0) { unsigned int u8c; @@ -1306,7 +1252,7 @@ retry: goto rewind_retry; } if (conv_error == 0) { - conv_error = readfile_linenr(linecnt, (char_u *)ptr, p); + conv_error = readfile_linenr(linecnt, ptr, (char *)p); } if (bad_char_behavior == BAD_DROP) { continue; @@ -1334,7 +1280,7 @@ retry: goto rewind_retry; } if (conv_error == 0) { - conv_error = readfile_linenr(linecnt, (char_u *)ptr, p); + conv_error = readfile_linenr(linecnt, ptr, (char *)p); } if (bad_char_behavior == BAD_DROP) { continue; @@ -1364,7 +1310,7 @@ retry: if (*--p < 0x80) { u8c = *p; } else { - len = utf_head_off((char_u *)ptr, p); + len = utf_head_off(ptr, (char *)p); p -= len; u8c = (unsigned)utf_ptr2char((char *)p); if (len == 0) { @@ -1375,7 +1321,7 @@ retry: goto rewind_retry; } if (conv_error == 0) { - conv_error = readfile_linenr(linecnt, (char_u *)ptr, p); + conv_error = readfile_linenr(linecnt, ptr, (char *)p); } if (bad_char_behavior == BAD_DROP) { continue; @@ -1413,7 +1359,7 @@ retry: // an incomplete character at the end though, the next // read() will get the next bytes, we'll check it // then. - l = utf_ptr2len_len(p, todo); + l = utf_ptr2len_len((char *)p, todo); if (l > todo && !incomplete_tail) { // Avoid retrying with a different encoding when // a truncated file is more likely, or attempting @@ -1439,15 +1385,15 @@ retry: if (can_retry && !incomplete_tail) { break; } -#ifdef HAVE_ICONV + // When we did a conversion report an error. if (iconv_fd != (iconv_t)-1 && conv_error == 0) { - conv_error = readfile_linenr(linecnt, (char_u *)ptr, p); + conv_error = readfile_linenr(linecnt, ptr, (char *)p); } -#endif + // Remember the first linenr with an illegal byte if (conv_error == 0 && illegal_byte == 0) { - illegal_byte = readfile_linenr(linecnt, (char_u *)ptr, p); + illegal_byte = readfile_linenr(linecnt, ptr, (char *)p); } // Drop, keep or replace the bad byte. @@ -1467,17 +1413,13 @@ retry: // Detected a UTF-8 error. rewind_retry: // Retry reading with another conversion. -#ifdef HAVE_ICONV if (*p_ccv != NUL && iconv_fd != (iconv_t)-1) { // iconv() failed, try 'charconvert' did_iconv = true; } else { -#endif - // use next item from 'fileencodings' - advance_fenc = true; -#ifdef HAVE_ICONV - } -#endif + // use next item from 'fileencodings' + advance_fenc = true; + } file_rewind = true; goto retry; } @@ -1486,9 +1428,7 @@ rewind_retry: // count the number of characters (after conversion!) filesize += size; - /* - * when reading the first part of a file: guess EOL type - */ + // when reading the first part of a file: guess EOL type if (fileformat == EOL_UNKNOWN) { // First try finding a NL, for Dos and Unix if (try_dos || try_unix) { @@ -1553,10 +1493,8 @@ rewind_retry: } } - /* - * This loop is executed once for every character read. - * Keep it fast! - */ + // This loop is executed once for every character read. + // Keep it fast! if (fileformat == EOL_MAC) { ptr--; while (++ptr, --size >= 0) { @@ -1577,7 +1515,7 @@ rewind_retry: break; } if (read_undo_file) { - sha256_update(&sha_ctx, (char_u *)line_start, (size_t)len); + sha256_update(&sha_ctx, (uint8_t *)line_start, (size_t)len); } lnum++; if (--read_count == 0) { @@ -1633,7 +1571,7 @@ rewind_retry: break; } if (read_undo_file) { - sha256_update(&sha_ctx, (char_u *)line_start, (size_t)len); + sha256_update(&sha_ctx, (uint8_t *)line_start, (size_t)len); } lnum++; if (--read_count == 0) { @@ -1658,21 +1596,31 @@ failed: error = false; } - /* - * If we get EOF in the middle of a line, note the fact and - * complete the line ourselves. - * In Dos format ignore a trailing CTRL-Z, unless 'binary' set. - */ + // In Dos format ignore a trailing CTRL-Z, unless 'binary' is set. + // In old days the file length was in sector count and the CTRL-Z the + // marker where the file really ended. Assuming we write it to a file + // system that keeps file length properly the CTRL-Z should be dropped. + // Set the 'endoffile' option so the user can decide what to write later. + // In Unix format the CTRL-Z is just another character. + if (linerest != 0 + && !curbuf->b_p_bin + && fileformat == EOL_DOS + && ptr[-1] == Ctrl_Z) { + ptr--; + linerest--; + if (set_options) { + curbuf->b_p_eof = true; + } + } + + // If we get EOF in the middle of a line, note the fact and + // complete the line ourselves. if (!error && !got_int - && linerest != 0 - && !(!curbuf->b_p_bin - && fileformat == EOL_DOS - && *line_start == Ctrl_Z - && ptr == line_start + 1)) { + && linerest != 0) { // remember for when writing if (set_options) { - curbuf->b_p_eol = FALSE; + curbuf->b_p_eol = false; } *ptr = NUL; len = (colnr_T)(ptr - line_start + 1); @@ -1680,7 +1628,7 @@ failed: error = true; } else { if (read_undo_file) { - sha256_update(&sha_ctx, (char_u *)line_start, (size_t)len); + sha256_update(&sha_ctx, (uint8_t *)line_start, (size_t)len); } read_no_eol_lnum = ++lnum; } @@ -1696,11 +1644,9 @@ failed: if (fenc_alloced) { xfree(fenc); } -#ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { iconv_close(iconv_fd); } -#endif if (!read_buffer && !read_stdin) { close(fd); // errors are ignored @@ -1712,7 +1658,7 @@ failed: if (read_stdin) { close(fd); if (stdin_fd < 0) { -#ifndef WIN32 +#ifndef MSWIN // On Unix, use stderr for stdin, makes shell commands work. vim_ignored = dup(2); #else @@ -1720,7 +1666,7 @@ failed: HANDLE conin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, OPEN_EXISTING, 0, (HANDLE)NULL); - vim_ignored = _open_osfhandle(conin, _O_RDONLY); + vim_ignored = _open_osfhandle((intptr_t)conin, _O_RDONLY); #endif } } @@ -1729,11 +1675,9 @@ failed: os_remove(tmpname); // delete converted file xfree(tmpname); } - --no_wait_return; // may wait for return now + no_wait_return--; // may wait for return now - /* - * In recovery mode everything but autocommands is skipped. - */ + // In recovery mode everything but autocommands is skipped. if (!recoverymode) { // need to delete the last line, which comes from the empty buffer if (newfile && wasempty && !(curbuf->b_ml.ml_flags & ML_EMPTY)) { @@ -1749,7 +1693,7 @@ failed: linecnt = 0; } if (newfile || read_buffer) { - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); // After reading the text into the buffer the diff info needs to // be updated. diff_invalidate(curbuf); @@ -1760,20 +1704,11 @@ failed: appended_lines_mark(from, linecnt); } - /* - * If we were reading from the same terminal as where messages go, - * the screen will have been messed up. - * Switch on raw mode now and clear the screen. - */ - if (read_stdin) { - screenclear(); - } - if (got_int) { if (!(flags & READ_DUMMY)) { - filemess(curbuf, (char_u *)sfname, (char_u *)_(e_interr), 0); + filemess(curbuf, sfname, _(e_interr), 0); if (newfile) { - curbuf->b_p_ro = TRUE; // must use "w!" now + curbuf->b_p_ro = true; // must use "w!" now } } msg_scroll = msg_save; @@ -1782,36 +1717,36 @@ failed: } if (!filtering && !(flags & READ_DUMMY) && !silent) { - add_quoted_fname((char *)IObuff, IOSIZE, curbuf, (const char *)sfname); + add_quoted_fname(IObuff, IOSIZE, curbuf, (const char *)sfname); c = false; #ifdef UNIX if (S_ISFIFO(perm)) { // fifo STRCAT(IObuff, _("[fifo]")); - c = TRUE; + c = true; } if (S_ISSOCK(perm)) { // or socket STRCAT(IObuff, _("[socket]")); - c = TRUE; + c = true; } # ifdef OPEN_CHR_FILES if (S_ISCHR(perm)) { // or character special STRCAT(IObuff, _("[character special]")); - c = TRUE; + c = true; } # endif #endif if (curbuf->b_p_ro) { STRCAT(IObuff, shortmess(SHM_RO) ? _("[RO]") : _("[readonly]")); - c = TRUE; + c = true; } if (read_no_eol_lnum) { msg_add_eol(); - c = TRUE; + c = true; } if (ff_error == EOL_DOS) { STRCAT(IObuff, _("[CR missing]")); - c = TRUE; + c = true; } if (split) { STRCAT(IObuff, _("[long lines split]")); @@ -1819,25 +1754,25 @@ failed: } if (notconverted) { STRCAT(IObuff, _("[NOT converted]")); - c = TRUE; + c = true; } else if (converted) { STRCAT(IObuff, _("[converted]")); - c = TRUE; + c = true; } if (conv_error != 0) { - sprintf((char *)IObuff + STRLEN(IObuff), - _("[CONVERSION ERROR in line %" PRId64 "]"), (int64_t)conv_error); - c = TRUE; + snprintf(IObuff + strlen(IObuff), IOSIZE - strlen(IObuff), + _("[CONVERSION ERROR in line %" PRId64 "]"), (int64_t)conv_error); + c = true; } else if (illegal_byte > 0) { - sprintf((char *)IObuff + STRLEN(IObuff), - _("[ILLEGAL BYTE in line %" PRId64 "]"), (int64_t)illegal_byte); - c = TRUE; + snprintf(IObuff + strlen(IObuff), IOSIZE - strlen(IObuff), + _("[ILLEGAL BYTE in line %" PRId64 "]"), (int64_t)illegal_byte); + c = true; } else if (error) { STRCAT(IObuff, _("[READ ERRORS]")); - c = TRUE; + c = true; } if (msg_add_fileformat(fileformat)) { - c = TRUE; + c = true; } msg_add_lines(c, (long)linecnt, filesize); @@ -1847,7 +1782,7 @@ failed: msg_scrolled_ign = true; if (!read_stdin && !read_buffer) { - p = (char_u *)msg_trunc_attr((char *)IObuff, false, 0); + p = (char_u *)msg_trunc_attr(IObuff, false, 0); } if (read_stdin || read_buffer || restart_edit != 0 @@ -1865,17 +1800,14 @@ failed: // with errors writing the file requires ":w!" if (newfile && (error || conv_error != 0 - || (illegal_byte > 0 && bad_char_behavior != BAD_KEEP) - )) { - curbuf->b_p_ro = TRUE; + || (illegal_byte > 0 && bad_char_behavior != BAD_KEEP))) { + curbuf->b_p_ro = true; } u_clearline(); // cannot use "U" command after adding lines - /* - * In Ex mode: cursor at last new line. - * Otherwise: cursor at first new line. - */ + // In Ex mode: cursor at last new line. + // Otherwise: cursor at first new line. if (exmode_active) { curwin->w_cursor.lnum = from + linecnt; } else { @@ -1894,17 +1826,13 @@ failed: } msg_scroll = msg_save; - /* - * Get the marks before executing autocommands, so they can be used there. - */ + // Get the marks before executing autocommands, so they can be used there. check_marks_read(); - /* - * We remember if the last line of the read didn't have - * an eol even when 'binary' is off, to support turning 'fixeol' off, - * or writing the read again with 'binary' on. The latter is required - * for ":autocmd FileReadPost *.gz set bin|'[,']!gunzip" to work. - */ + // We remember if the last line of the read didn't have + // an eol even when 'binary' is off, to support turning 'fixeol' off, + // or writing the read again with 'binary' on. The latter is required + // for ":autocmd FileReadPost *.gz set bin|'[,']!gunzip" to work. curbuf->b_no_eol_lnum = read_no_eol_lnum; // When reloading a buffer put the cursor at the first line that is @@ -1913,14 +1841,12 @@ failed: u_find_first_changed(); } - /* - * When opening a new file locate undo info and read it. - */ + // When opening a new file locate undo info and read it. if (read_undo_file) { char_u hash[UNDO_HASH_SIZE]; sha256_finish(&sha_ctx, hash); - u_read_undo(NULL, hash, (char_u *)fname); + u_read_undo(NULL, hash, fname); } if (!read_stdin && !read_fifo && (!read_buffer || sfname != NULL)) { @@ -1933,11 +1859,9 @@ failed: save_file_ff(curbuf); } - /* - * The output from the autocommands should not overwrite anything and - * should not be overwritten: Set msg_scroll, restore its value if no - * output was done. - */ + // The output from the autocommands should not overwrite anything and + // should not be overwritten: Set msg_scroll, restore its value if no + // output was done. msg_scroll = true; if (filtering) { apply_autocmds_exarg(EVENT_FILTERREADPOST, NULL, sfname, @@ -1948,7 +1872,7 @@ failed: if (!au_did_filetype && *curbuf->b_p_ft != NUL) { // EVENT_FILETYPE was not triggered but the buffer already has a // filetype. Trigger EVENT_FILETYPE using the existing filetype. - apply_autocmds(EVENT_FILETYPE, (char *)curbuf->b_p_ft, curbuf->b_fname, true, curbuf); + apply_autocmds(EVENT_FILETYPE, curbuf->b_p_ft, curbuf->b_fname, true, curbuf); } } else { apply_autocmds_exarg(EVENT_FILEREADPOST, sfname, sfname, @@ -1978,7 +1902,7 @@ failed: bool is_dev_fd_file(char *fname) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - return STRNCMP(fname, "/dev/fd/", 8) == 0 + return strncmp(fname, "/dev/fd/", 8) == 0 && ascii_isdigit((uint8_t)fname[8]) && *skipdigits(fname + 9) == NUL && (fname[9] != NUL @@ -1993,13 +1917,13 @@ bool is_dev_fd_file(char *fname) /// @param linecnt line count before reading more bytes /// @param p start of more bytes read /// @param endp end of more bytes read -static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp) +static linenr_T readfile_linenr(linenr_T linecnt, char *p, const char *endp) { - char_u *s; + char *s; linenr_T lnum; lnum = curbuf->b_ml.ml_line_count - linecnt + 1; - for (s = p; s < endp; ++s) { + for (s = p; s < endp; s++) { if (*s == '\n') { lnum++; } @@ -2012,17 +1936,17 @@ static linenr_T readfile_linenr(linenr_T linecnt, char_u *p, char_u *endp) void prep_exarg(exarg_T *eap, const buf_T *buf) FUNC_ATTR_NONNULL_ALL { - const size_t cmd_len = 15 + STRLEN(buf->b_p_fenc); + const size_t cmd_len = 15 + strlen(buf->b_p_fenc); eap->cmd = xmalloc(cmd_len); snprintf(eap->cmd, cmd_len, "e ++enc=%s", buf->b_p_fenc); eap->force_enc = 8; eap->bad_char = buf->b_bad_char; - eap->force_ff = *buf->b_p_ff; + eap->force_ff = (unsigned char)(*buf->b_p_ff); eap->force_bin = buf->b_p_bin ? FORCE_BIN : FORCE_NOBIN; - eap->read_edit = FALSE; - eap->forceit = FALSE; + eap->read_edit = false; + eap->forceit = false; } /// Set default or forced 'fileformat' and 'binary'. @@ -2049,11 +1973,13 @@ void set_file_options(int set_options, exarg_T *eap) /// Set forced 'fileencoding'. void set_forced_fenc(exarg_T *eap) { - if (eap->force_enc != 0) { - char_u *fenc = enc_canonize((char_u *)eap->cmd + eap->force_enc); - set_string_option_direct("fenc", -1, (char *)fenc, OPT_FREE|OPT_LOCAL, 0); - xfree(fenc); + if (eap->force_enc == 0) { + return; } + + char *fenc = enc_canonize(eap->cmd + eap->force_enc); + set_string_option_direct("fenc", -1, fenc, OPT_FREE|OPT_LOCAL, 0); + xfree(fenc); } /// Find next fileencoding to use from 'fileencodings'. @@ -2062,24 +1988,24 @@ void set_forced_fenc(exarg_T *eap) /// NULL. /// When *pp is not set to NULL, the result is in allocated memory and "alloced" /// is set to true. -static char_u *next_fenc(char **pp, bool *alloced) +static char *next_fenc(char **pp, bool *alloced) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - char_u *p; - char_u *r; + char *p; + char *r; *alloced = false; if (**pp == NUL) { *pp = NULL; - return (char_u *)""; + return ""; } - p = (char_u *)vim_strchr((*pp), ','); + p = vim_strchr(*pp, ','); if (p == NULL) { - r = enc_canonize((char_u *)(*pp)); - *pp += STRLEN(*pp); + r = enc_canonize(*pp); + *pp += strlen(*pp); } else { - r = vim_strnsave((char_u *)(*pp), (size_t)(p - (char_u *)(*pp))); - *pp = (char *)p + 1; + r = xstrnsave(*pp, (size_t)(p - *pp)); + *pp = p + 1; p = enc_canonize(r); xfree(r); r = p; @@ -2098,9 +2024,9 @@ static char_u *next_fenc(char **pp, bool *alloced) /// /// @return name of the resulting converted file (the caller should delete it after reading it). /// Returns NULL if the conversion failed ("*fdp" is not set) . -static char_u *readfile_charconvert(char_u *fname, char_u *fenc, int *fdp) +static char *readfile_charconvert(char *fname, char *fenc, int *fdp) { - char_u *tmpname; + char *tmpname; char *errmsg = NULL; tmpname = vim_tempname(); @@ -2109,11 +2035,11 @@ static char_u *readfile_charconvert(char_u *fname, char_u *fenc, int *fdp) } else { close(*fdp); // close the input file, ignore errors *fdp = -1; - if (eval_charconvert((char *)fenc, "utf-8", - (char *)fname, (char *)tmpname) == FAIL) { + if (eval_charconvert(fenc, "utf-8", + fname, tmpname) == FAIL) { errmsg = _("Conversion with 'charconvert' failed"); } - if (errmsg == NULL && (*fdp = os_open((char *)tmpname, O_RDONLY, 0)) < 0) { + if (errmsg == NULL && (*fdp = os_open(tmpname, O_RDONLY, 0)) < 0) { errmsg = _("can't read output of 'charconvert'"); } } @@ -2123,14 +2049,14 @@ static char_u *readfile_charconvert(char_u *fname, char_u *fenc, int *fdp) // another type of conversion might still work. msg(errmsg); if (tmpname != NULL) { - os_remove((char *)tmpname); // delete converted file + os_remove(tmpname); // delete converted file XFREE_CLEAR(tmpname); } } // If the input file is closed, open it (caller should check for error). if (*fdp < 0) { - *fdp = os_open((char *)fname, O_RDONLY, 0); + *fdp = os_open(fname, O_RDONLY, 0); } return tmpname; @@ -2145,8 +2071,8 @@ static void check_marks_read(void) shada_read_marks(); } - /* Always set b_marks_read; needed when 'shada' is changed to include - * the ' parameter after opening a buffer. */ + // Always set b_marks_read; needed when 'shada' is changed to include + // the ' parameter after opening a buffer. curbuf->b_marks_read = true; } @@ -2161,9 +2087,9 @@ char *new_file_message(void) /// /// If "forceit" is true, we don't care for errors when attempting backups. /// In case of an error everything possible is done to restore the original -/// file. But when "forceit" is TRUE, we risk losing it. +/// file. But when "forceit" is true, we risk losing it. /// -/// When "reset_changed" is TRUE and "append" == FALSE and "start" == 1 and +/// When "reset_changed" is true and "append" == false and "start" == 1 and /// "end" == curbuf->b_ml.ml_line_count, reset curbuf->b_changed. /// /// This function must NOT use NameBuff (because it's called by autowrite()). @@ -2204,9 +2130,9 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en int bufsize; long perm; // file permissions int retval = OK; - int newfile = false; // TRUE if file doesn't exist yet + int newfile = false; // true if file doesn't exist yet int msg_save = msg_scroll; - int overwriting; // TRUE if writing over original + int overwriting; // true if writing over original int no_eol = false; // no end-of-line written int device = false; // writing to a device int prev_got_int = got_int; @@ -2215,7 +2141,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en static char *err_readonly = "is read-only (cannot override: \"W\" in 'cpoptions')"; #if defined(UNIX) - int made_writable = FALSE; // 'w' bit has been set + int made_writable = false; // 'w' bit has been set #endif // writing everything int whole = (start == 1 && end == buf->b_ml.ml_line_count); @@ -2231,10 +2157,10 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en int wb_flags = 0; #endif #ifdef HAVE_ACL - vim_acl_T acl = NULL; /* ACL copied from original file to - backup or new file */ + vim_acl_T acl = NULL; // ACL copied from original file to + // backup or new file #endif - int write_undo_file = FALSE; + int write_undo_file = false; context_sha256_T sha_ctx; unsigned int bkc = get_bkc_value(buf); const pos_T orig_start = buf->b_op_start; @@ -2244,46 +2170,39 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en return FAIL; } if (buf->b_ml.ml_mfp == NULL) { - /* This can happen during startup when there is a stray "w" in the - * vimrc file. */ + // This can happen during startup when there is a stray "w" in the + // vimrc file. emsg(_(e_emptybuf)); return FAIL; } - /* - * Disallow writing from .exrc and .vimrc in current directory for - * security reasons. - */ + // Disallow writing in secure mode. if (check_secure()) { return FAIL; } // Avoid a crash for a long name. - if (STRLEN(fname) >= MAXPATHL) { + if (strlen(fname) >= MAXPATHL) { emsg(_(e_longname)); return FAIL; } // must init bw_conv_buf and bw_iconv_fd before jumping to "fail" write_info.bw_conv_buf = NULL; - write_info.bw_conv_error = FALSE; + write_info.bw_conv_error = false; write_info.bw_conv_error_lnum = 0; write_info.bw_restlen = 0; -#ifdef HAVE_ICONV write_info.bw_iconv_fd = (iconv_t)-1; -#endif - /* After writing a file changedtick changes but we don't want to display - * the line. */ + // After writing a file changedtick changes but we don't want to display + // the line. ex_no_reprint = true; - /* - * If there is no file name yet, use the one for the written file. - * BF_NOTEDITED is set to reflect this (in case the write fails). - * Don't do this when the write is for a filter command. - * Don't do this when appending. - * Only do this when 'cpoptions' contains the 'F' flag. - */ + // If there is no file name yet, use the one for the written file. + // BF_NOTEDITED is set to reflect this (in case the write fails). + // Don't do this when the write is for a filter command. + // Don't do this when appending. + // Only do this when 'cpoptions' contains the 'F' flag. if (buf->b_ffname == NULL && reset_changed && whole @@ -2292,7 +2211,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en && !filtering && (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL) && vim_strchr(p_cpo, CPO_FNAMEW) != NULL) { - if (set_rw_fname((char_u *)fname, (char_u *)sfname) == FAIL) { + if (set_rw_fname(fname, sfname) == FAIL) { return FAIL; } buf = curbuf; // just in case autocmds made "buf" invalid @@ -2311,17 +2230,15 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en fname = sfname; #endif - if (buf->b_ffname != NULL && FNAMECMP(ffname, buf->b_ffname) == 0) { + if (buf->b_ffname != NULL && path_fnamecmp(ffname, buf->b_ffname) == 0) { overwriting = true; } else { - overwriting = FALSE; + overwriting = false; } - ++no_wait_return; // don't wait for return yet + no_wait_return++; // don't wait for return yet - /* - * Set '[ and '] marks to the lines to be written. - */ + // Set '[ and '] marks to the lines to be written. buf->b_op_start.lnum = start; buf->b_op_start.col = 0; buf->b_op_end.lnum = end; @@ -2329,20 +2246,18 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en { aco_save_T aco; - int buf_ffname = FALSE; - int buf_sfname = FALSE; - int buf_fname_f = FALSE; - int buf_fname_s = FALSE; - int did_cmd = FALSE; - int nofile_err = FALSE; + int buf_ffname = false; + int buf_sfname = false; + int buf_fname_f = false; + int buf_fname_s = false; + int did_cmd = false; + int nofile_err = false; int empty_memline = (buf->b_ml.ml_mfp == NULL); bufref_T bufref; - /* - * Apply PRE autocommands. - * Set curbuf to the buffer to be written. - * Careful: The autocommands may call buf_write() recursively! - */ + // Apply PRE autocommands. + // Set curbuf to the buffer to be written. + // Careful: The autocommands may call buf_write() recursively! if (ffname == buf->b_ffname) { buf_ffname = true; } @@ -2380,9 +2295,9 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en sfname, sfname, false, curbuf, eap); if (did_cmd) { if (was_changed && !curbufIsChanged()) { - /* Written everything correctly and BufWriteCmd has reset - * 'modified': Correct the undo information so that an - * undo now sets 'modified'. */ + // Written everything correctly and BufWriteCmd has reset + // 'modified': Correct the undo information so that an + // undo now sets 'modified'. u_unchanged(curbuf); u_update_save_nr(curbuf); } @@ -2428,19 +2343,19 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en no_wait_return--; msg_scroll = msg_save; if (nofile_err) { - emsg(_("E676: No matching autocommands for acwrite buffer")); + semsg(_(e_no_matching_autocommands_for_buftype_str_buffer), curbuf->b_p_bt); } if (nofile_err || aborting()) { - /* An aborting error, interrupt or exception in the - * autocommands. */ + // An aborting error, interrupt or exception in the + // autocommands. return FAIL; } if (did_cmd) { if (buf == NULL) { - /* The buffer was deleted. We assume it was written - * (can't retry anyway). */ + // The buffer was deleted. We assume it was written + // (can't retry anyway). return OK; } if (overwriting) { @@ -2465,12 +2380,10 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en return FAIL; } - /* - * The autocommands may have changed the number of lines in the file. - * When writing the whole file, adjust the end. - * When writing part of the file, assume that the autocommands only - * changed the number of lines that are to be written (tricky!). - */ + // The autocommands may have changed the number of lines in the file. + // When writing the whole file, adjust the end. + // When writing part of the file, assume that the autocommands only + // changed the number of lines that are to be written (tricky!). if (buf->b_ml.ml_line_count != old_line_count) { if (whole) { // write all end = buf->b_ml.ml_line_count; @@ -2487,10 +2400,8 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } } - /* - * The autocommands may have changed the name of the buffer, which may - * be kept in fname, ffname and sfname. - */ + // The autocommands may have changed the name of the buffer, which may + // be kept in fname, ffname and sfname. if (buf_ffname) { ffname = buf->b_ffname; } @@ -2512,20 +2423,20 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } if (shortmess(SHM_OVER) && !exiting) { - msg_scroll = FALSE; // overwrite previous file message + msg_scroll = false; // overwrite previous file message } else { - msg_scroll = TRUE; // don't overwrite previous file message + msg_scroll = true; // don't overwrite previous file message } if (!filtering) { filemess(buf, #ifndef UNIX - (char_u *)sfname, + sfname, #else - (char_u *)fname, + fname, #endif - (char_u *)"", 0); // show that we are busy + "", 0); // show that we are busy } - msg_scroll = FALSE; // always overwrite the file message now + msg_scroll = false; // always overwrite the file message now buffer = verbose_try_malloc(BUFSIZE); // can't allocate big buffer, use small one (to be able to write when out of @@ -2537,9 +2448,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en bufsize = BUFSIZE; } - /* - * Get information about original file (if there is one). - */ + // Get information about original file (if there is one). FileInfo file_info_old; #if defined(UNIX) perm = -1; @@ -2556,23 +2465,23 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en SET_ERRMSG_NUM("E503", _("is not a file or writable device")); goto fail; } - /* It's a device of some kind (or a fifo) which we can write to - * but for which we can't make a backup. */ - device = TRUE; - newfile = TRUE; + // It's a device of some kind (or a fifo) which we can write to + // but for which we can't make a backup. + device = true; + newfile = true; perm = -1; } } #else // win32 // Check for a writable device name. - c = fname == NULL ? NODE_OTHER : os_nodetype((char *)fname); + c = fname == NULL ? NODE_OTHER : os_nodetype(fname); if (c == NODE_OTHER) { SET_ERRMSG_NUM("E503", _("is not a file or writable device")); goto fail; } if (c == NODE_WRITABLE) { - device = TRUE; - newfile = TRUE; + device = true; + newfile = true; perm = -1; } else { perm = os_getperm((const char *)fname); @@ -2583,16 +2492,14 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en goto fail; } if (overwriting) { - os_fileinfo((char *)fname, &file_info_old); + os_fileinfo(fname, &file_info_old); } } #endif // !UNIX if (!device && !newfile) { - /* - * Check if the file is really writable (when renaming the file to - * make a backup we won't discover it later). - */ + // Check if the file is really writable (when renaming the file to + // make a backup we won't discover it later). file_readonly = !os_file_is_writable(fname); if (!forceit && file_readonly) { @@ -2604,10 +2511,8 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en goto fail; } - /* - * Check if the timestamp hasn't changed since reading the file. - */ - if (overwriting) { + // If 'forceit' is false, check if the timestamp hasn't changed since reading the file. + if (overwriting && !forceit) { retval = check_mtime(buf, &file_info_old); if (retval == FAIL) { goto fail; @@ -2616,101 +2521,87 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } #ifdef HAVE_ACL - /* - * For systems that support ACL: get the ACL from the original file. - */ + // For systems that support ACL: get the ACL from the original file. if (!newfile) { - acl = mch_get_acl((char_u *)fname); + acl = os_get_acl(fname); } #endif - /* - * If 'backupskip' is not empty, don't make a backup for some files. - */ + // If 'backupskip' is not empty, don't make a backup for some files. dobackup = (p_wb || p_bk || *p_pm != NUL); - if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, (char_u *)sfname, (char_u *)ffname)) { + if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, sfname, ffname)) { dobackup = false; } - /* - * Save the value of got_int and reset it. We don't want a previous - * interruption cancel writing, only hitting CTRL-C while writing should - * abort it. - */ + // Save the value of got_int and reset it. We don't want a previous + // interruption cancel writing, only hitting CTRL-C while writing should + // abort it. prev_got_int = got_int; - got_int = FALSE; + got_int = false; // Mark the buffer as 'being saved' to prevent changed buffer warnings buf->b_saving = true; - /* - * If we are not appending or filtering, the file exists, and the - * 'writebackup', 'backup' or 'patchmode' option is set, need a backup. - * When 'patchmode' is set also make a backup when appending. - * - * Do not make any backup, if 'writebackup' and 'backup' are both switched - * off. This helps when editing large files on almost-full disks. - */ + // If we are not appending or filtering, the file exists, and the + // 'writebackup', 'backup' or 'patchmode' option is set, need a backup. + // When 'patchmode' is set also make a backup when appending. + // + // Do not make any backup, if 'writebackup' and 'backup' are both switched + // off. This helps when editing large files on almost-full disks. if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) { FileInfo file_info; const bool no_prepend_dot = false; if ((bkc & BKC_YES) || append) { // "yes" - backup_copy = TRUE; + backup_copy = true; } else if ((bkc & BKC_AUTO)) { // "auto" int i; - /* - * Don't rename the file when: - * - it's a hard link - * - it's a symbolic link - * - we don't have write permission in the directory - */ + // Don't rename the file when: + // - it's a hard link + // - it's a symbolic link + // - we don't have write permission in the directory if (os_fileinfo_hardlinks(&file_info_old) > 1 || !os_fileinfo_link(fname, &file_info) || !os_fileinfo_id_equal(&file_info, &file_info_old)) { - backup_copy = TRUE; + backup_copy = true; } else { - /* - * Check if we can create a file and set the owner/group to - * the ones from the original file. - * First find a file name that doesn't exist yet (use some - * arbitrary numbers). - */ + // Check if we can create a file and set the owner/group to + // the ones from the original file. + // First find a file name that doesn't exist yet (use some + // arbitrary numbers). STRCPY(IObuff, fname); for (i = 4913;; i += 123) { - char *tail = path_tail((char *)IObuff); - size_t size = (size_t)((char_u *)tail - IObuff); + char *tail = path_tail(IObuff); + size_t size = (size_t)(tail - IObuff); snprintf(tail, IOSIZE - size, "%d", i); - if (!os_fileinfo_link((char *)IObuff, &file_info)) { + if (!os_fileinfo_link(IObuff, &file_info)) { break; } } - fd = os_open((char *)IObuff, + fd = os_open(IObuff, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, (int)perm); if (fd < 0) { // can't write in directory - backup_copy = TRUE; + backup_copy = true; } else { #ifdef UNIX os_fchown(fd, (uv_uid_t)file_info_old.stat.st_uid, (uv_gid_t)file_info_old.stat.st_gid); - if (!os_fileinfo((char *)IObuff, &file_info) + if (!os_fileinfo(IObuff, &file_info) || file_info.stat.st_uid != file_info_old.stat.st_uid || file_info.stat.st_gid != file_info_old.stat.st_gid || (long)file_info.stat.st_mode != perm) { - backup_copy = TRUE; + backup_copy = true; } #endif - /* Close the file before removing it, on MS-Windows we - * can't delete an open file. */ + // Close the file before removing it, on MS-Windows we + // can't delete an open file. close(fd); - os_remove((char *)IObuff); + os_remove(IObuff); } } } - /* - * Break symlinks and/or hardlinks if we've been asked to. - */ + // Break symlinks and/or hardlinks if we've been asked to. if ((bkc & BKC_BREAKSYMLINK) || (bkc & BKC_BREAKHARDLINK)) { #ifdef UNIX bool file_info_link_ok = os_fileinfo_link(fname, &file_info); @@ -2719,7 +2610,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if ((bkc & BKC_BREAKSYMLINK) && file_info_link_ok && !os_fileinfo_id_equal(&file_info, &file_info_old)) { - backup_copy = FALSE; + backup_copy = false; } // Hardlinks. @@ -2727,7 +2618,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en && os_fileinfo_hardlinks(&file_info_old) > 1 && (!file_info_link_ok || os_fileinfo_id_equal(&file_info, &file_info_old))) { - backup_copy = FALSE; + backup_copy = false; } #endif } @@ -2736,7 +2627,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (*p_bex == NUL) { backup_ext = ".bak"; } else { - backup_ext = (char *)p_bex; + backup_ext = p_bex; } if (backup_copy) { @@ -2746,33 +2637,29 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en char *rootname; char *p; - /* - * Try to make the backup in each directory in the 'bdir' option. - * - * Unix semantics has it, that we may have a writable file, - * that cannot be recreated with a simple open(..., O_CREAT, ) e.g: - * - the directory is not writable, - * - the file may be a symbolic link, - * - the file may belong to another user/group, etc. - * - * For these reasons, the existing writable file must be truncated - * and reused. Creation of a backup COPY will be attempted. - */ - dirp = (char *)p_bdir; + // Try to make the backup in each directory in the 'bdir' option. + // + // Unix semantics has it, that we may have a writable file, + // that cannot be recreated with a simple open(..., O_CREAT, ) e.g: + // - the directory is not writable, + // - the file may be a symbolic link, + // - the file may belong to another user/group, etc. + // + // For these reasons, the existing writable file must be truncated + // and reused. Creation of a backup COPY will be attempted. + dirp = p_bdir; while (*dirp) { - /* - * Isolate one directory name, using an entry in 'bdir'. - */ - size_t dir_len = copy_option_part(&dirp, (char *)IObuff, IOSIZE, ","); - p = (char *)IObuff + dir_len; - bool trailing_pathseps = after_pathsep((char *)IObuff, p) && p[-1] == p[-2]; + // Isolate one directory name, using an entry in 'bdir'. + size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ","); + p = IObuff + dir_len; + bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2]; if (trailing_pathseps) { IObuff[dir_len - 2] = NUL; } if (*dirp == NUL && !os_isdir(IObuff)) { int ret; char *failed_dir; - if ((ret = os_mkdir_recurse((char *)IObuff, 0755, &failed_dir)) != 0) { + if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir)) != 0) { semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"), failed_dir, os_strerror(ret)); xfree(failed_dir); @@ -2780,16 +2667,16 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } if (trailing_pathseps) { // Ends with '//', Use Full path - if ((p = make_percent_swname((char *)IObuff, fname)) + if ((p = make_percent_swname(IObuff, fname)) != NULL) { backup = modname(p, backup_ext, no_prepend_dot); xfree(p); } } - rootname = (char *)get_file_in_dir((char_u *)fname, IObuff); + rootname = get_file_in_dir(fname, IObuff); if (rootname == NULL) { - some_error = TRUE; // out of memory + some_error = true; // out of memory goto nobackup; } @@ -2804,13 +2691,11 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en if (backup == NULL) { xfree(rootname); - some_error = TRUE; // out of memory + some_error = true; // out of memory goto nobackup; } - /* - * Check if backup file already exists. - */ + // Check if backup file already exists. if (os_fileinfo(backup, &file_info_new)) { if (os_fileinfo_id_equal(&file_info_new, &file_info_old)) { // @@ -2825,7 +2710,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en // delete an existing one, and try to use another name instead. // Change one character, just before the extension. // - wp = backup + STRLEN(backup) - 1 - STRLEN(backup_ext); + wp = backup + strlen(backup) - 1 - strlen(backup_ext); if (wp < backup) { // empty file name ??? wp = backup; } @@ -2842,9 +2727,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en } xfree(rootname); - /* - * Try to create the backup file - */ + // Try to create the backup file if (backup != NULL) { // remove old backup, if present os_remove(backup); @@ -2867,10 +2750,11 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en #endif // copy the file - if (os_copy(fname, backup, UV_FS_COPYFILE_FICLONE) - != 0) { - SET_ERRMSG(_("E506: Can't write to backup file " - "(add ! to override)")); + if (os_copy(fname, backup, UV_FS_COPYFILE_FICLONE) != 0) { + SET_ERRMSG(_("E509: Cannot create backup file (add ! to override)")); + XFREE_CLEAR(backup); + backup = NULL; + continue; } #ifdef UNIX @@ -2879,8 +2763,9 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en (double)file_info_old.stat.st_mtim.tv_sec); #endif #ifdef HAVE_ACL - mch_set_acl((char_u *)backup, acl); + os_set_acl(backup, acl); #endif + SET_ERRMSG(NULL); break; } } @@ -2889,7 +2774,7 @@ nobackup: if (backup == NULL && errmsg == NULL) { SET_ERRMSG(_("E509: Cannot create backup file (add ! to override)")); } - // Ignore errors when forceit is TRUE. + // Ignore errors when forceit is true. if ((some_error || errmsg != NULL) && !forceit) { retval = FAIL; goto fail; @@ -2900,40 +2785,32 @@ nobackup: char *p; char *rootname; - /* - * Make a backup by renaming the original file. - */ - /* - * If 'cpoptions' includes the "W" flag, we don't want to - * overwrite a read-only file. But rename may be possible - * anyway, thus we need an extra check here. - */ + // Make a backup by renaming the original file. + + // If 'cpoptions' includes the "W" flag, we don't want to + // overwrite a read-only file. But rename may be possible + // anyway, thus we need an extra check here. if (file_readonly && vim_strchr(p_cpo, CPO_FWRITE) != NULL) { SET_ERRMSG_NUM("E504", _(err_readonly)); goto fail; } - /* - * - * Form the backup file name - change path/fo.o.h to - * path/fo.o.h.bak Try all directories in 'backupdir', first one - * that works is used. - */ - dirp = (char *)p_bdir; + // Form the backup file name - change path/fo.o.h to + // path/fo.o.h.bak Try all directories in 'backupdir', first one + // that works is used. + dirp = p_bdir; while (*dirp) { - /* - * Isolate one directory name and make the backup file name. - */ - size_t dir_len = copy_option_part(&dirp, (char *)IObuff, IOSIZE, ","); - p = (char *)IObuff + dir_len; - bool trailing_pathseps = after_pathsep((char *)IObuff, p) && p[-1] == p[-2]; + // Isolate one directory name and make the backup file name. + size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ","); + p = IObuff + dir_len; + bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2]; if (trailing_pathseps) { IObuff[dir_len - 2] = NUL; } if (*dirp == NUL && !os_isdir(IObuff)) { int ret; char *failed_dir; - if ((ret = os_mkdir_recurse((char *)IObuff, 0755, &failed_dir)) != 0) { + if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir)) != 0) { semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"), failed_dir, os_strerror(ret)); xfree(failed_dir); @@ -2941,7 +2818,7 @@ nobackup: } if (trailing_pathseps) { // path ends with '//', use full path - if ((p = make_percent_swname((char *)IObuff, fname)) + if ((p = make_percent_swname(IObuff, fname)) != NULL) { backup = modname(p, backup_ext, no_prepend_dot); xfree(p); @@ -2949,7 +2826,7 @@ nobackup: } if (backup == NULL) { - rootname = (char *)get_file_in_dir((char_u *)fname, IObuff); + rootname = get_file_in_dir(fname, IObuff); if (rootname == NULL) { backup = NULL; } else { @@ -2959,18 +2836,16 @@ nobackup: } if (backup != NULL) { - /* - * If we are not going to keep the backup file, don't - * delete an existing one, try to use another name. - * Change one character, just before the extension. - */ - if (!p_bk && os_path_exists((char_u *)backup)) { - p = backup + STRLEN(backup) - 1 - STRLEN(backup_ext); + // If we are not going to keep the backup file, don't + // delete an existing one, try to use another name. + // Change one character, just before the extension. + if (!p_bk && os_path_exists(backup)) { + p = backup + strlen(backup) - 1 - strlen(backup_ext); if (p < backup) { // empty file name ??? p = backup; } *p = 'z'; - while (*p > 'a' && os_path_exists((char_u *)backup)) { + while (*p > 'a' && os_path_exists(backup)) { (*p)--; } // They all exist??? Must be something wrong! @@ -2988,7 +2863,7 @@ nobackup: // If the renaming of the original file to the backup file // works, quit here. /// - if (vim_rename((char_u *)fname, (char_u *)backup) == 0) { + if (vim_rename(fname, backup) == 0) { break; } @@ -3049,20 +2924,20 @@ nobackup: // Check for forced 'fileencoding' from "++opt=val" argument. if (eap != NULL && eap->force_enc != 0) { fenc = eap->cmd + eap->force_enc; - fenc = (char *)enc_canonize((char_u *)fenc); + fenc = enc_canonize(fenc); fenc_tofree = fenc; } else { - fenc = (char *)buf->b_p_fenc; + fenc = buf->b_p_fenc; } // Check if the file needs to be converted. - converted = need_conversion((char_u *)fenc); + converted = need_conversion(fenc); // Check if UTF-8 to UCS-2/4 or Latin1 conversion needs to be done. Or // Latin1 to Unicode conversion. This is handled in buf_write_bytes(). // Prepare the flags for it and allocate bw_conv_buf when needed. if (converted) { - wb_flags = get_fio_flags((char_u *)fenc); + wb_flags = get_fio_flags(fenc); if (wb_flags & (FIO_UCS2 | FIO_UCS4 | FIO_UTF16 | FIO_UTF8)) { // Need to allocate a buffer to translate into. if (wb_flags & (FIO_UCS2 | FIO_UTF16 | FIO_UTF8)) { @@ -3078,10 +2953,9 @@ nobackup: } if (converted && wb_flags == 0) { -#ifdef HAVE_ICONV // Use iconv() conversion when conversion is needed and it's not done // internally. - write_info.bw_iconv_fd = (iconv_t)my_iconv_open((char_u *)fenc, (char_u *)"utf-8"); + write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc, "utf-8"); if (write_info.bw_iconv_fd != (iconv_t)-1) { // We're going to use iconv(), allocate a buffer to convert in. write_info.bw_conv_buflen = (size_t)bufsize * ICONV_MULT; @@ -3089,33 +2963,29 @@ nobackup: if (!write_info.bw_conv_buf) { end = 0; } - write_info.bw_first = TRUE; - } else -#endif - - /* - * When the file needs to be converted with 'charconvert' after - * writing, write to a temp file instead and let the conversion - * overwrite the original file. - */ - if (*p_ccv != NUL) { - wfname = (char *)vim_tempname(); - if (wfname == NULL) { // Can't write without a tempfile! - SET_ERRMSG(_("E214: Can't find temp file for writing")); - goto restore_backup; + write_info.bw_first = true; + } else { + // When the file needs to be converted with 'charconvert' after + // writing, write to a temp file instead and let the conversion + // overwrite the original file. + if (*p_ccv != NUL) { + wfname = vim_tempname(); + if (wfname == NULL) { // Can't write without a tempfile! + SET_ERRMSG(_("E214: Can't find temp file for writing")); + goto restore_backup; + } } } } + if (converted && wb_flags == 0 -#ifdef HAVE_ICONV && write_info.bw_iconv_fd == (iconv_t)-1 -#endif && wfname == fname) { if (!forceit) { SET_ERRMSG(_("E213: Cannot convert (add ! to write without conversion)")); goto restore_backup; } - notconverted = TRUE; + notconverted = true; } // If conversion is taking place, we may first pretend to write and check @@ -3137,17 +3007,19 @@ nobackup: } else { // Open the file "wfname" for writing. // We may try to open the file twice: If we can't write to the file - // and forceit is TRUE we delete the existing file and try to + // and forceit is true we delete the existing file and try to // create a new one. If this still fails we may have lost the // original file! (this may happen when the user reached his // quotum for number of files). // Appending will fail if the file does not exist and forceit is - // FALSE. + // false. while ((fd = os_open(wfname, O_WRONLY | - (append ? - (forceit ? (O_APPEND | O_CREAT) : O_APPEND) - : (O_CREAT | O_TRUNC)), + (append + ? (forceit + ? (O_APPEND | O_CREAT) + : O_APPEND) + : (O_CREAT | O_TRUNC)), perm < 0 ? 0666 : (perm & 0777))) < 0) { // A forced write will try to create a new file if the old one // is still readonly. This may also happen when the directory @@ -3200,21 +3072,21 @@ restore_backup: // This may not work if the vim_rename() fails. // In that case we leave the copy around. // If file does not exist, put the copy in its place - if (!os_path_exists((char_u *)fname)) { - vim_rename((char_u *)backup, (char_u *)fname); + if (!os_path_exists(fname)) { + vim_rename(backup, fname); } // if original file does exist throw away the copy - if (os_path_exists((char_u *)fname)) { + if (os_path_exists(fname)) { os_remove(backup); } } else { // try to put the original file back - vim_rename((char_u *)backup, (char_u *)fname); + vim_rename(backup, fname); } } // if original file no longer exists give an extra warning - if (!newfile && !os_path_exists((char_u *)fname)) { + if (!newfile && !os_path_exists(fname)) { end = 0; } } @@ -3228,7 +3100,7 @@ restore_backup: } SET_ERRMSG(NULL); - write_info.bw_buf = (char_u *)buffer; + write_info.bw_buf = buffer; nchars = 0; // use "++bin", "++nobin" or 'binary' @@ -3241,7 +3113,7 @@ restore_backup: // Skip the BOM when appending and the file already existed, the BOM // only makes sense at the start of the file. if (buf->b_p_bomb && !write_bin && (!append || perm < 0)) { - write_info.bw_len = make_bom((char_u *)buffer, (char_u *)fenc); + write_info.bw_len = make_bom((char_u *)buffer, fenc); if (write_info.bw_len > 0) { // don't convert write_info.bw_flags = FIO_NOCONVERT | wb_flags; @@ -3271,9 +3143,9 @@ restore_backup: for (lnum = start; lnum <= end; lnum++) { // The next while loop is done once for each character written. // Keep it fast! - ptr = (char *)ml_get_buf(buf, lnum, false) - 1; + ptr = ml_get_buf(buf, lnum, false) - 1; if (write_undo_file) { - sha256_update(&sha_ctx, (char_u *)ptr + 1, (uint32_t)(STRLEN(ptr + 1) + 1)); + sha256_update(&sha_ctx, (uint8_t *)ptr + 1, (uint32_t)(strlen(ptr + 1) + 1)); } while ((c = *++ptr) != NUL) { if (c == NL) { @@ -3347,6 +3219,11 @@ restore_backup: nchars += len; } + if (!buf->b_p_fixeol && buf->b_p_eof) { + // write trailing CTRL-Z + (void)write_eintr(write_info.bw_fd, "\x1a", 1); + } + // Stop when writing done or an error was encountered. if (!checking_conversion || end == 0) { break; @@ -3366,7 +3243,7 @@ restore_backup: // been written to disk and we don't lose it. // For a device do try the fsync() but don't complain if it does not work // (could be a pipe). - // If the 'fsync' option is FALSE, don't fsync(). Useful for laptops. + // If the 'fsync' option is false, don't fsync(). Useful for laptops. int error; if (p_fs && (error = os_fsync(fd)) != 0 && !device // fsync not supported on this storage. @@ -3414,7 +3291,7 @@ restore_backup: // Probably need to set the ACL before changing the user (can't set the // ACL on a file the user doesn't own). if (!backup_copy) { - mch_set_acl((char_u *)wfname, acl); + os_set_acl(wfname, acl); } #endif @@ -3442,7 +3319,7 @@ restore_backup: } else { errmsg_allocated = true; SET_ERRMSG(xmalloc(300)); - vim_snprintf(errmsg, 300, + vim_snprintf(errmsg, 300, // NOLINT(runtime/printf) _("E513: write error, conversion failed in line %" PRIdLINENR " (make 'fenc' empty to override)"), write_info.bw_conv_error_lnum); @@ -3476,7 +3353,7 @@ restore_backup: end = 1; // success } } else { - if (vim_rename((char_u *)backup, (char_u *)fname) == 0) { + if (vim_rename(backup, fname) == 0) { end = 1; } } @@ -3485,42 +3362,42 @@ restore_backup: } lnum -= start; // compute number of written lines - --no_wait_return; // may wait for return now + no_wait_return--; // may wait for return now #if !defined(UNIX) fname = sfname; // use shortname now, for the messages #endif if (!filtering) { - add_quoted_fname((char *)IObuff, IOSIZE, buf, (const char *)fname); + add_quoted_fname(IObuff, IOSIZE, buf, (const char *)fname); c = false; if (write_info.bw_conv_error) { STRCAT(IObuff, _(" CONVERSION ERROR")); - c = TRUE; + c = true; if (write_info.bw_conv_error_lnum != 0) { - vim_snprintf_add((char *)IObuff, IOSIZE, _(" in line %" PRId64 ";"), + vim_snprintf_add(IObuff, IOSIZE, _(" in line %" PRId64 ";"), (int64_t)write_info.bw_conv_error_lnum); } } else if (notconverted) { STRCAT(IObuff, _("[NOT converted]")); - c = TRUE; + c = true; } else if (converted) { STRCAT(IObuff, _("[converted]")); - c = TRUE; + c = true; } if (device) { STRCAT(IObuff, _("[Device]")); - c = TRUE; + c = true; } else if (newfile) { STRCAT(IObuff, new_file_message()); c = true; } if (no_eol) { msg_add_eol(); - c = TRUE; + c = true; } // may add [unix/dos/mac] if (msg_add_fileformat(fileformat)) { - c = TRUE; + c = true; } msg_add_lines(c, (long)lnum, nchars); // add line/char count if (!shortmess(SHM_WRITE)) { @@ -3531,11 +3408,11 @@ restore_backup: } } - set_keep_msg(msg_trunc_attr((char *)IObuff, false, 0), 0); + set_keep_msg(msg_trunc_attr(IObuff, false, 0), 0); } - /* When written everything correctly: reset 'modified'. Unless not - * writing to the original file and '+' is not in 'cpoptions'. */ + // When written everything correctly: reset 'modified'. Unless not + // writing to the original file and '+' is not in 'cpoptions'. if (reset_changed && whole && !append && !write_info.bw_conv_error && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) { @@ -3550,10 +3427,8 @@ restore_backup: u_update_save_nr(buf); } - /* - * If written to the current file, update the timestamp of the swap file - * and reset the BF_WRITE_MASK flags. Also sets buf->b_mtime. - */ + // If written to the current file, update the timestamp of the swap file + // and reset the BF_WRITE_MASK flags. Also sets buf->b_mtime. if (overwriting) { ml_timestamp(buf); if (append) { @@ -3563,22 +3438,18 @@ restore_backup: } } - /* - * If we kept a backup until now, and we are in patch mode, then we make - * the backup file our 'original' file. - */ + // If we kept a backup until now, and we are in patch mode, then we make + // the backup file our 'original' file. if (*p_pm && dobackup) { - char *const org = modname(fname, (char *)p_pm, false); + char *const org = modname(fname, p_pm, false); if (backup != NULL) { - /* - * If the original file does not exist yet - * the current backup file becomes the original file - */ + // If the original file does not exist yet + // the current backup file becomes the original file if (org == NULL) { emsg(_("E205: Patchmode: can't save original file")); - } else if (!os_path_exists((char_u *)org)) { - vim_rename((char_u *)backup, (char_u *)org); + } else if (!os_path_exists(org)) { + vim_rename(backup, org); XFREE_CLEAR(backup); // don't delete the file #ifdef UNIX os_file_settime(org, @@ -3586,12 +3457,9 @@ restore_backup: (double)file_info_old.stat.st_mtim.tv_sec); #endif } - } - /* - * If there is no backup file, remember that a (new) file was - * created. - */ - else { + } else { + // If there is no backup file, remember that a (new) file was + // created. int empty_fd; if (org == NULL @@ -3609,9 +3477,7 @@ restore_backup: } } - /* - * Remove the backup unless 'backup' option is set - */ + // Remove the backup unless 'backup' option is set if (!p_bk && backup != NULL && !write_info.bw_conv_error && os_remove(backup) != 0) { @@ -3620,11 +3486,9 @@ restore_backup: goto nofail; - /* - * Finish up. We get here either after failure or success. - */ + // Finish up. We get here either after failure or success. fail: - --no_wait_return; // may wait for return now + no_wait_return--; // may wait for return now nofail: // Done saving, we accept changed buffer warnings again @@ -3636,22 +3500,20 @@ nofail: } xfree(fenc_tofree); xfree(write_info.bw_conv_buf); -#ifdef HAVE_ICONV if (write_info.bw_iconv_fd != (iconv_t)-1) { iconv_close(write_info.bw_iconv_fd); write_info.bw_iconv_fd = (iconv_t)-1; } -#endif #ifdef HAVE_ACL - mch_free_acl(acl); + os_free_acl(acl); #endif if (errmsg != NULL) { // - 100 to save some space for further error message #ifndef UNIX - add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)sfname); + add_quoted_fname(IObuff, IOSIZE - 100, buf, (const char *)sfname); #else - add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)fname); + add_quoted_fname(IObuff, IOSIZE - 100, buf, (const char *)fname); #endif if (errnum != NULL) { if (errmsgarg != 0) { @@ -3687,15 +3549,13 @@ nofail: } msg_scroll = msg_save; - /* - * When writing the whole file and 'undofile' is set, also write the undo - * file. - */ + // When writing the whole file and 'undofile' is set, also write the undo + // file. if (retval == OK && write_undo_file) { - char hash[UNDO_HASH_SIZE]; + uint8_t hash[UNDO_HASH_SIZE]; - sha256_finish(&sha_ctx, (char_u *)hash); - u_write_undo(NULL, false, buf, (char_u *)hash); + sha256_finish(&sha_ctx, hash); + u_write_undo(NULL, false, buf, hash); } if (!should_abort(retval)) { @@ -3703,31 +3563,29 @@ nofail: curbuf->b_no_eol_lnum = 0; // in case it was set by the previous read - /* - * Apply POST autocommands. - * Careful: The autocommands may call buf_write() recursively! - */ + // Apply POST autocommands. + // Careful: The autocommands may call buf_write() recursively! aucmd_prepbuf(&aco, buf); if (append) { apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname, - FALSE, curbuf, eap); + false, curbuf, eap); } else if (filtering) { apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname, - FALSE, curbuf, eap); + false, curbuf, eap); } else if (reset_changed && whole) { apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname, - FALSE, curbuf, eap); + false, curbuf, eap); } else { apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname, - FALSE, curbuf, eap); + false, curbuf, eap); } // restore curwin/curbuf and a few other things aucmd_restbuf(&aco); if (aborting()) { // autocmds may abort script processing - retval = FALSE; + retval = false; } } @@ -3741,7 +3599,7 @@ nofail: /// Set the name of the current buffer. Use when the buffer doesn't have a /// name and a ":r" or ":w" command with a file name is used. -static int set_rw_fname(char_u *fname, char_u *sfname) +static int set_rw_fname(char *fname, char *sfname) { buf_T *buf = curbuf; @@ -3759,7 +3617,7 @@ static int set_rw_fname(char_u *fname, char_u *sfname) return FAIL; } - if (setfname(curbuf, (char *)fname, (char *)sfname, false) == OK) { + if (setfname(curbuf, fname, sfname, false) == OK) { curbuf->b_flags |= BF_NOTEDITED; } @@ -3832,22 +3690,22 @@ static bool msg_add_fileformat(int eol_type) /// Append line and character count to IObuff. void msg_add_lines(int insert_space, long lnum, off_T nchars) { - char_u *p; + char *p; - p = IObuff + STRLEN(IObuff); + p = IObuff + strlen(IObuff); if (insert_space) { *p++ = ' '; } if (shortmess(SHM_LINES)) { - vim_snprintf((char *)p, (size_t)(IOSIZE - (p - IObuff)), "%" PRId64 "L, %" PRId64 "B", + vim_snprintf(p, (size_t)(IOSIZE - (p - IObuff)), "%" PRId64 "L, %" PRId64 "B", (int64_t)lnum, (int64_t)nchars); } else { - vim_snprintf((char *)p, (size_t)(IOSIZE - (p - IObuff)), + vim_snprintf(p, (size_t)(IOSIZE - (p - IObuff)), NGETTEXT("%" PRId64 " line, ", "%" PRId64 " lines, ", lnum), (int64_t)lnum); - p += STRLEN(p); - vim_snprintf((char *)p, (size_t)(IOSIZE - (p - IObuff)), + p += strlen(p); + vim_snprintf(p, (size_t)(IOSIZE - (p - IObuff)), NGETTEXT("%" PRId64 " byte", "%" PRId64 " bytes", nchars), (int64_t)nchars); } @@ -3890,7 +3748,7 @@ static bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) F || file_info->stat.st_mtim.tv_sec - mtime > 1 || mtime - file_info->stat.st_mtim.tv_sec > 1; #else - || (long)file_info->stat.st_mtim.tv_sec != mtime; + || file_info->stat.st_mtim.tv_sec != mtime; #endif } @@ -3901,35 +3759,29 @@ static bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) F static int buf_write_bytes(struct bw_info *ip) { int wlen; - char_u *buf = ip->bw_buf; // data to write + char *buf = ip->bw_buf; // data to write int len = ip->bw_len; // length of data #ifdef HAS_BW_FLAGS int flags = ip->bw_flags; // extra flags #endif - /* - * Skip conversion when writing the BOM. - */ + // Skip conversion when writing the BOM. if (!(flags & FIO_NOCONVERT)) { - char_u *p; + char *p; unsigned c; int n; if (flags & FIO_UTF8) { - /* - * Convert latin1 in the buffer to UTF-8 in the file. - */ + // Convert latin1 in the buffer to UTF-8 in the file. p = ip->bw_conv_buf; // translate to buffer for (wlen = 0; wlen < len; wlen++) { - p += utf_char2bytes(buf[wlen], (char *)p); + p += utf_char2bytes((uint8_t)buf[wlen], p); } buf = ip->bw_conv_buf; len = (int)(p - ip->bw_conv_buf); } else if (flags & (FIO_UCS4 | FIO_UTF16 | FIO_UCS2 | FIO_LATIN1)) { - /* - * Convert UTF-8 bytes in the buffer to UCS-2, UCS-4, UTF-16 or - * Latin1 chars in the file. - */ + // Convert UTF-8 bytes in the buffer to UCS-2, UCS-4, UTF-16 or + // Latin1 chars in the file. if (flags & FIO_LATIN1) { p = buf; // translate in-place (can only get shorter) } else { @@ -3939,19 +3791,19 @@ static int buf_write_bytes(struct bw_info *ip) if (wlen == 0 && ip->bw_restlen != 0) { int l; - /* Use remainder of previous call. Append the start of - * buf[] to get a full sequence. Might still be too - * short! */ + // Use remainder of previous call. Append the start of + // buf[] to get a full sequence. Might still be too + // short! l = CONV_RESTLEN - ip->bw_restlen; if (l > len) { l = len; } memmove(ip->bw_rest + ip->bw_restlen, buf, (size_t)l); - n = utf_ptr2len_len(ip->bw_rest, ip->bw_restlen + l); + n = utf_ptr2len_len((char *)ip->bw_rest, ip->bw_restlen + l); if (n > ip->bw_restlen + len) { - /* We have an incomplete byte sequence at the end to - * be written. We can't convert it without the - * remaining bytes. Keep them for the next call. */ + // We have an incomplete byte sequence at the end to + // be written. We can't convert it without the + // remaining bytes. Keep them for the next call. if (ip->bw_restlen + len > CONV_RESTLEN) { return FAIL; } @@ -3975,9 +3827,9 @@ static int buf_write_bytes(struct bw_info *ip) } else { n = utf_ptr2len_len(buf + wlen, len - wlen); if (n > len - wlen) { - /* We have an incomplete byte sequence at the end to - * be written. We can't convert it without the - * remaining bytes. Keep them for the next call. */ + // We have an incomplete byte sequence at the end to + // be written. We can't convert it without the + // remaining bytes. Keep them for the next call. if (len - wlen > CONV_RESTLEN) { return FAIL; } @@ -3987,18 +3839,18 @@ static int buf_write_bytes(struct bw_info *ip) break; } if (n > 1) { - c = (unsigned)utf_ptr2char((char *)buf + wlen); + c = (unsigned)utf_ptr2char(buf + wlen); } else { - c = buf[wlen]; + c = (uint8_t)buf[wlen]; } } if (ucs2bytes(c, &p, flags) && !ip->bw_conv_error) { - ip->bw_conv_error = TRUE; + ip->bw_conv_error = true; ip->bw_conv_error_lnum = ip->bw_start_lnum; } if (c == NL) { - ++ip->bw_start_lnum; + ip->bw_start_lnum++; } } if (flags & FIO_LATIN1) { @@ -4009,7 +3861,6 @@ static int buf_write_bytes(struct bw_info *ip) } } -#ifdef HAVE_ICONV if (ip->bw_iconv_fd != (iconv_t)-1) { const char *from; size_t fromlen; @@ -4020,21 +3871,21 @@ static int buf_write_bytes(struct bw_info *ip) if (ip->bw_restlen > 0) { char *fp; - /* Need to concatenate the remainder of the previous call and - * the bytes of the current call. Use the end of the - * conversion buffer for this. */ + // Need to concatenate the remainder of the previous call and + // the bytes of the current call. Use the end of the + // conversion buffer for this. fromlen = (size_t)len + (size_t)ip->bw_restlen; - fp = (char *)ip->bw_conv_buf + ip->bw_conv_buflen - fromlen; + fp = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen; memmove(fp, ip->bw_rest, (size_t)ip->bw_restlen); memmove(fp + ip->bw_restlen, buf, (size_t)len); from = fp; tolen = ip->bw_conv_buflen - fromlen; } else { - from = (const char *)buf; + from = buf; fromlen = (size_t)len; tolen = ip->bw_conv_buflen; } - to = (char *)ip->bw_conv_buf; + to = ip->bw_conv_buf; if (ip->bw_first) { size_t save_len = tolen; @@ -4042,23 +3893,20 @@ static int buf_write_bytes(struct bw_info *ip) // output the initial shift state sequence (void)iconv(ip->bw_iconv_fd, NULL, NULL, &to, &tolen); - /* There is a bug in iconv() on Linux (which appears to be - * wide-spread) which sets "to" to NULL and messes up "tolen". - */ + // There is a bug in iconv() on Linux (which appears to be + // wide-spread) which sets "to" to NULL and messes up "tolen". if (to == NULL) { - to = (char *)ip->bw_conv_buf; + to = ip->bw_conv_buf; tolen = save_len; } - ip->bw_first = FALSE; + ip->bw_first = false; } - /* - * If iconv() has an error or there is not enough room, fail. - */ + // If iconv() has an error or there is not enough room, fail. if ((iconv(ip->bw_iconv_fd, (void *)&from, &fromlen, &to, &tolen) == (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL) || fromlen > CONV_RESTLEN) { - ip->bw_conv_error = TRUE; + ip->bw_conv_error = true; return FAIL; } @@ -4069,9 +3917,8 @@ static int buf_write_bytes(struct bw_info *ip) ip->bw_restlen = (int)fromlen; buf = ip->bw_conv_buf; - len = (int)((char_u *)to - ip->bw_conv_buf); + len = (int)(to - ip->bw_conv_buf); } -#endif } if (ip->bw_fd < 0) { @@ -4089,9 +3936,9 @@ static int buf_write_bytes(struct bw_info *ip) /// @param flags FIO_ flags that specify which encoding to use /// /// @return true for an error, false when it's OK. -static bool ucs2bytes(unsigned c, char_u **pp, int flags) FUNC_ATTR_NONNULL_ALL +static bool ucs2bytes(unsigned c, char **pp, int flags) FUNC_ATTR_NONNULL_ALL { - char_u *p = *pp; + char_u *p = (char_u *)(*pp); bool error = false; int cc; @@ -4110,8 +3957,8 @@ static bool ucs2bytes(unsigned c, char_u **pp, int flags) FUNC_ATTR_NONNULL_ALL } else if (flags & (FIO_UCS2 | FIO_UTF16)) { if (c >= 0x10000) { if (flags & FIO_UTF16) { - /* Make two words, ten bits of the character in each. First - * word is 0xd800 - 0xdbff, second one 0xdc00 - 0xdfff */ + // Make two words, ten bits of the character in each. First + // word is 0xd800 - 0xdbff, second one 0xdc00 - 0xdfff c -= 0x10000; if (c >= 0x100000) { error = true; @@ -4145,7 +3992,7 @@ static bool ucs2bytes(unsigned c, char_u **pp, int flags) FUNC_ATTR_NONNULL_ALL } } - *pp = p; + *pp = (char *)p; return error; } @@ -4155,15 +4002,15 @@ static bool ucs2bytes(unsigned c, char_u **pp, int flags) FUNC_ATTR_NONNULL_ALL /// @param fenc file encoding to check /// /// @return true if conversion is required -static bool need_conversion(const char_u *fenc) +static bool need_conversion(const char *fenc) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { int same_encoding; int enc_flags; int fenc_flags; - if (*fenc == NUL || STRCMP(p_enc, fenc) == 0) { - same_encoding = TRUE; + if (*fenc == NUL || strcmp(p_enc, fenc) == 0) { + same_encoding = true; fenc_flags = 0; } else { // Ignore difference between "ansi" and "latin1", "ucs-4" and @@ -4187,7 +4034,7 @@ static bool need_conversion(const char_u *fenc) /// use 'encoding'. /// /// @param name string to check for encoding -static int get_fio_flags(const char_u *name) +static int get_fio_flags(const char *name) { int prop; @@ -4228,8 +4075,9 @@ static int get_fio_flags(const char_u *name) /// /// @return the name of the encoding and set "*lenp" to the length or, /// NULL when no BOM found. -static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags) +static char *check_for_bom(const char *p_in, long size, int *lenp, int flags) { + const uint8_t *p = (const uint8_t *)p_in; char *name = NULL; int len = 2; @@ -4265,16 +4113,16 @@ static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags) } *lenp = len; - return (char_u *)name; + return name; } /// Generate a BOM in "buf[4]" for encoding "name". /// /// @return the length of the BOM (zero when no BOM). -static int make_bom(char_u *buf, char_u *name) +static int make_bom(char_u *buf, char *name) { int flags; - char_u *p; + char *p; flags = get_fio_flags(name); @@ -4289,21 +4137,21 @@ static int make_bom(char_u *buf, char_u *name) buf[2] = 0xbf; return 3; } - p = buf; + p = (char *)buf; (void)ucs2bytes(0xfeff, &p, flags); - return (int)(p - buf); + return (int)((char_u *)p - buf); } /// Shorten filename of a buffer. /// -/// @param force when TRUE: Use full path from now on for files currently being +/// @param force when true: Use full path from now on for files currently being /// edited, both for file name and swap file name. Try to shorten the file /// names a bit, if safe to do so. -/// when FALSE: Only try to shorten absolute file names. +/// when false: Only try to shorten absolute file names. /// /// For buffers that have buftype "nofile" or "scratch": never change the file /// name. -void shorten_buf_fname(buf_T *buf, char_u *dirname, int force) +void shorten_buf_fname(buf_T *buf, char *dirname, int force) { char *p; @@ -4312,11 +4160,11 @@ void shorten_buf_fname(buf_T *buf, char_u *dirname, int force) && !path_with_url(buf->b_fname) && (force || buf->b_sfname == NULL - || path_is_absolute((char_u *)buf->b_sfname))) { + || path_is_absolute(buf->b_sfname))) { if (buf->b_sfname != buf->b_ffname) { XFREE_CLEAR(buf->b_sfname); } - p = (char *)path_shorten_fname((char_u *)buf->b_ffname, dirname); + p = path_shorten_fname(buf->b_ffname, dirname); if (p != NULL) { buf->b_sfname = xstrdup(p); buf->b_fname = buf->b_sfname; @@ -4330,11 +4178,11 @@ void shorten_buf_fname(buf_T *buf, char_u *dirname, int force) /// Shorten filenames for all buffers. void shorten_fnames(int force) { - char_u dirname[MAXPATHL]; + char dirname[MAXPATHL]; os_dirname(dirname, MAXPATHL); FOR_ALL_BUFFERS(buf) { - shorten_buf_fname(buf, dirname, force); + shorten_buf_fname(buf, (char *)dirname, force); // Always make the swap file name a full path, a "nofile" buffer may // also have a swap file. @@ -4379,18 +4227,18 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) // (we need the full path in case :cd is used). if (fname == NULL || *fname == NUL) { retval = xmalloc(MAXPATHL + extlen + 3); // +3 for PATHSEP, "_" (Win), NUL - if (os_dirname((char_u *)retval, MAXPATHL) == FAIL + if (os_dirname(retval, MAXPATHL) == FAIL || strlen(retval) == 0) { xfree(retval); return NULL; } add_pathsep(retval); fnamelen = strlen(retval); - prepend_dot = FALSE; // nothing to prepend a dot to + prepend_dot = false; // nothing to prepend a dot to } else { fnamelen = strlen(fname); retval = xmalloc(fnamelen + extlen + 3); - strcpy(retval, fname); + strcpy(retval, fname); // NOLINT(runtime/printf) } // Search backwards until we hit a '/', '\' or ':'. @@ -4408,12 +4256,11 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) ptr[BASENAMELEN] = '\0'; } - char *s; - s = ptr + strlen(ptr); + char *s = ptr + strlen(ptr); // Append the extension. // ext can start with '.' and cannot exceed 3 more characters. - strcpy(s, ext); + strcpy(s, ext); // NOLINT(runtime/printf) char *e; // Prepend the dot if needed. @@ -4447,7 +4294,8 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) /// @param fp file to read from /// /// @return true for EOF or error -bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL +bool vim_fgets(char *buf, int size, FILE *fp) + FUNC_ATTR_NONNULL_ALL { char *retval; @@ -4456,7 +4304,7 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL do { errno = 0; - retval = fgets((char *)buf, size, fp); + retval = fgets(buf, size, fp); } while (retval == NULL && errno == EINTR && ferror(fp)); if (buf[size - 2] != NUL && buf[size - 2] != '\n') { @@ -4608,7 +4456,7 @@ int put_time(FILE *fd, time_t time_) /// function will (attempts to?) copy the file across if rename fails -- webb /// /// @return -1 for failure, 0 for success -int vim_rename(const char_u *from, const char_u *to) +int vim_rename(const char *from, const char *to) FUNC_ATTR_NONNULL_ALL { int fd_in; @@ -4622,13 +4470,11 @@ int vim_rename(const char_u *from, const char_u *to) #endif bool use_tmp_file = false; - /* - * When the names are identical, there is nothing to do. When they refer - * to the same file (ignoring case and slash/backslash differences) but - * the file name differs we need to go through a temp file. - */ - if (FNAMECMP(from, to) == 0) { - if (p_fic && (STRCMP(path_tail((char *)from), path_tail((char *)to)) + // When the names are identical, there is nothing to do. When they refer + // to the same file (ignoring case and slash/backslash differences) but + // the file name differs we need to go through a temp file. + if (path_fnamecmp(from, to) == 0) { + if (p_fic && (strcmp(path_tail((char *)from), path_tail((char *)to)) != 0)) { use_tmp_file = true; } else { @@ -4652,19 +4498,17 @@ int vim_rename(const char_u *from, const char_u *to) } if (use_tmp_file) { - char_u tempname[MAXPATHL + 1]; + char tempname[MAXPATHL + 1]; - /* - * Find a name that doesn't exist and is in the same directory. - * Rename "from" to "tempname" and then rename "tempname" to "to". - */ - if (STRLEN(from) >= MAXPATHL - 5) { + // Find a name that doesn't exist and is in the same directory. + // Rename "from" to "tempname" and then rename "tempname" to "to". + if (strlen(from) >= MAXPATHL - 5) { return -1; } STRCPY(tempname, from); for (n = 123; n < 99999; n++) { - char *tail = path_tail((char *)tempname); - snprintf(tail, (size_t)((MAXPATHL + 1) - (tail - (char *)tempname - 1)), "%d", n); + char *tail = path_tail(tempname); + snprintf(tail, (size_t)((MAXPATHL + 1) - (tail - tempname - 1)), "%d", n); if (!os_path_exists(tempname)) { if (os_rename(from, tempname) == OK) { @@ -4684,33 +4528,27 @@ int vim_rename(const char_u *from, const char_u *to) return -1; } - /* - * Delete the "to" file, this is required on some systems to make the - * os_rename() work, on other systems it makes sure that we don't have - * two files when the os_rename() fails. - */ + // Delete the "to" file, this is required on some systems to make the + // os_rename() work, on other systems it makes sure that we don't have + // two files when the os_rename() fails. os_remove((char *)to); - /* - * First try a normal rename, return if it works. - */ + // First try a normal rename, return if it works. if (os_rename(from, to) == OK) { return 0; } - /* - * Rename() failed, try copying the file. - */ - perm = os_getperm((const char *)from); + // Rename() failed, try copying the file. + perm = os_getperm(from); #ifdef HAVE_ACL // For systems that support ACL: get the ACL from the original file. - acl = mch_get_acl(from); + acl = os_get_acl(from); #endif fd_in = os_open((char *)from, O_RDONLY, 0); if (fd_in < 0) { #ifdef HAVE_ACL - mch_free_acl(acl); + os_free_acl(acl); #endif return -1; } @@ -4721,7 +4559,7 @@ int vim_rename(const char_u *from, const char_u *to) if (fd_out < 0) { close(fd_in); #ifdef HAVE_ACL - mch_free_acl(acl); + os_free_acl(acl); #endif return -1; } @@ -4733,7 +4571,7 @@ int vim_rename(const char_u *from, const char_u *to) close(fd_out); close(fd_in); #ifdef HAVE_ACL - mch_free_acl(acl); + os_free_acl(acl); #endif return -1; } @@ -4755,11 +4593,11 @@ int vim_rename(const char_u *from, const char_u *to) to = from; } #ifndef UNIX // For Unix os_open() already set the permission. - os_setperm((const char *)to, perm); + os_setperm(to, perm); #endif #ifdef HAVE_ACL - mch_set_acl(to, acl); - mch_free_acl(acl); + os_set_acl(to, acl); + os_free_acl(acl); #endif if (errmsg != NULL) { semsg(errmsg, to); @@ -4769,7 +4607,7 @@ int vim_rename(const char_u *from, const char_u *to) return 0; } -static int already_warned = FALSE; +static int already_warned = false; /// Check if any not hidden buffer has been changed. /// Postpone the check if there are characters in the stuff buffer, a global @@ -4778,7 +4616,7 @@ static int already_warned = FALSE; /// /// @param focus called for GUI focus event /// -/// @return TRUE if some message was written (screen should be redrawn and cursor positioned). +/// @return true if some message was written (screen should be redrawn and cursor positioned). int check_timestamps(int focus) { int didit = 0; @@ -4786,15 +4624,15 @@ int check_timestamps(int focus) // Don't check timestamps while system() or another low-level function may // cause us to lose and gain focus. if (no_check_timestamps > 0) { - return FALSE; + return false; } // Avoid doing a check twice. The OK/Reload dialog can cause a focus // event and we would keep on checking if the file is steadily growing. // Do check again after typing something. if (focus && did_check_timestamps) { - need_check_timestamps = TRUE; - return FALSE; + need_check_timestamps = true; + return false; } if (!stuff_empty() || global_busy || !typebuf_typed() @@ -4841,13 +4679,13 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf) buf_T *tbuf = curbuf; int retval = OK; linenr_T lnum; - char_u *p; + char *p; // Copy the lines in "frombuf" to "tobuf". curbuf = tobuf; for (lnum = 1; lnum <= frombuf->b_ml.ml_line_count; lnum++) { - p = vim_strsave(ml_get_buf(frombuf, lnum, false)); - if (ml_append(lnum - 1, (char *)p, 0, false) == FAIL) { + p = xstrdup(ml_get_buf(frombuf, lnum, false)); + if (ml_append(lnum - 1, p, 0, false) == FAIL) { xfree(p); retval = FAIL; break; @@ -4882,7 +4720,7 @@ int buf_check_timestamp(buf_T *buf) FUNC_ATTR_NONNULL_ALL { int retval = 0; - char_u *path; + char *path; char *mesg = NULL; char *mesg2 = ""; bool helpmesg = false; @@ -4897,7 +4735,7 @@ int buf_check_timestamp(buf_T *buf) uint64_t orig_size = buf->b_orig_size; int orig_mode = buf->b_orig_mode; static bool busy = false; - char_u *s; + char *s; char *reason; bufref_T bufref; @@ -4937,7 +4775,7 @@ int buf_check_timestamp(buf_T *buf) buf_store_file_info(buf, &file_info); } - if (os_isdir((char_u *)buf->b_fname)) { + if (os_isdir(buf->b_fname)) { // Don't do anything for a directory. Might contain the file explorer. } else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar) && !bufIsChanged(buf) && file_info_ok) { @@ -4972,12 +4810,12 @@ int buf_check_timestamp(buf_T *buf) if (!bufref_valid(&bufref)) { emsg(_("E246: FileChangedShell autocommand deleted buffer")); } - s = (char_u *)get_vim_var_str(VV_FCS_CHOICE); - if (STRCMP(s, "reload") == 0 && *reason != 'd') { + s = get_vim_var_str(VV_FCS_CHOICE); + if (strcmp(s, "reload") == 0 && *reason != 'd') { reload = RELOAD_NORMAL; - } else if (STRCMP(s, "edit") == 0) { + } else if (strcmp(s, "edit") == 0) { reload = RELOAD_DETECT; - } else if (STRCMP(s, "ask") == 0) { + } else if (strcmp(s, "ask") == 0) { n = false; } else { return 2; @@ -5017,7 +4855,7 @@ int buf_check_timestamp(buf_T *buf) } } } else if ((buf->b_flags & BF_NEW) && !(buf->b_flags & BF_NEW_W) - && os_path_exists((char_u *)buf->b_ffname)) { + && os_path_exists(buf->b_ffname)) { retval = 1; mesg = _("W13: Warning: File \"%s\" has been created after editing started"); buf->b_flags |= BF_NEW_W; @@ -5025,11 +4863,11 @@ int buf_check_timestamp(buf_T *buf) } if (mesg != NULL) { - path = (char_u *)home_replace_save(buf, buf->b_fname); + path = home_replace_save(buf, buf->b_fname); if (!helpmesg) { mesg2 = ""; } - const size_t tbuf_len = STRLEN(path) + STRLEN(mesg) + STRLEN(mesg2) + 2; + const size_t tbuf_len = strlen(path) + strlen(mesg) + strlen(mesg2) + 2; char *const tbuf = xmalloc(tbuf_len); snprintf(tbuf, tbuf_len, mesg, path); // Set warningmsg here, before the unimportant and output-specific @@ -5040,8 +4878,8 @@ int buf_check_timestamp(buf_T *buf) xstrlcat(tbuf, "\n", tbuf_len - 1); xstrlcat(tbuf, mesg2, tbuf_len - 1); } - switch (do_dialog(VIM_WARNING, (char_u *)_("Warning"), (char_u *)tbuf, - (char_u *)_("&OK\n&Load File\nLoad File &and Options"), + switch (do_dialog(VIM_WARNING, _("Warning"), tbuf, + _("&OK\n&Load File\nLoad File &and Options"), 1, NULL, true)) { case 2: reload = RELOAD_NORMAL; @@ -5066,7 +4904,7 @@ int buf_check_timestamp(buf_T *buf) } msg_clr_eos(); (void)msg_end(); - if (emsg_silent == 0) { + if (emsg_silent == 0 && !in_assert_fails) { ui_flush(); // give the user some time to think about it os_delay(1004L, true); @@ -5075,7 +4913,7 @@ int buf_check_timestamp(buf_T *buf) redraw_cmdline = false; } } - already_warned = TRUE; + already_warned = true; } xfree(path); @@ -5117,7 +4955,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options) aco_save_T aco; int flags = READ_NEW; - // set curwin/curbuf for "buf" and save some things + // Set curwin/curbuf for "buf" and save some things. aucmd_prepbuf(&aco, buf); // Unless reload_options is set, we only want to read the text from the @@ -5264,11 +5102,11 @@ void write_lnum_adjust(linenr_T offset) #if defined(BACKSLASH_IN_FILENAME) /// Convert all backslashes in fname to forward slashes in-place, /// unless when it looks like a URL. -void forward_slash(char_u *fname) +void forward_slash(char *fname) { - char_u *p; + char *p; - if (path_with_url((const char *)fname)) { + if (path_with_url(fname)) { return; } for (p = fname; *p != NUL; p++) { @@ -5281,6 +5119,9 @@ void forward_slash(char_u *fname) /// Path to Nvim's own temp dir. Ends in a slash. static char *vim_tempdir = NULL; +#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD) +DIR *vim_tempdir_dp = NULL; ///< File descriptor of temp dir +#endif /// Creates a directory for private use by this instance of Nvim, trying each of /// `TEMP_DIR_NAMES` until one succeeds. @@ -5306,8 +5147,8 @@ static void vim_mktempdir(void) mode_t umask_save = umask(0077); for (size_t i = 0; i < ARRAY_SIZE(temp_dirs); i++) { // Expand environment variables, leave room for "/tmp/nvim.<user>/XXXXXX/999999999". - expand_env((char_u *)temp_dirs[i], (char_u *)tmp, TEMP_FILE_PATH_MAXLEN - 64); - if (!os_isdir((char_u *)tmp)) { + expand_env((char *)temp_dirs[i], tmp, TEMP_FILE_PATH_MAXLEN - 64); + if (!os_isdir(tmp)) { continue; } @@ -5317,7 +5158,7 @@ static void vim_mktempdir(void) xstrlcat(tmp, user, sizeof(tmp)); (void)os_mkdir(tmp, 0700); // Always create, to avoid a race. bool owned = os_file_owned(tmp); - bool isdir = os_isdir((char_u *)tmp); + bool isdir = os_isdir(tmp); #ifdef UNIX int perm = os_getperm(tmp); // XDG_RUNTIME_DIR must be owned by the user, mode 0700. bool valid = isdir && owned && 0700 == (perm & 0777); @@ -5353,10 +5194,9 @@ static void vim_mktempdir(void) if (vim_settempdir(path)) { // Successfully created and set temporary directory so stop trying. break; - } else { - // Couldn't set `vim_tempdir` to `path` so remove created directory. - os_rmdir(path); } + // Couldn't set `vim_tempdir` to `path` so remove created directory. + os_rmdir(path); } (void)umask(umask_save); } @@ -5423,7 +5263,7 @@ int delete_recursive(const char *name) garray_T ga; if (readdir_core(&ga, exp, NULL, NULL) == OK) { for (int i = 0; i < ga.ga_len; i++) { - vim_snprintf((char *)NameBuff, MAXPATHL, "%s/%s", exp, ((char_u **)ga.ga_data)[i]); + vim_snprintf(NameBuff, MAXPATHL, "%s/%s", exp, ((char **)ga.ga_data)[i]); if (delete_recursive((const char *)NameBuff) != 0) { // Remember the failure but continue deleting any further // entries. @@ -5446,15 +5286,50 @@ int delete_recursive(const char *name) return result; } +#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD) +/// Open temporary directory and take file lock to prevent +/// to be auto-cleaned. +static void vim_opentempdir(void) +{ + if (vim_tempdir_dp != NULL) { + return; + } + + DIR *dp = opendir(vim_tempdir); + if (dp == NULL) { + return; + } + + vim_tempdir_dp = dp; + flock(dirfd(vim_tempdir_dp), LOCK_SH); +} + +/// Close temporary directory - it automatically release file lock. +static void vim_closetempdir(void) +{ + if (vim_tempdir_dp == NULL) { + return; + } + + closedir(vim_tempdir_dp); + vim_tempdir_dp = NULL; +} +#endif + /// Delete the temp directory and all files it contains. void vim_deltempdir(void) { - if (vim_tempdir != NULL) { - // remove the trailing path separator - path_tail(vim_tempdir)[-1] = NUL; - delete_recursive(vim_tempdir); - XFREE_CLEAR(vim_tempdir); + if (vim_tempdir == NULL) { + return; } + +#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD) + vim_closetempdir(); +#endif + // remove the trailing path separator + path_tail(vim_tempdir)[-1] = NUL; + delete_recursive(vim_tempdir); + XFREE_CLEAR(vim_tempdir); } /// Gets path to Nvim's own temp dir (ending with slash). @@ -5479,12 +5354,16 @@ char *vim_gettempdir(void) static bool vim_settempdir(char *tempdir) { char *buf = verbose_try_malloc(MAXPATHL + 2); - if (!buf) { + if (buf == NULL) { return false; } + vim_FullName(tempdir, buf, MAXPATHL, false); add_pathsep(buf); vim_tempdir = xstrdup(buf); +#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD) + vim_opentempdir(); +#endif xfree(buf); return true; } @@ -5495,7 +5374,7 @@ static bool vim_settempdir(char *tempdir) /// /// @return pointer to the temp file name or NULL if Nvim can't create /// temporary directory for its own temporary files. -char_u *vim_tempname(void) +char *vim_tempname(void) { // Temp filename counter. static uint64_t temp_count; @@ -5507,10 +5386,10 @@ char_u *vim_tempname(void) // There is no need to check if the file exists, because we own the directory // and nobody else creates a file in it. - char_u template[TEMP_FILE_PATH_MAXLEN]; - snprintf((char *)template, TEMP_FILE_PATH_MAXLEN, + char template[TEMP_FILE_PATH_MAXLEN]; + snprintf(template, TEMP_FILE_PATH_MAXLEN, "%s%" PRIu64, tempdir, temp_count++); - return vim_strsave(template); + return xstrdup(template); } /// Tries matching a filename with a "pattern" ("prog" is NULL), or use the @@ -5542,12 +5421,10 @@ bool match_file_pat(char *pattern, regprog_T **prog, char *fname, char *sfname, } } - /* - * Try for a match with the pattern with: - * 1. the full file name, when the pattern has a '/'. - * 2. the short file name, when the pattern has a '/'. - * 3. the tail of the file name, when the pattern has no '/'. - */ + // Try for a match with the pattern with: + // 1. the full file name, when the pattern has a '/'. + // 2. the short file name, when the pattern has a '/'. + // 3. the tail of the file name, when the pattern has no '/'. if (regmatch.regprog != NULL && ((allow_dirs && (vim_regexec(®match, fname, (colnr_T)0) @@ -5574,28 +5451,27 @@ bool match_file_pat(char *pattern, regprog_T **prog, char *fname, char *sfname, /// @param ffname full file name /// /// @return true if there was a match -bool match_file_list(char_u *list, char_u *sfname, char_u *ffname) +bool match_file_list(char *list, char *sfname, char *ffname) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 3) { - char_u buf[100]; - char_u *tail; - char_u *regpat; + char buf[100]; + char *tail; + char *regpat; char allow_dirs; bool match; char *p; - tail = (char_u *)path_tail((char *)sfname); + tail = path_tail(sfname); // try all patterns in 'wildignore' - p = (char *)list; + p = list; while (*p) { - copy_option_part(&p, (char *)buf, ARRAY_SIZE(buf), ","); - regpat = (char_u *)file_pat_to_reg_pat((char *)buf, NULL, &allow_dirs, false); + copy_option_part(&p, buf, ARRAY_SIZE(buf), ","); + regpat = file_pat_to_reg_pat(buf, NULL, &allow_dirs, false); if (regpat == NULL) { break; } - match = match_file_pat((char *)regpat, NULL, (char *)ffname, (char *)sfname, (char *)tail, - (int)allow_dirs); + match = match_file_pat(regpat, NULL, ffname, sfname, tail, (int)allow_dirs); xfree(regpat); if (match) { return true; @@ -5606,8 +5482,8 @@ bool match_file_list(char_u *list, char_u *sfname, char_u *ffname) /// Convert the given pattern "pat" which has shell style wildcards in it, into /// a regular expression, and return the result in allocated memory. If there -/// is a directory path separator to be matched, then TRUE is put in -/// allow_dirs, otherwise FALSE is put there -- webb. +/// is a directory path separator to be matched, then true is put in +/// allow_dirs, otherwise false is put there -- webb. /// Handle backslashes before special characters, like "\*" and "\ ". /// /// @param pat_end first char after pattern or NULL @@ -5625,10 +5501,10 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs bool add_dollar = true; if (allow_dirs != NULL) { - *allow_dirs = FALSE; + *allow_dirs = false; } if (pat_end == NULL) { - pat_end = pat + STRLEN(pat); + pat_end = pat + strlen(pat); } if (pat_end == pat) { @@ -5704,14 +5580,14 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs // "\*" to "\\.*" e.g., "dir\*.c" // "\?" to "\\." e.g., "dir\??.c" // "\+" to "\+" e.g., "fileX\+.c" - if ((vim_isfilec(p[1]) || p[1] == '*' || p[1] == '?') + if ((vim_isfilec((uint8_t)p[1]) || p[1] == '*' || p[1] == '?') && p[1] != '+') { reg_pat[i++] = '['; reg_pat[i++] = '\\'; reg_pat[i++] = '/'; reg_pat[i++] = ']'; if (allow_dirs != NULL) { - *allow_dirs = TRUE; + *allow_dirs = true; } break; } @@ -5745,7 +5621,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs && (!no_bslash || *p != '\\') #endif ) { - *allow_dirs = TRUE; + *allow_dirs = true; } reg_pat[i++] = '\\'; reg_pat[i++] = *p; @@ -5758,7 +5634,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs reg_pat[i++] = '/'; reg_pat[i++] = ']'; if (allow_dirs != NULL) { - *allow_dirs = TRUE; + *allow_dirs = true; } break; #endif diff --git a/src/nvim/fileio.h b/src/nvim/fileio.h index 650977deac..dabcda5bf2 100644 --- a/src/nvim/fileio.h +++ b/src/nvim/fileio.h @@ -3,6 +3,7 @@ #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/garray.h" #include "nvim/os/os.h" @@ -15,6 +16,7 @@ #define READ_KEEP_UNDO 0x20 // keep undo info #define READ_FIFO 0x40 // read from fifo or socket #define READ_NOWINENTER 0x80 // do not trigger BufWinEnter +#define READ_NOFILE 0x100 // do not read a file, do trigger BufReadCmd #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 8ce24fd378..7306131574 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -5,10 +5,15 @@ // fold.c: code for folding +#include <assert.h> #include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/charset.h" @@ -16,14 +21,17 @@ #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" -#include "nvim/ex_docmd.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_session.h" #include "nvim/extmark.h" #include "nvim/fold.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/indent.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" @@ -35,6 +43,7 @@ #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/syntax.h" +#include "nvim/types.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -57,9 +66,11 @@ typedef struct { // folds too } fold_T; -#define FD_OPEN 0 // fold is open (nested ones can be closed) -#define FD_CLOSED 1 // fold is closed -#define FD_LEVEL 2 // depends on 'foldlevel' (nested folds too) +enum { + FD_OPEN = 0, // fold is open (nested ones can be closed) + FD_CLOSED = 1, // fold is closed + FD_LEVEL = 2, // depends on 'foldlevel' (nested folds too) +}; #define MAX_LEVEL 20 // maximum fold depth @@ -111,7 +122,7 @@ static int prev_lnum_lvl = -1; #define DONE_FOLD 2 // did find a fold static size_t foldstartmarkerlen; -static char_u *foldendmarker; +static char *foldendmarker; static size_t foldendmarkerlen; // Exported folding functions. {{{1 @@ -394,7 +405,7 @@ void opFoldRange(pos_T firstpos, pos_T lastpos, int opening, int recurse, int ha } // Force a redraw to remove the Visual highlighting. if (had_visual) { - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } } @@ -466,12 +477,15 @@ static void newFoldLevelWin(win_T *wp) /// Apply 'foldlevel' to all folds that don't contain the cursor. void foldCheckClose(void) { - if (*p_fcl != NUL) { // can only be "all" right now - checkupdate(curwin); - if (checkCloseRec(&curwin->w_folds, curwin->w_cursor.lnum, - (int)curwin->w_p_fdl)) { - changed_window_setting(); - } + if (*p_fcl == NUL) { + return; + } + + // 'foldclose' can only be "all" right now + checkupdate(curwin); + if (checkCloseRec(&curwin->w_folds, curwin->w_cursor.lnum, + (int)curwin->w_p_fdl)) { + changed_window_setting(); } } @@ -721,7 +735,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const emsg(_(e_nofold)); // Force a redraw to remove the Visual highlighting. if (had_visual) { - redraw_buf_later(wp->w_buffer, INVERTED); + redraw_buf_later(wp->w_buffer, UPD_INVERTED); } } else { // Deleting markers may make cursor column invalid @@ -757,7 +771,7 @@ void clearFolding(win_T *win) /// The changes in lines from top to bot (inclusive). void foldUpdate(win_T *wp, linenr_T top, linenr_T bot) { - if (disable_fold_update || compl_busy || State & MODE_INSERT) { + if (disable_fold_update || State & MODE_INSERT) { return; } @@ -819,7 +833,7 @@ void foldUpdateAfterInsert(void) void foldUpdateAll(win_T *win) { win->w_foldinvalid = true; - redraw_later(win, NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } // foldMoveTo() {{{2 @@ -982,7 +996,7 @@ void foldAdjustVisual(void) } pos_T *start, *end; - char_u *ptr; + char *ptr; if (ltoreq(VIsual, curwin->w_cursor)) { start = &VIsual; @@ -994,15 +1008,18 @@ void foldAdjustVisual(void) if (hasFolding(start->lnum, &start->lnum, NULL)) { start->col = 0; } - if (hasFolding(end->lnum, NULL, &end->lnum)) { - ptr = ml_get(end->lnum); - end->col = (colnr_T)STRLEN(ptr); - if (end->col > 0 && *p_sel == 'o') { - end->col--; - } - // prevent cursor from moving on the trail byte - mb_adjust_cursor(); + + if (!hasFolding(end->lnum, NULL, &end->lnum)) { + return; + } + + ptr = ml_get(end->lnum); + end->col = (colnr_T)strlen(ptr); + if (end->col > 0 && *p_sel == 'o') { + end->col--; } + // prevent cursor from moving on the trail byte + mb_adjust_cursor(); } // cursor_foldstart() {{{2 @@ -1083,7 +1100,7 @@ static int foldLevelWin(win_T *wp, linenr_T lnum) { fold_T *fp; linenr_T lnum_rel = lnum; - int level = 0; + int level = 0; // Recursively search for a fold that contains "lnum". garray_T *gap = &wp->w_folds; @@ -1104,10 +1121,12 @@ static int foldLevelWin(win_T *wp, linenr_T lnum) /// Check if the folds in window "wp" are invalid and update them if needed. static void checkupdate(win_T *wp) { - if (wp->w_foldinvalid) { - foldUpdate(wp, (linenr_T)1, (linenr_T)MAXLNUM); // will update all - wp->w_foldinvalid = false; + if (!wp->w_foldinvalid) { + return; } + + foldUpdate(wp, (linenr_T)1, (linenr_T)MAXLNUM); // will update all + wp->w_foldinvalid = false; } // setFoldRepeat() {{{2 @@ -1425,15 +1444,13 @@ static void foldMarkAdjustRecurse(win_T *wp, garray_T *gap, linenr_T line1, line // 5. fold is below line1 and contains line2; need to // correct nested folds too if (amount == MAXLNUM) { - foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top, - line2 - fp->fd_top, amount, - amount_after + (fp->fd_top - top)); + foldMarkAdjustRecurse(wp, &fp->fd_nested, 0, line2 - fp->fd_top, + amount, amount_after + (fp->fd_top - top)); fp->fd_len -= line2 - fp->fd_top + 1; fp->fd_top = line1; } else { - foldMarkAdjustRecurse(wp, &fp->fd_nested, line1 - fp->fd_top, - line2 - fp->fd_top, amount, - amount_after - amount); + foldMarkAdjustRecurse(wp, &fp->fd_nested, 0, line2 - fp->fd_top, + amount, amount_after - amount); fp->fd_len += amount_after - amount; fp->fd_top += amount; } @@ -1514,23 +1531,25 @@ static bool check_closed(win_T *const wp, fold_T *const fp, bool *const use_leve /// @param lnum_off offset for fp->fd_top static void checkSmall(win_T *const wp, fold_T *const fp, const linenr_T lnum_off) { - if (fp->fd_small == kNone) { - // Mark any nested folds to maybe-small - setSmallMaybe(&fp->fd_nested); + if (fp->fd_small != kNone) { + return; + } - if (fp->fd_len > wp->w_p_fml) { - fp->fd_small = kFalse; - } else { - int count = 0; - for (int n = 0; n < fp->fd_len; n++) { - count += plines_win_nofold(wp, fp->fd_top + lnum_off + n); - if (count > wp->w_p_fml) { - fp->fd_small = kFalse; - return; - } + // Mark any nested folds to maybe-small + setSmallMaybe(&fp->fd_nested); + + if (fp->fd_len > wp->w_p_fml) { + fp->fd_small = kFalse; + } else { + int count = 0; + for (int n = 0; n < fp->fd_len; n++) { + count += plines_win_nofold(wp, fp->fd_top + lnum_off + n); + if (count > wp->w_p_fml) { + fp->fd_small = kFalse; + return; } - fp->fd_small = kTrue; } + fp->fd_small = kTrue; } } @@ -1573,39 +1592,41 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end) // foldAddMarker() {{{2 /// Add "marker[markerlen]" in 'commentstring' to position `pos`. -static void foldAddMarker(buf_T *buf, pos_T pos, const char_u *marker, size_t markerlen) +static void foldAddMarker(buf_T *buf, pos_T pos, const char *marker, size_t markerlen) { - char_u *cms = buf->b_p_cms; - char_u *newline; - char_u *p = (char_u *)strstr((char *)buf->b_p_cms, "%s"); + char *cms = buf->b_p_cms; + char *newline; + char *p = strstr(buf->b_p_cms, "%s"); bool line_is_comment = false; linenr_T lnum = pos.lnum; // Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end - char_u *line = ml_get_buf(buf, lnum, false); - size_t line_len = STRLEN(line); + char *line = ml_get_buf(buf, lnum, false); + size_t line_len = strlen(line); size_t added = 0; - if (u_save(lnum - 1, lnum + 1) == OK) { - // Check if the line ends with an unclosed comment - skip_comment(line, false, false, &line_is_comment); - newline = xmalloc(line_len + markerlen + STRLEN(cms) + 1); - STRCPY(newline, line); - // Append the marker to the end of the line - if (p == NULL || line_is_comment) { - STRLCPY(newline + line_len, marker, markerlen + 1); - added = markerlen; - } else { - STRCPY(newline + line_len, cms); - memcpy(newline + line_len + (p - cms), marker, markerlen); - STRCPY(newline + line_len + (p - cms) + markerlen, p + 2); - added = markerlen + STRLEN(cms) - 2; - } - ml_replace_buf(buf, lnum, newline, false); - if (added) { - extmark_splice_cols(buf, (int)lnum - 1, (int)line_len, - 0, (int)added, kExtmarkUndo); - } + if (u_save(lnum - 1, lnum + 1) != OK) { + return; + } + + // Check if the line ends with an unclosed comment + skip_comment(line, false, false, &line_is_comment); + newline = xmalloc(line_len + markerlen + strlen(cms) + 1); + STRCPY(newline, line); + // Append the marker to the end of the line + if (p == NULL || line_is_comment) { + xstrlcpy(newline + line_len, marker, markerlen + 1); + added = markerlen; + } else { + STRCPY(newline + line_len, cms); + memcpy(newline + line_len + (p - cms), marker, markerlen); + STRCPY(newline + line_len + (p - cms) + markerlen, p + 2); + added = markerlen + strlen(cms) - 2; + } + ml_replace_buf(buf, lnum, newline, false); + if (added) { + extmark_splice_cols(buf, (int)lnum - 1, (int)line_len, + 0, (int)added, kExtmarkUndo); } } @@ -1632,17 +1653,17 @@ static void deleteFoldMarkers(win_T *wp, fold_T *fp, int recursive, linenr_T lnu /// Delete 'commentstring' if it matches. /// If the marker is not found, there is no error message. Could be a missing /// close-marker. -static void foldDelMarker(buf_T *buf, linenr_T lnum, char_u *marker, size_t markerlen) +static void foldDelMarker(buf_T *buf, linenr_T lnum, char *marker, size_t markerlen) { // end marker may be missing and fold extends below the last line if (lnum > buf->b_ml.ml_line_count) { return; } - char_u *cms = buf->b_p_cms; - char_u *line = ml_get_buf(buf, lnum, false); - for (char_u *p = line; *p != NUL; p++) { - if (STRNCMP(p, marker, markerlen) != 0) { + char *cms = buf->b_p_cms; + char *line = ml_get_buf(buf, lnum, false); + for (char *p = line; *p != NUL; p++) { + if (strncmp(p, marker, markerlen) != 0) { continue; } // Found the marker, include a digit if it's there. @@ -1652,17 +1673,17 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char_u *marker, size_t mark } if (*cms != NUL) { // Also delete 'commentstring' if it matches. - char_u *cms2 = (char_u *)strstr((char *)cms, "%s"); + char *cms2 = strstr(cms, "%s"); if (p - line >= cms2 - cms - && STRNCMP(p - (cms2 - cms), cms, cms2 - cms) == 0 - && STRNCMP(p + len, cms2 + 2, STRLEN(cms2 + 2)) == 0) { + && strncmp(p - (cms2 - cms), cms, (size_t)(cms2 - cms)) == 0 + && strncmp(p + len, cms2 + 2, strlen(cms2 + 2)) == 0) { p -= cms2 - cms; - len += STRLEN(cms) - 2; + len += strlen(cms) - 2; } } if (u_save(lnum - 1, lnum + 1) == OK) { // Make new line: text-before-marker + text-after-marker - char_u *newline = xmalloc(STRLEN(line) - len + 1); + char *newline = xmalloc(strlen(line) - len + 1); assert(p >= line); memcpy(newline, line, (size_t)(p - line)); STRCPY(newline + (p - line), p + len); @@ -1683,10 +1704,10 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char_u *marker, size_t mark /// @return the text for a closed fold /// /// Otherwise the result is in allocated memory. -char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo, char_u *buf) +char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo, char *buf) FUNC_ATTR_NONNULL_ARG(1) { - char_u *text = NULL; + char *text = NULL; // an error occurred when evaluating 'fdt' setting static bool got_fdt_error = false; int save_did_emsg = did_emsg; @@ -1728,9 +1749,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin curbuf = wp->w_buffer; emsg_silent++; // handle exceptions, but don't display errors - text = - (char_u *)eval_to_string_safe((char *)wp->w_p_fdt, NULL, - was_set_insecurely(wp, "foldtext", OPT_LOCAL)); + text = eval_to_string_safe(wp->w_p_fdt, NULL, was_set_insecurely(wp, "foldtext", OPT_LOCAL)); emsg_silent--; if (text == NULL || did_emsg) { @@ -1751,32 +1770,32 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin if (text != NULL) { // Replace unprintable characters, if there are any. But // replace a TAB with a space. - char_u *p; + char *p; for (p = text; *p != NUL; p++) { - int len = utfc_ptr2len((char *)p); + int len = utfc_ptr2len(p); if (len > 1) { - if (!vim_isprintc(utf_ptr2char((char *)p))) { + if (!vim_isprintc(utf_ptr2char(p))) { break; } p += len - 1; } else if (*p == TAB) { *p = ' '; - } else if (ptr2cells((char *)p) > 1) { + } else if (ptr2cells(p) > 1) { break; } } if (*p != NUL) { - p = (char_u *)transstr((const char *)text, true); + p = transstr((const char *)text, true); xfree(text); text = p; } } } if (text == NULL) { - unsigned long count = (unsigned long)(lnume - lnum + 1); + long count = lnume - lnum + 1; - vim_snprintf((char *)buf, FOLD_TEXT_LEN, + vim_snprintf(buf, FOLD_TEXT_LEN, NGETTEXT("+--%3ld line folded", "+--%3ld lines folded ", count), count); @@ -1787,17 +1806,17 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin // foldtext_cleanup() {{{2 /// Remove 'foldmarker' and 'commentstring' from "str" (in-place). -static void foldtext_cleanup(char_u *str) +static void foldtext_cleanup(char *str) { // Ignore leading and trailing white space in 'commentstring'. - char_u *cms_start = (char_u *)skipwhite((char *)curbuf->b_p_cms); - size_t cms_slen = STRLEN(cms_start); + char *cms_start = skipwhite(curbuf->b_p_cms); + size_t cms_slen = strlen(cms_start); while (cms_slen > 0 && ascii_iswhite(cms_start[cms_slen - 1])) { cms_slen--; } // locate "%s" in 'commentstring', use the part before and after it. - char_u *cms_end = (char_u *)strstr((char *)cms_start, "%s"); + char *cms_end = strstr(cms_start, "%s"); size_t cms_elen = 0; if (cms_end != NULL) { cms_elen = cms_slen - (size_t)(cms_end - cms_start); @@ -1809,7 +1828,7 @@ static void foldtext_cleanup(char_u *str) } // skip "%s" and white space after it - char_u *s = (char_u *)skipwhite((char *)cms_end + 2); + char *s = skipwhite(cms_end + 2); cms_elen -= (size_t)(s - cms_end); cms_end = s; } @@ -1818,11 +1837,11 @@ static void foldtext_cleanup(char_u *str) bool did1 = false; bool did2 = false; - for (char_u *s = str; *s != NUL;) { + for (char *s = str; *s != NUL;) { size_t len = 0; - if (STRNCMP(s, curwin->w_p_fmr, foldstartmarkerlen) == 0) { + if (strncmp(s, curwin->w_p_fmr, foldstartmarkerlen) == 0) { len = foldstartmarkerlen; - } else if (STRNCMP(s, foldendmarker, foldendmarkerlen) == 0) { + } else if (strncmp(s, foldendmarker, foldendmarkerlen) == 0) { len = foldendmarkerlen; } if (len > 0) { @@ -1832,19 +1851,19 @@ static void foldtext_cleanup(char_u *str) // May remove 'commentstring' start. Useful when it's a double // quote and we already removed a double quote. - char_u *p; + char *p; for (p = s; p > str && ascii_iswhite(p[-1]); p--) {} if (p >= str + cms_slen - && STRNCMP(p - cms_slen, cms_start, cms_slen) == 0) { + && strncmp(p - cms_slen, cms_start, cms_slen) == 0) { len += (size_t)(s - p) + cms_slen; s = p - cms_slen; } } else if (cms_end != NULL) { - if (!did1 && cms_slen > 0 && STRNCMP(s, cms_start, cms_slen) == 0) { + if (!did1 && cms_slen > 0 && strncmp(s, cms_start, cms_slen) == 0) { len = cms_slen; did1 = true; } else if (!did2 && cms_elen > 0 - && STRNCMP(s, cms_end, cms_elen) == 0) { + && strncmp(s, cms_end, cms_elen) == 0) { len = cms_elen; did2 = true; } @@ -2850,11 +2869,11 @@ static void foldlevelIndent(fline_T *flp) linenr_T lnum = flp->lnum + flp->off; buf_T *buf = flp->wp->w_buffer; - char_u *s = (char_u *)skipwhite((char *)ml_get_buf(buf, lnum, false)); + char *s = skipwhite(ml_get_buf(buf, lnum, false)); // empty line or lines starting with a character in 'foldignore': level // depends on surrounding lines - if (*s == NUL || vim_strchr((char *)flp->wp->w_p_fdi, *s) != NULL) { + if (*s == NUL || vim_strchr(flp->wp->w_p_fdi, (uint8_t)(*s)) != NULL) { // first and last line can't be undefined, use level 0 if (lnum == 1 || lnum == buf->b_ml.ml_line_count) { flp->lvl = 0; @@ -2907,7 +2926,7 @@ static void foldlevelExpr(fline_T *flp) const bool save_keytyped = KeyTyped; int c; - const int n = eval_foldexpr((char *)flp->wp->w_p_fde, &c); + const int n = eval_foldexpr(flp->wp->w_p_fde, &c); KeyTyped = save_keytyped; switch (c) { @@ -2985,9 +3004,9 @@ static void foldlevelExpr(fline_T *flp) /// Relies on the option value to have been checked for correctness already. static void parseMarker(win_T *wp) { - foldendmarker = (char_u *)vim_strchr((char *)wp->w_p_fmr, ','); + foldendmarker = vim_strchr(wp->w_p_fmr, ','); foldstartmarkerlen = (size_t)(foldendmarker++ - wp->w_p_fmr); - foldendmarkerlen = STRLEN(foldendmarker); + foldendmarkerlen = strlen(foldendmarker); } // foldlevelMarker() {{{2 @@ -3003,23 +3022,23 @@ static void foldlevelMarker(fline_T *flp) int start_lvl = flp->lvl; // cache a few values for speed - char_u *startmarker = flp->wp->w_p_fmr; - int cstart = *startmarker; + char *startmarker = flp->wp->w_p_fmr; + char cstart = *startmarker; startmarker++; - int cend = *foldendmarker; + char cend = *foldendmarker; // Default: no start found, next level is same as current level flp->start = 0; flp->lvl_next = flp->lvl; - char_u *s = ml_get_buf(flp->wp->w_buffer, flp->lnum + flp->off, false); + char *s = ml_get_buf(flp->wp->w_buffer, flp->lnum + flp->off, false); while (*s) { if (*s == cstart - && STRNCMP(s + 1, startmarker, foldstartmarkerlen - 1) == 0) { + && strncmp(s + 1, startmarker, foldstartmarkerlen - 1) == 0) { // found startmarker: set flp->lvl s += foldstartmarkerlen; if (ascii_isdigit(*s)) { - int n = atoi((char *)s); + int n = atoi(s); if (n > 0) { flp->lvl = n; flp->lvl_next = n; @@ -3034,12 +3053,12 @@ static void foldlevelMarker(fline_T *flp) flp->lvl_next++; flp->start++; } - } else if (*s == cend && STRNCMP(s + 1, foldendmarker + 1, - foldendmarkerlen - 1) == 0) { + } else if (*s == cend + && strncmp(s + 1, foldendmarker + 1, foldendmarkerlen - 1) == 0) { // found endmarker: set flp->lvl_next s += foldendmarkerlen; if (ascii_isdigit(*s)) { - int n = atoi((char *)s); + int n = atoi(s); if (n > 0) { flp->lvl = n; flp->lvl_next = n - 1; @@ -3117,7 +3136,7 @@ static int put_folds_recurse(FILE *fd, garray_T *gap, linenr_T off) return FAIL; } if (fprintf(fd, "%" PRId64 ",%" PRId64 "fold", - (int64_t)(fp->fd_top + off), + (int64_t)fp->fd_top + off, (int64_t)(fp->fd_top + off + fp->fd_len - 1)) < 0 || put_eol(fd) == FAIL) { return FAIL; @@ -3138,7 +3157,7 @@ static int put_foldopen_recurse(FILE *fd, win_T *wp, garray_T *gap, linenr_T off if (fp->fd_flags != FD_LEVEL) { if (!GA_EMPTY(&fp->fd_nested)) { // open nested folds while this fold is open - if (fprintf(fd, "%" PRId64, (int64_t)(fp->fd_top + off)) < 0 + if (fprintf(fd, "%" PRId64, (int64_t)fp->fd_top + off) < 0 || put_eol(fd) == FAIL || put_line(fd, "normal! zo") == FAIL) { return FAIL; @@ -3212,19 +3231,19 @@ static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) } /// "foldclosed()" function -void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_foldclosed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { foldclosed_both(argvars, rettv, false); } /// "foldclosedend()" function -void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_foldclosedend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { foldclosed_both(argvars, rettv, true); } /// "foldlevel()" function -void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_foldlevel(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const linenr_T lnum = tv_get_lnum(argvars); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { @@ -3233,14 +3252,14 @@ void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "foldtext()" function -void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_foldtext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; linenr_T foldstart = (linenr_T)get_vim_var_nr(VV_FOLDSTART); linenr_T foldend = (linenr_T)get_vim_var_nr(VV_FOLDEND); - char_u *dashes = (char_u *)get_vim_var_str(VV_FOLDDASHES); + char *dashes = get_vim_var_str(VV_FOLDDASHES); if (foldstart > 0 && foldend <= curbuf->b_ml.ml_line_count) { // Find first non-empty line in the fold. linenr_T lnum; @@ -3251,37 +3270,37 @@ void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // Find interesting text in this line. - char_u *s = (char_u *)skipwhite((char *)ml_get(lnum)); + char *s = skipwhite(ml_get(lnum)); // skip C comment-start if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) { - s = (char_u *)skipwhite((char *)s + 2); - if (*skipwhite((char *)s) == NUL && lnum + 1 < foldend) { - s = (char_u *)skipwhite((char *)ml_get(lnum + 1)); + s = skipwhite(s + 2); + if (*skipwhite(s) == NUL && lnum + 1 < foldend) { + s = skipwhite(ml_get(lnum + 1)); if (*s == '*') { - s = (char_u *)skipwhite((char *)s + 1); + s = skipwhite(s + 1); } } } - int count = foldend - foldstart + 1; + long count = foldend - foldstart + 1; char *txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count); - size_t len = STRLEN(txt) - + STRLEN(dashes) // for %s + size_t len = strlen(txt) + + strlen(dashes) // for %s + 20 // for %3ld - + STRLEN(s); // concatenated - char_u *r = xmalloc(len); - snprintf((char *)r, len, txt, dashes, count); - len = STRLEN(r); + + strlen(s); // concatenated + char *r = xmalloc(len); + snprintf(r, len, txt, dashes, count); + len = strlen(r); STRCAT(r, s); // remove 'foldmarker' and 'commentstring' foldtext_cleanup(r + len); - rettv->vval.v_string = (char *)r; + rettv->vval.v_string = r; } } /// "foldtextresult(lnum)" function -void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_foldtextresult(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - char_u buf[FOLD_TEXT_LEN]; + char buf[FOLD_TEXT_LEN]; static bool entered = false; rettv->v_type = VAR_STRING; @@ -3298,11 +3317,11 @@ void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) foldinfo_T info = fold_info(curwin, lnum); if (info.fi_lines > 0) { - char_u *text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf); + char *text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf); if (text == buf) { - text = vim_strsave(text); + text = xstrdup(text); } - rettv->vval.v_string = (char *)text; + rettv->vval.v_string = text; } entered = false; diff --git a/src/nvim/fold.h b/src/nvim/fold.h index f34e6d43c3..ac1e8c9419 100644 --- a/src/nvim/fold.h +++ b/src/nvim/fold.h @@ -5,13 +5,12 @@ #include "nvim/buffer_defs.h" #include "nvim/garray.h" +#include "nvim/macros.h" #include "nvim/pos.h" #include "nvim/types.h" -/* - * Info used to pass info about a fold from the fold-detection code to the - * code that displays the foldcolumn. - */ +// Info used to pass info about a fold from the fold-detection code to the +// code that displays the foldcolumn. typedef struct foldinfo { linenr_T fi_lnum; // line number where fold starts int fi_level; // level of the fold; when this is zero the @@ -21,8 +20,6 @@ typedef struct foldinfo { linenr_T fi_lines; } foldinfo_T; -#define FOLDINFO_INIT { 0, 0, 0, 0 } - EXTERN int disable_fold_update INIT(= 0); #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/garray.c b/src/nvim/garray.c index 7a3c14b1bb..aa9a44d410 100644 --- a/src/nvim/garray.c +++ b/src/nvim/garray.c @@ -5,22 +5,17 @@ /// /// Functions for handling growing arrays. -#include <inttypes.h> #include <string.h> -#include "nvim/ascii.h" #include "nvim/garray.h" #include "nvim/log.h" #include "nvim/memory.h" #include "nvim/path.h" #include "nvim/strings.h" -#include "nvim/vim.h" - -// #include "nvim/globals.h" -#include "nvim/memline.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "garray.c.generated.h" +# include "garray.c.generated.h" // IWYU pragma: export #endif /// Clear an allocated growing array. @@ -123,7 +118,7 @@ void ga_remove_duplicate_strings(garray_T *gap) // loop over the growing array in reverse for (int i = gap->ga_len - 1; i > 0; i--) { - if (FNAMECMP(fnames[i - 1], fnames[i]) == 0) { + if (path_fnamecmp(fnames[i - 1], fnames[i]) == 0) { xfree(fnames[i]); // close the gap (move all strings one slot lower) @@ -131,7 +126,7 @@ void ga_remove_duplicate_strings(garray_T *gap) fnames[j - 1] = fnames[j]; } - --gap->ga_len; + gap->ga_len--; } } } @@ -167,7 +162,7 @@ char *ga_concat_strings_sep(const garray_T *gap, const char *sep) s = xstpcpy(s, strings[i]); s = xstpcpy(s, sep); } - strcpy(s, strings[nelem - 1]); + strcpy(s, strings[nelem - 1]); // NOLINT(runtime/printf) return ret; } @@ -178,9 +173,9 @@ char *ga_concat_strings_sep(const garray_T *gap, const char *sep) /// @param gap /// /// @returns the concatenated strings -char_u *ga_concat_strings(const garray_T *gap) FUNC_ATTR_NONNULL_RET +char *ga_concat_strings(const garray_T *gap) FUNC_ATTR_NONNULL_RET { - return (char_u *)ga_concat_strings_sep(gap, ","); + return ga_concat_strings_sep(gap, ","); } /// Concatenate a string to a growarray which contains characters. @@ -198,7 +193,7 @@ void ga_concat(garray_T *gap, const char *restrict s) return; } - ga_concat_len(gap, s, STRLEN(s)); + ga_concat_len(gap, s, strlen(s)); } /// Concatenate a string to a growarray which contains characters @@ -221,7 +216,7 @@ void ga_concat_len(garray_T *const gap, const char *restrict s, const size_t len /// /// @param gap /// @param c -void ga_append(garray_T *gap, char c) +void ga_append(garray_T *gap, uint8_t c) { - GA_APPEND(char, gap, c); + GA_APPEND(uint8_t, gap, c); } diff --git a/src/nvim/garray.h b/src/nvim/garray.h index 56bd5c9130..1623c4db7b 100644 --- a/src/nvim/garray.h +++ b/src/nvim/garray.h @@ -1,10 +1,12 @@ #ifndef NVIM_GARRAY_H #define NVIM_GARRAY_H -#include <stddef.h> // for size_t +#include <stdbool.h> +#include <stddef.h> #include "nvim/log.h" -#include "nvim/types.h" // for char_u +#include "nvim/memory.h" +#include "nvim/types.h" /// Structure used for growing arrays. /// This is used to store information that only grows, is deleted all at @@ -25,7 +27,8 @@ typedef struct growarray { #define GA_APPEND(item_type, gap, item) \ do { \ ga_grow(gap, 1); \ - ((item_type *)(gap)->ga_data)[(gap)->ga_len++] = (item); \ + ((item_type *)(gap)->ga_data)[(gap)->ga_len] = (item); \ + (gap)->ga_len++; \ } while (0) #define GA_APPEND_VIA_PTR(item_type, gap) \ diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index 70a7be86b5..3e89b60b4a 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -26,6 +26,8 @@ local c_id = ( local c_void = P('void') local c_param_type = ( ((P('Error') * fill * P('*') * fill) * Cc('error')) + + ((P('Arena') * fill * P('*') * fill) * Cc('arena')) + + ((P('lua_State') * fill * P('*') * fill) * Cc('lstate')) + C((P('const ') ^ -1) * (c_id) * (ws ^ 1) * P('*')) + (C(c_id) * (ws ^ 1)) ) @@ -47,9 +49,9 @@ local c_proto = Ct( (fill * Cg((P('FUNC_API_LUA_ONLY') * Cc(true)), 'lua_only') ^ -1) * (fill * Cg((P('FUNC_API_CHECK_TEXTLOCK') * Cc(true)), 'check_textlock') ^ -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 * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1) * (fill * Cg((P('FUNC_API_CLIENT_IMPL') * Cc(true)), 'client_impl') ^ -1) * + (fill * Cg((P('FUNC_API_CLIENT_IGNORE') * Cc(true)), 'client_ignore') ^ -1) * fill * P(';') ) diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index b167767f7a..35f6bf8455 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -17,6 +17,7 @@ local nvimdir = arg[1] package.path = nvimdir .. '/?.lua;' .. package.path _G.vim = loadfile(nvimdir..'/../../runtime/lua/vim/shared.lua')() +_G.vim.inspect = loadfile(nvimdir..'/../../runtime/lua/vim/inspect.lua')() local hashy = require'generators.hashy' @@ -59,6 +60,12 @@ for i = 6, #arg do if public and not fn.noexport then functions[#functions + 1] = tmp[j] function_names[fn.name] = true + if #fn.parameters >= 2 and fn.parameters[2][1] == 'Array' and fn.parameters[2][2] == 'uidata' then + -- function receives the "args" as a parameter + fn.receives_array_args = true + -- remove the args parameter + table.remove(fn.parameters, 2) + end if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then -- this function should receive the channel id fn.receives_channel_id = true @@ -72,6 +79,15 @@ for i = 6, #arg do -- for specifying errors fn.parameters[#fn.parameters] = nil end + if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then + -- return value is allocated in an arena + fn.arena_return = true + fn.parameters[#fn.parameters] = nil + end + if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then + fn.has_lua_imp = true + fn.parameters[#fn.parameters] = nil + end end end input:close() @@ -149,7 +165,7 @@ local exported_attributes = {'name', 'return_type', 'method', 'since', 'deprecated_since'} local exported_functions = {} for _,f in ipairs(functions) do - if not (startswith(f.name, "nvim__") or f.name == "nvim_error_event") then + if not (startswith(f.name, "nvim__") or f.name == "nvim_error_event" or f.name == "redraw") then local f_exported = {} for _,attr in ipairs(exported_attributes) do f_exported[attr] = f[attr] @@ -179,6 +195,35 @@ funcs_metadata_output:close() -- start building the dispatch wrapper output local output = io.open(dispatch_outputf, 'wb') + +-- =========================================================================== +-- NEW API FILES MUST GO HERE. +-- +-- When creating a new API file, you must include it here, +-- so that the dispatcher can find the C functions that you are creating! +-- =========================================================================== +output:write([[ +#include "nvim/log.h" +#include "nvim/map.h" +#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/vim.h" + +#include "nvim/api/autocmd.h" +#include "nvim/api/buffer.h" +#include "nvim/api/command.h" +#include "nvim/api/deprecated.h" +#include "nvim/api/extmark.h" +#include "nvim/api/options.h" +#include "nvim/api/tabpage.h" +#include "nvim/api/ui.h" +#include "nvim/api/vim.h" +#include "nvim/api/vimscript.h" +#include "nvim/api/win_config.h" +#include "nvim/api/window.h" +#include "nvim/ui_client.h" + +]]) + local function real_type(type) local rv = type local rmatch = string.match(type, "Dict%(([_%w]+)%)") @@ -210,7 +255,7 @@ for i = 1, #functions do if fn.impl_name == nil and fn.remote then local args = {} - output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)') + output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Arena* arena, Error *error)') output:write('\n{') output:write('\n#if MIN_LOG_LEVEL <= LOGLVL_DBG') output:write('\n logmsg(LOGLVL_DBG, "RPC: ", NULL, -1, true, "ch %" PRIu64 ": invoke ' @@ -225,11 +270,13 @@ for i = 1, #functions do output:write('\n '..rt..' '..converted..';') end output:write('\n') - output:write('\n if (args.size != '..#fn.parameters..') {') - output:write('\n api_set_error(error, kErrorTypeException, \ - "Wrong number of arguments: expecting '..#fn.parameters..' but got %zu", args.size);') - output:write('\n goto cleanup;') - output:write('\n }\n') + if not fn.receives_array_args then + output:write('\n if (args.size != '..#fn.parameters..') {') + output:write('\n api_set_error(error, kErrorTypeException, \ + "Wrong number of arguments: expecting '..#fn.parameters..' but got %zu", args.size);') + output:write('\n goto cleanup;') + output:write('\n }\n') + end -- Validation/conversion for each argument for j = 1, #fn.parameters do @@ -311,12 +358,40 @@ for i = 1, #functions do if fn.receives_channel_id then -- if the function receives the channel id, pass it as first argument if #args > 0 or fn.can_fail then - output:write('channel_id, '..call_args) + output:write('channel_id, ') + if fn.receives_array_args then + -- if the function receives the array args, pass it the second argument + output:write('args, ') + end + output:write(call_args) else output:write('channel_id') + if fn.receives_array_args then + output:write(', args') + end end else - output:write(call_args) + if fn.receives_array_args then + if #args > 0 or fn.call_fail then + output:write('args, '..call_args) + else + output:write('args') + end + else + output:write(call_args) + end + end + + if fn.arena_return then + output:write(', arena') + end + + if fn.has_lua_imp then + if #args > 0 then + output:write(', NULL') + else + output:write('NULL') + end end if fn.can_fail then @@ -351,15 +426,18 @@ for _,fn in ipairs(functions) do end remote_fns.redraw = {impl_name="ui_client_redraw", fast=true} -local hashorder, hashfun = hashy.hashy_hash("msgpack_rpc_get_handler_for", vim.tbl_keys(remote_fns), function (idx) +local names = vim.tbl_keys(remote_fns) +table.sort(names) +local hashorder, hashfun = hashy.hashy_hash("msgpack_rpc_get_handler_for", names, function (idx) return "method_handlers["..idx.."].name" end) -output:write("static const MsgpackRpcRequestHandler method_handlers[] = {\n") -for _, name in ipairs(hashorder) do +output:write("const MsgpackRpcRequestHandler method_handlers[] = {\n") +for n, name in ipairs(hashorder) do local fn = remote_fns[name] + fn.handler_id = n-1 output:write(' { .name = "'..name..'", .fn = handle_'.. (fn.impl_name or fn.name).. - ', .fast = '..tostring(fn.fast)..'},\n') + ', .fast = '..tostring(fn.fast)..', .arena_return = '..tostring(not not fn.arena_return)..'},\n') end output:write("};\n\n") output:write(hashfun) @@ -400,6 +478,8 @@ output:write([[ #include "nvim/api/private/helpers.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" +#include "nvim/memory.h" + ]]) include_headers(output, headers) output:write('\n') @@ -477,6 +557,17 @@ local function process_function(fn) if fn.receives_channel_id then cparams = 'LUA_INTERNAL_CALL, ' .. cparams end + if fn.arena_return then + cparams = cparams .. '&arena, ' + write_shifted_output(output, [[ + Arena arena = ARENA_EMPTY; + ]]) + end + + if fn.has_lua_imp then + cparams = cparams .. 'lstate, ' + end + if fn.can_fail then cparams = cparams .. '&err' else @@ -511,15 +602,35 @@ local function process_function(fn) else return_type = fn.return_type end + local free_retval + if fn.arena_return then + free_retval = "arena_mem_free(arena_finish(&arena));" + else + free_retval = "api_free_"..return_type:lower().."(ret);" + end write_shifted_output(output, string.format([[ const %s ret = %s(%s); + ]], fn.return_type, fn.name, cparams)) + + if fn.has_lua_imp then + -- only push onto the Lua stack if we haven't already + write_shifted_output(output, string.format([[ + if (lua_gettop(lstate) == 0) { + nlua_push_%s(lstate, ret, true); + } + ]], return_type)) + else + write_shifted_output(output, string.format([[ nlua_push_%s(lstate, ret, true); - api_free_%s(ret); + ]], return_type)) + end + + write_shifted_output(output, string.format([[ + %s %s %s return 1; - ]], fn.return_type, fn.name, cparams, return_type, return_type:lower(), - free_at_exit_code, err_throw_code)) + ]], free_retval, free_at_exit_code, err_throw_code)) else write_shifted_output(output, string.format([[ %s(%s); diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index 93bbaab74c..827097f69d 100755 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -3,14 +3,12 @@ local mpack = require('mpack') local nvimdir = arg[1] package.path = nvimdir .. '/?.lua;' .. package.path -assert(#arg == 8) +assert(#arg == 6) local input = io.open(arg[2], 'rb') -local proto_output = io.open(arg[3], 'wb') -local call_output = io.open(arg[4], 'wb') -local remote_output = io.open(arg[5], 'wb') -local bridge_output = io.open(arg[6], 'wb') -local metadata_output = io.open(arg[7], 'wb') -local client_output = io.open(arg[8], 'wb') +local call_output = io.open(arg[3], 'wb') +local remote_output = io.open(arg[4], 'wb') +local metadata_output = io.open(arg[5], 'wb') +local client_output = io.open(arg[6], 'wb') local c_grammar = require('generators.c_grammar') local events = c_grammar.grammar:match(input:read('*all')) @@ -75,17 +73,16 @@ local function call_ui_event_method(output, ev) hlattrs_args_count = hlattrs_args_count + 1 elseif kind == 'Object' then output:write('args.items['..(j-1)..'];\n') + elseif kind == 'Window' then + output:write('(Window)args.items['..(j-1)..'].data.integer;\n') else output:write('args.items['..(j-1)..'].data.'..string.lower(kind)..';\n') end end - output:write(' ui_call_'..ev.name..'(') + output:write(' tui_'..ev.name..'(tui') for j = 1, #ev.parameters do - output:write('arg_'..j) - if j ~= #ev.parameters then - output:write(', ') - end + output:write(', arg_'..j) end output:write(');\n') @@ -103,12 +100,9 @@ for i = 1, #events do 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 and not ev.noexport then - remote_output:write('static void remote_ui_'..ev.name) + remote_output:write('void remote_ui_'..ev.name) write_signature(remote_output, ev, 'UI *ui') remote_output:write('\n{\n') remote_output:write(' UIData *data = ui->data;\n') @@ -117,62 +111,6 @@ for i = 1, #events do remote_output:write(' push_call(ui, "'..ev.name..'", args);\n') remote_output:write('}\n\n') end - - if not ev.bridge_impl and not ev.noexport then - local send, argv, recv, recv_argv, recv_cleanup = '', '', '', '', '' - local argc = 1 - for j = 1, #ev.parameters do - local param = ev.parameters[j] - local 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..' = 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] == 'Object' then - send = send..' Object *'..copy..' = xmalloc(sizeof(Object));\n' - send = send..' *'..copy..' = copy_object('..param[2]..');\n' - argv = argv..', '..copy - recv = recv..' Object '..param[2]..' = *(Object *)argv['..argc..'];\n' - recv_argv = recv_argv..', '..param[2] - recv_cleanup = (recv_cleanup..' api_free_object('..param[2]..');\n'.. - ' xfree(argv['..argc..']);\n') - argc = argc+1 - 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_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n\n') - end end if not (ev.remote_only and ev.remote_impl) then @@ -185,6 +123,9 @@ for i = 1, #events do call_output:write(' UI_LOG('..ev.name..');\n') call_output:write(' ui_call_event("'..ev.name..'", args);\n') elseif ev.compositor_impl then + call_output:write(' ui_comp_'..ev.name) + write_signature(call_output, ev, '', true) + call_output:write(";\n") call_output:write(' UI_CALL') write_signature(call_output, ev, '!ui->composed, '..ev.name..', ui', true) call_output:write(";\n") @@ -206,14 +147,14 @@ for i = 1, #events do call_output:write("}\n\n") end - if (not ev.remote_only) and (not ev.noexport) and (not ev.client_impl) then + if (not ev.remote_only) and (not ev.noexport) and (not ev.client_impl) and (not ev.client_ignore) then call_ui_event_method(client_output, ev) end end local client_events = {} for _,ev in ipairs(events) do - if (not ev.noexport) and ((not ev.remote_only) or ev.client_impl) then + if (not ev.noexport) and ((not ev.remote_only) or ev.client_impl) and (not ev.client_ignore) then client_events[ev.name] = ev end end @@ -231,7 +172,6 @@ end client_output:write('\n};\n\n') client_output:write(hashfun) -proto_output:close() call_output:close() remote_output:close() client_output:close() diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua index c7d5a1a191..4097ff7dc5 100755 --- a/src/nvim/generators/gen_declarations.lua +++ b/src/nvim/generators/gen_declarations.lua @@ -182,8 +182,7 @@ Additionally uses the following environment variables: If set to 1 then all generated declarations receive a comment with file name and line number after the declaration. This may be useful for debugging gen_declarations script, but not much beyond that with - configured development environment (i.e. with ctags/cscope/finding - definitions with clang/etc). + configured development environment (i.e. with with clang/etc). WARNING: setting this to 1 will cause extensive rebuilds: declarations generator script will not regenerate non-static.h file if its @@ -218,7 +217,7 @@ local footer = [[ local non_static = header .. [[ #ifndef DLLEXPORT -# ifdef WIN32 +# ifdef MSWIN # define DLLEXPORT __declspec(dllexport) # else # define DLLEXPORT diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index c72249161b..baed6a74c2 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -27,26 +27,59 @@ local hashy = require'generators.hashy' local hashpipe = io.open(funcsfname, 'wb') +hashpipe:write([[ +#include "nvim/arglist.h" +#include "nvim/cmdexpand.h" +#include "nvim/cmdhist.h" +#include "nvim/digraph.h" +#include "nvim/eval/buffer.h" +#include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/vars.h" +#include "nvim/eval/window.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" +#include "nvim/fold.h" +#include "nvim/getchar.h" +#include "nvim/insexpand.h" +#include "nvim/mapping.h" +#include "nvim/match.h" +#include "nvim/mbyte.h" +#include "nvim/menu.h" +#include "nvim/move.h" +#include "nvim/quickfix.h" +#include "nvim/search.h" +#include "nvim/sign.h" +#include "nvim/testing.h" + +]]) + local funcs = require('eval').funcs +for _, func in pairs(funcs) do + if func.float_func then + func.func = "float_op_wrapper" + func.data = "{ .float_func = &"..func.float_func.." }" + end +end + local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all")) for _,fun in ipairs(metadata) do if fun.eval then funcs[fun.name] = { args=#fun.parameters, func='api_wrapper', - data='&handle_'..fun.name, + data='{ .api_handler = &method_handlers['..fun.handler_id..'] }' } end end +local func_names = vim.tbl_keys(funcs) +table.sort(func_names) local funcsdata = io.open(funcs_file, 'w') -funcsdata:write(mpack.pack(funcs)) +funcsdata:write(mpack.pack(func_names)) funcsdata:close() - -local names = vim.tbl_keys(funcs) - -local neworder, hashfun = hashy.hashy_hash("find_internal_func", names, function (idx) +local neworder, hashfun = hashy.hashy_hash("find_internal_func", func_names, function (idx) return "functions["..idx.."].name" end) hashpipe:write("static const EvalFuncDef functions[] = {\n") @@ -60,12 +93,12 @@ for _, name in ipairs(neworder) do end local base = def.base or "BASE_NONE" local func = def.func or ('f_' .. name) - local data = def.data or "NULL" + local data = def.data or "{ .nullptr = NULL }" local fast = def.fast and 'true' or 'false' - hashpipe:write((' { "%s", %s, %s, %s, %s, &%s, (FunPtr)%s },\n') + hashpipe:write((' { "%s", %s, %s, %s, %s, &%s, %s },\n') :format(name, args[1], args[2], base, fast, func, data)) end -hashpipe:write(' { NULL, 0, 0, BASE_NONE, false, NULL, NULL },\n') +hashpipe:write(' { NULL, 0, 0, BASE_NONE, false, NULL, { .nullptr = NULL } },\n') hashpipe:write("};\n\n") hashpipe:write(hashfun) hashpipe:close() diff --git a/src/nvim/generators/gen_events.lua b/src/nvim/generators/gen_events.lua index 6ee45a14af..8db7f22452 100644 --- a/src/nvim/generators/gen_events.lua +++ b/src/nvim/generators/gen_events.lua @@ -32,7 +32,9 @@ for i, event in ipairs(events) do end end -for alias, event in pairs(aliases) do +for _, v in ipairs(aliases) do + local alias = v[1] + local event = v[2] names_tgt:write(('\n {%u, "%s", EVENT_%s},'):format(#alias, alias, event:upper())) end diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua index 255c415a4d..0c1051b04e 100644 --- a/src/nvim/generators/gen_ex_cmds.lua +++ b/src/nvim/generators/gen_ex_cmds.lua @@ -49,6 +49,43 @@ enumfile:write([[ typedef enum CMD_index { ]]) defsfile:write(string.format([[ +#include "nvim/arglist.h" +#include "nvim/autocmd.h" +#include "nvim/buffer.h" +#include "nvim/cmdhist.h" +#include "nvim/debugger.h" +#include "nvim/diff.h" +#include "nvim/digraph.h" +#include "nvim/eval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/eval/vars.h" +#include "nvim/ex_cmds.h" +#include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" +#include "nvim/ex_eval.h" +#include "nvim/ex_session.h" +#include "nvim/help.h" +#include "nvim/indent.h" +#include "nvim/locale.h" +#include "nvim/lua/executor.h" +#include "nvim/mapping.h" +#include "nvim/mark.h" +#include "nvim/match.h" +#include "nvim/menu.h" +#include "nvim/message.h" +#include "nvim/ops.h" +#include "nvim/option.h" +#include "nvim/profile.h" +#include "nvim/quickfix.h" +#include "nvim/runtime.h" +#include "nvim/sign.h" +#include "nvim/spell.h" +#include "nvim/spellfile.h" +#include "nvim/syntax.h" +#include "nvim/undo.h" +#include "nvim/usercmd.h" +#include "nvim/version.h" + static const int command_count = %u; static CommandDefinition cmdnames[%u] = { ]], #defs, #defs)) diff --git a/src/nvim/generators/gen_keysets.lua b/src/nvim/generators/gen_keysets.lua index 633c5da184..b1c1f3e2d8 100644 --- a/src/nvim/generators/gen_keysets.lua +++ b/src/nvim/generators/gen_keysets.lua @@ -1,4 +1,3 @@ - local nvimsrcdir = arg[1] local shared_file = arg[2] local funcs_file = arg[3] @@ -38,7 +37,9 @@ local function sanitize(key) return key end -for name, keys in pairs(keysets) do +for _, v in ipairs(keysets) do + local name = v[1] + local keys = v[2] local neworder, hashfun = hashy.hashy_hash(name, keys, function (idx) return name.."_table["..idx.."].str" end) diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 0454c54faf..edb7dae159 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -29,14 +29,14 @@ local type_flags={ } local redraw_flags={ + ui_option='P_UI_OPTION', + tabline='P_RTABL', statuslines='P_RSTAT', current_window='P_RWIN', current_window_only='P_RWINONLY', current_buffer='P_RBUF', all_windows='P_RALL', - everything='P_RCLR', curswant='P_CURSWANT', - ui_option='P_UI_OPTION', } local list_flags={ @@ -78,6 +78,7 @@ local get_flags = function(o) {'deny_in_modelines', 'P_NO_ML'}, {'deny_duplicates', 'P_NODUP'}, {'modelineexpr', 'P_MLE'}, + {'func'} }) do local key_name = flag_desc[1] local def_name = flag_desc[2] or ('P_' .. key_name:upper()) @@ -115,7 +116,7 @@ local value_dumpers = { } local get_value = function(v) - return '(char_u *) ' .. value_dumpers[type(v)](v) + return '(char *) ' .. value_dumpers[type(v)](v) end local get_defaults = function(d,n) @@ -158,7 +159,7 @@ local dump_option = function(i, o) if #o.scope == 2 then pv_name = 'OPT_BOTH(' .. pv_name .. ')' end - defines['PV_' .. varname:sub(3):upper()] = pv_name + table.insert(defines, { 'PV_' .. varname:sub(3):upper() , pv_name}) w(' .indir=' .. pv_name) end if o.enable_if then @@ -191,7 +192,7 @@ w(' [' .. ('%u'):format(#options.options) .. ']={.fullname=NULL}') w('};') w('') -for k, v in pairs(defines) do - w('#define ' .. k .. ' ' .. v) +for _, v in ipairs(defines) do + w('#define ' .. v[1] .. ' ' .. v[2]) end opt_fd:close() diff --git a/src/nvim/generators/gen_unicode_tables.lua b/src/nvim/generators/gen_unicode_tables.lua index 36553f4649..9ad99c8029 100644 --- a/src/nvim/generators/gen_unicode_tables.lua +++ b/src/nvim/generators/gen_unicode_tables.lua @@ -153,7 +153,8 @@ local build_combining_table = function(ut_fp, dataprops) local start = -1 local end_ = -1 for _, p in ipairs(dataprops) do - if (({Mn=true, Mc=true, Me=true})[p[3]]) then + -- The 'Mc' property was removed, it does take up space. + if (({Mn=true, Me=true})[p[3]]) then local n = tonumber(p[1], 16) if start >= 0 and end_ + 1 == n then -- Continue with the same range. diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 0f55158733..9c5364e1b1 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -7,11 +7,15 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include "lauxlib.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" -#include "nvim/assert.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" @@ -19,16 +23,21 @@ #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/input.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/main.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" @@ -44,8 +53,11 @@ #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/plines.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -81,33 +93,29 @@ static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 }; static int typeahead_char = 0; // typeahead char that's not flushed -/* - * when block_redo is TRUE redo buffer will not be changed - * used by edit() to repeat insertions and 'V' command for redoing - */ -static int block_redo = FALSE; +// when block_redo is true redo buffer will not be changed +// used by edit() to repeat insertions and 'V' command for redoing +static int block_redo = false; static int KeyNoremap = 0; // remapping flags -/* - * Variables used by vgetorpeek() and flush_buffers() - * - * typebuf.tb_buf[] contains all characters that are not consumed yet. - * typebuf.tb_buf[typebuf.tb_off] is the first valid character. - * typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1] is the last valid char. - * typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] must be NUL. - * The head of the buffer may contain the result of mappings, abbreviations - * and @a commands. The length of this part is typebuf.tb_maplen. - * typebuf.tb_silent is the part where <silent> applies. - * After the head are characters that come from the terminal. - * typebuf.tb_no_abbr_cnt is the number of characters in typebuf.tb_buf that - * should not be considered for abbreviations. - * Some parts of typebuf.tb_buf may not be mapped. These parts are remembered - * in typebuf.tb_noremap[], which is the same length as typebuf.tb_buf and - * contains RM_NONE for the characters that are not to be remapped. - * typebuf.tb_noremap[typebuf.tb_off] is the first valid flag. - * (typebuf has been put in globals.h, because check_termcode() needs it). - */ +// Variables used by vgetorpeek() and flush_buffers() +// +// typebuf.tb_buf[] contains all characters that are not consumed yet. +// typebuf.tb_buf[typebuf.tb_off] is the first valid character. +// typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1] is the last valid char. +// typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] must be NUL. +// The head of the buffer may contain the result of mappings, abbreviations +// and @a commands. The length of this part is typebuf.tb_maplen. +// typebuf.tb_silent is the part where <silent> applies. +// After the head are characters that come from the terminal. +// typebuf.tb_no_abbr_cnt is the number of characters in typebuf.tb_buf that +// should not be considered for abbreviations. +// Some parts of typebuf.tb_buf may not be mapped. These parts are remembered +// in typebuf.tb_noremap[], which is the same length as typebuf.tb_buf and +// contains RM_NONE for the characters that are not to be remapped. +// typebuf.tb_noremap[typebuf.tb_off] is the first valid flag. +// (typebuf has been put in globals.h, because check_termcode() needs it). #define RM_YES 0 // tb_noremap: remap #define RM_NONE 1 // tb_noremap: don't remap #define RM_SCRIPT 2 // tb_noremap: remap local script mappings @@ -126,9 +134,7 @@ static size_t last_recorded_len = 0; // number of last recorded chars # include "getchar.c.generated.h" #endif -/* - * Free and clear a buffer. - */ +// Free and clear a buffer. void free_buff(buffheader_T *buf) { buffblock_T *p, *np; @@ -145,16 +151,16 @@ void free_buff(buffheader_T *buf) /// K_SPECIAL in the returned string is escaped. /// /// @param dozero count == zero is not an error -static char_u *get_buffcont(buffheader_T *buffer, int dozero) +static char *get_buffcont(buffheader_T *buffer, int dozero) { size_t count = 0; - char_u *p = NULL; - char_u *p2; + char *p = NULL; + char *p2; // compute the total length of the string for (const buffblock_T *bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) { - count += STRLEN(bp->b_str); + count += strlen(bp->b_str); } if (count || dozero) { @@ -162,7 +168,7 @@ static char_u *get_buffcont(buffheader_T *buffer, int dozero) p2 = p; for (const buffblock_T *bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) { - for (const char_u *str = bp->b_str; *str;) { + for (const char *str = bp->b_str; *str;) { *p2++ = *str++; } } @@ -176,38 +182,34 @@ static char_u *get_buffcont(buffheader_T *buffer, int dozero) /// K_SPECIAL in the returned string is escaped. char_u *get_recorded(void) { - char_u *p; + char *p; size_t len; - p = get_buffcont(&recordbuff, TRUE); + p = get_buffcont(&recordbuff, true); free_buff(&recordbuff); - /* - * Remove the characters that were added the last time, these must be the - * (possibly mapped) characters that stopped the recording. - */ - len = STRLEN(p); + // Remove the characters that were added the last time, these must be the + // (possibly mapped) characters that stopped the recording. + len = strlen(p); if (len >= last_recorded_len) { len -= last_recorded_len; p[len] = NUL; } - /* - * When stopping recording from Insert mode with CTRL-O q, also remove the - * CTRL-O. - */ + // When stopping recording from Insert mode with CTRL-O q, also remove the + // CTRL-O. if (len > 0 && restart_edit != 0 && p[len - 1] == Ctrl_O) { p[len - 1] = NUL; } - return p; + return (char_u *)p; } /// Return the contents of the redo buffer as a single string. /// K_SPECIAL in the returned string is escaped. -char_u *get_inserted(void) +char *get_inserted(void) { - return get_buffcont(&redobuff, FALSE); + return get_buffcont(&redobuff, false); } /// Add string after the current block of the given buffer @@ -235,14 +237,14 @@ static void add_buff(buffheader_T *const buf, const char *const s, ptrdiff_t sle } else if (buf->bh_index != 0) { memmove(buf->bh_first.b_next->b_str, buf->bh_first.b_next->b_str + buf->bh_index, - STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1); + strlen(buf->bh_first.b_next->b_str + buf->bh_index) + 1); } buf->bh_index = 0; size_t len; if (buf->bh_space >= (size_t)slen) { - len = STRLEN(buf->bh_curr->b_str); - STRLCPY(buf->bh_curr->b_str + len, s, slen + 1); + len = strlen(buf->bh_curr->b_str); + xstrlcpy(buf->bh_curr->b_str + len, s, (size_t)slen + 1); buf->bh_space -= (size_t)slen; } else { if (slen < MINIMAL_SIZE) { @@ -252,7 +254,7 @@ static void add_buff(buffheader_T *const buf, const char *const s, ptrdiff_t sle } buffblock_T *p = xmalloc(sizeof(buffblock_T) + len); buf->bh_space = len - (size_t)slen; - STRLCPY(p->b_str, s, slen + 1); + xstrlcpy(p->b_str, s, (size_t)slen + 1); p->b_next = buf->bh_curr->b_next; buf->bh_curr->b_next = p; @@ -269,11 +271,13 @@ static void delete_buff_tail(buffheader_T *buf, int slen) if (buf->bh_curr == NULL) { return; // nothing to delete } - len = (int)STRLEN(buf->bh_curr->b_str); - if (len >= slen) { - buf->bh_curr->b_str[len - slen] = NUL; - buf->bh_space += (size_t)slen; + len = (int)strlen(buf->bh_curr->b_str); + if (len < slen) { + return; } + + buf->bh_curr->b_str[len - slen] = NUL; + buf->bh_space += (size_t)slen; } /// Add number "n" to buffer "buf". @@ -319,7 +323,7 @@ static void add_char_buff(buffheader_T *buf, int c) /// Get one byte from the read buffers. Use readbuf1 one first, use readbuf2 /// if that one is empty. -/// If advance == TRUE go to the next char. +/// If advance == true go to the next char. /// No translation is done K_SPECIAL is escaped. static int read_readbuffers(int advance) { @@ -341,7 +345,7 @@ static int read_readbuf(buffheader_T *buf, int advance) } buffblock_T *const curr = buf->bh_first.b_next; - c = curr->b_str[buf->bh_index]; + c = (char_u)curr->b_str[buf->bh_index]; if (advance) { if (curr->b_str[++buf->bh_index] == NUL) { @@ -353,9 +357,7 @@ static int read_readbuf(buffheader_T *buf, int advance) return c; } -/* - * Prepare the read buffers for reading (if they contain something). - */ +// Prepare the read buffers for reading (if they contain something). static void start_stuff(void) { if (readbuf1.bh_first.b_next != NULL) { @@ -368,38 +370,30 @@ static void start_stuff(void) } } -/* - * Return TRUE if the stuff buffer is empty. - */ +/// Return true if the stuff buffer is empty. int stuff_empty(void) FUNC_ATTR_PURE { return (readbuf1.bh_first.b_next == NULL && readbuf2.bh_first.b_next == NULL); } -/* - * Return TRUE if readbuf1 is empty. There may still be redo characters in - * redbuf2. - */ +/// Return true if readbuf1 is empty. There may still be redo characters in +/// redbuf2. int readbuf1_empty(void) FUNC_ATTR_PURE { return (readbuf1.bh_first.b_next == NULL); } -/* - * Set a typeahead character that won't be flushed. - */ +// Set a typeahead character that won't be flushed. void typeahead_noflush(int c) { typeahead_char = c; } -/* - * Remove the contents of the stuff buffer and the mapped characters in the - * typeahead buffer (used in case of an error). If "flush_typeahead" is true, - * flush all typeahead characters (used when interrupted by a CTRL-C). - */ +// Remove the contents of the stuff buffer and the mapped characters in the +// typeahead buffer (used in case of an error). If "flush_typeahead" is true, +// flush all typeahead characters (used when interrupted by a CTRL-C). void flush_buffers(flush_buffers_T flush_typeahead) { init_typebuf(); @@ -443,32 +437,32 @@ void beep_flush(void) } } -/* - * The previous contents of the redo buffer is kept in old_redobuffer. - * This is used for the CTRL-O <.> command in insert mode. - */ +// The previous contents of the redo buffer is kept in old_redobuffer. +// This is used for the CTRL-O <.> command in insert mode. void ResetRedobuff(void) { - if (!block_redo) { - free_buff(&old_redobuff); - old_redobuff = redobuff; - redobuff.bh_first.b_next = NULL; + if (block_redo) { + return; } + + free_buff(&old_redobuff); + old_redobuff = redobuff; + redobuff.bh_first.b_next = NULL; } -/* - * Discard the contents of the redo buffer and restore the previous redo - * buffer. - */ +// Discard the contents of the redo buffer and restore the previous redo +// buffer. void CancelRedo(void) { - if (!block_redo) { - free_buff(&redobuff); - redobuff = old_redobuff; - old_redobuff.bh_first.b_next = NULL; - start_stuff(); - while (read_readbuffers(true) != NUL) {} + if (block_redo) { + return; } + + free_buff(&redobuff); + redobuff = old_redobuff; + old_redobuff.bh_first.b_next = NULL; + start_stuff(); + while (read_readbuffers(true) != NUL) {} } /// Save redobuff and old_redobuff to save_redobuff and save_old_redobuff. @@ -481,11 +475,13 @@ void saveRedobuff(save_redo_T *save_redo) old_redobuff.bh_first.b_next = NULL; // Make a copy, so that ":normal ." in a function works. - char *const s = (char *)get_buffcont(&save_redo->sr_redobuff, false); - if (s != NULL) { - add_buff(&redobuff, s, -1L); - xfree(s); + char *const s = get_buffcont(&save_redo->sr_redobuff, false); + if (s == NULL) { + return; } + + add_buff(&redobuff, s, -1L); + xfree(s); } /// Restore redobuff and old_redobuff from save_redobuff and save_old_redobuff. @@ -533,7 +529,7 @@ void AppendToRedobuffLit(const char *str, int len) s--; } if (s > start) { - add_buff(&redobuff, start, (long)(s - start)); + add_buff(&redobuff, start, s - start); } if (*s == NUL || (len >= 0 && s - str >= len)) { @@ -542,7 +538,7 @@ void AppendToRedobuffLit(const char *str, int len) // Handle a special or multibyte character. // Composing chars separately are handled separately. - const int c = mb_cptr2char_adv((const char_u **)&s); + const int c = mb_cptr2char_adv(&s); if (c < ' ' || c == DEL || (*s == NUL && (c == '0' || c == '^'))) { add_char_buff(&redobuff, Ctrl_V); } @@ -565,9 +561,7 @@ void AppendCharToRedobuff(int c) } } -/* - * Append a number to the redo buffer. - */ +// Append a number to the redo buffer. void AppendNumberToRedobuff(long n) { if (!block_redo) { @@ -605,7 +599,7 @@ void stuffReadbuffSpec(const char *s) stuffReadbuffLen(s, 3); s += 3; } else { - int c = mb_cptr2char_adv((const char_u **)&s); + int c = mb_cptr2char_adv(&s); if (c == CAR || c == NL || c == ESC) { c = ' '; } @@ -621,14 +615,40 @@ void stuffcharReadbuff(int c) add_char_buff(&readbuf1, c); } -/* - * Append a number to the stuff buffer. - */ +// Append a number to the stuff buffer. void stuffnumReadbuff(long n) { add_num_buff(&readbuf1, n); } +/// Stuff a string into the typeahead buffer, such that edit() will insert it +/// literally ("literally" true) or interpret is as typed characters. +void stuffescaped(const char *arg, bool literally) +{ + while (*arg != NUL) { + // Stuff a sequence of normal ASCII characters, that's fast. Also + // stuff K_SPECIAL to get the effect of a special key when "literally" + // is true. + const char *const start = arg; + while ((*arg >= ' ' && *arg < DEL) || ((uint8_t)(*arg) == K_SPECIAL + && !literally)) { + arg++; + } + if (arg > start) { + stuffReadbuffLen(start, (arg - start)); + } + + // stuff a single special character + if (*arg != NUL) { + const int c = mb_cptr2char_adv(&arg); + if (literally && ((c < ' ' && c != TAB) || c == DEL)) { + stuffcharReadbuff(Ctrl_V); + } + stuffcharReadbuff(c); + } + } +} + /// Read a character from the redo buffer. Translates K_SPECIAL and /// multibyte characters. /// The redo buffer is left as it is. @@ -649,7 +669,7 @@ static int read_redo(bool init, bool old_redo) if (bp == NULL) { return FAIL; } - p = bp->b_str; + p = (char_u *)bp->b_str; return OK; } if ((c = *p) == NUL) { @@ -670,7 +690,7 @@ static int read_redo(bool init, bool old_redo) } if (*++p == NUL && bp->b_next != NULL) { bp = bp->b_next; - p = bp->b_str; + p = (char_u *)bp->b_str; } buf[i] = (char_u)c; if (i == n - 1) { // last byte of a character @@ -761,11 +781,9 @@ int start_redo(long count, bool old_redo) return OK; } -/* - * Repeat the last insert (R, o, O, a, A, i or I command) by stuffing - * the redo buffer into readbuf2. - * return FAIL for failure, OK otherwise - */ +// Repeat the last insert (R, o, O, a, A, i or I command) by stuffing +// the redo buffer into readbuf2. +// return FAIL for failure, OK otherwise int start_redo_ins(void) { int c; @@ -793,24 +811,24 @@ int start_redo_ins(void) void stop_redo_ins(void) { - block_redo = FALSE; + block_redo = false; } -/* - * Initialize typebuf.tb_buf to point to typebuf_init. - * alloc() cannot be used here: In out-of-memory situations it would - * be impossible to type anything. - */ +// Initialize typebuf.tb_buf to point to typebuf_init. +// alloc() cannot be used here: In out-of-memory situations it would +// be impossible to type anything. static void init_typebuf(void) { - if (typebuf.tb_buf == NULL) { - typebuf.tb_buf = typebuf_init; - typebuf.tb_noremap = noremapbuf_init; - typebuf.tb_buflen = TYPELEN_INIT; - typebuf.tb_len = 0; - typebuf.tb_off = MAXMAPLEN + 4; - typebuf.tb_change_cnt = 1; + if (typebuf.tb_buf != NULL) { + return; } + + typebuf.tb_buf = typebuf_init; + typebuf.tb_noremap = noremapbuf_init; + typebuf.tb_buflen = TYPELEN_INIT; + typebuf.tb_len = 0; + typebuf.tb_off = MAXMAPLEN + 4; + typebuf.tb_change_cnt = 1; } /// @return true when keys cannot be remapped. @@ -849,7 +867,7 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent) typebuf.tb_change_cnt = 1; } - addlen = (int)STRLEN(str); + addlen = (int)strlen(str); if (offset == 0 && addlen <= typebuf.tb_off) { // Easy case: there is room in front of typebuf.tb_buf[typebuf.tb_off] @@ -868,7 +886,7 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent) // often. int newoff = MAXMAPLEN + 4; int extra = addlen + newoff + 4 * (MAXMAPLEN + 4); - if (typebuf.tb_len > 2147483674 - extra) { + if (typebuf.tb_len > INT_MAX - extra) { // string is getting too long for 32 bit int emsg(_(e_toocompl)); // also calls flush_buffers setcursor(); @@ -917,14 +935,12 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent) val = RM_NONE; } - /* - * Adjust typebuf.tb_noremap[] for the new characters: - * If noremap == REMAP_NONE or REMAP_SCRIPT: new characters are - * (sometimes) not remappable - * If noremap == REMAP_YES: all the new characters are mappable - * If noremap > 0: "noremap" characters are not remappable, the rest - * mappable - */ + // Adjust typebuf.tb_noremap[] for the new characters: + // If noremap == REMAP_NONE or REMAP_SCRIPT: new characters are + // (sometimes) not remappable + // If noremap == REMAP_YES: all the new characters are mappable + // If noremap > 0: "noremap" characters are not remappable, the rest + // mappable if (noremap == REMAP_SKIP) { nrm = 1; } else if (noremap < 0) { @@ -932,9 +948,9 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent) } else { nrm = noremap; } - for (i = 0; i < addlen; ++i) { + for (i = 0; i < addlen; i++) { typebuf.tb_noremap[typebuf.tb_off + i + offset] = - (char_u)((--nrm >= 0) ? val : RM_YES); + (uint8_t)((--nrm >= 0) ? val : RM_YES); } // tb_maplen and tb_silent only remember the length of mapped and/or @@ -970,7 +986,7 @@ int ins_char_typebuf(int c, int modifiers) return (int)len; } -/// Return TRUE if the typeahead buffer was changed (while waiting for a +/// Return true if the typeahead buffer was changed (while waiting for a /// character to arrive). Happens when a message was received from a client or /// from feedkeys(). /// But check in a more generic way to avoid trouble: When "typebuf.tb_buf" @@ -986,28 +1002,22 @@ bool typebuf_changed(int tb_change_cnt) || typebuf_was_filled); } -/* - * Return TRUE if there are no characters in the typeahead buffer that have - * not been typed (result from a mapping or come from ":normal"). - */ +/// Return true if there are no characters in the typeahead buffer that have +/// not been typed (result from a mapping or come from ":normal"). int typebuf_typed(void) FUNC_ATTR_PURE { return typebuf.tb_maplen == 0; } -/* - * Return the number of characters that are mapped (or not typed). - */ +// Return the number of characters that are mapped (or not typed). int typebuf_maplen(void) FUNC_ATTR_PURE { return typebuf.tb_maplen; } -/* - * remove "len" characters from typebuf.tb_buf[typebuf.tb_off + offset] - */ +// remove "len" characters from typebuf.tb_buf[typebuf.tb_off + offset] void del_typebuf(int len, int offset) { int i; @@ -1018,21 +1028,14 @@ void del_typebuf(int len, int offset) typebuf.tb_len -= len; - /* - * Easy case: Just increase typebuf.tb_off. - */ + // Easy case: Just increase typebuf.tb_off. if (offset == 0 && typebuf.tb_buflen - (typebuf.tb_off + len) >= 3 * MAXMAPLEN + 3) { typebuf.tb_off += len; - } - /* - * Have to move the characters in typebuf.tb_buf[] and typebuf.tb_noremap[] - */ - else { + } else { + // Have to move the characters in typebuf.tb_buf[] and typebuf.tb_noremap[] i = typebuf.tb_off + offset; - /* - * Leave some extra room at the end to avoid reallocation. - */ + // Leave some extra room at the end to avoid reallocation. if (typebuf.tb_off > MAXMAPLEN) { memmove(typebuf.tb_buf + MAXMAPLEN, typebuf.tb_buf + typebuf.tb_off, (size_t)offset); @@ -1081,10 +1084,8 @@ void del_typebuf(int len, int offset) } } -/* - * Write typed characters to script file. - * If recording is on put the character in the recordbuffer. - */ +// Write typed characters to script file. +// If recording is on put the character in the recordbuffer. static void gotchars(const char_u *chars, size_t len) FUNC_ATTR_NONNULL_ALL { @@ -1135,20 +1136,20 @@ static void gotchars(const char_u *chars, size_t len) /// Only affects recorded characters. void ungetchars(int len) { - if (reg_recording != 0) { - delete_buff_tail(&recordbuff, len); - last_recorded_len -= (size_t)len; + if (reg_recording == 0) { + return; } + + delete_buff_tail(&recordbuff, len); + last_recorded_len -= (size_t)len; } -/* - * Sync undo. Called when typed characters are obtained from the typeahead - * buffer, or when a menu is used. - * Do not sync: - * - In Insert mode, unless cursor key has been used. - * - While reading a script file. - * - When no_u_sync is non-zero. - */ +// Sync undo. Called when typed characters are obtained from the typeahead +// buffer, or when a menu is used. +// Do not sync: +// - In Insert mode, unless cursor key has been used. +// - While reading a script file. +// - When no_u_sync is non-zero. void may_sync_undo(void) { if ((!(State & (MODE_INSERT | MODE_CMDLINE)) || arrow_used) @@ -1157,9 +1158,7 @@ void may_sync_undo(void) } } -/* - * Make "typebuf" empty and allocate new buffers. - */ +// Make "typebuf" empty and allocate new buffers. void alloc_typebuf(void) { typebuf.tb_buf = xmalloc(TYPELEN_INIT); @@ -1175,9 +1174,7 @@ void alloc_typebuf(void) } } -/* - * Free the buffers of "typebuf". - */ +// Free the buffers of "typebuf". void free_typebuf(void) { if (typebuf.tb_buf == typebuf_init) { @@ -1192,10 +1189,8 @@ void free_typebuf(void) } } -/* - * When doing ":so! file", the current typeahead needs to be saved, and - * restored when "file" has been read completely. - */ +// When doing ":so! file", the current typeahead needs to be saved, and +// restored when "file" has been read completely. static typebuf_T saved_typebuf[NSCRIPT]; void save_typebuf(void) @@ -1219,9 +1214,7 @@ static bool can_get_old_char(void) return old_char != -1 && (old_KeyStuffed || stuff_empty()); } -/* - * Save all three kinds of typeahead, so that the user must type at a prompt. - */ +// Save all three kinds of typeahead, so that the user must type at a prompt. void save_typeahead(tasave_T *tp) { tp->save_typebuf = typebuf; @@ -1237,10 +1230,8 @@ void save_typeahead(tasave_T *tp) readbuf2.bh_first.b_next = NULL; } -/* - * Restore the typeahead to what it was before calling save_typeahead(). - * The allocated memory is freed, can only be called once! - */ +// Restore the typeahead to what it was before calling save_typeahead(). +// The allocated memory is freed, can only be called once! void restore_typeahead(tasave_T *tp) { if (tp->typebuf_valid) { @@ -1260,7 +1251,7 @@ void restore_typeahead(tasave_T *tp) /// Open a new script file for the ":source!" command. /// /// @param directly when true execute directly -void openscript(char_u *name, bool directly) +void openscript(char *name, bool directly) { if (curscript + 1 == NSCRIPT) { emsg(_(e_nesting)); @@ -1284,7 +1275,7 @@ void openscript(char_u *name, bool directly) // use NameBuff for expanded name expand_env(name, NameBuff, MAXPATHL); int error; - if ((scriptin[curscript] = file_open_new(&error, (char *)NameBuff, + if ((scriptin[curscript] = file_open_new(&error, NameBuff, kFileReadOnly, 0)) == NULL) { semsg(_(e_notopen_2), name, os_strerror(error)); if (curscript) { @@ -1294,12 +1285,10 @@ void openscript(char_u *name, bool directly) } save_typebuf(); - /* - * Execute the commands from the file right now when using ":source!" - * after ":global" or ":argdo" or in a loop. Also when another command - * follows. This means the display won't be updated. Don't do this - * always, "make test" would fail. - */ + // Execute the commands from the file right now when using ":source!" + // after ":global" or ":argdo" or in a loop. Also when another command + // follows. This means the display won't be updated. Don't do this + // always, "make test" would fail. if (directly) { oparg_T oa; int oldcurscript; @@ -1328,9 +1317,7 @@ void openscript(char_u *name, bool directly) } } -/* - * Close the currently active input script. - */ +// Close the currently active input script. static void closescript(void) { free_typebuf(); @@ -1353,9 +1340,7 @@ void close_all_scripts(void) #endif -/* - * Return TRUE when reading keys from a script file. - */ +/// Return true when reading keys from a script file. int using_script(void) FUNC_ATTR_PURE { @@ -1434,10 +1419,8 @@ int vgetc(void) garbage_collect(false); } - /* - * If a character was put back with vungetc, it was already processed. - * Return it directly. - */ + // If a character was put back with vungetc, it was already processed. + // Return it directly. if (can_get_old_char()) { c = old_char; old_char = -1; @@ -1610,11 +1593,9 @@ int vgetc(void) last_vgetc_recorded_len = last_recorded_len; } - /* - * In the main loop "may_garbage_collect" can be set to do garbage - * collection in the first next vgetc(). It's disabled after that to - * avoid internally used Lists and Dicts to be freed. - */ + // In the main loop "may_garbage_collect" can be set to do garbage + // collection in the first next vgetc(). It's disabled after that to + // avoid internally used Lists and Dicts to be freed. may_garbage_collect = false; // Execute Lua on_key callbacks. @@ -1623,10 +1604,8 @@ int vgetc(void) return c; } -/* - * Like vgetc(), but never return a NUL when called recursively, get a key - * directly from the user (ignoring typeahead). - */ +// Like vgetc(), but never return a NUL when called recursively, get a key +// directly from the user (ignoring typeahead). int safe_vgetc(void) { int c; @@ -1638,10 +1617,8 @@ int safe_vgetc(void) return c; } -/* - * Like safe_vgetc(), but loop to handle K_IGNORE. - * Also ignore scrollbar events. - */ +// Like safe_vgetc(), but loop to handle K_IGNORE. +// Also ignore scrollbar events. int plain_vgetc(void) { int c; @@ -1654,12 +1631,10 @@ int plain_vgetc(void) return c; } -/* - * Check if a character is available, such that vgetc() will not block. - * If the next character is a special character or multi-byte, the returned - * character is not valid!. - * Returns NUL if no character is available. - */ +// Check if a character is available, such that vgetc() will not block. +// If the next character is a special character or multi-byte, the returned +// character is not valid!. +// Returns NUL if no character is available. int vpeekc(void) { if (can_get_old_char()) { @@ -1668,11 +1643,9 @@ int vpeekc(void) return vgetorpeek(false); } -/* - * Check if any character is available, also half an escape sequence. - * Trick: when no typeahead found, but there is something in the typeahead - * buffer, it must be an ESC that is recognized as the start of a key code. - */ +// Check if any character is available, also half an escape sequence. +// Trick: when no typeahead found, but there is something in the typeahead +// buffer, it must be an ESC that is recognized as the start of a key code. int vpeekc_any(void) { int c; @@ -1684,10 +1657,8 @@ int vpeekc_any(void) return c; } -/* - * Call vpeekc() without causing anything to be mapped. - * Return TRUE if a character is available, FALSE otherwise. - */ +// Call vpeekc() without causing anything to be mapped. +// Return true if a character is available, false otherwise. int char_avail(void) { int retval; @@ -1708,9 +1679,10 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) no_mapping++; allow_keys++; for (;;) { - // Position the cursor. Needed after a message that ends in a space, - // or if event processing caused a redraw. - ui_cursor_goto(msg_row, msg_col); + if (msg_col > 0) { + // Position the cursor. Needed after a message that ends in a space. + ui_cursor_goto(msg_row, msg_col); + } if (argvars[0].v_type == VAR_UNKNOWN) { // getchar(): blocking wait. @@ -1718,7 +1690,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) if (!char_avail()) { // flush output before waiting ui_flush(); - (void)os_inchar(NULL, 0, -1, 0, main_loop.events); + (void)os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events); if (!multiqueue_empty(main_loop.events)) { state_handle_k_event(); continue; @@ -1748,11 +1720,6 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) no_mapping--; allow_keys--; - if (!ui_has_messages()) { - // redraw the screen after getchar() - update_screen(CLEAR); - } - set_vim_var_nr(VV_MOUSE_WIN, 0); set_vim_var_nr(VV_MOUSE_WINID, 0); set_vim_var_nr(VV_MOUSE_LNUM, 0); @@ -1760,26 +1727,26 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) rettv->vval.v_number = n; if (n != 0 && (IS_SPECIAL(n) || mod_mask != 0)) { - char_u temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 + char temp[10]; // modifier: 3, mbyte-char: 6, NUL: 1 int i = 0; // Turn a special key into three bytes, plus modifier. if (mod_mask != 0) { - temp[i++] = K_SPECIAL; - temp[i++] = KS_MODIFIER; - temp[i++] = (char_u)mod_mask; + temp[i++] = (char)K_SPECIAL; + temp[i++] = (char)KS_MODIFIER; + temp[i++] = (char)mod_mask; } if (IS_SPECIAL(n)) { - temp[i++] = K_SPECIAL; - temp[i++] = (char_u)K_SECOND(n); - temp[i++] = K_THIRD(n); + temp[i++] = (char)K_SPECIAL; + temp[i++] = (char)K_SECOND(n); + temp[i++] = (char)K_THIRD(n); } else { - i += utf_char2bytes((int)n, (char *)temp + i); + i += utf_char2bytes((int)n, temp + i); } assert(i < 10); temp[i++] = NUL; rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char *)vim_strsave(temp); + rettv->vval.v_string = xstrdup(temp); if (is_mouse_key((int)n)) { int row = mouse_row; @@ -1810,33 +1777,35 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) } /// "getchar()" function -void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getchar_common(argvars, rettv); } /// "getcharstr()" function -void f_getcharstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getcharstr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { getchar_common(argvars, rettv); - if (rettv->v_type == VAR_NUMBER) { - char temp[7]; // mbyte-char: 6, NUL: 1 - const varnumber_T n = rettv->vval.v_number; - int i = 0; + if (rettv->v_type != VAR_NUMBER) { + return; + } - if (n != 0) { - i += utf_char2bytes((int)n, (char *)temp); - } - assert(i < 7); - temp[i++] = NUL; - rettv->v_type = VAR_STRING; - rettv->vval.v_string = xstrdup(temp); + char temp[7]; // mbyte-char: 6, NUL: 1 + const varnumber_T n = rettv->vval.v_number; + int i = 0; + + if (n != 0) { + i += utf_char2bytes((int)n, (char *)temp); } + assert(i < 7); + temp[i++] = NUL; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = xstrdup(temp); } /// "getcharmod()" function -void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getcharmod(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = mod_mask; } @@ -1881,7 +1850,7 @@ static bool at_ins_compl_key(void) c = p[3] & 0x1f; } return (ctrl_x_mode_not_default() && vim_is_ctrl_x_key(c)) - || ((compl_cont_status & CONT_LOCAL) && (c == Ctrl_N || c == Ctrl_P)); + || (compl_status_local() && (c == Ctrl_N || c == Ctrl_P)); } /// Check if typebuf.tb_buf[] contains a modifier plus key that can be changed @@ -1943,7 +1912,7 @@ static int check_simplify_modifier(int max_offset) /// - When there is no match yet, return map_result_nomatch, need to get more /// typeahead. /// - On failure (out of memory) return map_result_fail. -static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) +static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth) { mapblock_T *mp = NULL; mapblock_T *mp2; @@ -2013,21 +1982,33 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // Only consider an entry if the first character matches and it is // for the current state. // Skip ":lmap" mappings if keys were mapped. - if (mp->m_keys[0] == tb_c1 && (mp->m_mode & local_State) + if ((uint8_t)mp->m_keys[0] == tb_c1 && (mp->m_mode & local_State) && ((mp->m_mode & MODE_LANGMAP) == 0 || typebuf.tb_maplen == 0)) { int nomap = nolmaplen; - int c2; + int modifiers = 0; // find the match length of this mapping for (mlen = 1; mlen < typebuf.tb_len; mlen++) { - c2 = typebuf.tb_buf[typebuf.tb_off + mlen]; + int c2 = typebuf.tb_buf[typebuf.tb_off + mlen]; if (nomap > 0) { + if (nomap == 2 && c2 == KS_MODIFIER) { + modifiers = 1; + } else if (nomap == 1 && modifiers == 1) { + modifiers = c2; + } nomap--; - } else if (c2 == K_SPECIAL) { - nomap = 2; } else { - LANGMAP_ADJUST(c2, true); + if (c2 == K_SPECIAL) { + nomap = 2; + } else if (merge_modifiers(c2, &modifiers) == c2) { + // Only apply 'langmap' if merging modifiers into + // the key will not result in another character, + // so that 'langmap' behaves consistently in + // different terminals and GUIs. + LANGMAP_ADJUST(c2, true); + } + modifiers = 0; } - if (mp->m_keys[mlen] != c2) { + if ((uint8_t)mp->m_keys[mlen] != c2) { break; } } @@ -2035,7 +2016,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // Don't allow mapping the first byte(s) of a multi-byte char. // Happens when mapping <M-a> and then changing 'encoding'. // Beware that 0x80 is escaped. - char_u *p1 = mp->m_keys; + char_u *p1 = (char_u *)mp->m_keys; char_u *p2 = (char_u *)mb_unescape((const char **)&p1); if (p2 != NULL && MB_BYTE2LEN(tb_c1) > utfc_ptr2len((char *)p2)) { @@ -2047,15 +2028,14 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // - Partly match: mlen == typebuf.tb_len keylen = mp->m_keylen; if (mlen == keylen || (mlen == typebuf.tb_len && typebuf.tb_len < keylen)) { - char_u *s; int n; // If only script-local mappings are allowed, check if the // mapping starts with K_SNR. - s = typebuf.tb_noremap + typebuf.tb_off; + uint8_t *s = typebuf.tb_noremap + typebuf.tb_off; if (*s == RM_SCRIPT - && (mp->m_keys[0] != K_SPECIAL - || mp->m_keys[1] != KS_EXTRA + && ((uint8_t)mp->m_keys[0] != K_SPECIAL + || (uint8_t)mp->m_keys[1] != KS_EXTRA || mp->m_keys[2] != KE_SNR)) { continue; } @@ -2103,7 +2083,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // Check for match with 'pastetoggle' if (*p_pt != NUL && mp == NULL && (State & (MODE_INSERT | MODE_NORMAL))) { - bool match = typebuf_match_len(p_pt, &mlen); + bool match = typebuf_match_len((char_u *)p_pt, &mlen); if (match) { // write chars to script file(s) if (mlen > typebuf.tb_maplen) { @@ -2112,7 +2092,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) } del_typebuf(mlen, 0); // remove the chars - set_option_value("paste", !p_paste, NULL, 0); + set_option_value_give_err("paste", !p_paste, NULL, 0); if (!(State & MODE_INSERT)) { msg_col = 0; msg_row = Rows - 1; @@ -2162,7 +2142,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) } else { keylen = 0; } - if (keylen == 0) { // no simplication has been done + if (keylen == 0) { // no simplification has been done // If there was no mapping at all use the character from the // typeahead buffer right here. if (mp == NULL) { @@ -2188,7 +2168,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // complete match if (keylen >= 0 && keylen <= typebuf.tb_len) { - char_u *map_str = NULL; + char *map_str = NULL; // Write chars to script file(s). // Note: :lmap mappings are written *after* being applied. #5658 @@ -2225,12 +2205,12 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // Copy the values from *mp that are used, because evaluating the // expression may invoke a function that redefines the mapping, thereby // making *mp invalid. - char save_m_expr = mp->m_expr; - int save_m_noremap = mp->m_noremap; - char save_m_silent = mp->m_silent; - char_u *save_m_keys = NULL; // only saved when needed - char_u *save_m_str = NULL; // only saved when needed - LuaRef save_m_luaref = mp->m_luaref; + const bool save_m_expr = mp->m_expr; + const int save_m_noremap = mp->m_noremap; + const bool save_m_silent = mp->m_silent; + char *save_m_keys = NULL; // only saved when needed + char *save_m_str = NULL; // only saved when needed + const LuaRef save_m_luaref = mp->m_luaref; // Handle ":map <expr>": evaluate the {rhs} as an // expression. Also save and restore the command line @@ -2246,9 +2226,9 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) vgetc_busy = 0; may_garbage_collect = false; - save_m_keys = vim_strsave(mp->m_keys); + save_m_keys = xstrdup(mp->m_keys); if (save_m_luaref == LUA_NOREF) { - save_m_str = vim_strsave(mp->m_str); + save_m_str = xstrdup(mp->m_str); } map_str = eval_map_expr(mp, NUL); @@ -2260,13 +2240,13 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // If an error was displayed and the expression returns an empty // string, generate a <Nop> to allow for a redraw. if (prev_did_emsg != did_emsg && (map_str == NULL || *map_str == NUL)) { - char_u buf[4]; + char buf[4]; xfree(map_str); - buf[0] = K_SPECIAL; - buf[1] = KS_EXTRA; + buf[0] = (char)K_SPECIAL; + buf[1] = (char)KS_EXTRA; buf[2] = KE_IGNORE; buf[3] = NUL; - map_str = vim_strsave(buf); + map_str = xstrdup(buf); if (State & MODE_CMDLINE) { // redraw the command below the error msg_didout = true; @@ -2295,18 +2275,18 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // If this is a LANGMAP mapping, then we didn't record the keys // at the start of the function and have to record them now. if (keylen > typebuf.tb_maplen && (mp->m_mode & MODE_LANGMAP) != 0) { - gotchars(map_str, STRLEN(map_str)); + gotchars((char_u *)map_str, strlen(map_str)); } if (save_m_noremap != REMAP_YES) { noremap = save_m_noremap; - } else if (STRNCMP(map_str, save_m_keys != NULL ? save_m_keys : mp->m_keys, + } else if (strncmp(map_str, save_m_keys != NULL ? save_m_keys : mp->m_keys, (size_t)keylen) != 0) { noremap = REMAP_YES; } else { noremap = REMAP_SKIP; } - i = ins_typebuf((char *)map_str, noremap, 0, true, cmd_silent || save_m_silent); + i = ins_typebuf(map_str, noremap, 0, true, cmd_silent || save_m_silent); if (save_m_expr) { xfree(map_str); } @@ -2364,8 +2344,8 @@ void check_end_reg_executing(bool advance) /// /// if "advance" is true (vgetc()): /// Really get the character. -/// KeyTyped is set to TRUE in the case the user typed the key. -/// KeyStuffed is TRUE if the character comes from the stuff buffer. +/// KeyTyped is set to true in the case the user typed the key. +/// KeyStuffed is true if the character comes from the stuff buffer. /// if "advance" is false (vpeekc()): /// Just look whether there is a character available. /// Return NUL if not. @@ -2376,7 +2356,9 @@ void check_end_reg_executing(bool advance) static int vgetorpeek(bool advance) { int c, c1; - bool timedout = false; // waited for more than 1 second for mapping to complete + bool timedout = false; // waited for more than 'timeoutlen' + // for mapping to complete or + // 'ttimeoutlen' for complete key code int mapdepth = 0; // check for recursive mapping bool mode_deleted = false; // set when mode has been deleted int new_wcol, new_wrow; @@ -2452,7 +2434,7 @@ static int vgetorpeek(bool advance) // flush all input c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L); - // If inchar() returns TRUE (script file was active) or we + // If inchar() returns true (script file was active) or we // are inside a mapping, get out of Insert mode. // Otherwise we behave like having gotten a CTRL-C. // As a result typing CTRL-C in insert mode will @@ -2501,7 +2483,7 @@ static int vgetorpeek(bool advance) // write char to script file(s) gotchars(typebuf.tb_buf + typebuf.tb_off, 1); } - KeyNoremap = typebuf.tb_noremap[typebuf.tb_off]; + KeyNoremap = (unsigned char)typebuf.tb_noremap[typebuf.tb_off]; del_typebuf(1, 0); } break; // got character, break the for loop @@ -2530,7 +2512,7 @@ static int vgetorpeek(bool advance) && (State & MODE_INSERT) && (p_timeout || (keylen == KEYLEN_PART_KEY && p_ttimeout)) && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, 3, 25L)) == 0) { - colnr_T col = 0, vcol; + colnr_T col = 0; char_u *ptr; if (mode_displayed) { @@ -2548,22 +2530,27 @@ static int vgetorpeek(bool advance) // We are expecting to truncate the trailing // white-space, so find the last non-white // character -- webb - col = vcol = curwin->w_wcol = 0; - ptr = get_cursor_line_ptr(); - while (col < curwin->w_cursor.col) { - if (!ascii_iswhite(ptr[col])) { - curwin->w_wcol = vcol; + curwin->w_wcol = 0; + ptr = (char_u *)get_cursor_line_ptr(); + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, + curwin->w_cursor.lnum, 0, (char *)ptr, (char *)ptr); + while ((char_u *)cts.cts_ptr < ptr + curwin->w_cursor.col) { + if (!ascii_iswhite(*cts.cts_ptr)) { + curwin->w_wcol = cts.cts_vcol; } - vcol += lbr_chartabsize(ptr, ptr + col, vcol); - col += utfc_ptr2len((char *)ptr + col); + cts.cts_vcol += lbr_chartabsize(&cts); + cts.cts_ptr += utfc_ptr2len(cts.cts_ptr); } + clear_chartabsize_arg(&cts); + curwin->w_wrow = curwin->w_cline_row + curwin->w_wcol / curwin->w_width_inner; curwin->w_wcol %= curwin->w_width_inner; curwin->w_wcol += curwin_col_off(); col = 0; // no correction needed } else { - --curwin->w_wcol; + curwin->w_wcol--; col = curwin->w_cursor.col - 1; } } else if (curwin->w_p_wrap && curwin->w_wrow) { @@ -2574,8 +2561,8 @@ static int vgetorpeek(bool advance) if (col > 0 && curwin->w_wcol > 0) { // Correct when the cursor is on the right halve // of a double-wide character. - ptr = get_cursor_line_ptr(); - col -= utf_head_off(ptr, ptr + col); + ptr = (char_u *)get_cursor_line_ptr(); + col -= utf_head_off((char *)ptr, (char *)ptr + col); if (utf_ptr2cells((char *)ptr + col) > 1) { curwin->w_wcol--; } @@ -2654,7 +2641,7 @@ static int vgetorpeek(bool advance) // input buffer (e.g., termresponse). if (((State & MODE_INSERT) != 0 || p_lz) && (State & MODE_CMDLINE) == 0 && advance && must_redraw != 0 && !need_wait_return) { - update_screen(0); + update_screen(); setcursor(); // put cursor back where it belongs } @@ -2829,11 +2816,9 @@ int inchar(char_u *buf, int maxlen, long wait_time) ui_flush(); } - /* - * Don't reset these when at the hit-return prompt, otherwise an endless - * recursive loop may result (write error in swapfile, hit-return, timeout - * on char wait, flush swapfile, write error....). - */ + // Don't reset these when at the hit-return prompt, otherwise an endless + // recursive loop may result (write error in swapfile, hit-return, timeout + // on char wait, flush swapfile, write error....). if (State != MODE_HITRETURN) { did_outofmem_msg = false; // display out of memory message (again) did_swapwrite_msg = false; // display swap file write error again @@ -2866,7 +2851,7 @@ int inchar(char_u *buf, int maxlen, long wait_time) if (read_size <= 0) { // Did not get a character from script. // If we got an interrupt, skip all previously typed characters and - // return TRUE if quit reading script file. + // return true if quit reading script file. // Stop reading typeahead when a single CTRL-C was read, // fill_input_buf() returns this when not able to read from stdin. // Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] @@ -2929,7 +2914,7 @@ int fix_input_buffer(char_u *buf, int len) // Two characters are special: NUL and K_SPECIAL. // Replace NUL by K_SPECIAL KS_ZERO KE_FILLER // Replace K_SPECIAL by K_SPECIAL KS_SPECIAL KE_FILLER - for (i = len; --i >= 0; ++p) { + for (i = len; --i >= 0; p++) { if (p[0] == NUL || (p[0] == K_SPECIAL && (i < 2 || p[1] != KS_EXTRA))) { @@ -3008,16 +2993,16 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) ga_concat(&line_ga, "<SNR>"); } else { if (cmod != 0) { - ga_append(&line_ga, (char)K_SPECIAL); - ga_append(&line_ga, (char)KS_MODIFIER); - ga_append(&line_ga, (char)cmod); + ga_append(&line_ga, K_SPECIAL); + ga_append(&line_ga, KS_MODIFIER); + ga_append(&line_ga, (uint8_t)cmod); } if (IS_SPECIAL(c1)) { - ga_append(&line_ga, (char)K_SPECIAL); - ga_append(&line_ga, (char)K_SECOND(c1)); - ga_append(&line_ga, (char)K_THIRD(c1)); + ga_append(&line_ga, K_SPECIAL); + ga_append(&line_ga, (uint8_t)K_SECOND(c1)); + ga_append(&line_ga, (uint8_t)K_THIRD(c1)); } else { - ga_append(&line_ga, (char)c1); + ga_append(&line_ga, (uint8_t)c1); } } @@ -3053,7 +3038,7 @@ bool map_execute_lua(void) } else if (c1 == '\r' || c1 == '\n') { c1 = NUL; // end the line } else { - ga_append(&line_ga, (char)c1); + ga_append(&line_ga, (uint8_t)c1); } } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 231220c319..df4ae05522 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -23,7 +23,7 @@ #define MSG_BUF_CLEN (MSG_BUF_LEN / 6) // cell length (worst case: utf-8 // takes 6 bytes for one cell) -#ifdef WIN32 +#ifdef MSWIN # define _PATHSEPSTR "\\" #else # define _PATHSEPSTR "/" @@ -75,6 +75,10 @@ # define VIMRC_FILE ".nvimrc" #endif +#ifndef VIMRC_LUA_FILE +# define VIMRC_LUA_FILE ".nvim.lua" +#endif + EXTERN struct nvim_stats_s { int64_t fsync; int64_t redraw; @@ -159,31 +163,10 @@ EXTERN colnr_T dollar_vcol INIT(= -1); // Variables for Insert mode completion. -// Length in bytes of the text being completed (this is deleted to be replaced -// by the match.) -EXTERN int compl_length INIT(= 0); - -// Set when doing something for completion that may call edit() recursively, -// which is not allowed. Also used to disable folding during completion -EXTERN bool compl_busy INIT(= false); - -// List of flags for method of completion. -EXTERN int compl_cont_status INIT(= 0); -#define CONT_ADDING 1 // "normal" or "adding" expansion -#define CONT_INTRPT (2 + 4) // a ^X interrupted the current expansion - // it's set only iff N_ADDS is set -#define CONT_N_ADDS 4 // next ^X<> will add-new or expand-current -#define CONT_S_IPOS 8 // next ^X<> will set initial_pos? - // if so, word-wise-expansion will set SOL -#define CONT_SOL 16 // pattern includes start of line, just for - // word-wise expansion, not set for ^X^L -#define CONT_LOCAL 32 // for ctrl_x_mode 0, ^X^P/^X^N do a local - // expansion, (eg use complete=.) - -EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode -EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode -EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode -EXTERN hlf_T edit_submode_highl; // highl. method for extra info +EXTERN char *edit_submode INIT(= NULL); // msg for CTRL-X submode +EXTERN char *edit_submode_pre INIT(= NULL); // prepended to edit_submode +EXTERN char *edit_submode_extra INIT(= NULL); // appended to edit_submode +EXTERN hlf_T edit_submode_highl; // highl. method for extra info // state for putting characters in the message area EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left @@ -198,7 +181,7 @@ EXTERN bool msg_scrolled_ign INIT(= false); // is reset before the screen is redrawn, so we need to keep track of this. EXTERN bool msg_did_scroll INIT(= false); -EXTERN char_u *keep_msg INIT(= NULL); // msg to be shown after redraw +EXTERN char *keep_msg INIT(= NULL); // msg to be shown after redraw EXTERN int keep_msg_attr INIT(= 0); // highlight attr for keep_msg EXTERN bool need_fileinfo INIT(= false); // do fileinfo() after redraw EXTERN int msg_scroll INIT(= false); // msg_start() will scroll @@ -215,13 +198,18 @@ EXTERN int emsg_skip INIT(= 0); // don't display errors for // expression that is skipped EXTERN bool emsg_severe INIT(= false); // use message of next of several // emsg() calls for throw +// used by assert_fails() +EXTERN char *emsg_assert_fails_msg INIT(= NULL); +EXTERN long emsg_assert_fails_lnum INIT(= 0); +EXTERN char *emsg_assert_fails_context INIT(= NULL); + EXTERN bool did_endif INIT(= false); // just had ":endif" EXTERN dict_T vimvardict; // Dictionary with v: variables EXTERN dict_T globvardict; // Dictionary with g: variables /// g: value #define globvarht globvardict.dv_hashtab -EXTERN bool did_emsg; // set by emsg() when the message - // is displayed or thrown +EXTERN int did_emsg; // incremented by emsg() when a + // message is displayed or thrown EXTERN bool called_vim_beep; // set if vim_beep() is called EXTERN bool did_emsg_syntax; // did_emsg set because of a // syntax error @@ -262,9 +250,13 @@ EXTERN int do_profiling INIT(= PROF_NONE); ///< PROF_ values /// Exception currently being thrown. Used to pass an exception to a different /// cstack. Also used for discarding an exception before it is caught or made -/// pending. +/// pending. Only valid when did_throw is true. EXTERN except_T *current_exception; +/// An exception is being thrown. Reset when the exception is caught or as +/// long as it is pending in a finally clause. +EXTERN bool did_throw INIT(= false); + /// Set when a throw that cannot be handled in do_cmdline() must be propagated /// to the cstack of the previously called do_cmdline(). EXTERN bool need_rethrow INIT(= false); @@ -332,13 +324,10 @@ EXTERN int garbage_collect_at_exit INIT(= false); #define SID_STR (-10) // for sourcing a string with no script item // Script CTX being sourced or was sourced to define the current function. -EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 }); +EXTERN sctx_T current_sctx INIT(= { 0, 0, 0 }); // ID of the current channel making a client API call EXTERN uint64_t current_channel_id INIT(= 0); -// ID of the client channel. Used by ui client -EXTERN uint64_t ui_client_channel_id INIT(= 0); - EXTERN bool did_source_packages INIT(= false); // Scope information for the code that indirectly triggered the current @@ -430,12 +419,22 @@ EXTERN win_T *prevwin INIT(= NULL); // previous window // -V:FOR_ALL_WINDOWS_IN_TAB:501 #define FOR_ALL_WINDOWS_IN_TAB(wp, tp) \ for (win_T *wp = ((tp) == curtab) \ - ? firstwin : (tp)->tp_firstwin; wp != NULL; wp = wp->w_next) + ? firstwin : (tp)->tp_firstwin; wp != NULL; wp = wp->w_next) EXTERN win_T *curwin; // currently active window -EXTERN win_T *aucmd_win; // window used in aucmd_prepbuf() -EXTERN int aucmd_win_used INIT(= false); // aucmd_win is being used +typedef struct { + win_T *auc_win; ///< Window used in aucmd_prepbuf(). When not NULL the + ///< window has been allocated. + bool auc_win_used; ///< This auc_win is being used. +} aucmdwin_T; + +/// When executing autocommands for a buffer that is not in any window, a +/// special window is created to handle the side effects. When autocommands +/// nest we may need more than one. +EXTERN kvec_t(aucmdwin_T) aucmd_win_vec INIT(= KV_INITIAL_VALUE); +#define aucmd_win (aucmd_win_vec.items) +#define AUCMD_WIN_COUNT ((int)aucmd_win_vec.size) // The window layout is kept in a tree of frames. topframe points to the top // of the tree. @@ -492,17 +491,19 @@ EXTERN bool exiting INIT(= false); // internal value of v:dying EXTERN int v_dying INIT(= 0); // is stdin a terminal? -EXTERN int stdin_isatty INIT(= true); +EXTERN bool stdin_isatty INIT(= true); // is stdout a terminal? -EXTERN int stdout_isatty INIT(= true); +EXTERN bool stdout_isatty INIT(= true); +// is stderr a terminal? +EXTERN bool stderr_isatty INIT(= true); + /// filedesc set by embedder for reading first buffer like `cmd | nvim -` EXTERN int stdin_fd INIT(= -1); // true when doing full-screen output, otherwise only writing some messages. EXTERN int full_screen INIT(= false); -/// Non-zero when only "safe" commands are allowed, e.g. when sourcing .exrc or -/// .vimrc in current directory. +/// Non-zero when only "safe" commands are allowed EXTERN int secure INIT(= 0); /// Non-zero when changing text and jumping to another window or editing another buffer is not @@ -517,7 +518,7 @@ EXTERN int allbuf_lock INIT(= 0); /// not allowed then. EXTERN int sandbox INIT(= 0); -/// Batch-mode: "-es" or "-Es" commandline argument was given. +/// Batch-mode: "-es", "-Es", "-l" commandline argument was given. EXTERN int silent_mode INIT(= false); /// Start position of active Visual selection. @@ -580,6 +581,8 @@ EXTERN bool can_si INIT(= false); // one indent will be removed. EXTERN bool can_si_back INIT(= false); +EXTERN int old_indent INIT(= 0); ///< for ^^D command in insert mode + // w_cursor before formatting text. EXTERN pos_T saved_cursor INIT(= { 0, 0, 0 }); @@ -614,7 +617,7 @@ EXTERN int inhibit_delete_count INIT(= 0); #define DBCS_DEBUG (-1) /// Encoding used when 'fencs' is set to "default" -EXTERN char_u *fenc_default INIT(= NULL); +EXTERN char *fenc_default INIT(= NULL); /// "State" is the main state of Vim. /// There are other variables that modify the state: @@ -628,7 +631,7 @@ EXTERN int State INIT(= MODE_NORMAL); EXTERN bool debug_mode INIT(= false); EXTERN bool finish_op INIT(= false); // true while an operator is pending EXTERN long opcount INIT(= 0); // count for pending operator -EXTERN int motion_force INIT(=0); // motion force for pending operator +EXTERN int motion_force INIT(= 0); // motion force for pending operator // Ex Mode (Q) state EXTERN bool exmode_active INIT(= false); // true if Ex mode is active @@ -636,7 +639,7 @@ EXTERN bool exmode_active INIT(= false); // true if Ex mode is active /// Flag set when normal_check() should return 0 when entering Ex mode. EXTERN bool pending_exmode_active INIT(= false); -EXTERN bool ex_no_reprint INIT(=false); // No need to print after z or p. +EXTERN bool ex_no_reprint INIT(= false); // No need to print after z or p. // 'inccommand' command preview state EXTERN bool cmdpreview INIT(= false); @@ -676,6 +679,8 @@ EXTERN int emsg_silent INIT(= 0); // don't print error messages EXTERN bool emsg_noredir INIT(= false); // don't redirect error messages EXTERN bool cmd_silent INIT(= false); // don't echo the command line +EXTERN bool in_assert_fails INIT(= false); // assert_fails() active + // Values for swap_exists_action: what to do when swap file already exists #define SEA_NONE 0 // don't use dialog #define SEA_DIALOG 1 // use dialog when possible @@ -685,8 +690,8 @@ EXTERN bool cmd_silent INIT(= false); // don't echo the command line EXTERN int swap_exists_action INIT(= SEA_NONE); ///< For dialog when swap file already exists. EXTERN bool swap_exists_did_quit INIT(= false); ///< Selected "quit" at the dialog. -EXTERN char_u IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc. -EXTERN char_u NameBuff[MAXPATHL]; ///< Buffer for expanding file names +EXTERN char IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc. +EXTERN char NameBuff[MAXPATHL]; ///< Buffer for expanding file names EXTERN char msg_buf[MSG_BUF_LEN]; ///< Small buffer for messages EXTERN char os_buf[ ///< Buffer for the os/ layer #if MAXPATHL > IOSIZE @@ -750,9 +755,9 @@ EXTERN bool need_start_insertmode INIT(= false); ///< start insert mode soon // including the terminating NUL EXTERN char last_mode[MODE_MAX_LENGTH] INIT(= "n"); -EXTERN char_u *last_cmdline INIT(= NULL); // last command line (for ":) -EXTERN char_u *repeat_cmdline INIT(= NULL); // command line for "." -EXTERN char_u *new_last_cmdline INIT(= NULL); // new value for last_cmdline +EXTERN char *last_cmdline INIT(= NULL); // last command line (for ":) +EXTERN char *repeat_cmdline INIT(= NULL); // command line for "." +EXTERN char *new_last_cmdline INIT(= NULL); // new value for last_cmdline EXTERN char *autocmd_fname INIT(= NULL); // fname for <afile> on cmdline EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline EXTERN char *autocmd_match INIT(= NULL); // name for <amatch> on cmdline @@ -769,14 +774,14 @@ EXTERN bool g_tag_at_cursor INIT(= false); // whether the tag command comes EXTERN int replace_offset INIT(= 0); // offset for replace_push() -EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|"); // need backslash in cmd line +EXTERN char *escape_chars INIT(= " \t\\\"|"); // need backslash in cmd line EXTERN int keep_help_flag INIT(= false); // doing :ta from help file // When a string option is NULL (which only happens in out-of-memory // situations), it is set to empty_option, to avoid having to check for NULL // everywhere. -EXTERN char_u *empty_option INIT(= (char_u *)""); +EXTERN char *empty_option INIT(= ""); EXTERN bool redir_off INIT(= false); // no redirection for a moment EXTERN FILE *redir_fd INIT(= NULL); // message redirection file @@ -801,8 +806,8 @@ enum { extern char *default_vim_dir; extern char *default_vimruntime_dir; extern char *default_lib_dir; -extern char_u *compiled_user; -extern char_u *compiled_sys; +extern char *compiled_user; +extern char *compiled_sys; #endif // When a window has a local directory, the absolute path of the global @@ -816,12 +821,11 @@ EXTERN char *last_chdir_reason INIT(= NULL); EXTERN bool km_stopsel INIT(= false); EXTERN bool km_startsel INIT(= false); -EXTERN int cedit_key INIT(= -1); ///< key value of 'cedit' option EXTERN int cmdwin_type INIT(= 0); ///< type of cmdline window or 0 EXTERN int cmdwin_result INIT(= 0); ///< result of cmdline window or 0 EXTERN int cmdwin_level INIT(= 0); ///< cmdline recursion level -EXTERN char_u no_lines_msg[] INIT(= N_("--No lines in buffer--")); +EXTERN char no_lines_msg[] INIT(= N_("--No lines in buffer--")); // When ":global" is used to number of substitutions and changed lines is // accumulated until it's finished. @@ -840,9 +844,6 @@ EXTERN int stl_syntax INIT(= 0); // don't use 'hlsearch' temporarily EXTERN bool no_hlsearch INIT(= false); -// Page number used for %N in 'pageheader' and 'guitablabel'. -EXTERN linenr_T printer_page_num; - EXTERN bool typebuf_was_filled INIT(= false); // received text from client // or from feedkeys() @@ -873,7 +874,7 @@ EXTERN char e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job")); EXTERN char e_argreq[] INIT(= N_("E471: Argument required")); EXTERN char e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &")); EXTERN char e_cmdwin[] INIT(= N_("E11: Invalid in command-line window; <CR> executes, CTRL-C quits")); -EXTERN char e_curdir[] INIT(= N_("E12: Command not allowed from exrc/vimrc in current dir or tag search")); +EXTERN char e_curdir[] INIT(= N_("E12: Command not allowed in secure mode in current dir or tag search")); EXTERN char e_command_too_recursive[] INIT(= N_("E169: Command too recursive")); EXTERN char e_endif[] INIT(= N_("E171: Missing :endif")); EXTERN char e_endtry[] INIT(= N_("E600: Missing :endtry")); @@ -940,7 +941,7 @@ EXTERN char e_patnotf2[] INIT(= N_("E486: Pattern not found: %s")); EXTERN char e_positive[] INIT(= N_("E487: Argument must be positive")); EXTERN char e_prev_dir[] INIT(= N_("E459: Cannot go back to previous directory")); -EXTERN char e_quickfix[] INIT(= N_("E42: No Errors")); +EXTERN char e_no_errors[] INIT(= N_("E42: No Errors")); EXTERN char e_loclist[] INIT(= N_("E776: No location list")); EXTERN char e_re_damg[] INIT(= N_("E43: Damaged match string")); EXTERN char e_re_corr[] INIT(= N_("E44: Corrupted regexp program")); @@ -1001,7 +1002,6 @@ EXTERN char e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char e_float_as_string[] INIT(= N_("E806: using Float as a String")); EXTERN char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now")); -EXTERN char e_autocmd_err[] INIT(= N_("E5500: autocmd has thrown an exception: %s")); EXTERN char e_cmdmap_err[] INIT(= N_("E5520: <Cmd> mapping must end with <CR>")); EXTERN char e_cmdmap_repeated[] INIT(= N_("E5521: <Cmd> mapping must end with <CR> before second <Cmd>")); @@ -1013,27 +1013,28 @@ EXTERN char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called in a lu EXTERN char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain")); EXTERN char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float")); -EXTERN char e_non_empty_string_required[] INIT(= N_("E1142: Non-empty string required")); - EXTERN char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events")); EXTERN char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long")); EXTERN char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range")); +EXTERN char e_highlight_group_name_invalid_char[] INIT(= N_("E5248: Invalid character in group name")); + EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long")); +EXTERN char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line number: %ld")); + EXTERN char e_undobang_cannot_redo_or_move_branch[] INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch")); +EXTERN char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s")); + EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP")); EXTERN char line_msg[] INIT(= N_(" line ")); -// For undo we need to know the lowest time possible. -EXTERN time_t starttime; - EXTERN FILE *time_fd INIT(= NULL); // where to write startup timing // Some compilers warn for not using a return value, but in some situations we @@ -1043,7 +1044,7 @@ EXTERN int vim_ignored; // stdio is an RPC channel (--embed). EXTERN bool embedded_mode INIT(= false); -// Do not start a UI nor read/write to stdio (unless embedding). +// Do not start UI (--headless, -l) nor read/write to stdio (unless embedding). EXTERN bool headless_mode INIT(= false); // uncrustify:on @@ -1083,9 +1084,15 @@ typedef enum { // Only filled for Win32. EXTERN char windowsVersion[20] INIT(= { 0 }); -EXTERN int exit_need_delay INIT(= 0); +/// While executing a regexp and set to OPTION_MAGIC_ON or OPTION_MAGIC_OFF this +/// overrules p_magic. Otherwise set to OPTION_MAGIC_NOT_SET. +EXTERN optmagic_T magic_overruled INIT(= OPTION_MAGIC_NOT_SET); -// Set when 'cmdheight' is changed from zero to one temporarily. -EXTERN bool made_cmdheight_nonzero INIT(= false); +/// Skip win_fix_cursor() call for 'splitkeep' when cmdwin is closed. +EXTERN bool skip_win_fix_cursor INIT(= false); +/// Skip win_fix_scroll() call for 'splitkeep' when closing tab page. +EXTERN bool skip_win_fix_scroll INIT(= false); +/// Skip update_topline() call while executing win_fix_scroll(). +EXTERN bool skip_update_topline INIT(= false); #endif // NVIM_GLOBALS_H diff --git a/src/nvim/grid.c b/src/nvim/grid.c index f95ef3e705..46f8a59710 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -11,9 +11,19 @@ // // The grid_*() functions write to the screen and handle updating grid->lines[]. +#include <assert.h> +#include <limits.h> +#include <stdlib.h> + #include "nvim/arabic.h" +#include "nvim/buffer_defs.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" +#include "nvim/log.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -46,14 +56,14 @@ void grid_adjust(ScreenGrid **grid, int *row_off, int *col_off) } /// Put a unicode char, and up to MAX_MCO composing chars, in a screen cell. -int schar_from_cc(char_u *p, int c, int u8cc[MAX_MCO]) +int schar_from_cc(char *p, int c, int u8cc[MAX_MCO]) { - int len = utf_char2bytes(c, (char *)p); + int len = utf_char2bytes(c, p); for (int i = 0; i < MAX_MCO; i++) { if (u8cc[i] == 0) { break; } - len += utf_char2bytes(u8cc[i], (char *)p + len); + len += utf_char2bytes(u8cc[i], p + len); } p[len] = 0; return len; @@ -125,30 +135,30 @@ void grid_putchar(ScreenGrid *grid, int c, int row, int col, int attr) char buf[MB_MAXBYTES + 1]; buf[utf_char2bytes(c, buf)] = NUL; - grid_puts(grid, (char_u *)buf, row, col, attr); + grid_puts(grid, buf, row, col, attr); } /// get a single character directly from grid.chars into "bytes[]". /// Also return its attribute in *attrp; -void grid_getbytes(ScreenGrid *grid, int row, int col, char_u *bytes, int *attrp) +void grid_getbytes(ScreenGrid *grid, int row, int col, char *bytes, int *attrp) { - size_t off; - grid_adjust(&grid, &row, &col); // safety check - if (grid->chars != NULL && row < grid->rows && col < grid->cols) { - off = grid->line_offset[row] + (size_t)col; - *attrp = grid->attrs[off]; - schar_copy(bytes, grid->chars[off]); + if (grid->chars == NULL || row >= grid->rows || col >= grid->cols) { + return; } + + size_t off = grid->line_offset[row] + (size_t)col; + *attrp = grid->attrs[off]; + schar_copy(bytes, grid->chars[off]); } /// put string '*text' on the window grid at position 'row' and 'col', with /// attributes 'attr', and update chars[] and attrs[]. /// Note: only outputs within one row, message is truncated at grid boundary! /// Note: if grid, row and/or col is invalid, nothing is done. -void grid_puts(ScreenGrid *grid, char_u *text, int row, int col, int attr) +void grid_puts(ScreenGrid *grid, char *text, int row, int col, int attr) { grid_puts_len(grid, text, -1, row, col, attr); } @@ -171,11 +181,11 @@ void grid_puts_line_start(ScreenGrid *grid, int row) put_dirty_grid = grid; } -void grid_put_schar(ScreenGrid *grid, int row, int col, char_u *schar, int attr) +void grid_put_schar(ScreenGrid *grid, int row, int col, char *schar, int attr) { assert(put_dirty_row == row); size_t off = grid->line_offset[row] + (size_t)col; - if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar)) { + if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar) || rdb_flags & RDB_NODELTA) { schar_copy(grid->chars[off], schar); grid->attrs[off] = attr; @@ -187,10 +197,10 @@ void grid_put_schar(ScreenGrid *grid, int row, int col, char_u *schar, int attr) /// like grid_puts(), but output "text[len]". When "len" is -1 output up to /// a NUL. -void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col, int attr) +void grid_puts_len(ScreenGrid *grid, char *text, int textlen, int row, int col, int attr) { size_t off; - char_u *ptr = text; + char *ptr = text; int len = textlen; int c; size_t max_off; @@ -237,11 +247,11 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col while (col < grid->cols && (len < 0 || (int)(ptr - text) < len) && *ptr != NUL) { - c = *ptr; + c = (unsigned char)(*ptr); // check if this is the first byte of a multibyte mbyte_blen = len > 0 ? utfc_ptr2len_len(ptr, (int)((text + len) - ptr)) - : utfc_ptr2len((char *)ptr); + : utfc_ptr2len(ptr); u8c = len >= 0 ? utfc_ptr2char_len(ptr, u8cc, (int)((text + len) - ptr)) : utfc_ptr2char(ptr, u8cc); @@ -254,7 +264,8 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col nc1 = NUL; } else { nc = len >= 0 - ? utfc_ptr2char_len(ptr + mbyte_blen, pcc, (int)((text + len) - ptr - mbyte_blen)) + ? utfc_ptr2char_len(ptr + mbyte_blen, pcc, + (int)((text + len) - ptr - mbyte_blen)) : utfc_ptr2char(ptr + mbyte_blen, pcc); nc1 = pcc[0]; } @@ -279,7 +290,8 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col need_redraw = schar_cmp(grid->chars[off], buf) || (mbyte_cells == 2 && grid->chars[off + 1][0] != 0) || grid->attrs[off] != attr - || exmode_active; + || exmode_active + || rdb_flags & RDB_NODELTA; if (need_redraw) { // When at the end of the text and overwriting a two-cell @@ -320,7 +332,7 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col ptr += mbyte_blen; if (clear_next_cell) { // This only happens at the end, display one space next. - ptr = (char_u *)" "; + ptr = " "; len = -1; } } @@ -393,11 +405,11 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int // double wide-char clear out the right half. Only needed in a // terminal. if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) { - grid_puts_len(grid, (char_u *)" ", 1, row, start_col - 1, 0); + grid_puts_len(grid, " ", 1, row, start_col - 1, 0); } if (end_col < grid->cols && grid_fix_col(grid, end_col, row) != end_col) { - grid_puts_len(grid, (char_u *)" ", 1, row, end_col, 0); + grid_puts_len(grid, " ", 1, row, end_col, 0); } // if grid was resized (in ext_multigrid mode), the UI has no redraw updates @@ -411,8 +423,7 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int size_t lineoff = grid->line_offset[row]; for (col = start_col; col < end_col; col++) { size_t off = lineoff + (size_t)col; - if (schar_cmp(grid->chars[off], sc) - || grid->attrs[off] != attr) { + if (schar_cmp(grid->chars[off], sc) || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) { schar_copy(grid->chars[off], sc); grid->attrs[off] = attr; if (dirty_first == INT_MAX) { @@ -475,9 +486,9 @@ static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_ /// "endcol" gives the columns where valid characters are. /// "clear_width" is the width of the window. It's > 0 if the rest of the line /// needs to be cleared, negative otherwise. -/// "rlflag" is TRUE in a rightleft window: -/// When TRUE and "clear_width" > 0, clear columns 0 to "endcol" -/// When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width" +/// "rlflag" is true in a rightleft window: +/// When true and "clear_width" > 0, clear columns 0 to "endcol" +/// When false and "clear_width" > 0, clear columns "endcol" to "clear_width" /// If "wrap" is true, then hint to the UI that "row" contains a line /// which has wrapped into the next row. void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int clear_width, @@ -604,7 +615,8 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle while (col < clear_width) { if (grid->chars[off_to][0] != ' ' || grid->chars[off_to][1] != NUL - || grid->attrs[off_to] != bg_attr) { + || grid->attrs[off_to] != bg_attr + || rdb_flags & RDB_NODELTA) { grid->chars[off_to][0] = ' '; grid->chars[off_to][1] = NUL; grid->attrs[off_to] = bg_attr; diff --git a/src/nvim/grid.h b/src/nvim/grid.h index 6a93bf3d90..deb3d3785d 100644 --- a/src/nvim/grid.h +++ b/src/nvim/grid.h @@ -2,11 +2,14 @@ #define NVIM_GRID_H #include <stdbool.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/buffer_defs.h" #include "nvim/grid_defs.h" +#include "nvim/macros.h" #include "nvim/mbyte.h" +#include "nvim/memory.h" /// By default, all windows are drawn on a single rectangular grid, represented by /// this ScreenGrid instance. In multigrid mode each window will have its own @@ -29,30 +32,30 @@ EXTERN sattr_T *linebuf_attr INIT(= NULL); // screen grid. /// Put a ASCII character in a screen cell. -static inline void schar_from_ascii(char_u *p, const char c) +static inline void schar_from_ascii(char *p, const char c) { - p[0] = (char_u)c; + p[0] = c; p[1] = 0; } /// Put a unicode character in a screen cell. -static inline int schar_from_char(char_u *p, int c) +static inline int schar_from_char(char *p, int c) { - int len = utf_char2bytes(c, (char *)p); + int len = utf_char2bytes(c, p); p[len] = NUL; return len; } /// compare the contents of two screen cells. -static inline int schar_cmp(char_u *sc1, char_u *sc2) +static inline int schar_cmp(char *sc1, char *sc2) { - return strncmp((char *)sc1, (char *)sc2, sizeof(schar_T)); + return strncmp(sc1, sc2, sizeof(schar_T)); } /// copy the contents of screen cell `sc2` into cell `sc1` -static inline void schar_copy(char_u *sc1, char_u *sc2) +static inline void schar_copy(char *sc1, char *sc2) { - xstrlcpy((char *)sc1, (char *)sc2, sizeof(schar_T)); + xstrlcpy(sc1, sc2, sizeof(schar_T)); } #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index 9252b8a371..db31f7b984 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -10,7 +10,7 @@ #define MAX_MCO 6 // fixed value for 'maxcombine' // The characters and attributes drawn on grids. -typedef char_u schar_T[(MAX_MCO + 1) * 4 + 1]; +typedef char schar_T[(MAX_MCO + 1) * 4 + 1]; typedef int sattr_T; enum { @@ -110,24 +110,6 @@ struct ScreenGrid { false, 0, 0, NULL, false, true, 0, \ 0, 0, 0, 0, 0, false } -/// Status line click definition -typedef struct { - enum { - kStlClickDisabled = 0, ///< Clicks to this area are ignored. - kStlClickTabSwitch, ///< Switch to the given tab. - kStlClickTabClose, ///< Close given tab. - kStlClickFuncRun, ///< Run user function. - } type; ///< Type of the click. - int tabnr; ///< Tab page number. - char *func; ///< Function to run. -} StlClickDefinition; - -/// Used for tabline clicks -typedef struct { - StlClickDefinition def; ///< Click definition. - const char *start; ///< Location where region starts. -} StlClickRecord; - typedef struct { int args[3]; int icell; diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c deleted file mode 100644 index e8410d1ee7..0000000000 --- a/src/nvim/hardcopy.c +++ /dev/null @@ -1,3192 +0,0 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - -/* - * hardcopy.c: printing to paper - */ - -#include <assert.h> -#include <inttypes.h> -#include <string.h> - -#include "nvim/ascii.h" -#include "nvim/vim.h" -#ifdef HAVE_LOCALE_H -# include <locale.h> -#endif -#include "nvim/buffer.h" -#include "nvim/charset.h" -#include "nvim/eval.h" -#include "nvim/ex_docmd.h" -#include "nvim/fileio.h" -#include "nvim/garray.h" -#include "nvim/grid.h" -#include "nvim/hardcopy.h" -#include "nvim/highlight_group.h" -#include "nvim/indent.h" -#include "nvim/mbyte.h" -#include "nvim/memline.h" -#include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/option.h" -#include "nvim/os/input.h" -#include "nvim/os/os.h" -#include "nvim/path.h" -#include "nvim/runtime.h" -#include "nvim/statusline.h" -#include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/ui.h" -#include "nvim/version.h" - -/* - * To implement printing on a platform, the following functions must be - * defined: - * - * int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) - * Called once. Code should display printer dialogue (if appropriate) and - * determine printer font and margin settings. Reset has_color if the printer - * doesn't support colors at all. - * Returns FAIL to abort. - * - * int mch_print_begin(prt_settings_T *settings) - * Called to start the print job. - * Return FALSE to abort. - * - * int mch_print_begin_page(char_u *msg) - * Called at the start of each page. - * "msg" indicates the progress of the print job, can be NULL. - * Return FALSE to abort. - * - * int mch_print_end_page() - * Called at the end of each page. - * Return FALSE to abort. - * - * int mch_print_blank_page() - * Called to generate a blank page for collated, duplex, multiple copy - * document. Return FALSE to abort. - * - * void mch_print_end(prt_settings_T *psettings) - * Called at normal end of print job. - * - * void mch_print_cleanup() - * Called if print job ends normally or is abandoned. Free any memory, close - * devices and handles. Also called when mch_print_begin() fails, but not - * when mch_print_init() fails. - * - * void mch_print_set_font(int Bold, int Italic, int Underline); - * Called whenever the font style changes. - * - * void mch_print_set_bg(uint32_t bgcol); - * Called to set the background color for the following text. Parameter is an - * RGB value. - * - * void mch_print_set_fg(uint32_t fgcol); - * Called to set the foreground color for the following text. Parameter is an - * RGB value. - * - * mch_print_start_line(int margin, int page_line) - * Sets the current position at the start of line "page_line". - * If margin is TRUE start in the left margin (for header and line number). - * - * int mch_print_text_out(char_u *p, size_t len); - * Output one character of text p[len] at the current position. - * Return TRUE if there is no room for another character in the same line. - * - * Note that the generic code has no idea of margins. The machine code should - * simply make the page look smaller! The header and the line numbers are - * printed in the margin. - */ - -static option_table_T printer_opts[OPT_PRINT_NUM_OPTIONS] - = - { - { "top", TRUE, 0, NULL, 0, FALSE }, - { "bottom", TRUE, 0, NULL, 0, FALSE }, - { "left", TRUE, 0, NULL, 0, FALSE }, - { "right", TRUE, 0, NULL, 0, FALSE }, - { "header", TRUE, 0, NULL, 0, FALSE }, - { "syntax", FALSE, 0, NULL, 0, FALSE }, - { "number", FALSE, 0, NULL, 0, FALSE }, - { "wrap", FALSE, 0, NULL, 0, FALSE }, - { "duplex", FALSE, 0, NULL, 0, FALSE }, - { "portrait", FALSE, 0, NULL, 0, FALSE }, - { "paper", FALSE, 0, NULL, 0, FALSE }, - { "collate", FALSE, 0, NULL, 0, FALSE }, - { "jobsplit", FALSE, 0, NULL, 0, FALSE }, - { "formfeed", FALSE, 0, NULL, 0, FALSE }, - } -; - -static const uint32_t cterm_color_8[8] = { - 0x000000, 0xff0000, 0x00ff00, 0xffff00, - 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff -}; - -static const uint32_t cterm_color_16[16] = { - 0x000000, 0x0000c0, 0x008000, 0x004080, - 0xc00000, 0xc000c0, 0x808000, 0xc0c0c0, - 0x808080, 0x6060ff, 0x00ff00, 0x00ffff, - 0xff8080, 0xff40ff, 0xffff00, 0xffffff -}; - -static int current_syn_id; - -#define PRCOLOR_BLACK 0 -#define PRCOLOR_WHITE 0xffffff - -static TriState curr_italic; -static TriState curr_bold; -static TriState curr_underline; -static uint32_t curr_bg; -static uint32_t curr_fg; -static int page_count; - -#define OPT_MBFONT_USECOURIER 0 -#define OPT_MBFONT_ASCII 1 -#define OPT_MBFONT_REGULAR 2 -#define OPT_MBFONT_BOLD 3 -#define OPT_MBFONT_OBLIQUE 4 -#define OPT_MBFONT_BOLDOBLIQUE 5 -#define OPT_MBFONT_NUM_OPTIONS 6 - -static option_table_T mbfont_opts[OPT_MBFONT_NUM_OPTIONS] = -{ - { "c", FALSE, 0, NULL, 0, FALSE }, - { "a", FALSE, 0, NULL, 0, FALSE }, - { "r", FALSE, 0, NULL, 0, FALSE }, - { "b", FALSE, 0, NULL, 0, FALSE }, - { "i", FALSE, 0, NULL, 0, FALSE }, - { "o", FALSE, 0, NULL, 0, FALSE }, -}; - -/* - * These values determine the print position on a page. - */ -typedef struct { - int lead_spaces; // remaining spaces for a TAB - int print_pos; // virtual column for computing TABs - colnr_T column; // byte column - linenr_T file_line; // line nr in the buffer - size_t bytes_printed; // bytes printed so far - int ff; // seen form feed character -} prt_pos_T; - -struct prt_mediasize_S { - char *name; - double width; // width and height in points for portrait - double height; -}; - -// PS font names, must be in Roman, Bold, Italic, Bold-Italic order -struct prt_ps_font_S { - int wx; - int uline_offset; - int uline_width; - int bbox_min_y; - int bbox_max_y; - char *(ps_fontname[4]); -}; - -/* Structures to map user named encoding and mapping to PS equivalents for - * building CID font name */ -struct prt_ps_encoding_S { - char *encoding; - char *cmap_encoding; - int needs_charset; -}; - -struct prt_ps_charset_S { - char *charset; - char *cmap_charset; - int has_charset; -}; - -// Collections of encodings and charsets for multi-byte printing -struct prt_ps_mbfont_S { - int num_encodings; - struct prt_ps_encoding_S *encodings; - int num_charsets; - struct prt_ps_charset_S *charsets; - char *ascii_enc; - char *defcs; -}; - -// Types of PS resource file currently used -typedef enum { - PRT_RESOURCE_TYPE_PROCSET = 0, - PRT_RESOURCE_TYPE_ENCODING = 1, - PRT_RESOURCE_TYPE_CMAP = 2, -} PrtResourceType; - -// String versions of PS resource types -static const char *const prt_resource_types[] = -{ - [PRT_RESOURCE_TYPE_PROCSET] = "procset", - [PRT_RESOURCE_TYPE_ENCODING] = "encoding", - [PRT_RESOURCE_TYPE_CMAP] = "cmap", -}; - -struct prt_ps_resource_S { - char_u name[64]; - char_u filename[MAXPATHL + 1]; - PrtResourceType type; - char_u title[256]; - char_u version[256]; -}; - -struct prt_dsc_comment_S { - char *string; - int len; - int type; -}; - -struct prt_dsc_line_S { - int type; - char_u *string; - int len; -}; - -/* Static buffer to read initial comments in a resource file, some can have a - * couple of KB of comments! */ -#define PRT_FILE_BUFFER_LEN (2048) -struct prt_resfile_buffer_S { - char_u buffer[PRT_FILE_BUFFER_LEN]; - int len; - int line_start; - int line_end; -}; - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "hardcopy.c.generated.h" -#endif - -/* - * Parse 'printoptions' and set the flags in "printer_opts". - * Returns an error message or NULL; - */ -char *parse_printoptions(void) -{ - return parse_list_options(p_popt, printer_opts, OPT_PRINT_NUM_OPTIONS); -} - -/* - * Parse 'printoptions' and set the flags in "printer_opts". - * Returns an error message or NULL; - */ -char *parse_printmbfont(void) -{ - return parse_list_options(p_pmfn, mbfont_opts, OPT_MBFONT_NUM_OPTIONS); -} - -/* - * Parse a list of options in the form - * option:value,option:value,option:value - * - * "value" can start with a number which is parsed out, e.g. margin:12mm - * - * Returns an error message for an illegal option, NULL otherwise. - * Only used for the printer at the moment... - */ -static char *parse_list_options(char_u *option_str, option_table_T *table, size_t table_size) -{ - option_table_T *old_opts; - char *ret = NULL; - char_u *stringp; - char_u *colonp; - char *commap; - char *p; - size_t idx = 0; // init for GCC - int len; - - // Save the old values, so that they can be restored in case of an error. - old_opts = (option_table_T *)xmalloc(sizeof(option_table_T) * table_size); - - for (idx = 0; idx < table_size; idx++) { - old_opts[idx] = table[idx]; - table[idx].present = false; - } - - /* - * Repeat for all comma separated parts. - */ - stringp = option_str; - while (*stringp) { - colonp = (char_u *)vim_strchr((char *)stringp, ':'); - if (colonp == NULL) { - ret = N_("E550: Missing colon"); - break; - } - commap = vim_strchr((char *)stringp, ','); - if (commap == NULL) { - commap = (char *)option_str + STRLEN(option_str); - } - - len = (int)(colonp - stringp); - - for (idx = 0; idx < table_size; ++idx) { - if (STRNICMP(stringp, table[idx].name, len) == 0) { - break; - } - } - - if (idx == table_size) { - ret = N_("E551: Illegal component"); - break; - } - - p = (char *)colonp + 1; - table[idx].present = true; - - if (table[idx].hasnum) { - if (!ascii_isdigit(*p)) { - ret = N_("E552: digit expected"); - break; - } - - table[idx].number = getdigits_int(&p, false, 0); - } - - table[idx].string = (char_u *)p; - table[idx].strlen = (int)(commap - p); - - stringp = (char_u *)commap; - if (*stringp == ',') { - stringp++; - } - } - - if (ret != NULL) { - // Restore old options in case of error - for (idx = 0; idx < table_size; idx++) { - table[idx] = old_opts[idx]; - } - } - - xfree(old_opts); - return ret; -} - -/* - * If using a dark background, the colors will probably be too bright to show - * up well on white paper, so reduce their brightness. - */ -static uint32_t darken_rgb(uint32_t rgb) -{ - return ((rgb >> 17) << 16) - + (((rgb & 0xff00) >> 9) << 8) - + ((rgb & 0xff) >> 1); -} - -static uint32_t prt_get_term_color(int colorindex) -{ - // TODO(vim): Should check for xterm with 88 or 256 colors. - if (t_colors > 8) { - return cterm_color_16[colorindex % 16]; - } - return cterm_color_8[colorindex % 8]; -} - -static uint32_t prt_get_color(int hl_id, int modec) -{ - int colorindex; - uint32_t fg_color; - - const char *color = highlight_color(hl_id, "fg#", 'g'); - if (color != NULL) { - RgbValue rgb = name_to_color(color, &colorindex); - if (rgb != -1) { - return (uint32_t)rgb; - } - } - - color = highlight_color(hl_id, "fg", modec); - if (color == NULL) { - colorindex = 0; - } else { - colorindex = atoi(color); - } - - if (colorindex >= 0 && colorindex < t_colors) { - fg_color = prt_get_term_color(colorindex); - } else { - fg_color = PRCOLOR_BLACK; - } - - return fg_color; -} - -static void prt_get_attr(int hl_id, prt_text_attr_T *pattr, int modec) -{ - pattr->bold = (highlight_has_attr(hl_id, HL_BOLD, modec) != NULL); - pattr->italic = (highlight_has_attr(hl_id, HL_ITALIC, modec) != NULL); - pattr->underline = (highlight_has_attr(hl_id, HL_UNDERLINE, modec) != NULL); - pattr->undercurl = (highlight_has_attr(hl_id, HL_UNDERCURL, modec) != NULL); - pattr->underdouble = (highlight_has_attr(hl_id, HL_UNDERDOUBLE, modec) != NULL); - pattr->underdotted = (highlight_has_attr(hl_id, HL_UNDERDOTTED, modec) != NULL); - pattr->underdashed = (highlight_has_attr(hl_id, HL_UNDERDASHED, modec) != NULL); - - uint32_t fg_color = prt_get_color(hl_id, modec); - - if (fg_color == PRCOLOR_WHITE) { - fg_color = PRCOLOR_BLACK; - } else if (*p_bg == 'd') { - fg_color = darken_rgb(fg_color); - } - - pattr->fg_color = fg_color; - pattr->bg_color = PRCOLOR_WHITE; -} - -static void prt_set_fg(uint32_t fg) -{ - if (fg != curr_fg) { - curr_fg = fg; - mch_print_set_fg(fg); - } -} - -static void prt_set_bg(uint32_t bg) -{ - if (bg != curr_bg) { - curr_bg = bg; - mch_print_set_bg(bg); - } -} - -static void prt_set_font(const TriState bold, const TriState italic, const TriState underline) -{ - if (curr_bold != bold - || curr_italic != italic - || curr_underline != underline) { - curr_underline = underline; - curr_italic = italic; - curr_bold = bold; - mch_print_set_font(bold, italic, underline); - } -} - -// Print the line number in the left margin. -static void prt_line_number(prt_settings_T *const psettings, const int page_line, - const linenr_T lnum) -{ - prt_set_fg(psettings->number.fg_color); - prt_set_bg(psettings->number.bg_color); - prt_set_font(psettings->number.bold, psettings->number.italic, - psettings->number.underline); - mch_print_start_line(true, page_line); - - // Leave two spaces between the number and the text; depends on - // PRINT_NUMBER_WIDTH. - char tbuf[20]; - snprintf(tbuf, sizeof(tbuf), "%6ld", (long)lnum); - for (int i = 0; i < 6; i++) { - (void)mch_print_text_out((char_u *)(&tbuf[i]), 1); - } - - if (psettings->do_syntax) { - // Set colors for next character. - current_syn_id = -1; - } else { - // Set colors and font back to normal. - prt_set_fg(PRCOLOR_BLACK); - prt_set_bg(PRCOLOR_WHITE); - prt_set_font(kFalse, kFalse, kFalse); - } -} - -/* - * Get the currently effective header height. - */ -int prt_header_height(void) -{ - if (printer_opts[OPT_PRINT_HEADERHEIGHT].present) { - return printer_opts[OPT_PRINT_HEADERHEIGHT].number; - } - return 2; -} - -/* - * Return TRUE if using a line number for printing. - */ -int prt_use_number(void) -{ - return printer_opts[OPT_PRINT_NUMBER].present - && TOLOWER_ASC(printer_opts[OPT_PRINT_NUMBER].string[0]) == 'y'; -} - -/* - * Return the unit used in a margin item in 'printoptions'. - * Returns PRT_UNIT_NONE if not recognized. - */ -int prt_get_unit(int idx) -{ - int u = PRT_UNIT_NONE; - int i; - static char *(units[4]) = PRT_UNIT_NAMES; - - if (printer_opts[idx].present) { - for (i = 0; i < 4; ++i) { - if (STRNICMP(printer_opts[idx].string, units[i], 2) == 0) { - u = i; - break; - } - } - } - return u; -} - -// Print the page header. -static void prt_header(prt_settings_T *const psettings, const int pagenum, const linenr_T lnum) -{ - int width = psettings->chars_per_line; - - // Also use the space for the line number. - if (prt_use_number()) { - width += PRINT_NUMBER_WIDTH; - } - - assert(width >= 0); - const size_t tbuf_size = (size_t)width + IOSIZE; - char_u *tbuf = xmalloc(tbuf_size); - - if (*p_header != NUL) { - linenr_T tmp_lnum, tmp_topline, tmp_botline; - int use_sandbox = FALSE; - - /* - * Need to (temporarily) set current line number and first/last line - * number on the 'window'. Since we don't know how long the page is, - * set the first and current line number to the top line, and guess - * that the page length is 64. - */ - tmp_lnum = curwin->w_cursor.lnum; - tmp_topline = curwin->w_topline; - tmp_botline = curwin->w_botline; - curwin->w_cursor.lnum = lnum; - curwin->w_topline = lnum; - curwin->w_botline = lnum + 63; - printer_page_num = pagenum; - - use_sandbox = was_set_insecurely(curwin, "printheader", 0); - build_stl_str_hl(curwin, (char *)tbuf, (size_t)width + IOSIZE, - (char *)p_header, use_sandbox, - ' ', width, NULL, NULL); - - // Reset line numbers - curwin->w_cursor.lnum = tmp_lnum; - curwin->w_topline = tmp_topline; - curwin->w_botline = tmp_botline; - } else { - snprintf((char *)tbuf, tbuf_size, _("Page %d"), pagenum); - } - - prt_set_fg(PRCOLOR_BLACK); - prt_set_bg(PRCOLOR_WHITE); - prt_set_font(kTrue, kFalse, kFalse); - - // Use a negative line number to indicate printing in the top margin. - int page_line = 0 - prt_header_height(); - mch_print_start_line(true, page_line); - for (char_u *p = tbuf; *p != NUL;) { - const int l = utfc_ptr2len((char *)p); - assert(l >= 0); - if (mch_print_text_out(p, (size_t)l)) { - page_line++; - if (page_line >= 0) { // out of room in header - break; - } - mch_print_start_line(true, page_line); - } - p += l; - } - - xfree(tbuf); - - if (psettings->do_syntax) { - // Set colors for next character. - current_syn_id = -1; - } else { - // Set colors and font back to normal. - prt_set_fg(PRCOLOR_BLACK); - prt_set_bg(PRCOLOR_WHITE); - prt_set_font(kFalse, kFalse, kFalse); - } -} - -/* - * Display a print status message. - */ -static void prt_message(char_u *s) -{ - // TODO(bfredl): delete this - grid_fill(&default_grid, Rows - 1, Rows, 0, Columns, ' ', ' ', 0); - grid_puts(&default_grid, s, Rows - 1, 0, HL_ATTR(HLF_R)); - ui_flush(); -} - -void ex_hardcopy(exarg_T *eap) -{ - linenr_T lnum; - int collated_copies, uncollated_copies; - prt_settings_T settings; - size_t bytes_to_print = 0; - int page_line; - int jobsplit; - - CLEAR_FIELD(settings); - settings.has_color = true; - - if (*eap->arg == '>') { - char *errormsg = NULL; - - // Expand things like "%.ps". - if (expand_filename(eap, eap->cmdlinep, &errormsg) == FAIL) { - if (errormsg != NULL) { - emsg(errormsg); - } - return; - } - settings.outfile = (char_u *)skipwhite(eap->arg + 1); - } else if (*eap->arg != NUL) { - settings.arguments = (char_u *)eap->arg; - } - - /* - * Initialise for printing. Ask the user for settings, unless forceit is - * set. - * The mch_print_init() code should set up margins if applicable. (It may - * not be a real printer - for example the engine might generate HTML or - * PS.) - */ - if (mch_print_init(&settings, - curbuf->b_fname == NULL ? (char_u *)buf_spname(curbuf) : curbuf->b_sfname == - NULL ? (char_u *)curbuf->b_fname : (char_u *)curbuf->b_sfname, - eap->forceit) == FAIL) { - return; - } - - settings.modec = 'c'; - - if (!syntax_present(curwin)) { - settings.do_syntax = FALSE; - } else if (printer_opts[OPT_PRINT_SYNTAX].present - && TOLOWER_ASC(printer_opts[OPT_PRINT_SYNTAX].string[0]) != 'a') { - settings.do_syntax = - (TOLOWER_ASC(printer_opts[OPT_PRINT_SYNTAX].string[0]) == 'y'); - } else { - settings.do_syntax = settings.has_color; - } - - // Set up printing attributes for line numbers - settings.number.fg_color = PRCOLOR_BLACK; - settings.number.bg_color = PRCOLOR_WHITE; - settings.number.bold = kFalse; - settings.number.italic = kTrue; - settings.number.underline = kFalse; - - // Syntax highlighting of line numbers. - if (prt_use_number() && settings.do_syntax) { - int id = syn_name2id("LineNr"); - if (id > 0) { - id = syn_get_final_id(id); - } - - prt_get_attr(id, &settings.number, settings.modec); - } - - /* - * Estimate the total lines to be printed - */ - for (lnum = eap->line1; lnum <= eap->line2; lnum++) { - bytes_to_print += STRLEN(skipwhite((char *)ml_get(lnum))); - } - if (bytes_to_print == 0) { - msg(_("No text to be printed")); - goto print_fail_no_begin; - } - - // Set colors and font to normal. - curr_bg = 0xffffffff; - curr_fg = 0xffffffff; - curr_italic = kNone; - curr_bold = kNone; - curr_underline = kNone; - - prt_set_fg(PRCOLOR_BLACK); - prt_set_bg(PRCOLOR_WHITE); - prt_set_font(kFalse, kFalse, kFalse); - current_syn_id = -1; - - jobsplit = (printer_opts[OPT_PRINT_JOBSPLIT].present - && TOLOWER_ASC(printer_opts[OPT_PRINT_JOBSPLIT].string[0]) == 'y'); - - if (!mch_print_begin(&settings)) { - goto print_fail_no_begin; - } - - /* - * Loop over collated copies: 1 2 3, 1 2 3, ... - */ - page_count = 0; - for (collated_copies = 0; - collated_copies < settings.n_collated_copies; - collated_copies++) { - prt_pos_T prtpos; // current print position - prt_pos_T page_prtpos; // print position at page start - int side; - - CLEAR_FIELD(page_prtpos); - page_prtpos.file_line = eap->line1; - prtpos = page_prtpos; - - if (jobsplit && collated_copies > 0) { - // Splitting jobs: Stop a previous job and start a new one. - mch_print_end(&settings); - if (!mch_print_begin(&settings)) { - goto print_fail_no_begin; - } - } - - /* - * Loop over all pages in the print job: 1 2 3 ... - */ - for (page_count = 0; prtpos.file_line <= eap->line2; ++page_count) { - /* - * Loop over uncollated copies: 1 1 1, 2 2 2, 3 3 3, ... - * For duplex: 12 12 12 34 34 34, ... - */ - for (uncollated_copies = 0; - uncollated_copies < settings.n_uncollated_copies; - uncollated_copies++) { - // Set the print position to the start of this page. - prtpos = page_prtpos; - - /* - * Do front and rear side of a page. - */ - for (side = 0; side <= settings.duplex; ++side) { - /* - * Print one page. - */ - - // Check for interrupt character every page. - os_breakcheck(); - if (got_int || settings.user_abort) { - goto print_fail; - } - - assert(prtpos.bytes_printed <= SIZE_MAX / 100); - sprintf((char *)IObuff, _("Printing page %d (%zu%%)"), - page_count + 1 + side, - prtpos.bytes_printed * 100 / bytes_to_print); - if (!mch_print_begin_page(IObuff)) { - goto print_fail; - } - - if (settings.n_collated_copies > 1) { - sprintf((char *)IObuff + STRLEN(IObuff), - _(" Copy %d of %d"), - collated_copies + 1, - settings.n_collated_copies); - } - prt_message(IObuff); - - /* - * Output header if required - */ - if (prt_header_height() > 0) { - prt_header(&settings, page_count + 1 + side, - prtpos.file_line); - } - - for (page_line = 0; page_line < settings.lines_per_page; - ++page_line) { - prtpos.column = hardcopy_line(&settings, - page_line, &prtpos); - if (prtpos.column == 0) { - // finished a file line - prtpos.bytes_printed += - STRLEN(skipwhite((char *)ml_get(prtpos.file_line))); - if (++prtpos.file_line > eap->line2) { - break; // reached the end - } - } else if (prtpos.ff) { - // Line had a formfeed in it - start new page but - // stay on the current line - break; - } - } - - if (!mch_print_end_page()) { - goto print_fail; - } - if (prtpos.file_line > eap->line2) { - break; // reached the end - } - } - - /* - * Extra blank page for duplexing with odd number of pages and - * more copies to come. - */ - if (prtpos.file_line > eap->line2 && settings.duplex - && side == 0 - && uncollated_copies + 1 < settings.n_uncollated_copies) { - if (!mch_print_blank_page()) { - goto print_fail; - } - } - } - if (settings.duplex && prtpos.file_line <= eap->line2) { - page_count++; - } - - // Remember the position where the next page starts. - page_prtpos = prtpos; - } - - vim_snprintf((char *)IObuff, IOSIZE, _("Printed: %s"), - settings.jobname); - prt_message(IObuff); - } - -print_fail: - if (got_int || settings.user_abort) { - sprintf((char *)IObuff, "%s", _("Printing aborted")); - prt_message(IObuff); - } - mch_print_end(&settings); - -print_fail_no_begin: - mch_print_cleanup(); -} - -/* - * Print one page line. - * Return the next column to print, or zero if the line is finished. - */ -static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T *ppos) -{ - colnr_T col; - char_u *line; - int need_break = FALSE; - int outputlen; - int tab_spaces; - int print_pos; - prt_text_attr_T attr; - int id; - - if (ppos->column == 0 || ppos->ff) { - print_pos = 0; - tab_spaces = 0; - if (!ppos->ff && prt_use_number()) { - prt_line_number(psettings, page_line, ppos->file_line); - } - ppos->ff = FALSE; - } else { - // left over from wrap halfway through a tab - print_pos = ppos->print_pos; - tab_spaces = ppos->lead_spaces; - } - - mch_print_start_line(false, page_line); - line = ml_get(ppos->file_line); - - /* - * Loop over the columns until the end of the file line or right margin. - */ - for (col = ppos->column; line[col] != NUL && !need_break; col += outputlen) { - if ((outputlen = utfc_ptr2len((char *)line + col)) < 1) { - outputlen = 1; - } - // syntax highlighting stuff. - if (psettings->do_syntax) { - id = syn_get_id(curwin, ppos->file_line, col, 1, NULL, false); - if (id > 0) { - id = syn_get_final_id(id); - } else { - id = 0; - } - // Get the line again, a multi-line regexp may invalidate it. - line = ml_get(ppos->file_line); - - if (id != current_syn_id) { - current_syn_id = id; - prt_get_attr(id, &attr, psettings->modec); - prt_set_font(attr.bold, attr.italic, attr.underline); - prt_set_fg(attr.fg_color); - prt_set_bg(attr.bg_color); - } - } - - /* - * Appropriately expand any tabs to spaces. - */ - if (line[col] == TAB || tab_spaces != 0) { - if (tab_spaces == 0) { - tab_spaces = tabstop_padding(print_pos, - curbuf->b_p_ts, - curbuf->b_p_vts_array); - } - - while (tab_spaces > 0) { - need_break = mch_print_text_out((char_u *)" ", 1); - print_pos++; - tab_spaces--; - if (need_break) { - break; - } - } - // Keep the TAB if we didn't finish it. - if (need_break && tab_spaces > 0) { - break; - } - } else if (line[col] == FF - && printer_opts[OPT_PRINT_FORMFEED].present - && TOLOWER_ASC(printer_opts[OPT_PRINT_FORMFEED].string[0]) - == 'y') { - ppos->ff = TRUE; - need_break = 1; - } else { - need_break = mch_print_text_out(line + col, (size_t)outputlen); - print_pos += utf_ptr2cells((char *)line + col); - } - } - - ppos->lead_spaces = tab_spaces; - ppos->print_pos = print_pos; - - /* - * Start next line of file if we clip lines, or have reached end of the - * line, unless we are doing a formfeed. - */ - if (!ppos->ff - && (line[col] == NUL - || (printer_opts[OPT_PRINT_WRAP].present - && TOLOWER_ASC(printer_opts[OPT_PRINT_WRAP].string[0]) - == 'n'))) { - return 0; - } - return col; -} - -/* - * PS printer stuff. - * - * Sources of information to help maintain the PS printing code: - * - * 1. PostScript Language Reference, 3rd Edition, - * Addison-Wesley, 1999, ISBN 0-201-37922-8 - * 2. PostScript Language Program Design, - * Addison-Wesley, 1988, ISBN 0-201-14396-8 - * 3. PostScript Tutorial and Cookbook, - * Addison Wesley, 1985, ISBN 0-201-10179-3 - * 4. PostScript Language Document Structuring Conventions Specification, - * version 3.0, - * Adobe Technote 5001, 25th September 1992 - * 5. PostScript Printer Description File Format Specification, Version 4.3, - * Adobe technote 5003, 9th February 1996 - * 6. Adobe Font Metrics File Format Specification, Version 4.1, - * Adobe Technote 5007, 7th October 1998 - * 7. Adobe CMap and CIDFont Files Specification, Version 1.0, - * Adobe Technote 5014, 8th October 1996 - * 8. Adobe CJKV Character Collections and CMaps for CID-Keyed Fonts, - * Adoboe Technote 5094, 8th September, 2001 - * 9. CJKV Information Processing, 2nd Edition, - * O'Reilly, 2002, ISBN 1-56592-224-7 - * - * Some of these documents can be found in PDF form on Adobe's web site - - * http://www.adobe.com - */ - -#define PRT_PS_DEFAULT_DPI (72) // Default user space resolution -#define PRT_PS_DEFAULT_FONTSIZE (10) - -#define PRT_MEDIASIZE_LEN ARRAY_SIZE(prt_mediasize) - -static struct prt_mediasize_S prt_mediasize[] = -{ - { "A4", 595.0, 842.0 }, - { "letter", 612.0, 792.0 }, - { "10x14", 720.0, 1008.0 }, - { "A3", 842.0, 1191.0 }, - { "A5", 420.0, 595.0 }, - { "B4", 729.0, 1032.0 }, - { "B5", 516.0, 729.0 }, - { "executive", 522.0, 756.0 }, - { "folio", 595.0, 935.0 }, - { "ledger", 1224.0, 792.0 }, // Yes, it is wider than taller! - { "legal", 612.0, 1008.0 }, - { "quarto", 610.0, 780.0 }, - { "statement", 396.0, 612.0 }, - { "tabloid", 792.0, 1224.0 } -}; - -#define PRT_PS_FONT_ROMAN (0) -#define PRT_PS_FONT_BOLD (1) -#define PRT_PS_FONT_OBLIQUE (2) -#define PRT_PS_FONT_BOLDOBLIQUE (3) - -// Standard font metrics for Courier family -static struct prt_ps_font_S prt_ps_courier_font = -{ - 600, - -100, 50, - -250, 805, - { "Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique" } -}; - -// Generic font metrics for multi-byte fonts -static struct prt_ps_font_S prt_ps_mb_font = -{ - 1000, - -100, 50, - -250, 805, - { NULL, NULL, NULL, NULL } -}; - -// Pointer to current font set being used -static struct prt_ps_font_S *prt_ps_font; - -#define CS_JIS_C_1978 (0x01) -#define CS_JIS_X_1983 (0x02) -#define CS_JIS_X_1990 (0x04) -#define CS_NEC (0x08) -#define CS_MSWINDOWS (0x10) -#define CS_CP932 (0x20) -#define CS_KANJITALK6 (0x40) -#define CS_KANJITALK7 (0x80) - -// Japanese encodings and charsets -static struct prt_ps_encoding_S j_encodings[] = -{ - { "iso-2022-jp", NULL, (CS_JIS_C_1978|CS_JIS_X_1983|CS_JIS_X_1990| - CS_NEC) }, - { "euc-jp", "EUC", (CS_JIS_C_1978|CS_JIS_X_1983|CS_JIS_X_1990) }, - { "sjis", "RKSJ", (CS_JIS_C_1978|CS_JIS_X_1983|CS_MSWINDOWS| - CS_KANJITALK6|CS_KANJITALK7) }, - { "cp932", "RKSJ", CS_JIS_X_1983 }, - { "ucs-2", "UCS2", CS_JIS_X_1990 }, - { "utf-8", "UTF8", CS_JIS_X_1990 } -}; -static struct prt_ps_charset_S j_charsets[] = -{ - { "JIS_C_1978", "78", CS_JIS_C_1978 }, - { "JIS_X_1983", NULL, CS_JIS_X_1983 }, - { "JIS_X_1990", "Hojo", CS_JIS_X_1990 }, - { "NEC", "Ext", CS_NEC }, - { "MSWINDOWS", "90ms", CS_MSWINDOWS }, - { "CP932", "90ms", CS_JIS_X_1983 }, - { "KANJITALK6", "83pv", CS_KANJITALK6 }, - { "KANJITALK7", "90pv", CS_KANJITALK7 } -}; - -#define CS_GB_2312_80 (0x01) -#define CS_GBT_12345_90 (0x02) -#define CS_GBK2K (0x04) -#define CS_SC_MAC (0x08) -#define CS_GBT_90_MAC (0x10) -#define CS_GBK (0x20) -#define CS_SC_ISO10646 (0x40) - -// Simplified Chinese encodings and charsets -static struct prt_ps_encoding_S sc_encodings[] = -{ - { "iso-2022", NULL, (CS_GB_2312_80|CS_GBT_12345_90) }, - { "gb18030", NULL, CS_GBK2K }, - { "euc-cn", "EUC", (CS_GB_2312_80|CS_GBT_12345_90|CS_SC_MAC| - CS_GBT_90_MAC) }, - { "gbk", "EUC", CS_GBK }, - { "ucs-2", "UCS2", CS_SC_ISO10646 }, - { "utf-8", "UTF8", CS_SC_ISO10646 } -}; -static struct prt_ps_charset_S sc_charsets[] = -{ - { "GB_2312-80", "GB", CS_GB_2312_80 }, - { "GBT_12345-90", "GBT", CS_GBT_12345_90 }, - { "MAC", "GBpc", CS_SC_MAC }, - { "GBT-90_MAC", "GBTpc", CS_GBT_90_MAC }, - { "GBK", "GBK", CS_GBK }, - { "GB18030", "GBK2K", CS_GBK2K }, - { "ISO10646", "UniGB", CS_SC_ISO10646 } -}; - -#define CS_CNS_PLANE_1 (0x01) -#define CS_CNS_PLANE_2 (0x02) -#define CS_CNS_PLANE_1_2 (0x04) -#define CS_B5 (0x08) -#define CS_ETEN (0x10) -#define CS_HK_GCCS (0x20) -#define CS_HK_SCS (0x40) -#define CS_HK_SCS_ETEN (0x80) -#define CS_MTHKL (0x100) -#define CS_MTHKS (0x200) -#define CS_DLHKL (0x400) -#define CS_DLHKS (0x800) -#define CS_TC_ISO10646 (0x1000) - -// Traditional Chinese encodings and charsets -static struct prt_ps_encoding_S tc_encodings[] = -{ - { "iso-2022", NULL, (CS_CNS_PLANE_1|CS_CNS_PLANE_2) }, - { "euc-tw", "EUC", CS_CNS_PLANE_1_2 }, - { "big5", "B5", (CS_B5|CS_ETEN|CS_HK_GCCS|CS_HK_SCS| - CS_HK_SCS_ETEN|CS_MTHKL|CS_MTHKS|CS_DLHKL| - CS_DLHKS) }, - { "cp950", "B5", CS_B5 }, - { "ucs-2", "UCS2", CS_TC_ISO10646 }, - { "utf-8", "UTF8", CS_TC_ISO10646 }, - { "utf-16", "UTF16", CS_TC_ISO10646 }, - { "utf-32", "UTF32", CS_TC_ISO10646 } -}; -static struct prt_ps_charset_S tc_charsets[] = -{ - { "CNS_1992_1", "CNS1", CS_CNS_PLANE_1 }, - { "CNS_1992_2", "CNS2", CS_CNS_PLANE_2 }, - { "CNS_1993", "CNS", CS_CNS_PLANE_1_2 }, - { "BIG5", NULL, CS_B5 }, - { "CP950", NULL, CS_B5 }, - { "ETEN", "ETen", CS_ETEN }, - { "HK_GCCS", "HKgccs", CS_HK_GCCS }, - { "SCS", "HKscs", CS_HK_SCS }, - { "SCS_ETEN", "ETHK", CS_HK_SCS_ETEN }, - { "MTHKL", "HKm471", CS_MTHKL }, - { "MTHKS", "HKm314", CS_MTHKS }, - { "DLHKL", "HKdla", CS_DLHKL }, - { "DLHKS", "HKdlb", CS_DLHKS }, - { "ISO10646", "UniCNS", CS_TC_ISO10646 } -}; - -#define CS_KR_X_1992 (0x01) -#define CS_KR_MAC (0x02) -#define CS_KR_X_1992_MS (0x04) -#define CS_KR_ISO10646 (0x08) - -// Korean encodings and charsets -static struct prt_ps_encoding_S k_encodings[] = -{ - { "iso-2022-kr", NULL, CS_KR_X_1992 }, - { "euc-kr", "EUC", (CS_KR_X_1992|CS_KR_MAC) }, - { "johab", "Johab", CS_KR_X_1992 }, - { "cp1361", "Johab", CS_KR_X_1992 }, - { "uhc", "UHC", CS_KR_X_1992_MS }, - { "cp949", "UHC", CS_KR_X_1992_MS }, - { "ucs-2", "UCS2", CS_KR_ISO10646 }, - { "utf-8", "UTF8", CS_KR_ISO10646 } -}; -static struct prt_ps_charset_S k_charsets[] = -{ - { "KS_X_1992", "KSC", CS_KR_X_1992 }, - { "CP1361", "KSC", CS_KR_X_1992 }, - { "MAC", "KSCpc", CS_KR_MAC }, - { "MSWINDOWS", "KSCms", CS_KR_X_1992_MS }, - { "CP949", "KSCms", CS_KR_X_1992_MS }, - { "WANSUNG", "KSCms", CS_KR_X_1992_MS }, - { "ISO10646", "UniKS", CS_KR_ISO10646 } -}; - -static struct prt_ps_mbfont_S prt_ps_mbfonts[] = -{ - { - ARRAY_SIZE(j_encodings), - j_encodings, - ARRAY_SIZE(j_charsets), - j_charsets, - "jis_roman", - "JIS_X_1983" - }, - { - ARRAY_SIZE(sc_encodings), - sc_encodings, - ARRAY_SIZE(sc_charsets), - sc_charsets, - "gb_roman", - "GB_2312-80" - }, - { - ARRAY_SIZE(tc_encodings), - tc_encodings, - ARRAY_SIZE(tc_charsets), - tc_charsets, - "cns_roman", - "BIG5" - }, - { - ARRAY_SIZE(k_encodings), - k_encodings, - ARRAY_SIZE(k_charsets), - k_charsets, - "ks_roman", - "KS_X_1992" - } -}; - -/* The PS prolog file version number has to match - if the prolog file is - * updated, increment the number in the file and here. Version checking was - * added as of VIM 6.2. - * The CID prolog file version number behaves as per PS prolog. - * Table of VIM and prolog versions: - * - * VIM Prolog CIDProlog - * 6.2 1.3 - * 7.0 1.4 1.0 - */ -#define PRT_PROLOG_VERSION ((char_u *)"1.4") -#define PRT_CID_PROLOG_VERSION ((char_u *)"1.0") - -// Strings to look for in a PS resource file -#define PRT_RESOURCE_HEADER "%!PS-Adobe-" -#define PRT_RESOURCE_RESOURCE "Resource-" -#define PRT_RESOURCE_PROCSET "ProcSet" -#define PRT_RESOURCE_ENCODING "Encoding" -#define PRT_RESOURCE_CMAP "CMap" - -/* Data for table based DSC comment recognition, easy to extend if VIM needs to - * read more comments. */ -#define PRT_DSC_MISC_TYPE (-1) -#define PRT_DSC_TITLE_TYPE (1) -#define PRT_DSC_VERSION_TYPE (2) -#define PRT_DSC_ENDCOMMENTS_TYPE (3) - -#define PRT_DSC_TITLE "%%Title:" -#define PRT_DSC_VERSION "%%Version:" -#define PRT_DSC_ENDCOMMENTS "%%EndComments:" - -#define SIZEOF_CSTR(s) (sizeof(s) - 1) -static struct prt_dsc_comment_S prt_dsc_table[] = -{ - { PRT_DSC_TITLE, SIZEOF_CSTR(PRT_DSC_TITLE), PRT_DSC_TITLE_TYPE }, - { PRT_DSC_VERSION, SIZEOF_CSTR(PRT_DSC_VERSION), - PRT_DSC_VERSION_TYPE }, - { PRT_DSC_ENDCOMMENTS, SIZEOF_CSTR(PRT_DSC_ENDCOMMENTS), - PRT_DSC_ENDCOMMENTS_TYPE } -}; - -/* - * Variables for the output PostScript file. - */ -static FILE *prt_ps_fd; -static bool prt_file_error; -static char_u *prt_ps_file_name = NULL; - -/* - * Various offsets and dimensions in default PostScript user space (points). - * Used for text positioning calculations - */ -static double prt_page_width; -static double prt_page_height; -static double prt_left_margin; -static double prt_right_margin; -static double prt_top_margin; -static double prt_bottom_margin; -static double prt_line_height; -static double prt_first_line_height; -static double prt_char_width; -static double prt_number_width; -static double prt_bgcol_offset; -static double prt_pos_x_moveto = 0.0; -static double prt_pos_y_moveto = 0.0; - -/* - * Various control variables used to decide when and how to change the - * PostScript graphics state. - */ -static bool prt_need_moveto; -static bool prt_do_moveto; -static bool prt_need_font; -static int prt_font; -static bool prt_need_underline; -static TriState prt_underline; -static TriState prt_do_underline; -static bool prt_need_fgcol; -static uint32_t prt_fgcol; -static bool prt_need_bgcol; -static bool prt_do_bgcol; -static uint32_t prt_bgcol; -static uint32_t prt_new_bgcol; -static bool prt_attribute_change; -static double prt_text_run; -static int prt_page_num; -static int prt_bufsiz; - -/* - * Variables controlling physical printing. - */ -static int prt_media; -static int prt_portrait; -static int prt_num_copies; -static int prt_duplex; -static int prt_tumble; -static int prt_collate; - -/* - * Buffers used when generating PostScript output - */ -static char prt_line_buffer[257]; -static garray_T prt_ps_buffer = GA_EMPTY_INIT_VALUE; - -static int prt_do_conv; -static vimconv_T prt_conv; - -static int prt_out_mbyte; -static int prt_custom_cmap; -static char prt_cmap[80]; -static int prt_use_courier; -static bool prt_in_ascii; -static bool prt_half_width; -static char *prt_ascii_encoding; -static char_u prt_hexchar[] = "0123456789abcdef"; - -static void prt_write_file_raw_len(char_u *buffer, size_t bytes) -{ - if (!prt_file_error - && fwrite(buffer, sizeof(char_u), bytes, prt_ps_fd) != bytes) { - emsg(_("E455: Error writing to PostScript output file")); - prt_file_error = true; - } -} - -static void prt_write_file(char *buffer) -{ - prt_write_file_len((char_u *)buffer, STRLEN(buffer)); -} - -static void prt_write_file_len(char_u *buffer, size_t bytes) -{ - prt_write_file_raw_len(buffer, bytes); -} - -/* - * Write a string. - */ -static void prt_write_string(char *s) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), "%s", s); - prt_write_file(prt_line_buffer); -} - -/* - * Write an int and a space. - */ -static void prt_write_int(int i) -{ - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "%d ", i); - prt_write_file(prt_line_buffer); -} - -/* - * Write a boolean and a space. - */ -static void prt_write_boolean(int b) -{ - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "%s ", (b ? "T" : "F")); - prt_write_file(prt_line_buffer); -} - -/* - * Write PostScript to re-encode and define the font. - */ -static void prt_def_font(char *new_name, char *encoding, int height, char *font) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "/_%s /VIM-%s /%s ref\n", new_name, encoding, font); - prt_write_file(prt_line_buffer); - if (prt_out_mbyte) { - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "/%s %d %f /_%s sffs\n", - new_name, height, 500./prt_ps_courier_font.wx, new_name); - } else { - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "/%s %d /_%s ffs\n", new_name, height, new_name); - } - prt_write_file(prt_line_buffer); -} - -/* - * Write a line to define the CID font. - */ -static void prt_def_cidfont(char *new_name, int height, char *cidfont) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "/_%s /%s[/%s] vim_composefont\n", new_name, prt_cmap, cidfont); - prt_write_file(prt_line_buffer); - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "/%s %d /_%s ffs\n", new_name, height, new_name); - prt_write_file(prt_line_buffer); -} - -/* - * Write a line to define a duplicate of a CID font - */ -static void prt_dup_cidfont(char *original_name, char *new_name) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "/%s %s d\n", new_name, original_name); - prt_write_file(prt_line_buffer); -} - -/* - * Convert a real value into an integer and fractional part as integers, with - * the fractional part being in the range [0,10^precision). The fractional part - * is also rounded based on the precision + 1'th fractional digit. - */ -static void prt_real_bits(double real, int precision, int *pinteger, int *pfraction) -{ - int integer = (int)real; - double fraction = real - integer; - if (real < integer) { - fraction = -fraction; - } - for (int i = 0; i < precision; i++) { - fraction *= 10.0; - } - - *pinteger = integer; - *pfraction = (int)(fraction + 0.5); -} - -/* - * Write a real and a space. Save bytes if real value has no fractional part! - * We use prt_real_bits() as %f in sprintf uses the locale setting to decide - * what decimal point character to use, but PS always requires a '.'. - */ -static void prt_write_real(double val, int prec) -{ - int integer; - int fraction; - - prt_real_bits(val, prec, &integer, &fraction); - // Emit integer part - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "%d", integer); - prt_write_file(prt_line_buffer); - // Only emit fraction if necessary - if (fraction != 0) { - // Remove any trailing zeros - while ((fraction % 10) == 0) { - prec--; - fraction /= 10; - } - // Emit fraction left padded with zeros - snprintf(prt_line_buffer, sizeof(prt_line_buffer), ".%0*d", - prec, fraction); - prt_write_file(prt_line_buffer); - } - snprintf(prt_line_buffer, sizeof(prt_line_buffer), " "); - prt_write_file(prt_line_buffer); -} - -/* - * Write a line to define a numeric variable. - */ -static void prt_def_var(char *name, double value, int prec) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "/%s ", name); - prt_write_file(prt_line_buffer); - prt_write_real(value, prec); - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "d\n"); - prt_write_file(prt_line_buffer); -} - -// Convert size from font space to user space at current font scale -#define PRT_PS_FONT_TO_USER(scale, size) ((size) * ((scale)/1000.0)) - -static void prt_flush_buffer(void) -{ - if (!GA_EMPTY(&prt_ps_buffer)) { - // Any background color must be drawn first - if (prt_do_bgcol && (prt_new_bgcol != PRCOLOR_WHITE)) { - unsigned int r, g, b; - - if (prt_do_moveto) { - prt_write_real(prt_pos_x_moveto, 2); - prt_write_real(prt_pos_y_moveto, 2); - prt_write_string("m\n"); - prt_do_moveto = false; - } - - // Size of rect of background color on which text is printed - prt_write_real(prt_text_run, 2); - prt_write_real(prt_line_height, 2); - - // Lastly add the color of the background - r = (prt_new_bgcol & 0xff0000) >> 16; - g = (prt_new_bgcol & 0xff00) >> 8; - b = prt_new_bgcol & 0xff; - prt_write_real(r / 255.0, 3); - prt_write_real(g / 255.0, 3); - prt_write_real(b / 255.0, 3); - prt_write_string("bg\n"); - } - // Draw underlines before the text as it makes it slightly easier to - // find the starting point. - if (prt_do_underline) { - if (prt_do_moveto) { - prt_write_real(prt_pos_x_moveto, 2); - prt_write_real(prt_pos_y_moveto, 2); - prt_write_string("m\n"); - prt_do_moveto = false; - } - - // Underline length of text run - prt_write_real(prt_text_run, 2); - prt_write_string("ul\n"); - } - // Draw the text - if (prt_out_mbyte) { - prt_write_string("<"); - } else { - prt_write_string("("); - } - assert(prt_ps_buffer.ga_len >= 0); - prt_write_file_raw_len(prt_ps_buffer.ga_data, (size_t)prt_ps_buffer.ga_len); - if (prt_out_mbyte) { - prt_write_string(">"); - } else { - prt_write_string(")"); - } - // Add a moveto if need be and use the appropriate show procedure - if (prt_do_moveto) { - prt_write_real(prt_pos_x_moveto, 2); - prt_write_real(prt_pos_y_moveto, 2); - // moveto and a show - prt_write_string("ms\n"); - prt_do_moveto = false; - } else { // Simple show - prt_write_string("s\n"); - } - ga_clear(&prt_ps_buffer); - ga_init(&prt_ps_buffer, (int)sizeof(char), prt_bufsiz); - } -} - -static void prt_resource_name(char *filename, void *cookie) -{ - char_u *resource_filename = cookie; - - if (STRLEN(filename) >= MAXPATHL) { - *resource_filename = NUL; - } else { - STRCPY(resource_filename, filename); - } -} - -static int prt_find_resource(char *name, struct prt_ps_resource_S *resource) -{ - char *buffer; - int retval; - - buffer = xmallocz(MAXPATHL); - - STRLCPY(resource->name, name, 64); - // Look for named resource file in runtimepath - STRCPY(buffer, "print"); - add_pathsep(buffer); - STRLCAT(buffer, name, MAXPATHL); - STRLCAT(buffer, ".ps", MAXPATHL); - resource->filename[0] = NUL; - retval = (do_in_runtimepath(buffer, 0, prt_resource_name, resource->filename) - && resource->filename[0] != NUL); - xfree(buffer); - return retval; -} - -// PS CR and LF characters have platform independent values -#define PSLF (0x0a) -#define PSCR (0x0d) - -static struct prt_resfile_buffer_S prt_resfile; - -static int prt_resfile_next_line(void) -{ - int idx; - - // Move to start of next line and then find end of line - idx = prt_resfile.line_end + 1; - while (idx < prt_resfile.len) { - if (prt_resfile.buffer[idx] != PSLF && prt_resfile.buffer[idx] != PSCR) { - break; - } - idx++; - } - prt_resfile.line_start = idx; - - while (idx < prt_resfile.len) { - if (prt_resfile.buffer[idx] == PSLF || prt_resfile.buffer[idx] == PSCR) { - break; - } - idx++; - } - prt_resfile.line_end = idx; - - return idx < prt_resfile.len; -} - -static int prt_resfile_strncmp(int offset, const char *string, int len) - FUNC_ATTR_NONNULL_ALL -{ - // Force not equal if string is longer than remainder of line - if (len > (prt_resfile.line_end - (prt_resfile.line_start + offset))) { - return 1; - } - return STRNCMP(&prt_resfile.buffer[prt_resfile.line_start + offset], - string, len); -} - -static int prt_resfile_skip_nonws(int offset) -{ - int idx; - - idx = prt_resfile.line_start + offset; - while (idx < prt_resfile.line_end) { - if (isspace(prt_resfile.buffer[idx])) { - return idx - prt_resfile.line_start; - } - idx++; - } - return -1; -} - -static int prt_resfile_skip_ws(int offset) -{ - int idx; - - idx = prt_resfile.line_start + offset; - while (idx < prt_resfile.line_end) { - if (!isspace(prt_resfile.buffer[idx])) { - return idx - prt_resfile.line_start; - } - idx++; - } - return -1; -} - -/* prt_next_dsc() - returns detail on next DSC comment line found. Returns true - * if a DSC comment is found, else false */ -static bool prt_next_dsc(struct prt_dsc_line_S *p_dsc_line) - FUNC_ATTR_NONNULL_ALL -{ - int comment; - int offset; - - // Move to start of next line - if (!prt_resfile_next_line()) { - return false; - } - // DSC comments always start %% - if (prt_resfile_strncmp(0, "%%", 2) != 0) { - return false; - } - // Find type of DSC comment - for (comment = 0; comment < (int)ARRAY_SIZE(prt_dsc_table); comment++) { - if (prt_resfile_strncmp(0, prt_dsc_table[comment].string, - prt_dsc_table[comment].len) == 0) { - break; - } - } - if (comment != ARRAY_SIZE(prt_dsc_table)) { - // Return type of comment - p_dsc_line->type = prt_dsc_table[comment].type; - offset = prt_dsc_table[comment].len; - } else { - // Unrecognised DSC comment, skip to ws after comment leader - p_dsc_line->type = PRT_DSC_MISC_TYPE; - offset = prt_resfile_skip_nonws(0); - if (offset == -1) { - return false; - } - } - - // Skip ws to comment value - offset = prt_resfile_skip_ws(offset); - if (offset == -1) { - return false; - } - p_dsc_line->string = &prt_resfile.buffer[prt_resfile.line_start + offset]; - p_dsc_line->len = prt_resfile.line_end - (prt_resfile.line_start + offset); - - return true; -} - -/// Improved hand crafted parser to get the type, title, and version number of a -/// PS resource file so the file details can be added to the DSC header comments. -static bool prt_open_resource(struct prt_ps_resource_S *resource) - FUNC_ATTR_NONNULL_ALL -{ - struct prt_dsc_line_S dsc_line; - - FILE *fd_resource = os_fopen((char *)resource->filename, READBIN); - if (fd_resource == NULL) { - semsg(_("E624: Can't open file \"%s\""), resource->filename); - return false; - } - CLEAR_FIELD(prt_resfile.buffer); - - // Parse first line to ensure valid resource file - prt_resfile.len = (int)fread((char *)prt_resfile.buffer, sizeof(char_u), - PRT_FILE_BUFFER_LEN, fd_resource); - if (ferror(fd_resource)) { - semsg(_("E457: Can't read PostScript resource file \"%s\""), - resource->filename); - fclose(fd_resource); - return false; - } - fclose(fd_resource); - - prt_resfile.line_end = -1; - prt_resfile.line_start = 0; - if (!prt_resfile_next_line()) { - return false; - } - int offset = 0; - - if (prt_resfile_strncmp(offset, PRT_RESOURCE_HEADER, - (int)STRLEN(PRT_RESOURCE_HEADER)) != 0) { - semsg(_("E618: file \"%s\" is not a PostScript resource file"), - resource->filename); - return false; - } - - // Skip over any version numbers and following ws - offset += (int)STRLEN(PRT_RESOURCE_HEADER); - offset = prt_resfile_skip_nonws(offset); - if (offset == -1) { - return false; - } - offset = prt_resfile_skip_ws(offset); - if (offset == -1) { - return false; - } - if (prt_resfile_strncmp(offset, PRT_RESOURCE_RESOURCE, - (int)STRLEN(PRT_RESOURCE_RESOURCE)) != 0) { - semsg(_("E619: file \"%s\" is not a supported PostScript resource file"), - resource->filename); - return false; - } - offset += (int)STRLEN(PRT_RESOURCE_RESOURCE); - - // Decide type of resource in the file - if (prt_resfile_strncmp(offset, PRT_RESOURCE_PROCSET, - (int)STRLEN(PRT_RESOURCE_PROCSET)) == 0) { - resource->type = PRT_RESOURCE_TYPE_PROCSET; - } else if (prt_resfile_strncmp(offset, PRT_RESOURCE_ENCODING, - (int)STRLEN(PRT_RESOURCE_ENCODING)) == 0) { - resource->type = PRT_RESOURCE_TYPE_ENCODING; - } else if (prt_resfile_strncmp(offset, PRT_RESOURCE_CMAP, - (int)STRLEN(PRT_RESOURCE_CMAP)) == 0) { - resource->type = PRT_RESOURCE_TYPE_CMAP; - } else { - semsg(_("E619: file \"%s\" is not a supported PostScript resource file"), - resource->filename); - return false; - } - - // Look for title and version of resource - resource->title[0] = '\0'; - resource->version[0] = '\0'; - bool seen_title = false; - bool seen_version = false; - bool seen_all = false; - while (!seen_all && prt_next_dsc(&dsc_line)) { - switch (dsc_line.type) { - case PRT_DSC_TITLE_TYPE: - STRLCPY(resource->title, dsc_line.string, dsc_line.len + 1); - seen_title = true; - if (seen_version) { - seen_all = true; - } - break; - - case PRT_DSC_VERSION_TYPE: - STRLCPY(resource->version, dsc_line.string, dsc_line.len + 1); - seen_version = true; - if (seen_title) { - seen_all = true; - } - break; - - case PRT_DSC_ENDCOMMENTS_TYPE: - // Won't find title or resource after this comment, stop searching - seen_all = true; - break; - - case PRT_DSC_MISC_TYPE: - // Not interested in whatever comment this line had - break; - } - } - - if (!seen_title || !seen_version) { - semsg(_("E619: file \"%s\" is not a supported PostScript resource file"), - resource->filename); - return false; - } - - return true; -} - -static bool prt_check_resource(const struct prt_ps_resource_S *resource, const char_u *version) - FUNC_ATTR_NONNULL_ALL -{ - // Version number m.n should match, the revision number does not matter - if (STRNCMP(resource->version, version, STRLEN(version))) { - semsg(_("E621: \"%s\" resource file has wrong version"), - resource->name); - return false; - } - - // Other checks to be added as needed - return true; -} - -static void prt_dsc_start(void) -{ - prt_write_string("%!PS-Adobe-3.0\n"); -} - -static void prt_dsc_noarg(char *comment) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%%s\n", comment); - prt_write_file(prt_line_buffer); -} - -static void prt_dsc_textline(char *comment, char *text) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%%s: %s\n", comment, text); - prt_write_file(prt_line_buffer); -} - -static void prt_dsc_text(char *comment, char *text) -{ - // TODO(vim): - should scan 'text' for any chars needing escaping! - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%%s: (%s)\n", comment, text); - prt_write_file(prt_line_buffer); -} - -static void prt_dsc_ints(char *comment, int count, int *ints) -{ - int i; - - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%%s:", comment); - prt_write_file(prt_line_buffer); - - for (i = 0; i < count; i++) { - snprintf(prt_line_buffer, sizeof(prt_line_buffer), " %d", ints[i]); - prt_write_file(prt_line_buffer); - } - - prt_write_string("\n"); -} - -/// @param comment if NULL add to previous -static void prt_dsc_resources(const char *comment, const char *type, const char *string) - FUNC_ATTR_NONNULL_ARG(2, 3) -{ - if (comment != NULL) { - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%%s: %s", comment, type); - } else { - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%+ %s", type); - } - prt_write_file(prt_line_buffer); - - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - " %s\n", string); - prt_write_file(prt_line_buffer); -} - -static void prt_dsc_font_resource(char *resource, struct prt_ps_font_S *ps_font) -{ - int i; - - prt_dsc_resources(resource, "font", - ps_font->ps_fontname[PRT_PS_FONT_ROMAN]); - for (i = PRT_PS_FONT_BOLD; i <= PRT_PS_FONT_BOLDOBLIQUE; i++) { - if (ps_font->ps_fontname[i] != NULL) { - prt_dsc_resources(NULL, "font", ps_font->ps_fontname[i]); - } - } -} - -static void prt_dsc_requirements(int duplex, int tumble, int collate, int color, int num_copies) -{ - // Only output the comment if we need to. - // Note: tumble is ignored if we are not duplexing - if (!(duplex || collate || color || (num_copies > 1))) { - return; - } - - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "%%%%Requirements:"); - prt_write_file(prt_line_buffer); - - if (duplex) { - prt_write_string(" duplex"); - if (tumble) { - prt_write_string("(tumble)"); - } - } - if (collate) { - prt_write_string(" collate"); - } - if (color) { - prt_write_string(" color"); - } - if (num_copies > 1) { - prt_write_string(" numcopies("); - // Note: no space wanted so don't use prt_write_int() - snprintf(prt_line_buffer, sizeof(prt_line_buffer), "%d", - num_copies); - prt_write_file(prt_line_buffer); - prt_write_string(")"); - } - prt_write_string("\n"); -} - -static void prt_dsc_docmedia(char *paper_name, double width, double height, double weight, - char *colour, char *type) -{ - vim_snprintf(prt_line_buffer, sizeof(prt_line_buffer), - "%%%%DocumentMedia: %s ", paper_name); - prt_write_file(prt_line_buffer); - prt_write_real(width, 2); - prt_write_real(height, 2); - prt_write_real(weight, 2); - if (colour == NULL) { - prt_write_string("()"); - } else { - prt_write_string(colour); - } - prt_write_string(" "); - if (type == NULL) { - prt_write_string("()"); - } else { - prt_write_string(type); - } - prt_write_string("\n"); -} - -void mch_print_cleanup(void) -{ - if (prt_out_mbyte) { - int i; - - // Free off all CID font names created, but first clear duplicate - // pointers to the same string (when the same font is used for more than - // one style). - for (i = PRT_PS_FONT_ROMAN; i <= PRT_PS_FONT_BOLDOBLIQUE; i++) { - if (prt_ps_mb_font.ps_fontname[i] != NULL) { - xfree(prt_ps_mb_font.ps_fontname[i]); - } - prt_ps_mb_font.ps_fontname[i] = NULL; - } - } - - if (prt_do_conv) { - convert_setup(&prt_conv, NULL, NULL); - prt_do_conv = FALSE; - } - if (prt_ps_fd != NULL) { - fclose(prt_ps_fd); - prt_ps_fd = NULL; - prt_file_error = false; - } - if (prt_ps_file_name != NULL) { - XFREE_CLEAR(prt_ps_file_name); - } -} - -static double to_device_units(int idx, double physsize, int def_number) -{ - double ret; - int nr; - - int u = prt_get_unit(idx); - if (u == PRT_UNIT_NONE) { - u = PRT_UNIT_PERC; - nr = def_number; - } else { - nr = printer_opts[idx].number; - } - - switch (u) { - case PRT_UNIT_INCH: - ret = nr * PRT_PS_DEFAULT_DPI; - break; - case PRT_UNIT_MM: - ret = nr * PRT_PS_DEFAULT_DPI / 25.4; - break; - case PRT_UNIT_POINT: - ret = nr; - break; - case PRT_UNIT_PERC: - default: - ret = physsize * nr / 100; - break; - } - - return ret; -} - -/* - * Calculate margins for given width and height from printoptions settings. - */ -static void prt_page_margins(double width, double height, double *left, double *right, double *top, - double *bottom) -{ - *left = to_device_units(OPT_PRINT_LEFT, width, 10); - *right = width - to_device_units(OPT_PRINT_RIGHT, width, 5); - *top = height - to_device_units(OPT_PRINT_TOP, height, 5); - *bottom = to_device_units(OPT_PRINT_BOT, height, 5); -} - -static void prt_font_metrics(int font_scale) -{ - prt_line_height = (double)font_scale; - prt_char_width = PRT_PS_FONT_TO_USER(font_scale, prt_ps_font->wx); -} - -static int prt_get_cpl(void) -{ - if (prt_use_number()) { - prt_number_width = PRINT_NUMBER_WIDTH * prt_char_width; - // If we are outputting multi-byte characters then line numbers will be - // printed with half width characters - if (prt_out_mbyte) { - prt_number_width /= 2; - } - prt_left_margin += prt_number_width; - } else { - prt_number_width = 0.0; - } - - return (int)((prt_right_margin - prt_left_margin) / prt_char_width); -} - -static void prt_build_cid_fontname(int font, char_u *name, int name_len) -{ - assert(name_len >= 0); - char *fontname = xstrndup((char *)name, (size_t)name_len); - prt_ps_mb_font.ps_fontname[font] = fontname; -} - -/* - * Get number of lines of text that fit on a page (excluding the header). - */ -static int prt_get_lpp(void) -{ - int lpp; - - /* - * Calculate offset to lower left corner of background rect based on actual - * font height (based on its bounding box) and the line height, handling the - * case where the font height can exceed the line height. - */ - prt_bgcol_offset = PRT_PS_FONT_TO_USER(prt_line_height, - prt_ps_font->bbox_min_y); - if ((prt_ps_font->bbox_max_y - prt_ps_font->bbox_min_y) < 1000.0) { - prt_bgcol_offset -= PRT_PS_FONT_TO_USER(prt_line_height, - (1000.0 - (prt_ps_font->bbox_max_y - - prt_ps_font->bbox_min_y)) / 2); - } - - // Get height for topmost line based on background rect offset. - prt_first_line_height = prt_line_height + prt_bgcol_offset; - - // Calculate lpp - lpp = (int)((prt_top_margin - prt_bottom_margin) / prt_line_height); - - // Adjust top margin if there is a header - prt_top_margin -= prt_line_height * prt_header_height(); - - return lpp - prt_header_height(); -} - -static int prt_match_encoding(char *p_encoding, struct prt_ps_mbfont_S *p_cmap, - struct prt_ps_encoding_S **pp_mbenc) -{ - int mbenc; - int enc_len; - struct prt_ps_encoding_S *p_mbenc; - - *pp_mbenc = NULL; - // Look for recognised encoding - enc_len = (int)STRLEN(p_encoding); - p_mbenc = p_cmap->encodings; - for (mbenc = 0; mbenc < p_cmap->num_encodings; mbenc++) { - if (STRNICMP(p_mbenc->encoding, p_encoding, enc_len) == 0) { - *pp_mbenc = p_mbenc; - return TRUE; - } - p_mbenc++; - } - return FALSE; -} - -static int prt_match_charset(char *p_charset, struct prt_ps_mbfont_S *p_cmap, - struct prt_ps_charset_S **pp_mbchar) -{ - int mbchar; - int char_len; - struct prt_ps_charset_S *p_mbchar; - - // Look for recognised character set, using default if one is not given - if (*p_charset == NUL) { - p_charset = p_cmap->defcs; - } - char_len = (int)STRLEN(p_charset); - p_mbchar = p_cmap->charsets; - for (mbchar = 0; mbchar < p_cmap->num_charsets; mbchar++) { - if (STRNICMP(p_mbchar->charset, p_charset, char_len) == 0) { - *pp_mbchar = p_mbchar; - return TRUE; - } - p_mbchar++; - } - return FALSE; -} - -int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) -{ - int i; - char *paper_name; - int paper_strlen; - int fontsize; - char_u *p; - int props; - int cmap = 0; - char_u *p_encoding; - struct prt_ps_encoding_S *p_mbenc; - struct prt_ps_encoding_S *p_mbenc_first; - struct prt_ps_charset_S *p_mbchar = NULL; - - /* - * Set up font and encoding. - */ - p_encoding = enc_skip(p_penc); - if (*p_encoding == NUL) { - p_encoding = enc_skip(p_enc); - } - - // Look for a multi-byte font that matches the encoding and character set. - // Only look if multi-byte character set is defined, or using multi-byte - // encoding other than Unicode. This is because a Unicode encoding does not - // uniquely identify a CJK character set to use. - p_mbenc = NULL; - props = enc_canon_props(p_encoding); - if (!(props & ENC_8BIT) && ((*p_pmcs != NUL) || !(props & ENC_UNICODE))) { - p_mbenc_first = NULL; - int effective_cmap = 0; - for (cmap = 0; cmap < (int)ARRAY_SIZE(prt_ps_mbfonts); cmap++) { - if (prt_match_encoding((char *)p_encoding, &prt_ps_mbfonts[cmap], - &p_mbenc)) { - if (p_mbenc_first == NULL) { - p_mbenc_first = p_mbenc; - effective_cmap = cmap; - } - if (prt_match_charset((char *)p_pmcs, &prt_ps_mbfonts[cmap], &p_mbchar)) { - break; - } - } - } - - // Use first encoding matched if no charset matched - if (p_mbenc_first != NULL && p_mbchar == NULL) { - p_mbenc = p_mbenc_first; - cmap = effective_cmap; - } - - assert(p_mbenc == NULL || cmap < (int)ARRAY_SIZE(prt_ps_mbfonts)); - } - - prt_out_mbyte = (p_mbenc != NULL); - if (prt_out_mbyte) { - // Build CMap name - will be same for all multi-byte fonts used - prt_cmap[0] = NUL; - - prt_custom_cmap = (p_mbchar == NULL); - if (!prt_custom_cmap) { - // Check encoding and character set are compatible - if ((p_mbenc->needs_charset & p_mbchar->has_charset) == 0) { - emsg(_("E673: Incompatible multi-byte encoding and character set.")); - return false; - } - - // Add charset name if not empty - if (p_mbchar->cmap_charset != NULL) { - STRLCPY(prt_cmap, p_mbchar->cmap_charset, sizeof(prt_cmap) - 2); - STRCAT(prt_cmap, "-"); - } - } else { - // Add custom CMap character set name - if (*p_pmcs == NUL) { - emsg(_("E674: printmbcharset cannot be empty with multi-byte encoding.")); - return false; - } - STRLCPY(prt_cmap, p_pmcs, sizeof(prt_cmap) - 2); - STRCAT(prt_cmap, "-"); - } - - // CMap name ends with (optional) encoding name and -H for horizontal - if (p_mbenc->cmap_encoding != NULL && STRLEN(prt_cmap) - + STRLEN(p_mbenc->cmap_encoding) + 3 < sizeof(prt_cmap)) { - STRCAT(prt_cmap, p_mbenc->cmap_encoding); - STRCAT(prt_cmap, "-"); - } - STRCAT(prt_cmap, "H"); - - if (!mbfont_opts[OPT_MBFONT_REGULAR].present) { - emsg(_("E675: No default font specified for multi-byte printing.")); - return false; - } - - // Derive CID font names with fallbacks if not defined - prt_build_cid_fontname(PRT_PS_FONT_ROMAN, - mbfont_opts[OPT_MBFONT_REGULAR].string, - mbfont_opts[OPT_MBFONT_REGULAR].strlen); - if (mbfont_opts[OPT_MBFONT_BOLD].present) { - prt_build_cid_fontname(PRT_PS_FONT_BOLD, - mbfont_opts[OPT_MBFONT_BOLD].string, - mbfont_opts[OPT_MBFONT_BOLD].strlen); - } - if (mbfont_opts[OPT_MBFONT_OBLIQUE].present) { - prt_build_cid_fontname(PRT_PS_FONT_OBLIQUE, - mbfont_opts[OPT_MBFONT_OBLIQUE].string, - mbfont_opts[OPT_MBFONT_OBLIQUE].strlen); - } - if (mbfont_opts[OPT_MBFONT_BOLDOBLIQUE].present) { - prt_build_cid_fontname(PRT_PS_FONT_BOLDOBLIQUE, - mbfont_opts[OPT_MBFONT_BOLDOBLIQUE].string, - mbfont_opts[OPT_MBFONT_BOLDOBLIQUE].strlen); - } - - // Check if need to use Courier for ASCII code range, and if so pick up - // the encoding to use - prt_use_courier = ( - mbfont_opts[OPT_MBFONT_USECOURIER].present - && (TOLOWER_ASC(mbfont_opts[OPT_MBFONT_USECOURIER].string[0]) == 'y')); - if (prt_use_courier) { - // Use national ASCII variant unless ASCII wanted - if (mbfont_opts[OPT_MBFONT_ASCII].present - && (TOLOWER_ASC(mbfont_opts[OPT_MBFONT_ASCII].string[0]) == 'y')) { - prt_ascii_encoding = "ascii"; - } else { - prt_ascii_encoding = prt_ps_mbfonts[cmap].ascii_enc; - } - } - - prt_ps_font = &prt_ps_mb_font; - } else { - prt_use_courier = FALSE; - prt_ps_font = &prt_ps_courier_font; - } - - /* - * Find the size of the paper and set the margins. - */ - prt_portrait = (!printer_opts[OPT_PRINT_PORTRAIT].present - || TOLOWER_ASC(printer_opts[OPT_PRINT_PORTRAIT].string[0]) == - 'y'); - if (printer_opts[OPT_PRINT_PAPER].present) { - paper_name = (char *)printer_opts[OPT_PRINT_PAPER].string; - paper_strlen = printer_opts[OPT_PRINT_PAPER].strlen; - } else { - paper_name = "A4"; - paper_strlen = 2; - } - for (i = 0; i < (int)PRT_MEDIASIZE_LEN; ++i) { - if (STRLEN(prt_mediasize[i].name) == (unsigned)paper_strlen - && STRNICMP(prt_mediasize[i].name, paper_name, - paper_strlen) == 0) { - break; - } - } - if (i == PRT_MEDIASIZE_LEN) { - i = 0; - } - prt_media = i; - - /* - * Set PS pagesize based on media dimensions and print orientation. - * Note: Media and page sizes have defined meanings in PostScript and should - * be kept distinct. Media is the paper (or transparency, or ...) that is - * printed on, whereas the page size is the area that the PostScript - * interpreter renders into. - */ - if (prt_portrait) { - prt_page_width = prt_mediasize[i].width; - prt_page_height = prt_mediasize[i].height; - } else { - prt_page_width = prt_mediasize[i].height; - prt_page_height = prt_mediasize[i].width; - } - - // Set PS page margins based on the PS pagesize, not the mediasize - this - // needs to be done before the cpl and lpp are calculated. - double left, right, top, bottom; - prt_page_margins(prt_page_width, prt_page_height, &left, &right, &top, - &bottom); - prt_left_margin = left; - prt_right_margin = right; - prt_top_margin = top; - prt_bottom_margin = bottom; - - /* - * Set up the font size. - */ - fontsize = PRT_PS_DEFAULT_FONTSIZE; - for (p = p_pfn; (p = (char_u *)vim_strchr((char *)p, ':')) != NULL; p++) { - if (p[1] == 'h' && ascii_isdigit(p[2])) { - fontsize = atoi((char *)p + 2); - } - } - prt_font_metrics(fontsize); - - /* - * Return the number of characters per line, and lines per page for the - * generic print code. - */ - psettings->chars_per_line = prt_get_cpl(); - psettings->lines_per_page = prt_get_lpp(); - - // Catch margin settings that leave no space for output! - if (psettings->chars_per_line <= 0 || psettings->lines_per_page <= 0) { - return FAIL; - } - - /* - * Sort out the number of copies to be printed. PS by default will do - * uncollated copies for you, so once we know how many uncollated copies are - * wanted cache it away and lie to the generic code that we only want one - * uncollated copy. - */ - psettings->n_collated_copies = 1; - psettings->n_uncollated_copies = 1; - prt_num_copies = 1; - prt_collate = (!printer_opts[OPT_PRINT_COLLATE].present - || TOLOWER_ASC(printer_opts[OPT_PRINT_COLLATE].string[0]) == - 'y'); - if (prt_collate) { - // TODO(vim): Get number of collated copies wanted. - } else { - // TODO(vim): Get number of uncollated copies wanted and update the cached - // count. - } - - psettings->jobname = jobname; - - /* - * Set up printer duplex and tumble based on Duplex option setting - default - * is long sided duplex printing (i.e. no tumble). - */ - prt_duplex = TRUE; - prt_tumble = FALSE; - psettings->duplex = 1; - if (printer_opts[OPT_PRINT_DUPLEX].present) { - if (STRNICMP(printer_opts[OPT_PRINT_DUPLEX].string, "off", 3) == 0) { - prt_duplex = FALSE; - psettings->duplex = 0; - } else if (STRNICMP(printer_opts[OPT_PRINT_DUPLEX].string, "short", 5) - == 0) { - prt_tumble = TRUE; - } - } - - // For now user abort not supported - psettings->user_abort = 0; - - // If the user didn't specify a file name, use a temp file. - if (psettings->outfile == NULL) { - prt_ps_file_name = vim_tempname(); - if (prt_ps_file_name == NULL) { - emsg(_(e_notmp)); - return FAIL; - } - prt_ps_fd = os_fopen((char *)prt_ps_file_name, WRITEBIN); - } else { - p = (char_u *)expand_env_save((char *)psettings->outfile); - if (p != NULL) { - prt_ps_fd = os_fopen((char *)p, WRITEBIN); - xfree(p); - } - } - if (prt_ps_fd == NULL) { - emsg(_("E324: Can't open PostScript output file")); - mch_print_cleanup(); - return FAIL; - } - - prt_bufsiz = psettings->chars_per_line; - if (prt_out_mbyte) { - prt_bufsiz *= 2; - } - ga_init(&prt_ps_buffer, (int)sizeof(char), prt_bufsiz); - - prt_page_num = 0; - - prt_attribute_change = false; - prt_need_moveto = false; - prt_need_font = false; - prt_need_fgcol = false; - prt_need_bgcol = false; - prt_need_underline = false; - - prt_file_error = false; - - return OK; -} - -static bool prt_add_resource(struct prt_ps_resource_S *resource) -{ - FILE *fd_resource; - char_u resource_buffer[512]; - size_t bytes_read; - - fd_resource = os_fopen((char *)resource->filename, READBIN); - if (fd_resource == NULL) { - semsg(_("E456: Can't open file \"%s\""), resource->filename); - return false; - } - switch (resource->type) { - case PRT_RESOURCE_TYPE_PROCSET: - case PRT_RESOURCE_TYPE_ENCODING: - case PRT_RESOURCE_TYPE_CMAP: - prt_dsc_resources("BeginResource", prt_resource_types[resource->type], - (char *)resource->title); - break; - default: - return false; - } - - prt_dsc_textline("BeginDocument", (char *)resource->filename); - - for (;;) { - bytes_read = fread((char *)resource_buffer, sizeof(char_u), - sizeof(resource_buffer), fd_resource); - if (ferror(fd_resource)) { - semsg(_("E457: Can't read PostScript resource file \"%s\""), - resource->filename); - fclose(fd_resource); - return false; - } - if (bytes_read == 0) { - break; - } - prt_write_file_raw_len(resource_buffer, bytes_read); - if (prt_file_error) { - fclose(fd_resource); - return false; - } - } - fclose(fd_resource); - - prt_dsc_noarg("EndDocument"); - - prt_dsc_noarg("EndResource"); - - return true; -} - -bool mch_print_begin(prt_settings_T *psettings) -{ - int bbox[4]; - double left; - double right; - double top; - double bottom; - struct prt_ps_resource_S res_prolog; - struct prt_ps_resource_S res_encoding; - char buffer[256]; - char *p_encoding; - char_u *p; - struct prt_ps_resource_S res_cidfont; - struct prt_ps_resource_S res_cmap; - - /* - * PS DSC Header comments - no PS code! - */ - prt_dsc_start(); - prt_dsc_textline("Title", (char *)psettings->jobname); - if (os_get_username(buffer, 256) == FAIL) { - STRCPY(buffer, "Unknown"); - } - prt_dsc_textline("For", buffer); - prt_dsc_textline("Creator", longVersion); - // Note: to ensure Clean8bit I don't think we can use LC_TIME - char ctime_buf[50]; - char *p_time = os_ctime(ctime_buf, sizeof(ctime_buf)); - // Note: os_ctime() adds a \n so we have to remove it :-( - p = (char_u *)vim_strchr(p_time, '\n'); - if (p != NULL) { - *p = NUL; - } - prt_dsc_textline("CreationDate", p_time); - prt_dsc_textline("DocumentData", "Clean8Bit"); - prt_dsc_textline("Orientation", "Portrait"); - prt_dsc_text(("Pages"), "atend"); - prt_dsc_textline("PageOrder", "Ascend"); - // The bbox does not change with orientation - it is always in the default - // user coordinate system! We have to recalculate right and bottom - // coordinates based on the font metrics for the bbox to be accurate. - prt_page_margins(prt_mediasize[prt_media].width, - prt_mediasize[prt_media].height, - &left, &right, &top, &bottom); - bbox[0] = (int)left; - if (prt_portrait) { - // In portrait printing the fixed point is the top left corner so we - // derive the bbox from that point. We have the expected cpl chars - // across the media and lpp lines down the media. - bbox[1] = (int)(top - (psettings->lines_per_page + prt_header_height()) - * prt_line_height); - bbox[2] = (int)(left + psettings->chars_per_line * prt_char_width - + 0.5); - bbox[3] = (int)(top + 0.5); - } else { - // In landscape printing the fixed point is the bottom left corner so we - // derive the bbox from that point. We have lpp chars across the media - // and cpl lines up the media. - bbox[1] = (int)bottom; - bbox[2] = (int)(left + ((psettings->lines_per_page - + prt_header_height()) * prt_line_height) + 0.5); - bbox[3] = (int)(bottom + psettings->chars_per_line * prt_char_width - + 0.5); - } - prt_dsc_ints("BoundingBox", 4, bbox); - // The media width and height does not change with landscape printing! - prt_dsc_docmedia(prt_mediasize[prt_media].name, - prt_mediasize[prt_media].width, - prt_mediasize[prt_media].height, - (double)0, NULL, NULL); - // Define fonts needed - if (!prt_out_mbyte || prt_use_courier) { - prt_dsc_font_resource("DocumentNeededResources", &prt_ps_courier_font); - } - if (prt_out_mbyte) { - prt_dsc_font_resource((prt_use_courier ? NULL - : "DocumentNeededResources"), &prt_ps_mb_font); - if (!prt_custom_cmap) { - prt_dsc_resources(NULL, "cmap", prt_cmap); - } - } - - // Search for external resources VIM supplies - if (!prt_find_resource("prolog", &res_prolog)) { - emsg(_("E456: Can't find PostScript resource file \"prolog.ps\"")); - return false; - } - if (!prt_open_resource(&res_prolog)) { - return false; - } - if (!prt_check_resource(&res_prolog, PRT_PROLOG_VERSION)) { - return false; - } - if (prt_out_mbyte) { - // Look for required version of multi-byte printing procset - if (!prt_find_resource("cidfont", &res_cidfont)) { - emsg(_("E456: Can't find PostScript resource file \"cidfont.ps\"")); - return false; - } - if (!prt_open_resource(&res_cidfont)) { - return false; - } - if (!prt_check_resource(&res_cidfont, PRT_CID_PROLOG_VERSION)) { - return false; - } - } - - // Find an encoding to use for printing. - // Check 'printencoding'. If not set or not found, then use 'encoding'. If - // that cannot be found then default to "latin1". - // Note: VIM specific encoding header is always skipped. - if (!prt_out_mbyte) { - p_encoding = (char *)enc_skip(p_penc); - if (*p_encoding == NUL - || !prt_find_resource(p_encoding, &res_encoding)) { - // 'printencoding' not set or not supported - find alternate - int props; - - p_encoding = (char *)enc_skip(p_enc); - props = enc_canon_props((char_u *)p_encoding); - if (!(props & ENC_8BIT) - || !prt_find_resource(p_encoding, &res_encoding)) { - // 8-bit 'encoding' is not supported - // Use latin1 as default printing encoding - p_encoding = "latin1"; - if (!prt_find_resource(p_encoding, &res_encoding)) { - semsg(_("E456: Can't find PostScript resource file \"%s.ps\""), - p_encoding); - return false; - } - } - } - if (!prt_open_resource(&res_encoding)) { - return false; - } - // For the moment there are no checks on encoding resource files to - // perform - } else { - p_encoding = (char *)enc_skip(p_penc); - if (*p_encoding == NUL) { - p_encoding = (char *)enc_skip(p_enc); - } - if (prt_use_courier) { - // Include ASCII range encoding vector - if (!prt_find_resource(prt_ascii_encoding, &res_encoding)) { - semsg(_("E456: Can't find PostScript resource file \"%s.ps\""), - prt_ascii_encoding); - return false; - } - if (!prt_open_resource(&res_encoding)) { - return false; - } - // For the moment there are no checks on encoding resource files to - // perform - } - } - - prt_conv.vc_type = CONV_NONE; - if (!(enc_canon_props(p_enc) & enc_canon_props((char_u *)p_encoding) & ENC_8BIT)) { - // Set up encoding conversion if required - if (convert_setup(&prt_conv, p_enc, (char_u *)p_encoding) == FAIL) { - semsg(_("E620: Unable to convert to print encoding \"%s\""), - p_encoding); - return false; - } - } - prt_do_conv = prt_conv.vc_type != CONV_NONE; - - if (prt_out_mbyte && prt_custom_cmap) { - // Find user supplied CMap - if (!prt_find_resource(prt_cmap, &res_cmap)) { - semsg(_("E456: Can't find PostScript resource file \"%s.ps\""), - prt_cmap); - return false; - } - if (!prt_open_resource(&res_cmap)) { - return false; - } - } - - // List resources supplied - STRCPY(buffer, res_prolog.title); - STRCAT(buffer, " "); - STRCAT(buffer, res_prolog.version); - prt_dsc_resources("DocumentSuppliedResources", "procset", buffer); - if (prt_out_mbyte) { - STRCPY(buffer, res_cidfont.title); - STRCAT(buffer, " "); - STRCAT(buffer, res_cidfont.version); - prt_dsc_resources(NULL, "procset", buffer); - - if (prt_custom_cmap) { - STRCPY(buffer, res_cmap.title); - STRCAT(buffer, " "); - STRCAT(buffer, res_cmap.version); - prt_dsc_resources(NULL, "cmap", buffer); - } - } - if (!prt_out_mbyte || prt_use_courier) { - STRCPY(buffer, res_encoding.title); - STRCAT(buffer, " "); - STRCAT(buffer, res_encoding.version); - prt_dsc_resources(NULL, "encoding", buffer); - } - prt_dsc_requirements(prt_duplex, prt_tumble, prt_collate, - psettings->do_syntax, - prt_num_copies); - prt_dsc_noarg("EndComments"); - - /* - * PS Document page defaults - */ - prt_dsc_noarg("BeginDefaults"); - - // List font resources most likely common to all pages - if (!prt_out_mbyte || prt_use_courier) { - prt_dsc_font_resource("PageResources", &prt_ps_courier_font); - } - if (prt_out_mbyte) { - prt_dsc_font_resource((prt_use_courier ? NULL : "PageResources"), - &prt_ps_mb_font); - if (!prt_custom_cmap) { - prt_dsc_resources(NULL, "cmap", prt_cmap); - } - } - - // Paper will be used for all pages - prt_dsc_textline("PageMedia", prt_mediasize[prt_media].name); - - prt_dsc_noarg("EndDefaults"); - - /* - * PS Document prolog inclusion - all required procsets. - */ - prt_dsc_noarg("BeginProlog"); - - // Add required procsets - NOTE: order is important! - if (!prt_add_resource(&res_prolog)) { - return false; - } - if (prt_out_mbyte) { - // Add CID font procset, and any user supplied CMap - if (!prt_add_resource(&res_cidfont)) { - return false; - } - if (prt_custom_cmap && !prt_add_resource(&res_cmap)) { - return false; - } - } - - if (!prt_out_mbyte || prt_use_courier) { - // There will be only one Roman font encoding to be included in the PS - // file. - if (!prt_add_resource(&res_encoding)) { - return false; - } - } - - prt_dsc_noarg("EndProlog"); - - /* - * PS Document setup - must appear after the prolog - */ - prt_dsc_noarg("BeginSetup"); - - // Device setup - page size and number of uncollated copies - prt_write_int((int)prt_mediasize[prt_media].width); - prt_write_int((int)prt_mediasize[prt_media].height); - prt_write_int(0); - prt_write_string("sps\n"); - prt_write_int(prt_num_copies); - prt_write_string("nc\n"); - prt_write_boolean(prt_duplex); - prt_write_boolean(prt_tumble); - prt_write_string("dt\n"); - prt_write_boolean(prt_collate); - prt_write_string("c\n"); - - // Font resource inclusion and definition - if (!prt_out_mbyte || prt_use_courier) { - // When using Courier for ASCII range when printing multi-byte, need to - // pick up ASCII encoding to use with it. - if (prt_use_courier) { - p_encoding = prt_ascii_encoding; - } - prt_dsc_resources("IncludeResource", "font", - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_ROMAN]); - prt_def_font("F0", p_encoding, (int)prt_line_height, - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_ROMAN]); - prt_dsc_resources("IncludeResource", "font", - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_BOLD]); - prt_def_font("F1", p_encoding, (int)prt_line_height, - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_BOLD]); - prt_dsc_resources("IncludeResource", "font", - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_OBLIQUE]); - prt_def_font("F2", p_encoding, (int)prt_line_height, - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_OBLIQUE]); - prt_dsc_resources("IncludeResource", "font", - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]); - prt_def_font("F3", p_encoding, (int)prt_line_height, - prt_ps_courier_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]); - } - if (prt_out_mbyte) { - // Define the CID fonts to be used in the job. Typically CJKV fonts do - // not have an italic form being a western style, so where no font is - // defined for these faces VIM falls back to an existing face. - // Note: if using Courier for the ASCII range then the printout will - // have bold/italic/bolditalic regardless of the setting of printmbfont. - prt_dsc_resources("IncludeResource", "font", - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_ROMAN]); - if (!prt_custom_cmap) { - prt_dsc_resources("IncludeResource", "cmap", prt_cmap); - } - prt_def_cidfont("CF0", (int)prt_line_height, - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_ROMAN]); - - if (prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLD] != NULL) { - prt_dsc_resources("IncludeResource", "font", - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLD]); - if (!prt_custom_cmap) { - prt_dsc_resources("IncludeResource", "cmap", prt_cmap); - } - prt_def_cidfont("CF1", (int)prt_line_height, - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLD]); - } else { - // Use ROMAN for BOLD - prt_dup_cidfont("CF0", "CF1"); - } - if (prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE] != NULL) { - prt_dsc_resources("IncludeResource", "font", - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]); - if (!prt_custom_cmap) { - prt_dsc_resources("IncludeResource", "cmap", prt_cmap); - } - prt_def_cidfont("CF2", (int)prt_line_height, - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_OBLIQUE]); - } else { - // Use ROMAN for OBLIQUE - prt_dup_cidfont("CF0", "CF2"); - } - if (prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE] != NULL) { - prt_dsc_resources("IncludeResource", "font", - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]); - if (!prt_custom_cmap) { - prt_dsc_resources("IncludeResource", "cmap", prt_cmap); - } - prt_def_cidfont("CF3", (int)prt_line_height, - prt_ps_mb_font.ps_fontname[PRT_PS_FONT_BOLDOBLIQUE]); - } else { - // Use BOLD for BOLDOBLIQUE - prt_dup_cidfont("CF1", "CF3"); - } - } - - // Misc constant vars used for underlining and background rects - prt_def_var("UO", PRT_PS_FONT_TO_USER(prt_line_height, - prt_ps_font->uline_offset), 2); - prt_def_var("UW", PRT_PS_FONT_TO_USER(prt_line_height, - prt_ps_font->uline_width), 2); - prt_def_var("BO", prt_bgcol_offset, 2); - - prt_dsc_noarg("EndSetup"); - - // Fail if any problems writing out to the PS file - return !prt_file_error; -} - -void mch_print_end(prt_settings_T *psettings) -{ - prt_dsc_noarg("Trailer"); - - /* - * Output any info we don't know in toto until we finish - */ - prt_dsc_ints("Pages", 1, &prt_page_num); - - prt_dsc_noarg("EOF"); - - // Write CTRL-D to close serial communication link if used. - // NOTHING MUST BE WRITTEN AFTER THIS! - prt_write_file("\004"); - - if (!prt_file_error && psettings->outfile == NULL - && !got_int && !psettings->user_abort) { - // Close the file first. - if (prt_ps_fd != NULL) { - fclose(prt_ps_fd); - prt_ps_fd = NULL; - } - prt_message((char_u *)_("Sending to printer...")); - - // Not printing to a file: use 'printexpr' to print the file. - if (eval_printexpr((char *)prt_ps_file_name, (char *)psettings->arguments) - == FAIL) { - emsg(_("E365: Failed to print PostScript file")); - } else { - prt_message((char_u *)_("Print job sent.")); - } - } - - mch_print_cleanup(); -} - -int mch_print_end_page(void) -{ - prt_flush_buffer(); - - prt_write_string("re sp\n"); - - prt_dsc_noarg("PageTrailer"); - - return !prt_file_error; -} - -int mch_print_begin_page(char_u *str) -{ - int page_num[2]; - - prt_page_num++; - - page_num[0] = page_num[1] = prt_page_num; - prt_dsc_ints("Page", 2, page_num); - - prt_dsc_noarg("BeginPageSetup"); - - prt_write_string("sv\n0 g\n"); - prt_in_ascii = !prt_out_mbyte; - if (prt_out_mbyte) { - prt_write_string("CF0 sf\n"); - } else { - prt_write_string("F0 sf\n"); - } - prt_fgcol = PRCOLOR_BLACK; - prt_bgcol = PRCOLOR_WHITE; - prt_font = PRT_PS_FONT_ROMAN; - - // Set up page transformation for landscape printing. - if (!prt_portrait) { - prt_write_int(-((int)prt_mediasize[prt_media].width)); - prt_write_string("sl\n"); - } - - prt_dsc_noarg("EndPageSetup"); - - // We have reset the font attributes, force setting them again. - curr_bg = 0xffffffff; - curr_fg = 0xffffffff; - curr_bold = kNone; - - return !prt_file_error; -} - -int mch_print_blank_page(void) -{ - return mch_print_begin_page(NULL) ? (mch_print_end_page()) : FALSE; -} - -static double prt_pos_x = 0; -static double prt_pos_y = 0; - -void mch_print_start_line(const bool margin, const int page_line) -{ - prt_pos_x = prt_left_margin; - if (margin) { - prt_pos_x -= prt_number_width; - } - - prt_pos_y = prt_top_margin - prt_first_line_height - - page_line * prt_line_height; - - prt_attribute_change = true; - prt_need_moveto = true; - prt_half_width = false; -} - -int mch_print_text_out(char_u *const textp, size_t len) -{ - char_u *p = textp; - char_u ch; - char_u ch_buff[8]; - char_u *tofree = NULL; - double char_width = prt_char_width; - - // Ideally VIM would create a rearranged CID font to combine a Roman and - // CJKV font to do what VIM is doing here - use a Roman font for characters - // in the ASCII range, and the original CID font for everything else. - // The problem is that GhostScript still (as of 8.13) does not support - // rearranged fonts even though they have been documented by Adobe for 7 - // years! If they ever do, a lot of this code will disappear. - if (prt_use_courier) { - const bool in_ascii = (len == 1 && *p < 0x80); - if (prt_in_ascii) { - if (!in_ascii) { - // No longer in ASCII range - need to switch font - prt_in_ascii = false; - prt_need_font = true; - prt_attribute_change = true; - } - } else if (in_ascii) { - // Now in ASCII range - need to switch font - prt_in_ascii = true; - prt_need_font = true; - prt_attribute_change = true; - } - } - if (prt_out_mbyte) { - const bool half_width = (utf_ptr2cells((char *)p) == 1); - if (half_width) { - char_width /= 2; - } - if (prt_half_width) { - if (!half_width) { - prt_half_width = false; - prt_pos_x += prt_char_width/4; - prt_need_moveto = true; - prt_attribute_change = true; - } - } else if (half_width) { - prt_half_width = true; - prt_pos_x += prt_char_width/4; - prt_need_moveto = true; - prt_attribute_change = true; - } - } - - // Output any required changes to the graphics state, after flushing any - // text buffered so far. - if (prt_attribute_change) { - prt_flush_buffer(); - // Reset count of number of chars that will be printed - prt_text_run = 0; - - if (prt_need_moveto) { - prt_pos_x_moveto = prt_pos_x; - prt_pos_y_moveto = prt_pos_y; - prt_do_moveto = true; - - prt_need_moveto = false; - } - if (prt_need_font) { - if (!prt_in_ascii) { - prt_write_string("CF"); - } else { - prt_write_string("F"); - } - prt_write_int(prt_font); - prt_write_string("sf\n"); - prt_need_font = false; - } - if (prt_need_fgcol) { - unsigned int r, g, b; - r = (prt_fgcol & 0xff0000) >> 16; - g = (prt_fgcol & 0xff00) >> 8; - b = prt_fgcol & 0xff; - - prt_write_real(r / 255.0, 3); - if (r == g && g == b) { - prt_write_string("g\n"); - } else { - prt_write_real(g / 255.0, 3); - prt_write_real(b / 255.0, 3); - prt_write_string("r\n"); - } - prt_need_fgcol = false; - } - - if (prt_bgcol != PRCOLOR_WHITE) { - prt_new_bgcol = prt_bgcol; - if (prt_need_bgcol) { - prt_do_bgcol = true; - } - } else { - prt_do_bgcol = false; - } - prt_need_bgcol = false; - - if (prt_need_underline) { - prt_do_underline = prt_underline; - } - prt_need_underline = false; - - prt_attribute_change = false; - } - - if (prt_do_conv) { - // Convert from multi-byte to 8-bit encoding - tofree = p = string_convert(&prt_conv, p, &len); - if (p == NULL) { - p = (char_u *)""; - len = 0; - } - } - - if (prt_out_mbyte) { - // Multi-byte character strings are represented more efficiently as hex - // strings when outputting clean 8 bit PS. - while (len-- > 0) { - ch = prt_hexchar[(unsigned)(*p) >> 4]; - ga_append(&prt_ps_buffer, (char)ch); - ch = prt_hexchar[(*p) & 0xf]; - ga_append(&prt_ps_buffer, (char)ch); - p++; - } - } else { - // Add next character to buffer of characters to output. - // Note: One printed character may require several PS characters to - // represent it, but we only count them as one printed character. - ch = *p; - if (ch < 32 || ch == '(' || ch == ')' || ch == '\\') { - // Convert non-printing characters to either their escape or octal - // sequence, ensures PS sent over a serial line does not interfere - // with the comms protocol. - ga_append(&prt_ps_buffer, '\\'); - switch (ch) { - case BS: - ga_append(&prt_ps_buffer, 'b'); break; - case TAB: - ga_append(&prt_ps_buffer, 't'); break; - case NL: - ga_append(&prt_ps_buffer, 'n'); break; - case FF: - ga_append(&prt_ps_buffer, 'f'); break; - case CAR: - ga_append(&prt_ps_buffer, 'r'); break; - case '(': - ga_append(&prt_ps_buffer, '('); break; - case ')': - ga_append(&prt_ps_buffer, ')'); break; - case '\\': - ga_append(&prt_ps_buffer, '\\'); break; - - default: - sprintf((char *)ch_buff, "%03o", (unsigned int)ch); - ga_append(&prt_ps_buffer, (char)ch_buff[0]); - ga_append(&prt_ps_buffer, (char)ch_buff[1]); - ga_append(&prt_ps_buffer, (char)ch_buff[2]); - break; - } - } else { - ga_append(&prt_ps_buffer, (char)ch); - } - } - - // Need to free any translated characters - xfree(tofree); - - prt_text_run += char_width; - prt_pos_x += char_width; - - // The downside of fp - use relative error on right margin check - const double next_pos = prt_pos_x + prt_char_width; - const bool need_break = (next_pos > prt_right_margin) - && ((next_pos - prt_right_margin) > (prt_right_margin * 1e-5)); - - if (need_break) { - prt_flush_buffer(); - } - - return need_break; -} - -void mch_print_set_font(const TriState iBold, const TriState iItalic, const TriState iUnderline) -{ - int font = 0; - - if (iBold) { - font |= 0x01; - } - if (iItalic) { - font |= 0x02; - } - - if (font != prt_font) { - prt_font = font; - prt_attribute_change = true; - prt_need_font = true; - } - if (prt_underline != iUnderline) { - prt_underline = iUnderline; - prt_attribute_change = true; - prt_need_underline = true; - } -} - -void mch_print_set_bg(uint32_t bgcol) -{ - prt_bgcol = bgcol; - prt_attribute_change = true; - prt_need_bgcol = true; -} - -void mch_print_set_fg(uint32_t fgcol) -{ - if (fgcol != prt_fgcol) { - prt_fgcol = fgcol; - prt_attribute_change = true; - prt_need_fgcol = true; - } -} diff --git a/src/nvim/hardcopy.h b/src/nvim/hardcopy.h deleted file mode 100644 index 9ef4eb0074..0000000000 --- a/src/nvim/hardcopy.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef NVIM_HARDCOPY_H -#define NVIM_HARDCOPY_H - -#include <stdint.h> -#include <stdlib.h> // for size_t - -#include "nvim/ex_cmds_defs.h" // for exarg_T -#include "nvim/globals.h" // for TriState -#include "nvim/types.h" // for char_u - -/* - * Structure to hold printing color and font attributes. - */ -typedef struct { - uint32_t fg_color; - uint32_t bg_color; - TriState bold; - TriState italic; - TriState underline; - int undercurl; - int underdouble; - int underdotted; - int underdashed; -} prt_text_attr_T; - -/* - * Structure passed back to the generic printer code. - */ -typedef struct { - int n_collated_copies; - int n_uncollated_copies; - int duplex; - int chars_per_line; - int lines_per_page; - int has_color; - prt_text_attr_T number; - int modec; - int do_syntax; - int user_abort; - char_u *jobname; - char_u *outfile; - char_u *arguments; -} prt_settings_T; - -/* - * Generic option table item, only used for printer at the moment. - */ -typedef struct { - const char *name; - int hasnum; - int number; - char_u *string; // points into option string - int strlen; - int present; -} option_table_T; - -#define OPT_PRINT_TOP 0 -#define OPT_PRINT_BOT 1 -#define OPT_PRINT_LEFT 2 -#define OPT_PRINT_RIGHT 3 -#define OPT_PRINT_HEADERHEIGHT 4 -#define OPT_PRINT_SYNTAX 5 -#define OPT_PRINT_NUMBER 6 -#define OPT_PRINT_WRAP 7 -#define OPT_PRINT_DUPLEX 8 -#define OPT_PRINT_PORTRAIT 9 -#define OPT_PRINT_PAPER 10 -#define OPT_PRINT_COLLATE 11 -#define OPT_PRINT_JOBSPLIT 12 -#define OPT_PRINT_FORMFEED 13 -#define OPT_PRINT_NUM_OPTIONS 14 - -// For prt_get_unit(). -#define PRT_UNIT_NONE -1 -#define PRT_UNIT_PERC 0 -#define PRT_UNIT_INCH 1 -#define PRT_UNIT_MM 2 -#define PRT_UNIT_POINT 3 -#define PRT_UNIT_NAMES { "pc", "in", "mm", "pt" } - -#define PRINT_NUMBER_WIDTH 8 - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "hardcopy.h.generated.h" -#endif -#endif // NVIM_HARDCOPY_H diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index 951e72ea52..851e70caca 100644 --- a/src/nvim/hashtab.c +++ b/src/nvim/hashtab.c @@ -30,6 +30,7 @@ #include "nvim/hashtab.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/types.h" #include "nvim/vim.h" // Magic value for algorithm that walks through the array. @@ -67,7 +68,7 @@ void hash_clear(hashtab_T *ht) void hash_clear_all(hashtab_T *ht, unsigned int off) { size_t todo = ht->ht_used; - for (hashitem_T *hi = ht->ht_array; todo > 0; ++hi) { + for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { xfree(hi->hi_key - off); todo--; @@ -87,7 +88,7 @@ void hash_clear_all(hashtab_T *ht, unsigned int off) /// is changed in any way. hashitem_T *hash_find(const hashtab_T *const ht, const char *const key) { - return hash_lookup(ht, key, STRLEN(key), hash_hash((char_u *)key)); + return hash_lookup(ht, key, strlen(key), hash_hash(key)); } /// Like hash_find, but key is not NUL-terminated @@ -140,7 +141,7 @@ hashitem_T *hash_lookup(const hashtab_T *const ht, const char *const key, const if (hi->hi_key == HI_KEY_REMOVED) { freeitem = hi; } else if ((hi->hi_hash == hash) - && (STRNCMP(hi->hi_key, key, key_len) == 0) + && (strncmp(hi->hi_key, key, key_len) == 0) && hi->hi_key[key_len] == NUL) { return hi; } @@ -166,7 +167,7 @@ hashitem_T *hash_lookup(const hashtab_T *const ht, const char *const key, const if ((hi->hi_hash == hash) && (hi->hi_key != HI_KEY_REMOVED) - && (STRNCMP(hi->hi_key, key, key_len) == 0) + && (strncmp(hi->hi_key, key, key_len) == 0) && hi->hi_key[key_len] == NUL) { return hi; } @@ -194,17 +195,17 @@ void hash_debug_results(void) #endif // ifdef HT_DEBUG } -/// Add item for key "key" to hashtable "ht". +/// Add (empty) item for key `key` to hashtable `ht`. /// /// @param key Pointer to the key for the new item. The key has to be contained /// in the new item (@see hashitem_T). Must not be NULL. /// /// @return OK if success. /// FAIL if key already present -int hash_add(hashtab_T *ht, char_u *key) +int hash_add(hashtab_T *ht, char *key) { hash_T hash = hash_hash(key); - hashitem_T *hi = hash_lookup(ht, (const char *)key, STRLEN(key), hash); + hashitem_T *hi = hash_lookup(ht, key, strlen(key), hash); if (!HASHITEM_EMPTY(hi)) { internal_error("hash_add()"); return FAIL; @@ -220,9 +221,10 @@ int hash_add(hashtab_T *ht, char_u *key) /// @param key Pointer to the key for the new item. The key has to be contained /// in the new item (@see hashitem_T). Must not be NULL. /// @param hash The precomputed hash value for the key. -void hash_add_item(hashtab_T *ht, hashitem_T *hi, char_u *key, hash_T hash) +void hash_add_item(hashtab_T *ht, hashitem_T *hi, char *key, hash_T hash) { ht->ht_used++; + ht->ht_changed++; if (hi->hi_key == NULL) { ht->ht_filled++; } @@ -242,6 +244,7 @@ void hash_add_item(hashtab_T *ht, hashitem_T *hi, char_u *key, hash_T hash) void hash_remove(hashtab_T *ht, hashitem_T *hi) { ht->ht_used--; + ht->ht_changed++; hi->hi_key = HI_KEY_REMOVED; hash_may_resize(ht, 0); } @@ -265,7 +268,7 @@ void hash_unlock(hashtab_T *ht) hash_may_resize(ht, 0); } -/// Resize hastable (new size can be given or automatically computed). +/// Resize hashtable (new size can be given or automatically computed). /// /// @param minitems Minimum number of items the new table should hold. /// If zero, new size will depend on currently used items: @@ -290,6 +293,7 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) #endif // ifdef HT_DEBUG size_t minsize; + const size_t oldsize = ht->ht_mask + 1; if (minitems == 0) { // Return quickly for small tables with at least two NULL items. // items are required for the lookup to decide a key isn't there. @@ -302,7 +306,6 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) // removed items, so that they get cleaned up). // Shrink the array when it's less than 1/5 full. When growing it is // at least 1/4 full (avoids repeated grow-shrink operations) - size_t oldsize = ht->ht_mask + 1; if ((ht->ht_filled * 3 < oldsize * 2) && (ht->ht_used > oldsize / 5)) { return; } @@ -333,6 +336,13 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) } bool newarray_is_small = newsize == HT_INIT_SIZE; + + if (!newarray_is_small && newsize == oldsize && ht->ht_filled * 3 < oldsize * 2) { + // The hashtab is already at the desired size, and there are not too + // many removed items, bail out. + return; + } + bool keep_smallarray = newarray_is_small && ht->ht_array == ht->ht_smallarray; @@ -356,7 +366,7 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) hash_T newmask = newsize - 1; size_t todo = ht->ht_used; - for (hashitem_T *olditem = oldarray; todo > 0; ++olditem) { + for (hashitem_T *olditem = oldarray; todo > 0; olditem++) { if (HASHITEM_EMPTY(olditem)) { continue; } @@ -384,6 +394,7 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) ht->ht_array = newarray; ht->ht_mask = newmask; ht->ht_filled = ht->ht_used; + ht->ht_changed++; } #define HASH_CYCLE_BODY(hash, p) \ @@ -395,9 +406,9 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems) /// run a script that uses hashtables a lot. Vim will then print statistics /// when exiting. Try that with the current hash algorithm and yours. The /// lower the percentage the better. -hash_T hash_hash(const char_u *key) +hash_T hash_hash(const char *key) { - hash_T hash = *key; + hash_T hash = (uint8_t)(*key); if (hash == 0) { return (hash_T)0; @@ -405,7 +416,7 @@ hash_T hash_hash(const char_u *key) // A simplistic algorithm that appears to do very well. // Suggested by George Reilly. - const uint8_t *p = key + 1; + const uint8_t *p = (uint8_t *)key + 1; while (*p != NUL) { HASH_CYCLE_BODY(hash, p); } @@ -449,5 +460,5 @@ hash_T hash_hash_len(const char *key, const size_t len) const char_u *_hash_key_removed(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return HI_KEY_REMOVED; + return (char_u *)HI_KEY_REMOVED; } diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h index 7740a3e431..0a50fb2ef8 100644 --- a/src/nvim/hashtab.h +++ b/src/nvim/hashtab.h @@ -15,9 +15,9 @@ typedef size_t hash_T; /// The address of "hash_removed" is used as a magic number /// for hi_key to indicate a removed item. -#define HI_KEY_REMOVED ((char_u *)&hash_removed) +#define HI_KEY_REMOVED (&hash_removed) #define HASHITEM_EMPTY(hi) ((hi)->hi_key == NULL \ - || (hi)->hi_key == (char_u *)&hash_removed) + || (hi)->hi_key == &hash_removed) /// Hashtable item. /// @@ -45,7 +45,7 @@ typedef struct hashitem_S { /// NULL : Item was never used. /// HI_KEY_REMOVED : Item was removed. /// (Any other pointer value) : Item is currently being used. - char_u *hi_key; + char *hi_key; } hashitem_T; /// Initial size for a hashtable. @@ -61,14 +61,15 @@ typedef struct hashitem_S { /// /// The hashtable grows to accommodate more entries when needed. typedef struct hashtable_S { - hash_T ht_mask; /// mask used for hash value - /// (nr of items in array is "ht_mask" + 1) - size_t ht_used; /// number of items used - size_t ht_filled; /// number of items used or removed - int ht_locked; /// counter for hash_lock() - hashitem_T *ht_array; /// points to the array, allocated when it's - /// not "ht_smallarray" - hashitem_T ht_smallarray[HT_INIT_SIZE]; /// initial array + hash_T ht_mask; ///< mask used for hash value + ///< (nr of items in array is "ht_mask" + 1) + size_t ht_used; ///< number of items used + size_t ht_filled; ///< number of items used or removed + int ht_changed; ///< incremented when adding or removing an item + int ht_locked; ///< counter for hash_lock() + hashitem_T *ht_array; ///< points to the array, allocated when it's + ///< not "ht_smallarray" + hashitem_T ht_smallarray[HT_INIT_SIZE]; ///< initial array } hashtab_T; /// Iterate over a hashtab diff --git a/src/nvim/help.c b/src/nvim/help.c index 442f2e0b7b..bbc552fa4c 100644 --- a/src/nvim/help.c +++ b/src/nvim/help.c @@ -3,26 +3,39 @@ // help.c: functions for Vim help +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> +#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/fileio.h" #include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/help.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/optionstr.h" #include "nvim/os/input.h" +#include "nvim/os/os.h" #include "nvim/path.h" +#include "nvim/pos.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" +#include "nvim/types.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -75,7 +88,7 @@ void ex_help(exarg_T *eap) } // remove trailing blanks - p = arg + STRLEN(arg) - 1; + p = arg + strlen(arg) - 1; while (p > arg && ascii_iswhite(*p) && p[-1] != '\\') { *p-- = NUL; } @@ -95,7 +108,7 @@ void ex_help(exarg_T *eap) if (n != FAIL && lang != NULL) { // Find first item with the requested language. for (i = 0; i < num_matches; i++) { - len = (int)STRLEN(matches[i]); + len = (int)strlen(matches[i]); if (len > 3 && matches[i][len - 3] == '@' && STRICMP(matches[i] + len - 2, lang) == 0) { break; @@ -137,7 +150,7 @@ void ex_help(exarg_T *eap) } else { // There is no help window yet. // Try to open the file specified by the "helpfile" option. - if ((helpfd = os_fopen((char *)p_hf, READBIN)) == NULL) { + if ((helpfd = os_fopen(p_hf, READBIN)) == NULL) { smsg(_("Sorry, help file \"%s\" not found"), p_hf); goto erret; } @@ -149,7 +162,7 @@ void ex_help(exarg_T *eap) n = WSP_HELP; if (cmdmod.cmod_split == 0 && curwin->w_width != Columns && curwin->w_width < 80) { - n |= WSP_TOP; + n |= p_sb ? WSP_BOT : WSP_TOP; } if (win_split(0, n) == FAIL) { goto erret; @@ -180,7 +193,7 @@ void ex_help(exarg_T *eap) // It is needed for do_tag top open folds under the cursor. KeyTyped = old_KeyTyped; - do_tag((char_u *)tag, DT_HELP, 1, false, true); + do_tag(tag, DT_HELP, 1, false, true); // Delete the empty buffer if we're not using it. Careful: autocommands // may have jumped to another window, check that the buffer is not in a @@ -219,7 +232,7 @@ void ex_helpclose(exarg_T *eap) /// @return NULL if not found. char *check_help_lang(char *arg) { - int len = (int)STRLEN(arg); + int len = (int)strlen(arg); if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2]) && ASCII_ISALPHA(arg[len - 1])) { @@ -278,7 +291,7 @@ int help_heuristic(char *matched_string, int offset, int wrong_case) if (matched_string[0] == '+' && matched_string[1] != NUL) { offset += 100; } - return 100 * num_letters + (int)STRLEN(matched_string) + offset; + return 100 * num_letters + (int)strlen(matched_string) + offset; } /// Compare functions for qsort() below, that checks the help heuristics number @@ -359,7 +372,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep "!=?", "!~?", "<=?", "<?", "==?", "=~?", ">=?", ">?", "is?", "isnot?" }; - char *d = (char *)IObuff; // assume IObuff is long enough! + char *d = IObuff; // assume IObuff is long enough! d[0] = NUL; if (STRNICMP(arg, "expr-", 5) == 0) { @@ -367,7 +380,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep // the table, it is taken literally (but ~ is escaped). Otherwise '?' // is recognized as a wildcard. for (i = (int)ARRAY_SIZE(expr_table); --i >= 0;) { - if (STRCMP(arg + 5, expr_table[i]) == 0) { + if (strcmp(arg + 5, expr_table[i]) == 0) { for (int si = 0, di = 0;; si++) { if (arg[si] == '~') { d[di++] = '\\'; @@ -384,7 +397,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep // Recognize a few exceptions to the rule. Some strings that contain // '*'are changed to "star", otherwise '*' is recognized as a wildcard. for (i = 0; except_tbl[i][0] != NULL; i++) { - if (STRCMP(arg, except_tbl[i][0]) == 0) { + if (strcmp(arg, except_tbl[i][0]) == 0) { STRCPY(d, except_tbl[i][1]); break; } @@ -399,7 +412,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep // And also "\_$" and "\_^". if (arg[0] == '\\' && ((arg[1] != NUL && arg[2] == NUL) - || (vim_strchr("%_z@", arg[1]) != NULL + || (vim_strchr("%_z@", (uint8_t)arg[1]) != NULL && arg[2] != NUL))) { vim_snprintf(d, IOSIZE, "/\\\\%s", arg + 1); // Check for "/\\_$", should be "/\\_\$" @@ -428,7 +441,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep // completion. // Insert a backslash before '~', '$' and '.' to avoid their // special meaning. - if ((char_u *)d - IObuff > IOSIZE - 10) { // getting too long!? + if (d - IObuff > IOSIZE - 10) { // getting too long!? break; } switch (*s) { @@ -458,8 +471,8 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep // Insert '-' before and after "CTRL-X" when applicable. if (*s < ' ' || (*s == '^' && s[1] - && (ASCII_ISALPHA(s[1]) || vim_strchr("?@[\\]^", s[1]) != NULL))) { - if ((char_u *)d > IObuff && d[-1] != '_' && d[-1] != '\\') { + && (ASCII_ISALPHA(s[1]) || vim_strchr("?@[\\]^", (uint8_t)s[1]) != NULL))) { + if (d > IObuff && d[-1] != '_' && d[-1] != '\\') { *d++ = '_'; // prepend a '_' to make x_CTRL-x } STRCPY(d, "CTRL-"); @@ -513,18 +526,18 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep *d = NUL; if (*IObuff == '`') { - if ((char_u *)d > IObuff + 2 && d[-1] == '`') { + if (d > IObuff + 2 && d[-1] == '`') { // remove the backticks from `command` - memmove(IObuff, IObuff + 1, STRLEN(IObuff)); + memmove(IObuff, IObuff + 1, strlen(IObuff)); d[-2] = NUL; - } else if ((char_u *)d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') { + } else if (d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') { // remove the backticks and comma from `command`, - memmove(IObuff, IObuff + 1, STRLEN(IObuff)); + memmove(IObuff, IObuff + 1, strlen(IObuff)); d[-3] = NUL; - } else if ((char_u *)d > IObuff + 4 && d[-3] == '`' + } else if (d > IObuff + 4 && d[-3] == '`' && d[-2] == '\\' && d[-1] == '.') { // remove the backticks and dot from `command`\. - memmove(IObuff, IObuff + 1, STRLEN(IObuff)); + memmove(IObuff, IObuff + 1, strlen(IObuff)); d[-4] = NUL; } } @@ -542,7 +555,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep // Sort the matches found on the heuristic number that is after the // tag name. qsort((void *)(*matches), (size_t)(*num_matches), - sizeof(char_u *), help_compare); + sizeof(char *), help_compare); // Delete more than TAG_MANY to reduce the size of the listing. while (*num_matches > TAG_MANY) { xfree((*matches)[--*num_matches]); @@ -556,8 +569,8 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep /// tag matches it. Otherwise remove "@en" if "en" is the only language. void cleanup_help_tags(int num_file, char **file) { - char_u buf[4]; - char_u *p = buf; + char buf[4]; + char *p = buf; if (p_hlg[0] != NUL && (p_hlg[0] != 'e' || p_hlg[1] != 'n')) { *p++ = '@'; @@ -567,18 +580,18 @@ void cleanup_help_tags(int num_file, char **file) *p = NUL; for (int i = 0; i < num_file; i++) { - int len = (int)STRLEN(file[i]) - 3; + int len = (int)strlen(file[i]) - 3; if (len <= 0) { continue; } - if (STRCMP(file[i] + len, "@en") == 0) { + if (strcmp(file[i] + len, "@en") == 0) { // Sorting on priority means the same item in another language may // be anywhere. Search all items for a match up to the "@en". int j; for (j = 0; j < num_file; j++) { if (j != i - && (int)STRLEN(file[j]) == len + 3 - && STRNCMP(file[i], file[j], len + 1) == 0) { + && (int)strlen(file[j]) == len + 3 + && strncmp(file[i], file[j], (size_t)len + 1) == 0) { break; } } @@ -591,11 +604,11 @@ void cleanup_help_tags(int num_file, char **file) if (*buf != NUL) { for (int i = 0; i < num_file; i++) { - int len = (int)STRLEN(file[i]) - 3; + int len = (int)strlen(file[i]) - 3; if (len <= 0) { continue; } - if (STRCMP(file[i] + len, buf) == 0) { + if (strcmp(file[i] + len, buf) == 0) { // remove the default language file[i][len] = NUL; } @@ -615,7 +628,7 @@ void prepare_help_buffer(void) // latin1 word characters (for translated help files). // Only set it when needed, buf_init_chartab() is some work. char *p = "!-~,^*,^|,^\",192-255"; - if (STRCMP(curbuf->b_p_isk, p) != 0) { + if (strcmp(curbuf->b_p_isk, p) != 0) { set_string_option_direct("isk", -1, p, OPT_FREE|OPT_LOCAL, 0); check_buf_options(curbuf); (void)buf_init_chartab(curbuf, false); @@ -650,21 +663,21 @@ void fix_help_buffer(void) bool in_example = false; // Set filetype to "help". - if (STRCMP(curbuf->b_p_ft, "help") != 0) { + if (strcmp(curbuf->b_p_ft, "help") != 0) { curbuf->b_ro_locked++; - set_option_value("ft", 0L, "help", OPT_LOCAL); + set_option_value_give_err("ft", 0L, "help", OPT_LOCAL); curbuf->b_ro_locked--; } if (!syntax_present(curwin)) { for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) { - line = (char *)ml_get_buf(curbuf, lnum, false); - const size_t len = STRLEN(line); + line = ml_get_buf(curbuf, lnum, false); + const size_t len = strlen(line); if (in_example && len > 0 && !ascii_iswhite(line[0])) { // End of example: non-white or '<' in first column. if (line[0] == '<') { // blank-out a '<' in the first column - line = (char *)ml_get_buf(curbuf, lnum, true); + line = ml_get_buf(curbuf, lnum, true); line[0] = ' '; } in_example = false; @@ -672,12 +685,12 @@ void fix_help_buffer(void) if (!in_example && len > 0) { if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) { // blank-out a '>' in the last column (start of example) - line = (char *)ml_get_buf(curbuf, lnum, true); + line = ml_get_buf(curbuf, lnum, true); line[len - 1] = ' '; in_example = true; } else if (line[len - 1] == '~') { // blank-out a '~' at the end of line (header marker) - line = (char *)ml_get_buf(curbuf, lnum, true); + line = ml_get_buf(curbuf, lnum, true); line[len - 1] = ' '; } } @@ -687,26 +700,26 @@ void fix_help_buffer(void) // In the "help.txt" and "help.abx" file, add the locally added help // files. This uses the very first line in the help file. char *const fname = path_tail(curbuf->b_fname); - if (FNAMECMP(fname, "help.txt") == 0 - || (FNAMENCMP(fname, "help.", 5) == 0 + if (path_fnamecmp(fname, "help.txt") == 0 + || (path_fnamencmp(fname, "help.", 5) == 0 && ASCII_ISALPHA(fname[5]) && ASCII_ISALPHA(fname[6]) && TOLOWER_ASC(fname[7]) == 'x' && fname[8] == NUL)) { for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; lnum++) { - line = (char *)ml_get_buf(curbuf, lnum, false); + line = ml_get_buf(curbuf, lnum, false); if (strstr(line, "*local-additions*") == NULL) { continue; } // Go through all directories in 'runtimepath', skipping // $VIMRUNTIME. - char *p = (char *)p_rtp; + char *p = p_rtp; while (*p != NUL) { - copy_option_part(&p, (char *)NameBuff, MAXPATHL, ","); + copy_option_part(&p, NameBuff, MAXPATHL, ","); char *const rt = vim_getenv("VIMRUNTIME"); if (rt != NULL - && path_full_compare(rt, (char *)NameBuff, false, true) != kEqualFiles) { + && path_full_compare(rt, NameBuff, false, true) != kEqualFiles) { int fcount; char **fnames; char *s; @@ -714,50 +727,48 @@ void fix_help_buffer(void) char *cp; // Find all "doc/ *.txt" files in this directory. - if (!add_pathsep((char *)NameBuff) - || STRLCAT(NameBuff, "doc/*.??[tx]", // NOLINT - sizeof(NameBuff)) >= MAXPATHL) { + if (!add_pathsep(NameBuff) + || xstrlcat(NameBuff, "doc/*.??[tx]", // NOLINT + sizeof(NameBuff)) >= MAXPATHL) { emsg(_(e_fnametoolong)); continue; } // Note: We cannot just do `&NameBuff` because it is a statically sized array // so `NameBuff == &NameBuff` according to C semantics. - char *buff_list[1] = { (char *)NameBuff }; + char *buff_list[1] = { NameBuff }; if (gen_expand_wildcards(1, buff_list, &fcount, &fnames, EW_FILE|EW_SILENT) == OK && fcount > 0) { // If foo.abx is found use it instead of foo.txt in // the same directory. for (int i1 = 0; i1 < fcount; i1++) { - for (int i2 = 0; i2 < fcount; i2++) { - if (i1 == i2) { - continue; - } - if (fnames[i1] == NULL || fnames[i2] == NULL) { + const char *const f1 = fnames[i1]; + const char *const t1 = path_tail(f1); + const char *const e1 = strrchr(t1, '.'); + if (path_fnamecmp(e1, ".txt") != 0 + && path_fnamecmp(e1, fname + 4) != 0) { + // Not .txt and not .abx, remove it. + XFREE_CLEAR(fnames[i1]); + continue; + } + + for (int i2 = i1 + 1; i2 < fcount; i2++) { + const char *const f2 = fnames[i2]; + if (f2 == NULL) { continue; } - const char *const f1 = fnames[i1]; - const char *const f2 = fnames[i2]; - const char *const t1 = path_tail(f1); const char *const t2 = path_tail(f2); - const char *const e1 = (char *)STRRCHR(t1, '.'); - const char *const e2 = (char *)STRRCHR(t2, '.'); + const char *const e2 = strrchr(t2, '.'); if (e1 == NULL || e2 == NULL) { continue; } - if (FNAMECMP(e1, ".txt") != 0 - && FNAMECMP(e1, fname + 4) != 0) { - // Not .txt and not .abx, remove it. - XFREE_CLEAR(fnames[i1]); - continue; - } if (e1 - f1 != e2 - f2 - || FNAMENCMP(f1, f2, e1 - f1) != 0) { + || path_fnamencmp(f1, f2, (size_t)(e1 - f1)) != 0) { continue; } - if (FNAMECMP(e1, ".txt") == 0 - && FNAMECMP(e2, fname + 4) == 0) { + if (path_fnamecmp(e1, ".txt") == 0 + && path_fnamecmp(e2, fname + 4) == 0) { // use .abx instead of .txt XFREE_CLEAR(fnames[i1]); } @@ -774,7 +785,7 @@ void fix_help_buffer(void) } vim_fgets(IObuff, IOSIZE, fd); if (IObuff[0] == '*' - && (s = vim_strchr((char *)IObuff + 1, '*')) + && (s = vim_strchr(IObuff + 1, '*')) != NULL) { TriState this_utf = kNone; // Change tag definition to a @@ -788,7 +799,7 @@ void fix_help_buffer(void) // The text is utf-8 when a byte // above 127 is found and no // illegal byte sequence is found. - if ((char_u)(*s) >= 0x80 && this_utf != kFalse) { + if ((uint8_t)(*s) >= 0x80 && this_utf != kFalse) { this_utf = kTrue; const int l = utf_ptr2len(s); if (l == 1) { @@ -803,23 +814,23 @@ void fix_help_buffer(void) // 'encoding' may be required. vc.vc_type = CONV_NONE; convert_setup(&vc, - (char_u *)(this_utf == kTrue ? "utf-8" : "latin1"), + (this_utf == kTrue ? "utf-8" : "latin1"), p_enc); if (vc.vc_type == CONV_NONE) { // No conversion needed. - cp = (char *)IObuff; + cp = IObuff; } else { // Do the conversion. If it fails // use the unconverted text. - cp = (char *)string_convert(&vc, IObuff, NULL); + cp = string_convert(&vc, IObuff, NULL); if (cp == NULL) { - cp = (char *)IObuff; + cp = IObuff; } } convert_setup(&vc, NULL, NULL); ml_append(lnum, cp, (colnr_T)0, false); - if ((char_u *)cp != IObuff) { + if (cp != IObuff) { xfree(cp); } lnum++; @@ -869,17 +880,17 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool bool mix = false; // detected mixed encodings // Find all *.txt files. - size_t dirlen = STRLCPY(NameBuff, dir, sizeof(NameBuff)); + size_t dirlen = xstrlcpy(NameBuff, dir, sizeof(NameBuff)); if (dirlen >= MAXPATHL - || STRLCAT(NameBuff, "/**/*", sizeof(NameBuff)) >= MAXPATHL // NOLINT - || STRLCAT(NameBuff, ext, sizeof(NameBuff)) >= MAXPATHL) { + || xstrlcat(NameBuff, "/**/*", sizeof(NameBuff)) >= MAXPATHL // NOLINT + || xstrlcat(NameBuff, ext, sizeof(NameBuff)) >= MAXPATHL) { emsg(_(e_fnametoolong)); return; } // Note: We cannot just do `&NameBuff` because it is a statically sized array // so `NameBuff == &NameBuff` according to C semantics. - char *buff_list[1] = { (char *)NameBuff }; + char *buff_list[1] = { NameBuff }; const int res = gen_expand_wildcards(1, buff_list, &filecount, &files, EW_FILE|EW_SILENT); if (res == FAIL || filecount == 0) { @@ -895,13 +906,13 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool // Open the tags file for writing. // Do this before scanning through all the files. memcpy(NameBuff, dir, dirlen + 1); - if (!add_pathsep((char *)NameBuff) - || STRLCAT(NameBuff, tagfname, sizeof(NameBuff)) >= MAXPATHL) { + if (!add_pathsep(NameBuff) + || xstrlcat(NameBuff, tagfname, sizeof(NameBuff)) >= MAXPATHL) { emsg(_(e_fnametoolong)); return; } - FILE *const fd_tags = os_fopen((char *)NameBuff, "w"); + FILE *const fd_tags = os_fopen(NameBuff, "w"); if (fd_tags == NULL) { if (!ignore_writeerr) { semsg(_("E152: Cannot open %s for writing"), NameBuff); @@ -912,10 +923,10 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc" // add the "help-tags" tag. - ga_init(&ga, (int)sizeof(char_u *), 100); + ga_init(&ga, (int)sizeof(char *), 100); if (add_help_tags || path_full_compare("$VIMRUNTIME/doc", dir, false, true) == kEqualFiles) { - size_t s_len = 18 + STRLEN(tagfname); + size_t s_len = 18 + strlen(tagfname); s = xmalloc(s_len); snprintf(s, s_len, "help-tags\t%s\t1\n", tagfname); GA_APPEND(char *, &ga, s); @@ -930,13 +941,14 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool } const char *const fname = files[fi] + dirlen + 1; + bool in_example = false; bool firstline = true; while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { if (firstline) { // Detect utf-8 file by a non-ASCII char in the first line. TriState this_utf8 = kNone; - for (s = (char *)IObuff; *s != NUL; s++) { - if ((char_u)(*s) >= 0x80) { + for (s = IObuff; *s != NUL; s++) { + if ((uint8_t)(*s) >= 0x80) { this_utf8 = kTrue; const int l = utf_ptr2len(s); if (l == 1) { @@ -960,7 +972,14 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool } firstline = false; } - p1 = vim_strchr((char *)IObuff, '*'); // find first '*' + if (in_example) { + // skip over example; a non-white in the first column ends it + if (vim_strchr(" \t\n\r", (uint8_t)IObuff[0])) { + continue; + } + in_example = false; + } + p1 = vim_strchr(IObuff, '*'); // find first '*' while (p1 != NULL) { p2 = strchr((const char *)p1 + 1, '*'); // Find second '*'. if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**". @@ -974,12 +993,12 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool // characters, there is white space before it and is // followed by a white character or end-of-line. if (s == p2 - && ((char_u *)p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t') - && (vim_strchr(" \t\n\r", s[1]) != NULL + && (p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t') + && (vim_strchr(" \t\n\r", (uint8_t)s[1]) != NULL || s[1] == '\0')) { *p2 = '\0'; p1++; - size_t s_len= (size_t)(p2 - p1) + STRLEN(fname) + 2; + size_t s_len = (size_t)(p2 - p1) + strlen(fname) + 2; s = xmalloc(s_len); GA_APPEND(char *, &ga, s); snprintf(s, s_len, "%s\t%s", p1, fname); @@ -990,6 +1009,11 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool } p1 = p2; } + size_t len = strlen(IObuff); + if ((len == 2 && strcmp(&IObuff[len - 2], ">\n") == 0) + || (len >= 3 && strcmp(&IObuff[len - 3], " >\n") == 0)) { + in_example = true; + } line_breakcheck(); } @@ -1009,10 +1033,10 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool while (*p1 == *p2) { if (*p2 == '\t') { *p2 = NUL; - vim_snprintf((char *)NameBuff, MAXPATHL, + vim_snprintf(NameBuff, MAXPATHL, _("E154: Duplicate tag \"%s\" in file %s/%s"), ((char_u **)ga.ga_data)[i], dir, p2 + 1); - emsg((char *)NameBuff); + emsg(NameBuff); *p2 = '\t'; break; } @@ -1028,7 +1052,7 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool // Write the tags into the file. for (int i = 0; i < ga.ga_len; i++) { s = ((char **)ga.ga_data)[i]; - if (STRNCMP(s, "help-tags\t", 10) == 0) { + if (strncmp(s, "help-tags\t", 10) == 0) { // help-tags entry was added in formatted form fputs(s, fd_tags); } else { @@ -1065,16 +1089,16 @@ static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr) char **files; // Get a list of all files in the help directory and in subdirectories. - STRLCPY(NameBuff, dirname, sizeof(NameBuff)); + xstrlcpy(NameBuff, dirname, sizeof(NameBuff)); if (!add_pathsep((char *)NameBuff) - || STRLCAT(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) { + || xstrlcat(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) { emsg(_(e_fnametoolong)); return; } // Note: We cannot just do `&NameBuff` because it is a statically sized array // so `NameBuff == &NameBuff` according to C semantics. - char *buff_list[1] = { (char *)NameBuff }; + char *buff_list[1] = { NameBuff }; if (gen_expand_wildcards(1, buff_list, &filecount, &files, EW_FILE|EW_SILENT) == FAIL || filecount == 0) { @@ -1087,7 +1111,7 @@ static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr) int j; ga_init(&ga, 1, 10); for (int i = 0; i < filecount; i++) { - len = (int)STRLEN(files[i]); + len = (int)strlen(files[i]); if (len <= 4) { continue; } @@ -1109,7 +1133,7 @@ static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr) // Did we find this language already? for (j = 0; j < ga.ga_len; j += 2) { - if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0) { + if (strncmp(lang, ((char *)ga.ga_data) + j, 2) == 0) { break; } } @@ -1157,19 +1181,18 @@ void ex_helptags(exarg_T *eap) bool add_help_tags = false; // Check for ":helptags ++t {dir}". - if (STRNCMP(eap->arg, "++t", 3) == 0 && ascii_iswhite(eap->arg[3])) { + if (strncmp(eap->arg, "++t", 3) == 0 && ascii_iswhite(eap->arg[3])) { add_help_tags = true; eap->arg = skipwhite(eap->arg + 3); } - if (STRCMP(eap->arg, "ALL") == 0) { + if (strcmp(eap->arg, "ALL") == 0) { do_in_path(p_rtp, "doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags); } else { ExpandInit(&xpc); xpc.xp_context = EXPAND_DIRECTORIES; - dirname = (char *)ExpandOne(&xpc, (char_u *)eap->arg, NULL, - WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE); - if (dirname == NULL || !os_isdir((char_u *)dirname)) { + dirname = ExpandOne(&xpc, eap->arg, NULL, WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE); + if (dirname == NULL || !os_isdir(dirname)) { semsg(_("E150: Not a directory: %s"), eap->arg); } else { do_helptags(dirname, add_help_tags, false); diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index d6a18fcf8e..9dab91cc2b 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -3,18 +3,32 @@ // highlight.c: low level code for UI and syntax highlighting +#include <assert.h> +#include <inttypes.h> +#include <limits.h> +#include <string.h> + +#include "klib/kvec.h" +#include "lauxlib.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/api/ui.h" #include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/map.h" +#include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" #include "nvim/popupmenu.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -115,19 +129,15 @@ static int get_attr_entry(HlEntry entry) /// When a UI connects, we need to send it the table of highlights used so far. void ui_send_all_hls(UI *ui) { - if (ui->hl_attr_define) { - for (size_t i = 1; i < kv_size(attr_entries); i++) { - Array inspect = hl_inspect((int)i); - ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr, - kv_A(attr_entries, i).attr, inspect); - api_free_array(inspect); - } + for (size_t i = 1; i < kv_size(attr_entries); i++) { + Array inspect = hl_inspect((int)i); + remote_ui_hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr, + kv_A(attr_entries, i).attr, inspect); + api_free_array(inspect); } - if (ui->hl_group_set) { - for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) { - ui->hl_group_set(ui, cstr_as_string((char *)hlf_names[hlf]), - highlight_attr[hlf]); - } + for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) { + remote_ui_hl_group_set(ui, cstr_as_string((char *)hlf_names[hlf]), + highlight_attr[hlf]); } } @@ -141,10 +151,9 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) || at_en.rgb_ae_attr != 0 || ns_id != 0) { return get_attr_entry((HlEntry){ .attr = at_en, .kind = kHlSyntax, .id1 = idx, .id2 = ns_id }); - } else { - // If all the fields are cleared, clear the attr field back to default value - return 0; } + // If all the fields are cleared, clear the attr field back to default value + return 0; } void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight) *dict) @@ -223,7 +232,7 @@ int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault) } } - it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs); + it.attr_id = fallback ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs); it.version = p->hl_valid - tmp; it.is_default = attrs.rgb_ae_attr & HL_DEFAULT; it.link_global = attrs.rgb_ae_attr & HL_GLOBAL; @@ -238,12 +247,11 @@ int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault) if (link) { if (it.attr_id >= 0) { return 0; - } else { - if (it.link_global) { - *ns_hl = 0; - } - return it.link_id; } + if (it.link_global) { + *ns_hl = 0; + } + return it.link_id; } else { return it.attr_id; } @@ -406,8 +414,8 @@ void update_ns_hl(int ns_id) } int *hl_attrs = **alloc; - for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) { - int id = syn_check_group(hlf_names[hlf], STRLEN(hlf_names[hlf])); + for (int hlf = 0; hlf < HLF_COUNT; hlf++) { + int id = syn_check_group(hlf_names[hlf], strlen(hlf_names[hlf])); bool optional = (hlf == HLF_INACTIVE || hlf == HLF_NFLOAT); hl_attrs[hlf] = hl_get_ui_attr(ns_id, hlf, id, optional); } @@ -641,7 +649,7 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through) cattrs = battrs; cattrs.rgb_fg_color = rgb_blend(ratio, battrs.rgb_fg_color, fattrs.rgb_bg_color); - if (cattrs.rgb_ae_attr & (HL_ANY_UNDERLINE)) { + if (cattrs.rgb_ae_attr & (HL_UNDERLINE_MASK)) { cattrs.rgb_sp_color = rgb_blend(ratio, battrs.rgb_sp_color, fattrs.rgb_bg_color); } else { @@ -659,7 +667,7 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through) } cattrs.rgb_fg_color = rgb_blend(ratio/2, battrs.rgb_fg_color, fattrs.rgb_fg_color); - if (cattrs.rgb_ae_attr & (HL_ANY_UNDERLINE)) { + if (cattrs.rgb_ae_attr & (HL_UNDERLINE_MASK)) { cattrs.rgb_sp_color = rgb_blend(ratio/2, battrs.rgb_bg_color, fattrs.rgb_sp_color); } else { @@ -729,7 +737,7 @@ static int hl_cterm2rgb_color(int nr) 0x08, 0x12, 0x1C, 0x26, 0x30, 0x3A, 0x44, 0x4E, 0x58, 0x62, 0x6C, 0x76, 0x80, 0x8A, 0x94, 0x9E, 0xA8, 0xB2, 0xBC, 0xC6, 0xD0, 0xDA, 0xE4, 0xEE }; - static char_u ansi_table[16][4] = { + static uint8_t ansi_table[16][4] = { // R G B idx { 0, 0, 0, 1 }, // black { 224, 0, 0, 2 }, // dark red @@ -788,7 +796,7 @@ HlAttrs syn_attr2entry(int attr) } /// Gets highlight description for id `attr_id` as a map. -Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err) +Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Arena *arena, Error *err) { Dictionary dic = ARRAY_DICT_INIT; @@ -801,66 +809,68 @@ Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err) "Invalid attribute id: %" PRId64, attr_id); return dic; } - - return hlattrs2dict(NULL, syn_attr2entry((int)attr_id), rgb); + Dictionary retval = arena_dict(arena, HLATTRS_DICT_SIZE); + hlattrs2dict(&retval, syn_attr2entry((int)attr_id), rgb); + return retval; } /// Converts an HlAttrs into Dictionary /// -/// @param[out] hl optional pre-allocated dictionary for return value -/// if present, must be allocated with at least 16 elements! +/// @param[in/out] hl Dictionary with pre-allocated space for HLATTRS_DICT_SIZE elements /// @param[in] aep data to convert /// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*' -Dictionary hlattrs2dict(Dictionary *hl_alloc, HlAttrs ae, bool use_rgb) +void hlattrs2dict(Dictionary *dict, HlAttrs ae, bool use_rgb) { + assert(dict->capacity >= HLATTRS_DICT_SIZE); // at most 16 items + Dictionary hl = *dict; int mask = use_rgb ? ae.rgb_ae_attr : ae.cterm_ae_attr; - Dictionary hl = ARRAY_DICT_INIT; - if (hl_alloc) { - hl = *hl_alloc; - } else { - kv_ensure_space(hl, 16); + + if (mask & HL_INVERSE) { + PUT_C(hl, "reverse", BOOLEAN_OBJ(true)); } if (mask & HL_BOLD) { PUT_C(hl, "bold", BOOLEAN_OBJ(true)); } - if (mask & HL_STANDOUT) { - PUT_C(hl, "standout", BOOLEAN_OBJ(true)); + if (mask & HL_ITALIC) { + PUT_C(hl, "italic", BOOLEAN_OBJ(true)); } - if (mask & HL_UNDERLINE) { + switch (mask & HL_UNDERLINE_MASK) { + case HL_UNDERLINE: PUT_C(hl, "underline", BOOLEAN_OBJ(true)); - } - - if (mask & HL_UNDERCURL) { - PUT_C(hl, "undercurl", BOOLEAN_OBJ(true)); - } + break; - if (mask & HL_UNDERDOUBLE) { + case HL_UNDERDOUBLE: PUT_C(hl, "underdouble", BOOLEAN_OBJ(true)); - } + break; - if (mask & HL_UNDERDOTTED) { + case HL_UNDERCURL: + PUT_C(hl, "undercurl", BOOLEAN_OBJ(true)); + break; + + case HL_UNDERDOTTED: PUT_C(hl, "underdotted", BOOLEAN_OBJ(true)); - } + break; - if (mask & HL_UNDERDASHED) { + case HL_UNDERDASHED: PUT_C(hl, "underdashed", BOOLEAN_OBJ(true)); + break; } - if (mask & HL_ITALIC) { - PUT_C(hl, "italic", BOOLEAN_OBJ(true)); - } - - if (mask & HL_INVERSE) { - PUT_C(hl, "reverse", BOOLEAN_OBJ(true)); + if (mask & HL_STANDOUT) { + PUT_C(hl, "standout", BOOLEAN_OBJ(true)); } if (mask & HL_STRIKETHROUGH) { PUT_C(hl, "strikethrough", BOOLEAN_OBJ(true)); } + if (mask & HL_ALTFONT) { + PUT_C(hl, "altfont", BOOLEAN_OBJ(true)); + } + if (mask & HL_NOCOMBINE) { PUT_C(hl, "nocombine", BOOLEAN_OBJ(true)); } @@ -899,14 +909,7 @@ Dictionary hlattrs2dict(Dictionary *hl_alloc, HlAttrs ae, bool use_rgb) PUT_C(hl, "blend", INTEGER_OBJ(ae.hl_blend)); } - if (hl_alloc) { - *hl_alloc = hl; - return hl; - } else { - Dictionary allocated = copy_dictionary(hl); - kv_destroy(hl); - return allocated; - } + *dict = hl; } HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *err) @@ -923,32 +926,37 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e m = m | flag; \ } + CHECK_FLAG(dict, mask, reverse, , HL_INVERSE); CHECK_FLAG(dict, mask, bold, , HL_BOLD); - CHECK_FLAG(dict, mask, standout, , HL_STANDOUT); + CHECK_FLAG(dict, mask, italic, , HL_ITALIC); CHECK_FLAG(dict, mask, underline, , HL_UNDERLINE); - CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL); CHECK_FLAG(dict, mask, underdouble, , HL_UNDERDOUBLE); + CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL); CHECK_FLAG(dict, mask, underdotted, , HL_UNDERDOTTED); CHECK_FLAG(dict, mask, underdashed, , HL_UNDERDASHED); - CHECK_FLAG(dict, mask, italic, , HL_ITALIC); - CHECK_FLAG(dict, mask, reverse, , HL_INVERSE); + CHECK_FLAG(dict, mask, standout, , HL_STANDOUT); CHECK_FLAG(dict, mask, strikethrough, , HL_STRIKETHROUGH); + CHECK_FLAG(dict, mask, altfont, , HL_ALTFONT); + if (use_rgb) { + CHECK_FLAG(dict, mask, fg_indexed, , HL_FG_INDEXED); + CHECK_FLAG(dict, mask, bg_indexed, , HL_BG_INDEXED); + } CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE); CHECK_FLAG(dict, mask, default, _, HL_DEFAULT); if (HAS_KEY(dict->fg)) { - fg = object_to_color(dict->fg, "fg", true, err); + fg = object_to_color(dict->fg, "fg", use_rgb, err); } else if (HAS_KEY(dict->foreground)) { - fg = object_to_color(dict->foreground, "foreground", true, err); + fg = object_to_color(dict->foreground, "foreground", use_rgb, err); } if (ERROR_SET(err)) { return hlattrs; } if (HAS_KEY(dict->bg)) { - bg = object_to_color(dict->bg, "bg", true, err); + bg = object_to_color(dict->bg, "bg", use_rgb, err); } else if (HAS_KEY(dict->background)) { - bg = object_to_color(dict->background, "background", true, err); + bg = object_to_color(dict->background, "background", use_rgb, err); } if (ERROR_SET(err)) { return hlattrs; @@ -1004,13 +1012,14 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e } cterm_mask_provided = true; + CHECK_FLAG(cterm, cterm_mask, reverse, , HL_INVERSE); CHECK_FLAG(cterm, cterm_mask, bold, , HL_BOLD); - CHECK_FLAG(cterm, cterm_mask, standout, , HL_STANDOUT); + CHECK_FLAG(cterm, cterm_mask, italic, , HL_ITALIC); CHECK_FLAG(cterm, cterm_mask, underline, , HL_UNDERLINE); CHECK_FLAG(cterm, cterm_mask, undercurl, , HL_UNDERCURL); - CHECK_FLAG(cterm, cterm_mask, italic, , HL_ITALIC); - CHECK_FLAG(cterm, cterm_mask, reverse, , HL_INVERSE); + CHECK_FLAG(cterm, cterm_mask, standout, , HL_STANDOUT); CHECK_FLAG(cterm, cterm_mask, strikethrough, , HL_STRIKETHROUGH); + CHECK_FLAG(cterm, cterm_mask, altfont, , HL_ALTFONT); CHECK_FLAG(cterm, cterm_mask, nocombine, , HL_NOCOMBINE); } else if (dict->cterm.type == kObjectTypeArray && dict->cterm.data.array.size == 0) { // empty list from Lua API should clear all cterm attributes @@ -1035,11 +1044,11 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e } } - // apply gui mask as default for cterm mask - if (!cterm_mask_provided) { - cterm_mask = mask; - } if (use_rgb) { + // apply gui mask as default for cterm mask + if (!cterm_mask_provided) { + cterm_mask = mask; + } hlattrs.rgb_ae_attr = mask; hlattrs.rgb_bg_color = bg; hlattrs.rgb_fg_color = fg; @@ -1049,9 +1058,9 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : ctermfg + 1; hlattrs.cterm_ae_attr = cterm_mask; } else { - hlattrs.cterm_bg_color = ctermbg == -1 ? 0 : ctermbg + 1; - hlattrs.cterm_fg_color = ctermfg == -1 ? 0 : ctermfg + 1; - hlattrs.cterm_ae_attr = cterm_mask; + hlattrs.cterm_bg_color = bg == -1 ? 0 : bg + 1; + hlattrs.cterm_fg_color = fg == -1 ? 0 : fg + 1; + hlattrs.cterm_ae_attr = mask; } return hlattrs; @@ -1086,6 +1095,7 @@ int object_to_color(Object val, char *key, bool rgb, Error *err) Array hl_inspect(int attr) { + // TODO(bfredl): use arena allocation Array ret = ARRAY_DICT_INIT; if (hlstate_active) { hl_inspect_impl(&ret, attr); diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h index 50299bb91c..4da7dd65bb 100644 --- a/src/nvim/highlight.h +++ b/src/nvim/highlight.h @@ -6,6 +6,7 @@ #include "nvim/api/private/defs.h" #include "nvim/buffer_defs.h" #include "nvim/highlight_defs.h" +#include "nvim/option_defs.h" #include "nvim/ui.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -19,6 +20,8 @@ static inline int win_hl_attr(win_T *wp, int hlf) return ((wp->w_ns_hl_attr && ns_hl_fast < 0) ? wp->w_ns_hl_attr : hl_attr_active)[hlf]; } +#define HLATTRS_DICT_SIZE 16 + #define HL_SET_DEFAULT_COLORS(rgb_fg, rgb_bg, rgb_sp) \ do { \ bool dark_ = (*p_bg == 'd'); \ diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index ffcb0f3f22..a4dcf6eb60 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -15,19 +15,23 @@ typedef enum { HL_INVERSE = 0x01, HL_BOLD = 0x02, HL_ITALIC = 0x04, + // The next three bits are all underline styles + HL_UNDERLINE_MASK = 0x38, HL_UNDERLINE = 0x08, - HL_UNDERCURL = 0x10, - HL_UNDERDOUBLE = 0x20, - HL_UNDERDOTTED = 0x40, - HL_UNDERDASHED = 0x80, - HL_STANDOUT = 0x0100, - HL_NOCOMBINE = 0x0200, - HL_STRIKETHROUGH = 0x0400, + HL_UNDERDOUBLE = 0x10, + HL_UNDERCURL = 0x18, + HL_UNDERDOTTED = 0x20, + HL_UNDERDASHED = 0x28, + // 0x30 and 0x38 spare for underline styles + HL_STANDOUT = 0x0040, + HL_STRIKETHROUGH = 0x0080, + HL_ALTFONT = 0x0100, + // 0x0200 spare + HL_NOCOMBINE = 0x0400, HL_BG_INDEXED = 0x0800, HL_FG_INDEXED = 0x1000, HL_DEFAULT = 0x2000, HL_GLOBAL = 0x4000, - HL_ANY_UNDERLINE = HL_UNDERLINE | HL_UNDERDOUBLE | HL_UNDERCURL | HL_UNDERDOTTED | HL_UNDERDASHED, } HlAttrFlags; /// Stores a complete highlighting entry, including colors and attributes @@ -114,6 +118,7 @@ typedef enum { HLF_WBR, // Window bars HLF_WBRNC, // Window bars of not-current windows HLF_CU, // Cursor + HLF_BTITLE, // Float Border Title HLF_COUNT, // MUST be the last one } hlf_T; @@ -178,6 +183,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_WBR] = "WinBar", [HLF_WBRNC] = "WinBarNC", [HLF_CU] = "Cursor", + [HLF_BTITLE] = "FloatTitle", }); EXTERN int highlight_attr[HLF_COUNT + 1]; // Highl. attr for each context. diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 77424de3b8..5b1ea9967d 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -3,29 +3,53 @@ // highlight_group.c: code for managing highlight groups +#include <ctype.h> #include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor_shape.h" +#include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/fold.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/lua/executor.h" -#include "nvim/match.h" +#include "nvim/macros.h" +#include "nvim/map.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/option.h" +#include "nvim/os/time.h" #include "nvim/runtime.h" +#include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/ui.h" +#include "nvim/vim.h" /// \addtogroup SG_SET /// @{ -#define SG_CTERM 2 // cterm has been set -#define SG_GUI 4 // gui has been set -#define SG_LINK 8 // link has been set +enum { + SG_CTERM = 2, // cterm has been set + SG_GUI = 4, // gui has been set + SG_LINK = 8, // link has been set +}; /// @} #define MAX_SYN_NAME 200 @@ -43,32 +67,32 @@ Map(cstr_t, int) highlight_unames = MAP_INIT; static char *(hl_name_table[]) = { "bold", "standout", "underline", "undercurl", "underdouble", "underdotted", "underdashed", - "italic", "reverse", "inverse", "strikethrough", "nocombine", "NONE" }; + "italic", "reverse", "inverse", "strikethrough", "altfont", + "nocombine", "NONE" }; static int hl_attr_table[] = { HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_UNDERDOUBLE, HL_UNDERDOTTED, HL_UNDERDASHED, - HL_ITALIC, HL_INVERSE, HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 }; + HL_ITALIC, HL_INVERSE, HL_INVERSE, HL_STRIKETHROUGH, HL_ALTFONT, + HL_NOCOMBINE, 0 }; /// Structure that stores information about a highlight group. /// The ID of a highlight group is also called group ID. It is the index in /// the highlight_ga array PLUS ONE. typedef struct { - char_u *sg_name; ///< highlight group name - char *sg_name_u; ///< uppercase of sg_name + char *sg_name; ///< highlight group name + char *sg_name_u; ///< uppercase of sg_name bool sg_cleared; ///< "hi clear" was used int sg_attr; ///< Screen attr @see ATTR_ENTRY int sg_link; ///< link to this highlight group ID int sg_deflink; ///< default link; restored in highlight_clear() int sg_set; ///< combination of flags in \ref SG_SET sctx_T sg_deflink_sctx; ///< script where the default link was set - sctx_T sg_script_ctx; ///< script in which the group was last set - // for terminal UIs + sctx_T sg_script_ctx; ///< script in which the group was last set for terminal UIs int sg_cterm; ///< "cterm=" highlighting attr ///< (combination of \ref HlAttrFlags) int sg_cterm_fg; ///< terminal fg color number + 1 int sg_cterm_bg; ///< terminal bg color number + 1 - bool sg_cterm_bold; ///< bold attr was set for light color - // for RGB UIs + bool sg_cterm_bold; ///< bold attr was set for light color for RGB UIs int sg_gui; ///< "gui=" highlighting attributes ///< (combination of \ref HlAttrFlags) RgbValue sg_rgb_fg; ///< RGB foreground color @@ -79,6 +103,8 @@ typedef struct { int sg_rgb_sp_idx; ///< RGB special color index int sg_blend; ///< blend level (0-100 inclusive), -1 if unset + + int sg_parent; ///< parent of @nested.group } HlGroup; enum { @@ -96,8 +122,6 @@ enum { // The default highlight groups. These are compiled-in for fast startup and // they still work when the runtime files can't be found. -// -// When making changes here, also change runtime/colors/default.vim! static const char *highlight_init_both[] = { "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", @@ -131,6 +155,7 @@ static const char *highlight_init_both[] = { "default link MsgSeparator StatusLine", "default link NormalFloat Pmenu", "default link FloatBorder WinSeparator", + "default link FloatTitle Title", "default FloatShadow blend=80 guibg=Black", "default FloatShadowThrough blend=100 guibg=Black", "RedrawDebugNormal cterm=reverse gui=reverse", @@ -167,22 +192,94 @@ static const char *highlight_init_both[] = { "default DiagnosticWarn ctermfg=3 guifg=Orange", "default DiagnosticInfo ctermfg=4 guifg=LightBlue", "default DiagnosticHint ctermfg=7 guifg=LightGrey", + "default DiagnosticOk ctermfg=10 guifg=LightGreen", "default DiagnosticUnderlineError cterm=underline gui=underline guisp=Red", "default DiagnosticUnderlineWarn cterm=underline gui=underline guisp=Orange", "default DiagnosticUnderlineInfo cterm=underline gui=underline guisp=LightBlue", "default DiagnosticUnderlineHint cterm=underline gui=underline guisp=LightGrey", + "default DiagnosticUnderlineOk cterm=underline gui=underline guisp=LightGreen", "default link DiagnosticVirtualTextError DiagnosticError", "default link DiagnosticVirtualTextWarn DiagnosticWarn", "default link DiagnosticVirtualTextInfo DiagnosticInfo", "default link DiagnosticVirtualTextHint DiagnosticHint", + "default link DiagnosticVirtualTextOk DiagnosticOk", "default link DiagnosticFloatingError DiagnosticError", "default link DiagnosticFloatingWarn DiagnosticWarn", "default link DiagnosticFloatingInfo DiagnosticInfo", "default link DiagnosticFloatingHint DiagnosticHint", + "default link DiagnosticFloatingOk DiagnosticOk", "default link DiagnosticSignError DiagnosticError", "default link DiagnosticSignWarn DiagnosticWarn", "default link DiagnosticSignInfo DiagnosticInfo", "default link DiagnosticSignHint DiagnosticHint", + "default link DiagnosticSignOk DiagnosticOk", + + // 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 + "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 Identifier", + "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", + + // LSP semantic tokens + "default link @class Structure", + "default link @struct Structure", + "default link @enum Type", + "default link @enumMember Constant", + "default link @event Identifier", + "default link @interface Identifier", + "default link @modifier Identifier", + "default link @regexp SpecialChar", + "default link @typeParameter Type", + "default link @decorator Identifier", NULL }; @@ -474,7 +571,7 @@ int highlight_num_groups(void) } /// Returns the name of a highlight group. -char_u *highlight_group_name(int id) +char *highlight_group_name(int id) { return hl_table[id].sg_name; } @@ -504,11 +601,11 @@ void init_highlight(bool both, bool reset) // Try finding the color scheme file. Used when a color file was loaded // and 'background' or 't_Co' is changed. - char_u *p = get_var_value("g:colors_name"); + char *p = get_var_value("g:colors_name"); if (p != NULL) { // Value of g:colors_name could be freed in load_colors() and make // p invalid, so copy it. - char_u *copy_p = vim_strsave(p); + char *copy_p = xstrdup(p); bool okay = load_colors(copy_p); xfree(copy_p); if (okay) { @@ -557,11 +654,10 @@ void init_highlight(bool both, bool reset) } /// Load color file "name". -/// Return OK for success, FAIL for failure. -int load_colors(char_u *name) +/// +/// @return OK for success, FAIL for failure. +int load_colors(char *name) { - char_u *buf; - int retval = FAIL; static bool recursive = false; // When being called recursively, this is probably because setting @@ -572,17 +668,19 @@ int load_colors(char_u *name) } recursive = true; - size_t buflen = STRLEN(name) + 12; - buf = xmalloc(buflen); - apply_autocmds(EVENT_COLORSCHEMEPRE, (char *)name, curbuf->b_fname, false, curbuf); - snprintf((char *)buf, buflen, "colors/%s.vim", name); - retval = source_runtime((char *)buf, DIP_START + DIP_OPT); + size_t buflen = strlen(name) + 12; + char *buf = xmalloc(buflen); + apply_autocmds(EVENT_COLORSCHEMEPRE, name, curbuf->b_fname, false, curbuf); + snprintf(buf, buflen, "colors/%s.vim", name); + int retval = source_runtime(buf, DIP_START + DIP_OPT); if (retval == FAIL) { - snprintf((char *)buf, buflen, "colors/%s.lua", name); - retval = source_runtime((char *)buf, DIP_START + DIP_OPT); + snprintf(buf, buflen, "colors/%s.lua", name); + retval = source_runtime(buf, DIP_START + DIP_OPT); } xfree(buf); - apply_autocmds(EVENT_COLORSCHEME, (char *)name, curbuf->b_fname, false, curbuf); + if (retval == OK) { + apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, false, curbuf); + } recursive = false; @@ -640,7 +738,7 @@ static int color_numbers_8[28] = { 0, 4, 2, 6, // Lookup the "cterm" value to be used for color with index "idx" in // color_names[]. -// "boldp" will be set to TRUE or FALSE for a foreground color when using 8 +// "boldp" will be set to kTrue or kFalse for a foreground color when using 8 // colors, otherwise it will be unchanged. int lookup_color(const int idx, const bool foreground, TriState *const boldp) { @@ -740,7 +838,7 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) g->sg_attr = hl_get_syn_attr(0, id, attrs); // 'Normal' is special - if (STRCMP(g->sg_name_u, "NORMAL") == 0) { + if (strcmp(g->sg_name_u, "NORMAL") == 0) { cterm_normal_fg_color = g->sg_cterm_fg; cterm_normal_bg_color = g->sg_cterm_bg; normal_fg = g->sg_rgb_fg; @@ -756,7 +854,7 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) update: if (!updating_screen) { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } need_highlight_changed = true; } @@ -773,27 +871,8 @@ update: void do_highlight(const char *line, const bool forceit, const bool init) FUNC_ATTR_NONNULL_ALL { - const char *name_end; - const char *linep; - const char *key_start; - const char *arg_start; - int off; - int len; - int attr; - int id; - int idx; - HlGroup item_before; - bool did_change = false; - bool dodefault = false; - bool doclear = false; - bool dolink = false; - bool error = false; - int color; - bool is_normal_group = false; // "Normal" group - bool did_highlight_changed = false; - // If no argument, list current highlighting. - if (ends_excmd((uint8_t)(*line))) { + if (!init && ends_excmd((uint8_t)(*line))) { for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { // TODO(brammool): only call when the group has attributes set highlight_list_one(i); @@ -801,18 +880,23 @@ void do_highlight(const char *line, const bool forceit, const bool init) return; } + bool dodefault = false; + // Isolate the name. - name_end = (const char *)skiptowhite((const char_u *)line); - linep = (const char *)skipwhite(name_end); + const char *name_end = (const char *)skiptowhite(line); + const char *linep = (const char *)skipwhite(name_end); // Check for "default" argument. if (strncmp(line, "default", (size_t)(name_end - line)) == 0) { dodefault = true; line = linep; - name_end = (const char *)skiptowhite((const char_u *)line); + name_end = (const char *)skiptowhite(line); linep = (const char *)skipwhite(name_end); } + bool doclear = false; + bool dolink = false; + // Check for "clear" or "link" argument. if (strncmp(line, "clear", (size_t)(name_end - line)) == 0) { doclear = true; @@ -822,7 +906,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) // ":highlight {group-name}": list highlighting for one group. if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) { - id = syn_name2id_len(line, (size_t)(name_end - line)); + int id = syn_name2id_len(line, (size_t)(name_end - line)); if (id == 0) { semsg(_("E411: highlight group not found: %s"), line); } else { @@ -841,9 +925,9 @@ void do_highlight(const char *line, const bool forceit, const bool init) int to_id; HlGroup *hlgroup = NULL; - from_end = (const char *)skiptowhite((const char_u *)from_start); + from_end = (const char *)skiptowhite(from_start); to_start = (const char *)skipwhite(from_end); - to_end = (const char *)skiptowhite((const char_u *)to_start); + to_end = (const char *)skiptowhite(to_start); if (ends_excmd((uint8_t)(*from_start)) || ends_excmd((uint8_t)(*to_start))) { @@ -893,7 +977,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) hlgroup->sg_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&hlgroup->sg_script_ctx); hlgroup->sg_cleared = false; - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); // Only call highlight changed() once after multiple changes need_highlight_changed = true; @@ -916,19 +1000,19 @@ void do_highlight(const char *line, const bool forceit, const bool init) } init_highlight(true, true); highlight_changed(); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); return; } - name_end = (const char *)skiptowhite((const char_u *)line); + name_end = (const char *)skiptowhite(line); linep = (const char *)skipwhite(name_end); } // Find the group name in the table. If it does not exist yet, add it. - id = syn_check_group(line, (size_t)(name_end - line)); + int id = syn_check_group(line, (size_t)(name_end - line)); if (id == 0) { // Failed (out of memory). return; } - idx = id - 1; // Index is ID minus one. + int idx = id - 1; // Index is ID minus one. // Return if "default" was used and the group already has settings if (dodefault && hl_has_settings(idx, true)) { @@ -936,8 +1020,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) } // Make a copy so we can check if any attribute actually changed - item_before = hl_table[idx]; - is_normal_group = (STRCMP(hl_table[idx].sg_name_u, "NORMAL") == 0); + HlGroup item_before = hl_table[idx]; + bool is_normal_group = (strcmp(hl_table[idx].sg_name_u, "NORMAL") == 0); // Clear the highlighting for ":hi clear {group}" and ":hi clear". if (doclear || (forceit && init)) { @@ -947,11 +1031,16 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } + bool did_change = false; + bool error = false; + char key[64]; char arg[512]; if (!doclear) { + const char *arg_start; + while (!ends_excmd((uint8_t)(*linep))) { - key_start = linep; + const char *key_start = linep; if (*linep == '=') { semsg(_("E415: unexpected equal sign: %s"), key_start); error = true; @@ -971,7 +1060,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) } memcpy(key, key_start, key_len); key[key_len] = NUL; - vim_strup((char_u *)key); + vim_strup(key); linep = (const char *)skipwhite(linep); if (strcmp(key, "NONE") == 0) { @@ -1004,7 +1093,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } else { arg_start = linep; - linep = (const char *)skiptowhite((const char_u *)linep); + linep = (const char *)skiptowhite(linep); } if (linep == arg_start) { semsg(_("E417: missing argument: %s"), key_start); @@ -1028,12 +1117,12 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (strcmp(key, "TERM") == 0 || strcmp(key, "CTERM") == 0 || strcmp(key, "GUI") == 0) { - attr = 0; - off = 0; + int attr = 0; + int off = 0; int i; while (arg[off] != NUL) { for (i = ARRAY_SIZE(hl_attr_table); --i >= 0;) { - len = (int)STRLEN(hl_name_table[i]); + int len = (int)strlen(hl_name_table[i]); if (STRNICMP(arg + off, hl_name_table[i], len) == 0) { attr |= hl_attr_table[i]; off += len; @@ -1068,9 +1157,9 @@ void do_highlight(const char *line, const bool forceit, const bool init) hl_table[idx].sg_gui = attr; } } - } else if (STRCMP(key, "FONT") == 0) { + } else if (strcmp(key, "FONT") == 0) { // in non-GUI fonts are simply ignored - } else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0) { + } else if (strcmp(key, "CTERMFG") == 0 || strcmp(key, "CTERMBG") == 0) { if (!init || !(hl_table[idx].sg_set & SG_CTERM)) { if (!init) { hl_table[idx].sg_set |= SG_CTERM; @@ -1083,6 +1172,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) hl_table[idx].sg_cterm_bold = false; } + int color; if (ascii_isdigit(*arg)) { color = atoi(arg); } else if (STRICMP(arg, "fg") == 0) { @@ -1103,7 +1193,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } else { // Reduce calls to STRICMP a bit, it can be slow. - off = TOUPPER_ASC(*arg); + int off = TOUPPER_ASC(*arg); int i; for (i = ARRAY_SIZE(color_names); --i >= 0;) { if (off == color_names[i][0] @@ -1156,7 +1246,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (dark != -1 && dark != (*p_bg == 'd') && !option_was_set("bg")) { - set_option_value("bg", 0L, (dark ? "dark" : "light"), 0); + set_option_value_give_err("bg", 0L, (dark ? "dark" : "light"), 0); reset_option_was_set("bg"); } } @@ -1188,7 +1278,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) if (is_normal_group) { normal_fg = hl_table[idx].sg_rgb_fg; } - } else if (STRCMP(key, "GUIBG") == 0) { + } else if (strcmp(key, "GUIBG") == 0) { int *indexp = &hl_table[idx].sg_rgb_bg_idx; if (!init || !(hl_table[idx].sg_set & SG_GUI)) { @@ -1199,7 +1289,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) RgbValue old_color = hl_table[idx].sg_rgb_bg; int old_idx = hl_table[idx].sg_rgb_bg_idx; - if (STRCMP(arg, "NONE") != 0) { + if (strcmp(arg, "NONE") != 0) { hl_table[idx].sg_rgb_bg = name_to_color(arg, indexp); } else { hl_table[idx].sg_rgb_bg = -1; @@ -1260,6 +1350,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } + bool did_highlight_changed = false; + if (!error && is_normal_group) { // Need to update all groups, because they might be using "bg" and/or // "fg", which have been changed now. @@ -1270,12 +1362,12 @@ void do_highlight(const char *line, const bool forceit, const bool init) // changed ui_refresh(); } else { - // TUI and newer UIs will repaint the screen themselves. NOT_VALID + // TUI and newer UIs will repaint the screen themselves. UPD_NOT_VALID // redraw below will still handle usages of guibg=fg etc. ui_default_colors_set(); } did_highlight_changed = true; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } else { set_hl_attr(idx); } @@ -1292,7 +1384,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) // redrawing. This may happen when evaluating 'statusline' changes the // StatusLine group. if (!updating_screen) { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } need_highlight_changed = true; } @@ -1303,7 +1395,7 @@ void free_highlight(void) { ga_clear(&highlight_ga); map_destroy(cstr_t, int)(&highlight_unames); - arena_mem_free(arena_finish(&highlight_arena), NULL); + arena_mem_free(arena_finish(&highlight_arena)); } #endif @@ -1321,7 +1413,7 @@ void restore_cterm_colors(void) /// @param check_link if true also check for an existing link. /// -/// @return TRUE if highlight group "idx" has any settings. +/// @return true if highlight group "idx" has any settings. static int hl_has_settings(int idx, bool check_link) { return hl_table[idx].sg_cleared == 0 @@ -1375,6 +1467,11 @@ static void highlight_list_one(const int id) return; } + // don't list specialized groups if a parent is used instead + if (sgp->sg_parent && sgp->sg_cleared) { + return; + } + didh = highlight_list_arg(id, didh, LIST_ATTR, sgp->sg_cterm, NULL, "cterm"); didh = highlight_list_arg(id, didh, LIST_INT, @@ -1400,7 +1497,7 @@ static void highlight_list_one(const int id) didh = true; msg_puts_attr("links to", HL_ATTR(HLF_D)); msg_putchar(' '); - msg_outtrans((char *)hl_table[hl_table[id - 1].sg_link - 1].sg_name); + msg_outtrans(hl_table[hl_table[id - 1].sg_link - 1].sg_name); } if (!didh) { @@ -1411,19 +1508,21 @@ static void highlight_list_one(const int id) } } -Dictionary get_global_hl_defs(void) +Dictionary get_global_hl_defs(Arena *arena) { - Dictionary rv = ARRAY_DICT_INIT; - for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { + Dictionary rv = arena_dict(arena, (size_t)highlight_ga.ga_len); + for (int i = 1; i <= highlight_ga.ga_len; i++) { Dictionary attrs = ARRAY_DICT_INIT; HlGroup *h = &hl_table[i - 1]; if (h->sg_attr > 0) { - attrs = hlattrs2dict(NULL, syn_attr2entry(h->sg_attr), true); + attrs = arena_dict(arena, HLATTRS_DICT_SIZE); + hlattrs2dict(&attrs, syn_attr2entry(h->sg_attr), true); } else if (h->sg_link > 0) { - const char *link = (const char *)hl_table[h->sg_link - 1].sg_name; - PUT(attrs, "link", STRING_OBJ(cstr_to_string(link))); + attrs = arena_dict(arena, 1); + char *link = hl_table[h->sg_link - 1].sg_name; + PUT_C(attrs, "link", STRING_OBJ(cstr_as_string(link))); } - PUT(rv, (const char *)h->sg_name, DICTIONARY_OBJ(attrs)); + PUT_C(rv, (char *)h->sg_name, DICTIONARY_OBJ(attrs)); } return rv; @@ -1437,39 +1536,41 @@ Dictionary get_global_hl_defs(void) static bool highlight_list_arg(const int id, bool didh, const int type, int iarg, const char *sarg, const char *const name) { - char buf[100]; - if (got_int) { return false; } - if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0)) { - const char *ts = buf; - if (type == LIST_INT) { - snprintf((char *)buf, sizeof(buf), "%d", iarg - 1); - } else if (type == LIST_STRING) { - ts = sarg; - } else { // type == LIST_ATTR - buf[0] = NUL; - for (int i = 0; hl_attr_table[i] != 0; i++) { - if (iarg & hl_attr_table[i]) { - if (buf[0] != NUL) { - xstrlcat(buf, ",", 100); - } - xstrlcat(buf, hl_name_table[i], 100); - iarg &= ~hl_attr_table[i]; // don't want "inverse" + + if (type == LIST_STRING ? (sarg == NULL) : (iarg == 0)) { + return didh; + } + + char buf[100]; + const char *ts = buf; + if (type == LIST_INT) { + snprintf((char *)buf, sizeof(buf), "%d", iarg - 1); + } else if (type == LIST_STRING) { + ts = sarg; + } else { // type == LIST_ATTR + buf[0] = NUL; + for (int i = 0; hl_attr_table[i] != 0; i++) { + if (iarg & hl_attr_table[i]) { + if (buf[0] != NUL) { + xstrlcat(buf, ",", 100); } + xstrlcat(buf, hl_name_table[i], 100); + iarg &= ~hl_attr_table[i]; // don't want "inverse" } } + } - (void)syn_list_header(didh, vim_strsize((char *)ts) + (int)STRLEN(name) + 1, id, false); - didh = true; - if (!got_int) { - if (*name != NUL) { - msg_puts_attr(name, HL_ATTR(HLF_D)); - msg_puts_attr("=", HL_ATTR(HLF_D)); - } - msg_outtrans((char *)ts); + (void)syn_list_header(didh, vim_strsize((char *)ts) + (int)strlen(name) + 1, id, false); + didh = true; + if (!got_int) { + if (*name != NUL) { + msg_puts_attr(name, HL_ATTR(HLF_D)); + msg_puts_attr("=", HL_ATTR(HLF_D)); } + msg_outtrans((char *)ts); } return didh; } @@ -1484,19 +1585,24 @@ static bool highlight_list_arg(const int id, bool didh, const int type, int iarg const char *highlight_has_attr(const int id, const int flag, const int modec) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { - int attr; - if (id <= 0 || id > highlight_ga.ga_len) { return NULL; } + int attr; + if (modec == 'g') { attr = hl_table[id - 1].sg_gui; } else { attr = hl_table[id - 1].sg_cterm; } - return (attr & flag) ? "1" : NULL; + if (flag & HL_UNDERLINE_MASK) { + int ul = attr & HL_UNDERLINE_MASK; + return ul == flag ? "1" : NULL; + } else { + return (attr & flag) ? "1" : NULL; + } } /// Return color name of the given highlight group @@ -1512,7 +1618,6 @@ const char *highlight_color(const int id, const char *const what, const int mode FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { static char name[20]; - int n; bool fg = false; bool sp = false; bool font = false; @@ -1531,6 +1636,9 @@ const char *highlight_color(const int id, const char *const what, const int mode } else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g')) { return NULL; } + + int n; + if (modec == 'g') { if (what[2] == '#' && ui_rgb_attached()) { if (fg) { @@ -1592,7 +1700,7 @@ bool syn_list_header(const bool did_header, const int outlen, const int id, bool if (got_int) { return true; } - msg_outtrans((char *)hl_table[id - 1].sg_name); + msg_outtrans(hl_table[id - 1].sg_name); name_col = msg_col; endcol = 15; } else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) { @@ -1661,7 +1769,11 @@ static void set_hl_attr(int idx) int syn_name2id(const char *name) FUNC_ATTR_NONNULL_ALL { - return syn_name2id_len(name, STRLEN(name)); + if (name[0] == '@') { + // if we look up @aaa.bbb, we have to consider @aaa as well + return syn_check_group(name, strlen(name)); + } + return syn_name2id_len(name, strlen(name)); } /// Lookup a highlight group name and return its ID. @@ -1681,7 +1793,7 @@ int syn_name2id_len(const char *name, size_t len) // Avoid alloc()/free(), these are slow too. memcpy(name_u, name, len); name_u[len] = '\0'; - vim_strup((char_u *)name_u); + vim_strup(name_u); // map_get(..., int) returns 0 when no key is present, which is // the expected value for missing highlight group. @@ -1690,10 +1802,10 @@ int syn_name2id_len(const char *name, size_t len) /// Lookup a highlight group name and return its attributes. /// Return zero if not found. -int syn_name2attr(const char_u *name) +int syn_name2attr(const char *name) FUNC_ATTR_NONNULL_ALL { - int id = syn_name2id((char *)name); + int id = syn_name2id(name); if (id != 0) { return syn_id2attr(id); @@ -1701,7 +1813,7 @@ int syn_name2attr(const char_u *name) return 0; } -/// Return TRUE if highlight group "name" exists. +/// Return true if highlight group "name" exists. int highlight_exists(const char *name) { return syn_name2id(name) > 0; @@ -1709,10 +1821,10 @@ int highlight_exists(const char *name) /// Return the name of highlight group "id". /// When not a valid ID return an empty string. -char_u *syn_id2name(int id) +char *syn_id2name(int id) { if (id <= 0 || id > highlight_ga.ga_len) { - return (char_u *)""; + return ""; } return hl_table[id - 1].sg_name; } @@ -1750,11 +1862,19 @@ static int syn_add_group(const char *name, size_t len) if (!vim_isprintc(c)) { emsg(_("E669: Unprintable character in group name")); return 0; - } else if (!ASCII_ISALNUM(c) && c != '_') { - // This is an error, but since there previously was no check only give a warning. + } else if (!ASCII_ISALNUM(c) && c != '_' && c != '.' && c != '@') { + // '.' and '@' are allowed characters for use with treesitter capture names. msg_source(HL_ATTR(HLF_W)); - msg(_("W18: Invalid character in group name")); - break; + emsg(_(e_highlight_group_name_invalid_char)); + return 0; + } + } + + int scoped_parent = 0; + if (len > 1 && name[0] == '@') { + char *delim = xmemrchr(name, '.', len); + if (delim) { + scoped_parent = syn_check_group(name, (size_t)(delim - name)); } } @@ -1774,7 +1894,7 @@ static int syn_add_group(const char *name, size_t len) // Append another syntax_highlight entry. HlGroup *hlgp = GA_APPEND_VIA_PTR(HlGroup, &highlight_ga); CLEAR_POINTER(hlgp); - hlgp->sg_name = (char_u *)arena_memdupz(&highlight_arena, name, len); + hlgp->sg_name = arena_memdupz(&highlight_arena, name, len); hlgp->sg_rgb_bg = -1; hlgp->sg_rgb_fg = -1; hlgp->sg_rgb_sp = -1; @@ -1783,7 +1903,10 @@ static int syn_add_group(const char *name, size_t len) hlgp->sg_rgb_sp_idx = kColorIdxNone; hlgp->sg_blend = -1; hlgp->sg_name_u = arena_memdupz(&highlight_arena, name, len); - vim_strup((char_u *)hlgp->sg_name_u); + hlgp->sg_parent = scoped_parent; + // will get set to false by caller if settings are added + hlgp->sg_cleared = true; + vim_strup(hlgp->sg_name_u); int id = highlight_ga.ga_len; // ID is index plus one @@ -1844,10 +1967,13 @@ int syn_ns_get_final_id(int *ns_id, int hl_id) continue; } - if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) { + if (sgp->sg_link > 0 && sgp->sg_link <= highlight_ga.ga_len) { + hl_id = sgp->sg_link; + } else if (sgp->sg_cleared && sgp->sg_parent > 0) { + hl_id = sgp->sg_parent; + } else { break; } - hl_id = sgp->sg_link; } return hl_id; @@ -1920,17 +2046,15 @@ static void combine_stl_hlt(int id, int id_S, int id_alt, int hlcnt, int i, int /// screen redraw after any :highlight command. void highlight_changed(void) { - int id; char userhl[30]; // use 30 to avoid compiler warning int id_S = -1; int id_SNC = 0; - int hlcnt; need_highlight_changed = false; /// Translate builtin highlight groups into attributes for quick lookup. for (int hlf = 0; hlf < HLF_COUNT; hlf++) { - id = syn_check_group(hlf_names[hlf], STRLEN(hlf_names[hlf])); + int id = syn_check_group(hlf_names[hlf], strlen(hlf_names[hlf])); if (id == 0) { abort(); } @@ -1957,7 +2081,7 @@ void highlight_changed(void) } } - // sentinel value. used when no hightlight namespace is active + // sentinel value. used when no highlight namespace is active highlight_attr[HLF_COUNT] = 0; // @@ -1969,7 +2093,7 @@ void highlight_changed(void) // Must to be in there simultaneously in case of table overflows in // get_attr_entry() ga_grow(&highlight_ga, 10); - hlcnt = highlight_ga.ga_len; + int hlcnt = highlight_ga.ga_len; if (id_S == -1) { // Make sure id_S is always valid to simplify code below. Use the last entry CLEAR_POINTER(&hl_table[hlcnt + 9]); @@ -1977,7 +2101,7 @@ void highlight_changed(void) } for (int i = 0; i < 9; i++) { snprintf(userhl, sizeof(userhl), "User%d", i + 1); - id = syn_name2id(userhl); + int id = syn_name2id(userhl); if (id == 0) { highlight_user[i] = 0; highlight_stlnc[i] = 0; @@ -1987,6 +2111,8 @@ void highlight_changed(void) } } highlight_ga.ga_len = hlcnt; + + decor_provider_invalidate_hl(); } /// Handle command line completion for :highlight command. @@ -1998,47 +2124,53 @@ void set_context_in_highlight_cmd(expand_T *xp, const char *arg) include_link = 2; include_default = 1; + if (*arg == NUL) { + return; + } + // (part of) subcommand already typed - if (*arg != NUL) { - const char *p = (const char *)skiptowhite((const char_u *)arg); - if (*p != NUL) { // Past "default" or group name. - include_default = 0; - if (strncmp("default", arg, (unsigned)(p - arg)) == 0) { - arg = (const char *)skipwhite(p); - xp->xp_pattern = (char *)arg; - p = (const char *)skiptowhite((const char_u *)arg); - } - if (*p != NUL) { // past group name - include_link = 0; - if (arg[1] == 'i' && arg[0] == 'N') { - highlight_list(); - } - if (strncmp("link", arg, (unsigned)(p - arg)) == 0 - || strncmp("clear", arg, (unsigned)(p - arg)) == 0) { - xp->xp_pattern = skipwhite(p); - p = (const char *)skiptowhite((char_u *)xp->xp_pattern); - if (*p != NUL) { // Past first group name. - xp->xp_pattern = skipwhite(p); - p = (const char *)skiptowhite((char_u *)xp->xp_pattern); - } - } - if (*p != NUL) { // Past group name(s). - xp->xp_context = EXPAND_NOTHING; - } - } + const char *p = (const char *)skiptowhite(arg); + if (*p == NUL) { + return; + } + + // past "default" or group name + include_default = 0; + if (strncmp("default", arg, (unsigned)(p - arg)) == 0) { + arg = (const char *)skipwhite(p); + xp->xp_pattern = (char *)arg; + p = (const char *)skiptowhite(arg); + } + if (*p == NUL) { + return; + } + + // past group name + include_link = 0; + if (arg[1] == 'i' && arg[0] == 'N') { + highlight_list(); + } + if (strncmp("link", arg, (unsigned)(p - arg)) == 0 + || strncmp("clear", arg, (unsigned)(p - arg)) == 0) { + xp->xp_pattern = skipwhite(p); + p = (const char *)skiptowhite(xp->xp_pattern); + if (*p != NUL) { // past first group name + xp->xp_pattern = skipwhite(p); + p = (const char *)skiptowhite(xp->xp_pattern); } } + if (*p != NUL) { // past group name(s) + xp->xp_context = EXPAND_NOTHING; + } } /// List highlighting matches in a nice way. static void highlight_list(void) { - int i; - - for (i = 10; --i >= 0;) { + for (int i = 10; --i >= 0;) { highlight_list_two(i, HL_ATTR(HLF_D)); } - for (i = 40; --i >= 0;) { + for (int i = 40; --i >= 0;) { highlight_list_two(99, 0); } } @@ -2783,9 +2915,9 @@ color_name_table_T color_name_table[] = { /// return the hex value or -1 if could not find a correct value RgbValue name_to_color(const char *name, int *idx) { - if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) - && isxdigit(name[3]) && isxdigit(name[4]) && isxdigit(name[5]) - && isxdigit(name[6]) && name[7] == NUL) { + if (name[0] == '#' && isxdigit((uint8_t)name[1]) && isxdigit((uint8_t)name[2]) + && isxdigit((uint8_t)name[3]) && isxdigit((uint8_t)name[4]) && isxdigit((uint8_t)name[5]) + && isxdigit((uint8_t)name[6]) && name[7] == NUL) { // rgb hex string *idx = kColorIdxHex; return (RgbValue)strtol((char *)(name + 1), NULL, 16); @@ -2809,7 +2941,6 @@ RgbValue name_to_color(const char *name, int *idx) } else { // found match *idx = m; return color_name_table[m].color; - break; } } diff --git a/src/nvim/iconv.h b/src/nvim/iconv.h index 509f83c415..f5f3f25786 100644 --- a/src/nvim/iconv.h +++ b/src/nvim/iconv.h @@ -1,20 +1,18 @@ #ifndef NVIM_ICONV_H #define NVIM_ICONV_H -#include "auto/config.h" +#include <errno.h> +#include <iconv.h> -#ifdef HAVE_ICONV -# include <errno.h> -# include <iconv.h> +#include "auto/config.h" // define some missing constants if necessary -# ifndef EILSEQ -# define EILSEQ 123 -# endif -# define ICONV_ERRNO errno -# define ICONV_E2BIG E2BIG -# define ICONV_EINVAL EINVAL -# define ICONV_EILSEQ EILSEQ +#ifndef EILSEQ +# define EILSEQ 123 #endif +#define ICONV_ERRNO errno +#define ICONV_E2BIG E2BIG +#define ICONV_EINVAL EINVAL +#define ICONV_EILSEQ EILSEQ #endif // NVIM_ICONV_H diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c deleted file mode 100644 index 689d1fce0d..0000000000 --- a/src/nvim/if_cscope.c +++ /dev/null @@ -1,2052 +0,0 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - -/* - * CSCOPE support for Vim added by Andy Kahn <kahn@zk3.dec.com> - * Ported to Win32 by Sergey Khorev <sergey.khorev@gmail.com> - * - * The basic idea/structure of cscope for Vim was borrowed from Nvi. There - * might be a few lines of code that look similar to what Nvi has. - */ - -#include <assert.h> -#include <errno.h> -#include <fcntl.h> -#include <inttypes.h> -#include <stdbool.h> -#include <sys/stat.h> -#include <sys/types.h> - -#include "nvim/ascii.h" -#include "nvim/autocmd.h" -#include "nvim/buffer.h" -#include "nvim/charset.h" -#include "nvim/eval.h" -#include "nvim/event/stream.h" -#include "nvim/ex_eval.h" -#include "nvim/fileio.h" -#include "nvim/if_cscope.h" -#include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/os/input.h" -#include "nvim/os/os.h" -#include "nvim/os/time.h" -#include "nvim/path.h" -#include "nvim/quickfix.h" -#include "nvim/strings.h" -#include "nvim/tag.h" -#include "nvim/window.h" -#if defined(UNIX) -# include <sys/wait.h> -#endif -#include "nvim/if_cscope_defs.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "if_cscope.c.generated.h" -#endif - -static csinfo_T *csinfo = NULL; -static size_t csinfo_size = 0; // number of items allocated in csinfo[] - -static int eap_arg_len; // length of eap->arg, set in cs_lookup_cmd() -static cscmd_T cs_cmds[] = -{ - { "add", cs_add, - N_("Add a new database"), "add file|dir [pre-path] [flags]", 0 }, - { "find", cs_find, - N_("Query for a pattern"), "find a|c|d|e|f|g|i|s|t name", 1 }, - { "help", cs_help, - N_("Show this message"), "help", 0 }, - { "kill", cs_kill, - N_("Kill a connection"), "kill #", 0 }, - { "reset", cs_reset, - N_("Reinit all connections"), "reset", 0 }, - { "show", cs_show, - N_("Show connections"), "show", 0 }, - { NULL, NULL, NULL, NULL, 0 } -}; - -static void cs_usage_msg(csid_e x) -{ - (void)semsg(_("E560: Usage: cs[cope] %s"), cs_cmds[(int)x].usage); -} - -static enum { - EXP_CSCOPE_SUBCMD, // expand ":cscope" sub-commands - EXP_SCSCOPE_SUBCMD, // expand ":scscope" sub-commands - EXP_CSCOPE_FIND, // expand ":cscope find" arguments - EXP_CSCOPE_KILL, // expand ":cscope kill" arguments -} expand_what; - -/* - * Function given to ExpandGeneric() to obtain the cscope command - * expansion. - */ -char *get_cscope_name(expand_T *xp, int idx) -{ - int current_idx; - - switch (expand_what) { - case EXP_CSCOPE_SUBCMD: - // Complete with sub-commands of ":cscope": - // add, find, help, kill, reset, show - return cs_cmds[idx].name; - case EXP_SCSCOPE_SUBCMD: { - // Complete with sub-commands of ":scscope": same sub-commands as - // ":cscope" but skip commands which don't support split windows - int i; - for (i = 0, current_idx = 0; cs_cmds[i].name != NULL; i++) { - if (cs_cmds[i].cansplit) { - if (current_idx++ == idx) { - break; - } - } - } - return cs_cmds[i].name; - } - case EXP_CSCOPE_FIND: { - const char *query_type[] = - { - "a", "c", "d", "e", "f", "g", "i", "s", "t", NULL - }; - - // Complete with query type of ":cscope find {query_type}". - // {query_type} can be letters (c, d, ... a) or numbers (0, 1, - // ..., 9) but only complete with letters, since numbers are - // redundant. - return (char *)query_type[idx]; - } - case EXP_CSCOPE_KILL: { - static char connection[5]; - - // ":cscope kill" accepts connection numbers or partial names of - // the pathname of the cscope database as argument. Only complete - // with connection numbers. -1 can also be used to kill all - // connections. - size_t i; - for (i = 0, current_idx = 0; i < csinfo_size; i++) { - if (csinfo[i].fname == NULL) { - continue; - } - if (current_idx++ == idx) { - vim_snprintf(connection, sizeof(connection), "%zu", i); - return connection; - } - } - return (current_idx == idx && idx > 0) ? "-1" : NULL; - } - default: - return NULL; - } -} - -/* - * Handle command line completion for :cscope command. - */ -void set_context_in_cscope_cmd(expand_T *xp, const char *arg, cmdidx_T cmdidx) -{ - // Default: expand subcommands. - xp->xp_context = EXPAND_CSCOPE; - xp->xp_pattern = (char *)arg; - expand_what = ((cmdidx == CMD_scscope) - ? EXP_SCSCOPE_SUBCMD : EXP_CSCOPE_SUBCMD); - - // (part of) subcommand already typed - if (*arg != NUL) { - const char *p = (const char *)skiptowhite((const char_u *)arg); - if (*p != NUL) { // Past first word. - xp->xp_pattern = skipwhite(p); - if (*skiptowhite((char_u *)xp->xp_pattern) != NUL) { - xp->xp_context = EXPAND_NOTHING; - } else if (STRNICMP(arg, "add", p - arg) == 0) { - xp->xp_context = EXPAND_FILES; - } else if (STRNICMP(arg, "kill", p - arg) == 0) { - expand_what = EXP_CSCOPE_KILL; - } else if (STRNICMP(arg, "find", p - arg) == 0) { - expand_what = EXP_CSCOPE_FIND; - } else { - xp->xp_context = EXPAND_NOTHING; - } - } - } -} - -/// Find the command, print help if invalid, and then call the corresponding -/// command function. -/// -/// @param make_split whether to split window -static void do_cscope_general(exarg_T *eap, int make_split) -{ - cscmd_T *cmdp; - - if ((cmdp = cs_lookup_cmd(eap)) == NULL) { - cs_help(eap); - return; - } - - if (make_split) { - if (!cmdp->cansplit) { - (void)msg_puts(_("This cscope command does not support splitting the window.\n")); - return; - } - postponed_split = -1; - postponed_split_flags = cmdmod.cmod_split; - postponed_split_tab = cmdmod.cmod_tab; - } - - cmdp->func(eap); - - postponed_split_flags = 0; - postponed_split_tab = 0; -} - -/// Implementation of ":cscope" and ":lcscope" -void ex_cscope(exarg_T *eap) -{ - do_cscope_general(eap, FALSE); -} - -/// Implementation of ":scscope". Same as ex_cscope(), but splits window, too. -void ex_scscope(exarg_T *eap) -{ - do_cscope_general(eap, TRUE); -} - -/// Implementation of ":cstag" -void ex_cstag(exarg_T *eap) -{ - int ret = FALSE; - - if (*eap->arg == NUL) { - (void)emsg(_("E562: Usage: cstag <ident>")); - return; - } - - switch (p_csto) { - case 0: - if (cs_check_for_connections()) { - ret = cs_find_common("g", eap->arg, eap->forceit, false, - false, (char_u *)(*eap->cmdlinep)); - if (ret == false) { - cs_free_tags(); - if (msg_col) { - msg_putchar('\n'); - } - - if (cs_check_for_tags()) { - ret = do_tag((char_u *)eap->arg, DT_JUMP, 0, eap->forceit, false); - } - } - } else if (cs_check_for_tags()) { - ret = do_tag((char_u *)eap->arg, DT_JUMP, 0, eap->forceit, false); - } - break; - case 1: - if (cs_check_for_tags()) { - ret = do_tag((char_u *)eap->arg, DT_JUMP, 0, eap->forceit, false); - if (ret == false) { - if (msg_col) { - msg_putchar('\n'); - } - - if (cs_check_for_connections()) { - ret = cs_find_common("g", eap->arg, eap->forceit, - false, false, (char_u *)(*eap->cmdlinep)); - if (ret == false) { - cs_free_tags(); - } - } - } - } else if (cs_check_for_connections()) { - ret = cs_find_common("g", eap->arg, eap->forceit, false, - false, (char_u *)(*eap->cmdlinep)); - if (ret == false) { - cs_free_tags(); - } - } - break; - default: - break; - } - - if (!ret) { - (void)emsg(_("E257: cstag: tag not found")); - g_do_tagpreview = 0; - } -} - -/// This simulates a vim_fgets(), but for cscope, returns the next line -/// from the cscope output. should only be called from find_tags() -/// -/// @return true if eof, FALSE otherwise -bool cs_fgets(char_u *buf, int size) - FUNC_ATTR_NONNULL_ALL -{ - char *p; - - if ((p = cs_manage_matches(NULL, NULL, 0, Get)) == NULL) { - return true; - } - STRLCPY(buf, p, size); - - return false; -} - -/// Called only from do_tag(), when popping the tag stack. -void cs_free_tags(void) -{ - cs_manage_matches(NULL, NULL, 0, Free); -} - -/// Called from do_tag(). -void cs_print_tags(void) -{ - cs_manage_matches(NULL, NULL, 0, Print); -} - -/* - * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function - * - * Checks for the existence of a |cscope| connection. If no - * parameters are specified, then the function returns: - * - * 0, if cscope was not available (not compiled in), or if there - * are no cscope connections; or - * 1, if there is at least one cscope connection. - * - * If parameters are specified, then the value of {num} - * determines how existence of a cscope connection is checked: - * - * {num} Description of existence check - * ----- ------------------------------ - * 0 Same as no parameters (e.g., "cscope_connection()"). - * 1 Ignore {prepend}, and use partial string matches for - * {dbpath}. - * 2 Ignore {prepend}, and use exact string matches for - * {dbpath}. - * 3 Use {prepend}, use partial string matches for both - * {dbpath} and {prepend}. - * 4 Use {prepend}, use exact string matches for both - * {dbpath} and {prepend}. - * - * Note: All string comparisons are case sensitive! - */ -bool cs_connection(int num, char_u *dbpath, char_u *ppath) -{ - if (num < 0 || num > 4 || (num > 0 && !dbpath)) { - return false; - } - - for (size_t i = 0; i < csinfo_size; i++) { - if (!csinfo[i].fname) { - continue; - } - if (num == 0) { - return true; - } - switch (num) { - case 1: - if (strstr(csinfo[i].fname, (char *)dbpath)) { - return true; - } - break; - case 2: - if (strcmp(csinfo[i].fname, (char *)dbpath) == 0) { - return true; - } - break; - case 3: - if (strstr(csinfo[i].fname, (char *)dbpath) - && ((!ppath && !csinfo[i].ppath) - || (ppath - && csinfo[i].ppath - && strstr(csinfo[i].ppath, (char *)ppath)))) { - return true; - } - break; - case 4: - if ((strcmp(csinfo[i].fname, (char *)dbpath) == 0) - && ((!ppath && !csinfo[i].ppath) - || (ppath - && csinfo[i].ppath - && (strcmp(csinfo[i].ppath, (char *)ppath) == 0)))) { - return true; - } - break; - } - } - - return false; -} // cs_connection - -/* - * PRIVATE functions - ****************************************************************************/ - -/// Add cscope database or a directory name (to look for cscope.out) -/// to the cscope connection list. -static int cs_add(exarg_T *eap) -{ - char *fname, *ppath, *flags = NULL; - - if ((fname = strtok((char *)NULL, (const char *)" ")) == NULL) { - cs_usage_msg(Add); - return CSCOPE_FAILURE; - } - if ((ppath = strtok((char *)NULL, (const char *)" ")) != NULL) { - flags = strtok((char *)NULL, (const char *)" "); - } - - return cs_add_common(fname, ppath, flags); -} - -static void cs_stat_emsg(char *fname) -{ - int err = errno; - (void)semsg(_("E563: stat(%s) error: %d"), fname, err); -} - -/// The common routine to add a new cscope connection. Called by -/// cs_add() and cs_reset(). I really don't like to do this, but this -/// routine uses a number of goto statements. -/// -/// @param arg1 filename - may contain environment variables -/// @param arg2 prepend path - may contain environment variables -static int cs_add_common(char *arg1, char *arg2, char *flags) -{ - char *fname = NULL; - char *fname2 = NULL; - char *ppath = NULL; - size_t usedlen = 0; - char *fbuf = NULL; - - // get the filename (arg1), expand it, and try to stat it - fname = xmalloc(MAXPATHL + 1); - - expand_env((char_u *)arg1, (char_u *)fname, MAXPATHL); - size_t len = STRLEN(fname); - fbuf = fname; - (void)modify_fname(":p", false, &usedlen, &fname, &fbuf, &len); - if (fname == NULL) { - goto add_err; - } - fname = xstrnsave(fname, len); - xfree(fbuf); - FileInfo file_info; - bool file_info_ok = os_fileinfo(fname, &file_info); - if (!file_info_ok) { -staterr: - if (p_csverbose) { - cs_stat_emsg(fname); - } - goto add_err; - } - - // get the prepend path (arg2), expand it, and see if it exists - if (arg2 != NULL) { - ppath = xmalloc(MAXPATHL + 1); - expand_env((char_u *)arg2, (char_u *)ppath, MAXPATHL); - if (!os_path_exists((char_u *)ppath)) { - goto staterr; - } - } - - int i; - // if filename is a directory, append the cscope database name to it - if (S_ISDIR(file_info.stat.st_mode)) { - fname2 = (char *)xmalloc(strlen(CSCOPE_DBFILE) + strlen(fname) + 2); - - while (fname[strlen(fname) - 1] == '/' - ) { - fname[strlen(fname) - 1] = '\0'; - if (fname[0] == '\0') { - break; - } - } - if (fname[0] == '\0') { - (void)sprintf(fname2, "/%s", CSCOPE_DBFILE); - } else { - (void)sprintf(fname2, "%s/%s", fname, CSCOPE_DBFILE); - } - - file_info_ok = os_fileinfo(fname2, &file_info); - if (!file_info_ok) { - if (p_csverbose) { - cs_stat_emsg(fname2); - } - goto add_err; - } - - i = cs_insert_filelist(fname2, ppath, flags, &file_info); - } else if (S_ISREG(file_info.stat.st_mode) || S_ISLNK(file_info.stat.st_mode)) { - i = cs_insert_filelist(fname, ppath, flags, &file_info); - } else { - if (p_csverbose) { - (void)semsg(_("E564: %s is not a directory or a valid cscope database"), - fname); - } - goto add_err; - } - - if (i != -1) { - assert(i >= 0); - if (cs_create_connection((size_t)i) == CSCOPE_FAILURE - || cs_read_prompt((size_t)i) == CSCOPE_FAILURE) { - cs_release_csp((size_t)i, true); - goto add_err; - } - - if (p_csverbose) { - msg_clr_eos(); - (void)smsg_attr(HL_ATTR(HLF_R), - _("Added cscope database %s"), - csinfo[i].fname); - } - } - - xfree(fname); - xfree(fname2); - xfree(ppath); - return CSCOPE_SUCCESS; - -add_err: - xfree(fname2); - xfree(fname); - xfree(ppath); - return CSCOPE_FAILURE; -} - -static bool cs_check_for_connections(void) -{ - return cs_cnt_connections() > 0; -} - -static int cs_check_for_tags(void) -{ - return p_tags[0] != NUL && curbuf->b_p_tags != NULL; -} - -/// Count the number of cscope connections. -static size_t cs_cnt_connections(void) -{ - size_t cnt = 0; - - for (size_t i = 0; i < csinfo_size; i++) { - if (csinfo[i].fname != NULL) { - cnt++; - } - } - return cnt; -} - -/// @param idx connection index -static void cs_reading_emsg(size_t idx) -{ - semsg(_("E262: error reading cscope connection %" PRIu64), (uint64_t)idx); -} - -#define CSREAD_BUFSIZE 2048 -/// Count the number of matches for a given cscope connection. -static int cs_cnt_matches(size_t idx) -{ - char *stok; - int nlines = 0; - - char *buf = xmalloc(CSREAD_BUFSIZE); - for (;;) { - errno = 0; - if (!fgets(buf, CSREAD_BUFSIZE, csinfo[idx].fr_fp)) { - if (errno == EINTR) { - continue; - } - - if (feof(csinfo[idx].fr_fp)) { - errno = EIO; - } - - cs_reading_emsg(idx); - - xfree(buf); - return CSCOPE_FAILURE; - } - - // If the database is out of date, or there's some other problem, - // cscope will output error messages before the number-of-lines output. - // Display/discard any output that doesn't match what we want. - // Accept "\S*cscope: X lines", also matches "mlcscope". - // Bail out for the "Unable to search" error. - if (strstr((const char *)buf, "Unable to search database") != NULL) { - break; - } - if ((stok = strtok(buf, (const char *)" ")) == NULL) { - continue; - } - if (strstr((const char *)stok, "cscope:") == NULL) { - continue; - } - - if ((stok = strtok(NULL, (const char *)" ")) == NULL) { - continue; - } - nlines = atoi(stok); - if (nlines < 0) { - nlines = 0; - break; - } - - if ((stok = strtok(NULL, (const char *)" ")) == NULL) { - continue; - } - if (strncmp(stok, "lines", 5)) { - continue; - } - - break; - } - - xfree(buf); - return nlines; -} - -/// Creates the actual cscope command query from what the user entered. -static char *cs_create_cmd(char *csoption, char *pattern) -{ - char *cmd; - short search; - char *pat; - - switch (csoption[0]) { - case '0': - case 's': - search = 0; - break; - case '1': - case 'g': - search = 1; - break; - case '2': - case 'd': - search = 2; - break; - case '3': - case 'c': - search = 3; - break; - case '4': - case 't': - search = 4; - break; - case '6': - case 'e': - search = 6; - break; - case '7': - case 'f': - search = 7; - break; - case '8': - case 'i': - search = 8; - break; - case '9': - case 'a': - search = 9; - break; - default: - (void)emsg(_("E561: unknown cscope search type")); - cs_usage_msg(Find); - return NULL; - } - - // Skip white space before the patter, except for text and pattern search, - // they may want to use the leading white space. - pat = pattern; - if (search != 4 && search != 6) { - while (ascii_iswhite(*pat)) { - pat++; - } - } - - cmd = xmalloc(strlen(pat) + 2); - - (void)sprintf(cmd, "%d%s", search, pat); - - return cmd; -} - -/// This piece of code was taken/adapted from nvi. do we need to add -/// the BSD license notice? -static int cs_create_connection(size_t i) -{ -#ifdef UNIX - int to_cs[2], from_cs[2]; -#endif - char *prog, *cmd, *ppath = NULL; - -#if defined(UNIX) - /* - * Cscope reads from to_cs[0] and writes to from_cs[1]; vi reads from - * from_cs[0] and writes to to_cs[1]. - */ - to_cs[0] = to_cs[1] = from_cs[0] = from_cs[1] = -1; - if (pipe(to_cs) < 0 || pipe(from_cs) < 0) { - (void)emsg(_("E566: Could not create cscope pipes")); -err_closing: - if (to_cs[0] != -1) { - (void)close(to_cs[0]); - } - if (to_cs[1] != -1) { - (void)close(to_cs[1]); - } - if (from_cs[0] != -1) { - (void)close(from_cs[0]); - } - if (from_cs[1] != -1) { - (void)close(from_cs[1]); - } - return CSCOPE_FAILURE; - } - - switch (csinfo[i].pid = fork()) { - case -1: - (void)emsg(_("E622: Could not fork for cscope")); - goto err_closing; - case 0: // child: run cscope. - if (dup2(to_cs[0], STDIN_FILENO) == -1) { - PERROR("cs_create_connection 1"); - } - if (dup2(from_cs[1], STDOUT_FILENO) == -1) { - PERROR("cs_create_connection 2"); - } - if (dup2(from_cs[1], STDERR_FILENO) == -1) { - PERROR("cs_create_connection 3"); - } - - // close unused - (void)close(to_cs[1]); - (void)close(from_cs[0]); -#else - // Create pipes to communicate with cscope - int fd; - SECURITY_ATTRIBUTES sa; - PROCESS_INFORMATION pi; - BOOL pipe_stdin = FALSE, pipe_stdout = FALSE; // NOLINT(readability/bool) - STARTUPINFO si; - HANDLE stdin_rd, stdout_rd; - HANDLE stdout_wr, stdin_wr; - BOOL created; - - sa.nLength = sizeof(SECURITY_ATTRIBUTES); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = NULL; - - if (!(pipe_stdin = CreatePipe(&stdin_rd, &stdin_wr, &sa, 0)) - || !(pipe_stdout = CreatePipe(&stdout_rd, &stdout_wr, &sa, 0))) { - (void)emsg(_("E566: Could not create cscope pipes")); -err_closing: - if (pipe_stdin) { - CloseHandle(stdin_rd); - CloseHandle(stdin_wr); - } - if (pipe_stdout) { - CloseHandle(stdout_rd); - CloseHandle(stdout_wr); - } - return CSCOPE_FAILURE; - } -#endif - // expand the cscope exec for env var's - prog = xmalloc(MAXPATHL + 1); - expand_env(p_csprg, (char_u *)prog, MAXPATHL); - - // alloc space to hold the cscope command - size_t len = strlen(prog) + strlen(csinfo[i].fname) + 32; - if (csinfo[i].ppath) { - // expand the prepend path for env var's - ppath = xmalloc(MAXPATHL + 1); - expand_env((char_u *)csinfo[i].ppath, (char_u *)ppath, MAXPATHL); - - len += strlen(ppath); - } - - if (csinfo[i].flags) { - len += strlen(csinfo[i].flags); - } - - cmd = xmalloc(len); - - // run the cscope command; is there execl for non-unix systems? -#if defined(UNIX) - (void)snprintf(cmd, len, "exec %s -dl -f %s", prog, csinfo[i].fname); -#else - // WIN32 - (void)snprintf(cmd, len, "%s -dl -f %s", prog, csinfo[i].fname); -#endif - if (csinfo[i].ppath != NULL) { - (void)strcat(cmd, " -P"); - (void)strcat(cmd, csinfo[i].ppath); - } - if (csinfo[i].flags != NULL) { - (void)strcat(cmd, " "); - (void)strcat(cmd, csinfo[i].flags); - } -#ifdef UNIX - // on Win32 we still need prog - xfree(prog); -#endif - xfree(ppath); - -#if defined(UNIX) -# if defined(HAVE_SETSID) || defined(HAVE_SETPGID) - // Change our process group to avoid cscope receiving SIGWINCH. -# if defined(HAVE_SETSID) - (void)setsid(); -# else - if (setpgid(0, 0) == -1) { - PERROR(_("cs_create_connection setpgid failed")); - } -# endif -# endif - if (execl("/bin/sh", "sh", "-c", cmd, (char *)NULL) == -1) { - PERROR(_("cs_create_connection exec failed")); - } - - exit(127); - // NOTREACHED - default: // parent. - // Save the file descriptors for later duplication, and - // reopen as streams. - if ((csinfo[i].to_fp = fdopen(to_cs[1], "w")) == NULL) { - PERROR(_("cs_create_connection: fdopen for to_fp failed")); - } - if ((csinfo[i].fr_fp = fdopen(from_cs[0], "r")) == NULL) { - PERROR(_("cs_create_connection: fdopen for fr_fp failed")); - } - - // close unused - (void)close(to_cs[0]); - (void)close(from_cs[1]); - - break; - } - -#else - // WIN32 - // Create a new process to run cscope and use pipes to talk with it - GetStartupInfo(&si); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; // Hide child application window - si.hStdOutput = stdout_wr; - si.hStdError = stdout_wr; - si.hStdInput = stdin_rd; - created = CreateProcess(NULL, cmd, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, - NULL, NULL, &si, &pi); - xfree(prog); - xfree(cmd); - - if (!created) { - PERROR(_("cs_create_connection exec failed")); - (void)emsg(_("E623: Could not spawn cscope process")); - goto err_closing; - } - // else - csinfo[i].pid = pi.dwProcessId; - csinfo[i].hProc = pi.hProcess; - CloseHandle(pi.hThread); - - // TODO(neovim): tidy up after failure to create files on pipe handles. - if (((fd = _open_osfhandle((intptr_t)stdin_wr, _O_TEXT|_O_APPEND)) < 0) - || ((csinfo[i].to_fp = _fdopen(fd, "w")) == NULL)) { - PERROR(_("cs_create_connection: fdopen for to_fp failed")); - } - if (((fd = _open_osfhandle((intptr_t)stdout_rd, _O_TEXT|_O_RDONLY)) < 0) - || ((csinfo[i].fr_fp = _fdopen(fd, "r")) == NULL)) { - PERROR(_("cs_create_connection: fdopen for fr_fp failed")); - } - // Close handles for file descriptors inherited by the cscope process. - CloseHandle(stdin_rd); - CloseHandle(stdout_wr); - -#endif // !UNIX - - return CSCOPE_SUCCESS; -} - -/// Query cscope using command line interface. Parse the output and use tselect -/// to allow choices. Like Nvi, creates a pipe to send to/from query/cscope. -/// -/// @return TRUE if we jump to a tag or abort, FALSE if not. -static int cs_find(exarg_T *eap) -{ - char *opt, *pat; - - if (cs_check_for_connections() == false) { - (void)emsg(_("E567: no cscope connections")); - return false; - } - - if ((opt = strtok((char *)NULL, (const char *)" ")) == NULL) { - cs_usage_msg(Find); - return false; - } - - pat = opt + strlen(opt) + 1; - if (pat >= eap->arg + eap_arg_len) { - cs_usage_msg(Find); - return false; - } - - /* - * Let's replace the NULs written by strtok() with spaces - we need the - * spaces to correctly display the quickfix/location list window's title. - */ - for (int i = 0; i < eap_arg_len; ++i) { - if (NUL == eap->arg[i]) { - eap->arg[i] = ' '; - } - } - - return cs_find_common(opt, pat, eap->forceit, true, - eap->cmdidx == CMD_lcscope, (char_u *)(*eap->cmdlinep)); -} - -/// Common code for cscope find, shared by cs_find() and ex_cstag(). -static bool cs_find_common(char *opt, char *pat, int forceit, int verbose, bool use_ll, - char_u *cmdline) -{ - char *cmd; - int *nummatches; - size_t totmatches; - char cmdletter; - char *qfpos; - - // get cmd letter - switch (opt[0]) { - case '0': - cmdletter = 's'; - break; - case '1': - cmdletter = 'g'; - break; - case '2': - cmdletter = 'd'; - break; - case '3': - cmdletter = 'c'; - break; - case '4': - cmdletter = 't'; - break; - case '6': - cmdletter = 'e'; - break; - case '7': - cmdletter = 'f'; - break; - case '8': - cmdletter = 'i'; - break; - case '9': - cmdletter = 'a'; - break; - default: - cmdletter = opt[0]; - } - - qfpos = vim_strchr((char *)p_csqf, cmdletter); - if (qfpos != NULL) { - qfpos++; - // next symbol must be + or - - if (strchr(CSQF_FLAGS, *qfpos) == NULL) { - (void)semsg(_("E469: invalid cscopequickfix flag %c for %c"), *qfpos, *(qfpos - 1)); - return false; - } - - if (*qfpos != '0' - && apply_autocmds(EVENT_QUICKFIXCMDPRE, "cscope", curbuf->b_fname, true, curbuf)) { - if (aborting()) { - return false; - } - } - } - - // create the actual command to send to cscope - cmd = cs_create_cmd(opt, pat); - if (cmd == NULL) { - return false; - } - - nummatches = xmalloc(sizeof(int) * csinfo_size); - - // Send query to all open connections, then count the total number - // of matches so we can alloc all in one swell foop. - for (size_t i = 0; i < csinfo_size; i++) { - nummatches[i] = 0; - } - totmatches = 0; - for (size_t i = 0; i < csinfo_size; i++) { - if (csinfo[i].fname == NULL || csinfo[i].to_fp == NULL) { - continue; - } - - // send cmd to cscope - (void)fprintf(csinfo[i].to_fp, "%s\n", cmd); - (void)fflush(csinfo[i].to_fp); - - nummatches[i] = cs_cnt_matches(i); - - if (nummatches[i] > -1) { - totmatches += (size_t)nummatches[i]; - } - - if (nummatches[i] == 0) { - (void)cs_read_prompt(i); - } - } - xfree(cmd); - - if (totmatches == 0) { - if (verbose) { - (void)semsg(_("E259: no matches found for cscope query %s of %s"), opt, pat); - } - xfree(nummatches); - return false; - } - - if (qfpos != NULL && *qfpos != '0') { - // Fill error list. - FILE *f; - char_u *tmp = vim_tempname(); - qf_info_T *qi = NULL; - win_T *wp = NULL; - - f = os_fopen((char *)tmp, "w"); - if (f == NULL) { - semsg(_(e_notopen), tmp); - } else { - cs_file_results(f, nummatches); - fclose(f); - if (use_ll) { // Use location list - wp = curwin; - } - // '-' starts a new error list - if (qf_init(wp, (char *)tmp, "%f%*\\t%l%*\\t%m", - *qfpos == '-', (char *)cmdline, NULL) > 0) { - if (postponed_split != 0) { - (void)win_split(postponed_split > 0 ? postponed_split : 0, - postponed_split_flags); - RESET_BINDING(curwin); - postponed_split = 0; - } - - apply_autocmds(EVENT_QUICKFIXCMDPOST, "cscope", curbuf->b_fname, true, curbuf); - if (use_ll) { - /* - * In the location list window, use the displayed location - * list. Otherwise, use the location list for the window. - */ - qi = (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL) - ? wp->w_llist_ref : wp->w_llist; - } - qf_jump(qi, 0, 0, forceit); - } - } - os_remove((char *)tmp); - xfree(tmp); - xfree(nummatches); - return true; - } else { - char **matches = NULL, **contexts = NULL; - size_t matched = 0; - - // read output - cs_fill_results(pat, totmatches, nummatches, &matches, &contexts, &matched); - xfree(nummatches); - if (matches == NULL) { - return false; - } - - (void)cs_manage_matches(matches, contexts, matched, Store); - - return do_tag((char_u *)pat, DT_CSCOPE, 0, forceit, verbose); - } -} - -/// Print help. -static int cs_help(exarg_T *eap) -{ - cscmd_T *cmdp = cs_cmds; - - (void)msg_puts(_("cscope commands:\n")); - while (cmdp->name != NULL) { - char *help = _(cmdp->help); - int space_cnt = 30 - vim_strsize(help); - - // Use %*s rather than %30s to ensure proper alignment in utf-8 - if (space_cnt < 0) { - space_cnt = 0; - } - (void)smsg(_("%-5s: %s%*s (Usage: %s)"), - cmdp->name, - help, space_cnt, " ", - cmdp->usage); - if (strcmp(cmdp->name, "find") == 0) { - msg_puts(_("\n" - " a: Find assignments to this symbol\n" - " c: Find functions calling this function\n" - " d: Find functions called by this function\n" - " e: Find this egrep pattern\n" - " f: Find this file\n" - " g: Find this definition\n" - " i: Find files #including this file\n" - " s: Find this C symbol\n" - " t: Find this text string\n")); - } - - cmdp++; - } - - wait_return(TRUE); - return CSCOPE_SUCCESS; -} - -static void clear_csinfo(size_t i) -{ - csinfo[i].fname = NULL; - csinfo[i].ppath = NULL; - csinfo[i].flags = NULL; - csinfo[i].file_id = FILE_ID_EMPTY; - csinfo[i].pid = 0; - csinfo[i].fr_fp = NULL; - csinfo[i].to_fp = NULL; -} - -/// Insert a new cscope database filename into the filelist. -static int cs_insert_filelist(char *fname, char *ppath, char *flags, FileInfo *file_info) -{ - size_t i = 0; - bool empty_found = false; - - for (size_t j = 0; j < csinfo_size; j++) { - if (csinfo[j].fname != NULL - && os_fileid_equal_fileinfo(&(csinfo[j].file_id), file_info)) { - if (p_csverbose) { - (void)emsg(_("E568: duplicate cscope database not added")); - } - return CSCOPE_FAILURE; - } - - if (csinfo[j].fname == NULL && !empty_found) { - i = j; // remember first empty entry - empty_found = true; - } - } - - if (!empty_found) { - i = csinfo_size; - if (csinfo_size == 0) { - // First time allocation: allocate only 1 connection. It should - // be enough for most users. If more is needed, csinfo will be - // reallocated. - csinfo_size = 1; - csinfo = xcalloc(1, sizeof(csinfo_T)); - } else { - // Reallocate space for more connections. - csinfo_size *= 2; - csinfo = xrealloc(csinfo, sizeof(csinfo_T)*csinfo_size); - } - for (size_t j = csinfo_size/2; j < csinfo_size; j++) { - clear_csinfo(j); - } - } - - csinfo[i].fname = xmalloc(strlen(fname) + 1); - - (void)strcpy(csinfo[i].fname, (const char *)fname); - - if (ppath != NULL) { - csinfo[i].ppath = xmalloc(strlen(ppath) + 1); - (void)strcpy(csinfo[i].ppath, (const char *)ppath); - } else { - csinfo[i].ppath = NULL; - } - - if (flags != NULL) { - csinfo[i].flags = xmalloc(strlen(flags) + 1); - (void)strcpy(csinfo[i].flags, (const char *)flags); - } else { - csinfo[i].flags = NULL; - } - - os_fileinfo_id(file_info, &(csinfo[i].file_id)); - assert(i <= INT_MAX); - return (int)i; -} - -/// Find cscope command in command table. -static cscmd_T *cs_lookup_cmd(exarg_T *eap) -{ - cscmd_T *cmdp; - char *stok; - size_t len; - - if (eap->arg == NULL) { - return NULL; - } - - // Store length of eap->arg before it gets modified by strtok(). - eap_arg_len = (int)STRLEN(eap->arg); - - if ((stok = strtok(eap->arg, (const char *)" ")) == NULL) { // NOLINT(runtime/threadsafe_fn) - return NULL; - } - - len = strlen(stok); - for (cmdp = cs_cmds; cmdp->name != NULL; cmdp++) { - if (strncmp(stok, cmdp->name, len) == 0) { - return cmdp; - } - } - return NULL; -} - -/// Nuke em. -static int cs_kill(exarg_T *eap) -{ - char *stok; - int num; - size_t i = 0; - bool killall = false; - - if ((stok = strtok((char *)NULL, (const char *)" ")) == NULL) { - cs_usage_msg(Kill); - return CSCOPE_FAILURE; - } - - // Check if string is a number, only single digit - // positive and negative integers are allowed - if ((strlen(stok) < 2 && ascii_isdigit((int)(stok[0]))) - || (strlen(stok) < 3 && stok[0] == '-' - && ascii_isdigit((int)(stok[1])))) { - num = atoi(stok); - if (num == -1) { - killall = true; - } else if (num >= 0) { - i = (size_t)num; - } else { // All negative values besides -1 are invalid. - if (p_csverbose) { - (void)semsg(_("E261: cscope connection %s not found"), stok); - } - return CSCOPE_FAILURE; - } - } else { - // Else it must be part of a name. We will try to find a match - // within all the names in the csinfo data structure - for (i = 0; i < csinfo_size; i++) { - if (csinfo[i].fname != NULL && strstr(csinfo[i].fname, stok)) { - break; - } - } - } - - if (!killall && (i >= csinfo_size || csinfo[i].fname == NULL)) { - if (p_csverbose) { - (void)semsg(_("E261: cscope connection %s not found"), stok); - } - return CSCOPE_FAILURE; - } else { - if (killall) { - for (i = 0; i < csinfo_size; i++) { - if (csinfo[i].fname) { - cs_kill_execute(i, csinfo[i].fname); - } - } - } else { - cs_kill_execute(i, stok); - } - } - - return CSCOPE_SUCCESS; -} - -/// Actually kills a specific cscope connection. -/// -/// @param i cscope table index -/// @param cname cscope database name -static void cs_kill_execute(size_t i, char *cname) -{ - if (p_csverbose) { - msg_clr_eos(); - (void)smsg_attr(HL_ATTR(HLF_R) | MSG_HIST, - _("cscope connection %s closed"), cname); - } - cs_release_csp(i, TRUE); -} - -/// Convert the cscope output into a ctags style entry (as might be found -/// in a ctags tags file). there's one catch though: cscope doesn't tell you -/// the type of the tag you are looking for. for example, in Darren Hiebert's -/// ctags (the one that comes with vim), #define's use a line number to find the -/// tag in a file while function definitions use a regexp search pattern. -/// -/// I'm going to always use the line number because cscope does something -/// quirky (and probably other things i don't know about): -/// -/// if you have "# define" in your source file, which is -/// perfectly legal, cscope thinks you have "#define". this -/// will result in a failed regexp search. :( -/// -/// Besides, even if this particular case didn't happen, the search pattern -/// would still have to be modified to escape all the special regular expression -/// characters to comply with ctags formatting. -static char *cs_make_vim_style_matches(char *fname, char *slno, char *search, char *tagstr) -{ - // vim style is ctags: - // - // <tagstr>\t<filename>\t<linenum_or_search>"\t<extra> - // - // but as mentioned above, we'll always use the line number and - // put the search pattern (if one exists) as "extra" - // - // buf is used as part of vim's method of handling tags, and - // (i think) vim frees it when you pop your tags and get replaced - // by new ones on the tag stack. - char *buf; - size_t amt; - - if (search != NULL) { - amt = strlen(fname) + strlen(slno) + strlen(tagstr) + strlen(search) + 6; - buf = xmalloc(amt); - - (void)sprintf(buf, "%s\t%s\t%s;\"\t%s", tagstr, fname, slno, search); - } else { - amt = strlen(fname) + strlen(slno) + strlen(tagstr) + 5; - buf = xmalloc(amt); - - (void)sprintf(buf, "%s\t%s\t%s;\"", tagstr, fname, slno); - } - - return buf; -} - -/// This is kind of hokey, but i don't see an easy way round this. -/// -/// Store: keep a ptr to the (malloc'd) memory of matches originally -/// generated from cs_find(). the matches are originally lines directly -/// from cscope output, but transformed to look like something out of a -/// ctags. see cs_make_vim_style_matches for more details. -/// -/// Get: used only from cs_fgets(), this simulates a vim_fgets() to return -/// the next line from the cscope output. it basically keeps track of which -/// lines have been "used" and returns the next one. -/// -/// Free: frees up everything and resets -/// -/// Print: prints the tags -static char *cs_manage_matches(char **matches, char **contexts, size_t totmatches, mcmd_e cmd) -{ - static char **mp = NULL; - static char **cp = NULL; - static size_t cnt = 0; - static size_t next = 0; - char *p = NULL; - - switch (cmd) { - case Store: - assert(matches != NULL); - assert(totmatches > 0); - if (mp != NULL || cp != NULL) { - (void)cs_manage_matches(NULL, NULL, 0, Free); - } - mp = matches; - cp = contexts; - cnt = totmatches; - next = 0; - break; - case Get: - if (next >= cnt) { - return NULL; - } - - p = mp[next]; - next++; - break; - case Free: - if (mp != NULL) { - while (cnt--) { - xfree(mp[cnt]); - if (cp != NULL) { - xfree(cp[cnt]); - } - } - xfree(mp); - xfree(cp); - } - mp = NULL; - cp = NULL; - cnt = 0; - next = 0; - break; - case Print: - assert(mp != NULL); - assert(cp != NULL); - cs_print_tags_priv(mp, cp, cnt); - break; - default: // should not reach here - iemsg(_("E570: fatal error in cs_manage_matches")); - return NULL; - } - - return p; -} - -/// Parse cscope output. -static char *cs_parse_results(size_t cnumber, char *buf, int bufsize, char **context, - char **linenumber, char **search) -{ - int ch; - char *p; - char *name; - -retry: - errno = 0; - if (fgets(buf, bufsize, csinfo[cnumber].fr_fp) == NULL) { - if (errno == EINTR) { - goto retry; - } - - if (feof(csinfo[cnumber].fr_fp)) { - errno = EIO; - } - - cs_reading_emsg(cnumber); - - return NULL; - } - - // If the line's too long for the buffer, discard it. - if ((p = strchr(buf, '\n')) == NULL) { - while ((ch = getc(csinfo[cnumber].fr_fp)) != EOF && ch != '\n') {} - return NULL; - } - *p = '\0'; - - /* - * cscope output is in the following format: - * - * <filename> <context> <line number> <pattern> - */ - char *saveptr = NULL; - if ((name = os_strtok(buf, (const char *)" ", &saveptr)) == NULL) { - return NULL; - } - if ((*context = os_strtok(NULL, (const char *)" ", &saveptr)) == NULL) { - return NULL; - } - if ((*linenumber = os_strtok(NULL, (const char *)" ", &saveptr)) == NULL) { - return NULL; - } - *search = *linenumber + strlen(*linenumber) + 1; // +1 to skip \0 - - // --- nvi --- - // If the file is older than the cscope database, that is, - // the database was built since the file was last modified, - // or there wasn't a search string, use the line number. - if (strcmp(*search, "<unknown>") == 0) { - *search = NULL; - } - - name = cs_resolve_file(cnumber, name); - return name; -} - -/// Write cscope find results to file. -static void cs_file_results(FILE *f, int *nummatches_a) -{ - char *search, *slno; - char *fullname; - char *cntx; - char *context; - - char *buf = xmalloc(CSREAD_BUFSIZE); - - for (size_t i = 0; i < csinfo_size; i++) { - if (nummatches_a[i] < 1) { - continue; - } - - for (int j = 0; j < nummatches_a[i]; j++) { - if ((fullname = cs_parse_results(i, buf, CSREAD_BUFSIZE, &cntx, - &slno, &search)) == NULL) { - continue; - } - - size_t context_len = strlen(cntx) + 5; - context = xmalloc(context_len); - - if (strcmp(cntx, "<global>") == 0) { - xstrlcpy(context, "<<global>>", context_len); - } else { - snprintf(context, context_len, "<<%s>>", cntx); - } - - if (search == NULL) { - fprintf(f, "%s\t%s\t%s\n", fullname, slno, context); - } else { - fprintf(f, "%s\t%s\t%s %s\n", fullname, slno, context, search); - } - - xfree(context); - xfree(fullname); - } // for all matches - - (void)cs_read_prompt(i); - } // for all cscope connections - xfree(buf); -} - -/// Get parsed cscope output and calls cs_make_vim_style_matches to convert -/// into ctags format. -/// When there are no matches sets "*matches_p" to NULL. -static void cs_fill_results(char *tagstr, size_t totmatches, int *nummatches_a, char ***matches_p, - char ***cntxts_p, size_t *matched) -{ - char *buf; - char *search, *slno; - size_t totsofar = 0; - char **matches = NULL; - char **cntxts = NULL; - char *fullname; - char *cntx; - - assert(totmatches > 0); - - buf = xmalloc(CSREAD_BUFSIZE); - matches = xmalloc(sizeof(char *) * totmatches); - cntxts = xmalloc(sizeof(char *) * totmatches); - - for (size_t i = 0; i < csinfo_size; i++) { - if (nummatches_a[i] < 1) { - continue; - } - - for (int j = 0; j < nummatches_a[i]; j++) { - if ((fullname = cs_parse_results(i, buf, CSREAD_BUFSIZE, &cntx, - &slno, &search)) == NULL) { - continue; - } - - matches[totsofar] = cs_make_vim_style_matches(fullname, slno, search, - tagstr); - - xfree(fullname); - - if (strcmp(cntx, "<global>") == 0) { - cntxts[totsofar] = NULL; - } else { - cntxts[totsofar] = xstrdup(cntx); - } - - totsofar++; - } // for all matches - - (void)cs_read_prompt(i); - } // for all cscope connections - - if (totsofar == 0) { - // No matches, free the arrays and return NULL in "*matches_p". - XFREE_CLEAR(matches); - XFREE_CLEAR(cntxts); - } - *matched = totsofar; - *matches_p = matches; - *cntxts_p = cntxts; - - xfree(buf); -} - -// get the requested path components -static char *cs_pathcomponents(char *path) -{ - if (p_cspc == 0) { - return path; - } - - char *s = path + strlen(path) - 1; - for (int i = 0; i < p_cspc; i++) { - while (s > path && *--s != '/') {} - } - if ((s > path && *s == '/')) { - s++; - } - return s; -} - -/// Print cscope output that was converted into ctags style entries. -/// -/// Only called from cs_manage_matches(). -/// -/// @param matches Array of cscope lines in ctags style. Every entry was -// produced with a format string of the form -// "%s\t%s\t%s;\"\t%s" or -// "%s\t%s\t%s;\"" -// by cs_make_vim_style_matches(). -/// @param cntxts Context for matches. -/// @param num_matches Number of entries in matches/cntxts, always greater 0. -static void cs_print_tags_priv(char **matches, char **cntxts, - size_t num_matches) FUNC_ATTR_NONNULL_ALL -{ - char *globalcntx = "GLOBAL"; - char *cstag_msg = _("Cscope tag: %s"); - - assert(num_matches > 0); - assert(strcnt(matches[0], '\t') >= 2); - - char *ptag = matches[0]; - char *ptag_end = strchr(ptag, '\t'); - assert(ptag_end >= ptag); - // NUL terminate tag string in matches[0]. - *ptag_end = NUL; - - // The "%s" in cstag_msg won't appear in the result string, so we don't need - // extra memory for terminating NUL. - size_t newsize = strlen(cstag_msg) + (size_t)(ptag_end - ptag); - char *buf = xmalloc(newsize); - size_t bufsize = newsize; // Track available bufsize - (void)snprintf(buf, bufsize, cstag_msg, ptag); - msg_puts_attr(buf, HL_ATTR(HLF_T)); - msg_clr_eos(); - - // restore matches[0] - *ptag_end = '\t'; - - // Column headers for match number, line number and filename. - msg_puts_attr(_("\n # line"), HL_ATTR(HLF_T)); - msg_advance(msg_col + 2); - msg_puts_attr(_("filename / context / line\n"), HL_ATTR(HLF_T)); - - for (size_t i = 0; i < num_matches; i++) { - assert(strcnt(matches[i], '\t') >= 2); - - // Parse filename, line number and optional part. - char *fname = strchr(matches[i], '\t') + 1; - char *fname_end = strchr(fname, '\t'); - // Replace second '\t' in matches[i] with NUL to terminate fname. - *fname_end = NUL; - - char *lno = fname_end + 1; - char *extra = xstrchrnul(lno, '\t'); - // Ignore ;" at the end of lno. - char *lno_end = extra - 2; - *lno_end = NUL; - // Do we have an optional part? - extra = *extra ? extra + 1 : NULL; - - const char *csfmt_str = "%4zu %6s "; - // hopefully num_matches will be less than 10^16 - newsize = strlen(csfmt_str) + 16 + (size_t)(lno_end - lno); - if (bufsize < newsize) { - buf = xrealloc(buf, newsize); - bufsize = newsize; - } - (void)snprintf(buf, bufsize, csfmt_str, i + 1, lno); - msg_puts_attr(buf, HL_ATTR(HLF_CM)); - msg_outtrans_long_attr((char_u *)cs_pathcomponents(fname), HL_ATTR(HLF_CM)); - - // compute the required space for the context - char *context = cntxts[i] ? cntxts[i] : globalcntx; - - const char *cntxformat = " <<%s>>"; - // '%s' won't appear in result string, so: - // newsize = len(cntxformat) - 2 + len(context) + 1 (for NUL). - newsize = strlen(context) + strlen(cntxformat) - 1; - - if (bufsize < newsize) { - buf = xrealloc(buf, newsize); - bufsize = newsize; - } - int buf_len = snprintf(buf, bufsize, cntxformat, context); - assert(buf_len >= 0); - - // Print the context only if it fits on the same line. - if (msg_col + buf_len >= Columns) { - msg_putchar('\n'); - } - msg_advance(12); - msg_outtrans_long_attr((char_u *)buf, 0); - msg_putchar('\n'); - if (extra != NULL) { - msg_advance(13); - msg_outtrans_long_attr((char_u *)extra, 0); - } - - // restore matches[i] - *fname_end = '\t'; - *lno_end = ';'; - - if (msg_col) { - msg_putchar('\n'); - } - - os_breakcheck(); - if (got_int) { - got_int = false; // don't print any more matches - break; - } - } - - xfree(buf); -} - -/// Read a cscope prompt (basically, skip over the ">> "). -static int cs_read_prompt(size_t i) -{ - int ch; - char *buf = NULL; // buffer for possible error message from cscope - size_t bufpos = 0; - char *cs_emsg = _("E609: Cscope error: %s"); - size_t cs_emsg_len = strlen(cs_emsg); - static char *eprompt = "Press the RETURN key to continue:"; - size_t epromptlen = strlen(eprompt); - - // compute maximum allowed len for Cscope error message - assert(IOSIZE >= cs_emsg_len); - size_t maxlen = IOSIZE - cs_emsg_len; - - while (1) { - while (1) { - do { - errno = 0; - ch = fgetc(csinfo[i].fr_fp); - } while (ch == EOF && errno == EINTR && ferror(csinfo[i].fr_fp)); - if (ch == EOF || ch == CSCOPE_PROMPT[0]) { - break; - } - // if there is room and char is printable - if (bufpos < maxlen - 1 && vim_isprintc(ch)) { - // lazy buffer allocation - if (buf == NULL) { - buf = xmalloc(maxlen); - } - // append character to the message - buf[bufpos++] = (char)ch; - buf[bufpos] = NUL; - if (bufpos >= epromptlen - && strcmp(&buf[bufpos - epromptlen], eprompt) == 0) { - // remove eprompt from buf - buf[bufpos - epromptlen] = NUL; - - // print message to user - (void)semsg(cs_emsg, buf); - - // send RETURN to cscope - (void)putc('\n', csinfo[i].to_fp); - (void)fflush(csinfo[i].to_fp); - - // clear buf - bufpos = 0; - buf[bufpos] = NUL; - } - } - } - - for (size_t n = 0; n < strlen(CSCOPE_PROMPT); n++) { - if (n > 0) { - do { - errno = 0; - ch = fgetc(csinfo[i].fr_fp); - } while (ch == EOF && errno == EINTR && ferror(csinfo[i].fr_fp)); - } - if (ch == EOF) { - PERROR("cs_read_prompt EOF"); - if (buf != NULL && buf[0] != NUL) { - (void)semsg(cs_emsg, buf); - } else if (p_csverbose) { - cs_reading_emsg(i); // don't have additional information - } - cs_release_csp(i, true); - xfree(buf); - return CSCOPE_FAILURE; - } - - if (ch != CSCOPE_PROMPT[n]) { - ch = EOF; - break; - } - } - - if (ch == EOF) { - continue; // didn't find the prompt - } - break; // did find the prompt - } - - xfree(buf); - return CSCOPE_SUCCESS; -} - -#if defined(UNIX) && defined(SIGALRM) -/* - * Used to catch and ignore SIGALRM below. - */ -static void sig_handler(int s) -{ - // do nothing -} - -#endif - -/// Does the actual free'ing for the cs ptr with an optional flag of whether -/// or not to free the filename. Called by cs_kill and cs_reset. -static void cs_release_csp(size_t i, bool freefnpp) -{ - // Trying to exit normally (not sure whether it is fit to Unix cscope) - if (csinfo[i].to_fp != NULL) { - (void)fputs("q\n", csinfo[i].to_fp); - (void)fflush(csinfo[i].to_fp); - } -#if defined(UNIX) - { - int waitpid_errno; - int pstat; - pid_t pid; - -# if defined(HAVE_SIGACTION) - struct sigaction sa, old; - - // Use sigaction() to limit the waiting time to two seconds. - sigemptyset(&sa.sa_mask); - sa.sa_handler = sig_handler; -# ifdef SA_NODEFER - sa.sa_flags = SA_NODEFER; -# else - sa.sa_flags = 0; -# endif - sigaction(SIGALRM, &sa, &old); - alarm(2); // 2 sec timeout - - // Block until cscope exits or until timer expires - pid = waitpid(csinfo[i].pid, &pstat, 0); - waitpid_errno = errno; - - // cancel pending alarm if still there and restore signal - alarm(0); - sigaction(SIGALRM, &old, NULL); -# else - int waited; - - // Can't use sigaction(), loop for two seconds. First yield the CPU - // to give cscope a chance to exit quickly. - sleep(0); - for (waited = 0; waited < 40; ++waited) { - pid = waitpid(csinfo[i].pid, &pstat, WNOHANG); - waitpid_errno = errno; - if (pid != 0) { - break; // break unless the process is still running - } - os_delay(50L, false); // sleep 50 ms - } -# endif - /* - * If the cscope process is still running: kill it. - * Safety check: If the PID would be zero here, the entire X session - * would be killed. -1 and 1 are dangerous as well. - */ - if (pid < 0 && csinfo[i].pid > 1) { -# ifdef ECHILD - bool alive = true; - - if (waitpid_errno == ECHILD) { - /* - * When using 'vim -g', vim is forked and cscope process is - * no longer a child process but a sibling. So waitpid() - * fails with errno being ECHILD (No child processes). - * Don't send SIGKILL to cscope immediately but wait - * (polling) for it to exit normally as result of sending - * the "q" command, hence giving it a chance to clean up - * its temporary files. - */ - int waited; - - sleep(0); - for (waited = 0; waited < 40; waited++) { - // Check whether cscope process is still alive - if (kill(csinfo[i].pid, 0) != 0) { - alive = false; // cscope process no longer exists - break; - } - os_delay(50L, false); // sleep 50 ms - } - } - if (alive) -# endif - { - kill(csinfo[i].pid, SIGKILL); - (void)waitpid(csinfo[i].pid, &pstat, 0); - } - } - } -#else // !UNIX - if (csinfo[i].hProc != NULL) { - // Give cscope a chance to exit normally - if (WaitForSingleObject(csinfo[i].hProc, 1000) == WAIT_TIMEOUT) { - TerminateProcess(csinfo[i].hProc, 0); - } - CloseHandle(csinfo[i].hProc); - } -#endif - - if (csinfo[i].fr_fp != NULL) { - (void)fclose(csinfo[i].fr_fp); - } - if (csinfo[i].to_fp != NULL) { - (void)fclose(csinfo[i].to_fp); - } - - if (freefnpp) { - xfree(csinfo[i].fname); - xfree(csinfo[i].ppath); - xfree(csinfo[i].flags); - } - - clear_csinfo(i); -} - -/// Calls cs_kill on all cscope connections then reinits. -static int cs_reset(exarg_T *eap) -{ - char **dblist = NULL, **pplist = NULL, **fllist = NULL; - char buf[25]; // for snprintf " (#%zu)" - - if (csinfo_size == 0) { - return CSCOPE_SUCCESS; - } - - // malloc our db and ppath list - dblist = xmalloc(csinfo_size * sizeof(char *)); - pplist = xmalloc(csinfo_size * sizeof(char *)); - fllist = xmalloc(csinfo_size * sizeof(char *)); - - for (size_t i = 0; i < csinfo_size; i++) { - dblist[i] = csinfo[i].fname; - pplist[i] = csinfo[i].ppath; - fllist[i] = csinfo[i].flags; - if (csinfo[i].fname != NULL) { - cs_release_csp(i, FALSE); - } - } - - // rebuild the cscope connection list - for (size_t i = 0; i < csinfo_size; i++) { - if (dblist[i] != NULL) { - cs_add_common(dblist[i], pplist[i], fllist[i]); - if (p_csverbose) { - // don't use smsg_attr() because we want to display the - // connection number in the same line as - // "Added cscope database..." - snprintf(buf, ARRAY_SIZE(buf), " (#%zu)", i); - msg_puts_attr(buf, HL_ATTR(HLF_R)); - } - } - xfree(dblist[i]); - xfree(pplist[i]); - xfree(fllist[i]); - } - xfree(dblist); - xfree(pplist); - xfree(fllist); - - if (p_csverbose) { - msg_attr(_("All cscope databases reset"), HL_ATTR(HLF_R) | MSG_HIST); - } - return CSCOPE_SUCCESS; -} - -/// Construct the full pathname to a file found in the cscope database. -/// (Prepends ppath, if there is one and if it's not already prepended, -/// otherwise just uses the name found.) -/// -/// We need to prepend the prefix because on some cscope's (e.g., the one that -/// ships with Solaris 2.6), the output never has the prefix prepended. -/// Contrast this with my development system (Digital Unix), which does. -static char *cs_resolve_file(size_t i, char *name) -{ - char *fullname; - char_u *csdir = NULL; - - /* - * Ppath is freed when we destroy the cscope connection. - * Fullname is freed after cs_make_vim_style_matches, after it's been - * copied into the tag buffer used by Vim. - */ - size_t len = strlen(name) + 2; - if (csinfo[i].ppath != NULL) { - len += strlen(csinfo[i].ppath); - } else if (p_csre && csinfo[i].fname != NULL) { - // If 'cscoperelative' is set and ppath is not set, use cscope.out - // path in path resolution. - csdir = xmalloc(MAXPATHL); - STRLCPY(csdir, csinfo[i].fname, - path_tail(csinfo[i].fname) - - csinfo[i].fname + 1); - len += STRLEN(csdir); - } - - // Note/example: this won't work if the cscope output already starts - // "../.." and the prefix path is also "../..". if something like this - // happens, you are screwed up and need to fix how you're using cscope. - if (csinfo[i].ppath != NULL - && (strncmp(name, csinfo[i].ppath, strlen(csinfo[i].ppath)) != 0) - && (name[0] != '/')) { - fullname = xmalloc(len); - (void)sprintf(fullname, "%s/%s", csinfo[i].ppath, name); - } else if (csdir != NULL && csinfo[i].fname != NULL && *csdir != NUL) { - // Check for csdir to be non empty to avoid empty path concatenated to - // cscope output. - fullname = concat_fnames((char *)csdir, name, true); - } else { - fullname = xstrdup(name); - } - - xfree(csdir); - return fullname; -} - -/// Show all cscope connections. -static int cs_show(exarg_T *eap) -{ - if (cs_cnt_connections() == 0) { - msg_puts(_("no cscope connections\n")); - } else { - msg_puts_attr(_(" # pid database name prepend path\n"), - HL_ATTR(HLF_T)); - for (size_t i = 0; i < csinfo_size; i++) { - if (csinfo[i].fname == NULL) { - continue; - } - - if (csinfo[i].ppath != NULL) { - (void)smsg("%2zu %-5" PRId64 " %-34s %-32s", i, - (int64_t)csinfo[i].pid, csinfo[i].fname, csinfo[i].ppath); - } else { - (void)smsg("%2zu %-5" PRId64 " %-34s <none>", i, - (int64_t)csinfo[i].pid, csinfo[i].fname); - } - } - } - - wait_return(TRUE); - return CSCOPE_SUCCESS; -} - -/// Only called when VIM exits to quit any cscope sessions. -void cs_end(void) -{ - for (size_t i = 0; i < csinfo_size; i++) { - cs_release_csp(i, true); - } - xfree(csinfo); - csinfo_size = 0; -} - -// the end diff --git a/src/nvim/if_cscope.h b/src/nvim/if_cscope.h deleted file mode 100644 index 8dbc78943f..0000000000 --- a/src/nvim/if_cscope.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef NVIM_IF_CSCOPE_H -#define NVIM_IF_CSCOPE_H - -#include "nvim/ex_cmds_defs.h" // for exarg_T -#include "nvim/types.h" // for char_u and expand_T - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "if_cscope.h.generated.h" -#endif -#endif // NVIM_IF_CSCOPE_H diff --git a/src/nvim/if_cscope_defs.h b/src/nvim/if_cscope_defs.h deleted file mode 100644 index 6ded89fa0b..0000000000 --- a/src/nvim/if_cscope_defs.h +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef NVIM_IF_CSCOPE_DEFS_H -#define NVIM_IF_CSCOPE_DEFS_H - -// CSCOPE support for Vim added by Andy Kahn <kahn@zk3.dec.com> -// Ported to Win32 by Sergey Khorev <sergey.khorev@gmail.com> -// -// The basic idea/structure of cscope for Vim was borrowed from Nvi. -// There might be a few lines of code that look similar to what Nvi -// has. If this is a problem and requires inclusion of the annoying -// BSD license, then sue me; I'm not worth much anyway. - -#if defined(UNIX) -# include <sys/types.h> // pid_t -#endif - -#include "nvim/ex_cmds_defs.h" -#include "nvim/os/fs_defs.h" -#include "nvim/os/os_defs.h" - -#define CSCOPE_SUCCESS 0 -#define CSCOPE_FAILURE -1 - -#define CSCOPE_DBFILE "cscope.out" -#define CSCOPE_PROMPT ">> " - -// See ":help cscope-find" for the possible queries. - -typedef struct { - char *name; - int (*func)(exarg_T *eap); - char *help; - char *usage; - int cansplit; // if supports splitting window -} cscmd_T; - -typedef struct csi { - char *fname; // cscope db name - char *ppath; // path to prepend (the -P option) - char *flags; // additional cscope flags/options (e.g, -p2) -#if defined(UNIX) - pid_t pid; // PID of the connected cscope process -#else - DWORD pid; // PID of the connected cscope process - HANDLE hProc; // cscope process handle - DWORD nVolume; // Volume serial number, instead of st_dev - DWORD nIndexHigh; // st_ino has no meaning on Windows - DWORD nIndexLow; -#endif - FileID file_id; - - FILE *fr_fp; // from cscope: FILE. - FILE *to_fp; // to cscope: FILE. -} csinfo_T; - -typedef enum { Add, Find, Help, Kill, Reset, Show, } csid_e; - -typedef enum { - Store, - Get, - Free, - Print, -} mcmd_e; - -#endif // NVIM_IF_CSCOPE_DEFS_H diff --git a/src/nvim/indent.c b/src/nvim/indent.c index f18a6d7b32..ec6c72da6d 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -2,8 +2,10 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdlib.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/assert.h" @@ -11,21 +13,36 @@ #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/ex_docmd.h" #include "nvim/extmark.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/indent.h" +#include "nvim/indent_c.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/optionstr.h" +#include "nvim/os/input.h" #include "nvim/plines.h" +#include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" +#include "nvim/textformat.h" +#include "nvim/types.h" #include "nvim/undo.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "indent.c.generated.h" @@ -33,12 +50,13 @@ /// Set the integer values corresponding to the string setting of 'vartabstop'. /// "array" will be set, caller must free it if needed. -/// Return false for an error. -bool tabstop_set(char_u *var, long **array) +/// +/// @return false for an error. +bool tabstop_set(char *var, long **array) { long valcount = 1; int t; - char_u *cp; + char *cp; if (var[0] == NUL || (var[0] == '0' && var[1] == NUL)) { *array = NULL; @@ -49,8 +67,8 @@ bool tabstop_set(char_u *var, long **array) if (cp == var || cp[-1] == ',') { char *end; - if (strtol((char *)cp, &end, 10) <= 0) { - if (cp != (char_u *)end) { + if (strtol(cp, &end, 10) <= 0) { + if (cp != end) { emsg(_(e_positive)); } else { semsg(_(e_invarg2), cp); @@ -75,7 +93,7 @@ bool tabstop_set(char_u *var, long **array) t = 1; for (cp = var; *cp != NUL;) { - int n = atoi((char *)cp); + int n = atoi(cp); // Catch negative values, overflow and ridiculous big values. if (n <= 0 || n > TABSTOP_MAX) { @@ -98,7 +116,7 @@ bool tabstop_set(char_u *var, long **array) /// Calculate the number of screen spaces a tab will occupy. /// If "vts" is set then the tab widths are taken from that array, /// otherwise the value of ts is used. -int tabstop_padding(colnr_T col, long ts_arg, long *vts) +int tabstop_padding(colnr_T col, long ts_arg, const long *vts) { long ts = ts_arg == 0 ? 8 : ts_arg; colnr_T tabcol = 0; @@ -126,7 +144,7 @@ int tabstop_padding(colnr_T col, long ts_arg, long *vts) } /// Find the size of the tab that covers a particular column. -int tabstop_at(colnr_T col, long ts, long *vts) +int tabstop_at(colnr_T col, long ts, const long *vts) { colnr_T tabcol = 0; int t; @@ -175,7 +193,7 @@ colnr_T tabstop_start(colnr_T col, long ts, long *vts) /// Find the number of tabs and spaces necessary to get from one column /// to another. -void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, long *vts, int *ntabs, +void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long *vts, int *ntabs, int *nspcs) { int spaces = end_col - start_col; @@ -183,6 +201,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, long *vts, long padding = 0; int t; long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg; + assert(ts != 0); // suppress clang "Division by zero" if (vts == NULL || vts[0] == 0) { int tabs = 0; @@ -239,7 +258,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, long *vts, } /// See if two tabstop arrays contain the same values. -bool tabstop_eq(long *ts1, long *ts2) +bool tabstop_eq(const long *ts1, const long *ts2) { int t; @@ -263,7 +282,7 @@ bool tabstop_eq(long *ts1, long *ts2) } /// Copy a tabstop array, allocating space for the new array. -int *tabstop_copy(long *oldts) +int *tabstop_copy(const long *oldts) { long *newts; int t; @@ -361,7 +380,7 @@ int get_indent_lnum(linenr_T lnum) int get_indent_buf(buf_T *buf, linenr_T lnum) { return get_indent_str_vtab(ml_get_buf(buf, lnum, false), - curbuf->b_p_ts, + buf->b_p_ts, buf->b_p_vts_array, false); } @@ -369,7 +388,7 @@ int get_indent_buf(buf_T *buf, linenr_T lnum) /// Count the size (in window cells) of the indent in line "ptr", with /// 'tabstop' at "ts". /// If @param list is true, count only screen size for tabs. -int get_indent_str(const char_u *ptr, int ts, bool list) +int get_indent_str(const char *ptr, int ts, bool list) FUNC_ATTR_NONNULL_ALL { int count = 0; @@ -383,7 +402,7 @@ int get_indent_str(const char_u *ptr, int ts, bool list) } else { // In list mode, when tab is not set, count screen char width // for Tab, displays: ^I - count += ptr2cells((char *)ptr); + count += ptr2cells(ptr); } } else if (*ptr == ' ') { // Count a space for one. @@ -398,7 +417,7 @@ int get_indent_str(const char_u *ptr, int ts, bool list) /// Count the size (in window cells) of the indent in line "ptr", using /// variable tabstops. /// if "list" is true, count only screen size for tabs. -int get_indent_str_vtab(const char_u *ptr, long ts, long *vts, bool list) +int get_indent_str_vtab(const char *ptr, long ts, long *vts, bool list) { int count = 0; @@ -432,10 +451,10 @@ int get_indent_str_vtab(const char_u *ptr, long ts, long *vts, bool list) // Returns true if the line was changed. int set_indent(int size, int flags) { - char_u *p; - char_u *newline; - char_u *oldline; - char_u *s; + char *p; + char *newline; + char *oldline; + char *s; int todo; int ind_len; // Measured in characters. int line_len; @@ -547,9 +566,9 @@ int set_indent(int size, int flags) if (flags & SIN_INSERT) { p = oldline; } else { - p = (char_u *)skipwhite((char *)p); + p = skipwhite(p); } - line_len = (int)STRLEN(p) + 1; + line_len = (int)strlen(p) + 1; // If 'preserveindent' and 'expandtab' are both set keep the original // characters and allocate accordingly. We will fill the rest with spaces @@ -629,7 +648,7 @@ int set_indent(int size, int flags) todo -= tab_pad; ind_done += tab_pad; } - p = (char_u *)skipwhite((char *)p); + p = skipwhite(p); } for (;;) { @@ -657,7 +676,7 @@ int set_indent(int size, int flags) const colnr_T new_offset = (colnr_T)(s - newline); // this may free "newline" - ml_replace(curwin->w_cursor.lnum, (char *)newline, false); + ml_replace(curwin->w_cursor.lnum, newline, false); if (!(flags & SIN_NOMARK)) { extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum - 1, @@ -708,16 +727,16 @@ int get_number_indent(linenr_T lnum) // In format_lines() (i.e. not insert mode), fo+=q is needed too... if ((State & MODE_INSERT) || has_format_option(FO_Q_COMS)) { - lead_len = get_leader_len((char *)ml_get(lnum), NULL, false, true); + lead_len = get_leader_len(ml_get(lnum), NULL, false, true); } - regmatch.regprog = vim_regcomp((char *)curbuf->b_p_flp, RE_MAGIC); + regmatch.regprog = vim_regcomp(curbuf->b_p_flp, RE_MAGIC); if (regmatch.regprog != NULL) { regmatch.rm_ic = false; // vim_regexec() expects a pointer to a line. This lets us // start matching for the flp beyond any comment leader... - if (vim_regexec(®match, (char *)ml_get(lnum) + lead_len, (colnr_T)0)) { + if (vim_regexec(®match, ml_get(lnum) + lead_len, (colnr_T)0)) { pos.lnum = lnum; pos.col = (colnr_T)(*regmatch.endp - ml_get(lnum)); pos.coladd = 0; @@ -732,64 +751,133 @@ int get_number_indent(linenr_T lnum) return (int)col; } +/// This is called when 'breakindentopt' is changed and when a window is +/// initialized +bool briopt_check(win_T *wp) +{ + int bri_shift = 0; + int bri_min = 20; + bool bri_sbr = false; + int bri_list = 0; + int bri_vcol = 0; + + char *p = wp->w_p_briopt; + while (*p != NUL) { + if (strncmp(p, "shift:", 6) == 0 + && ((p[6] == '-' && ascii_isdigit(p[7])) || ascii_isdigit(p[6]))) { + p += 6; + bri_shift = getdigits_int(&p, true, 0); + } else if (strncmp(p, "min:", 4) == 0 && ascii_isdigit(p[4])) { + p += 4; + bri_min = getdigits_int(&p, true, 0); + } else if (strncmp(p, "sbr", 3) == 0) { + p += 3; + bri_sbr = true; + } else if (strncmp(p, "list:", 5) == 0) { + p += 5; + bri_list = (int)getdigits(&p, false, 0); + } else if (strncmp(p, "column:", 7) == 0) { + p += 7; + bri_vcol = (int)getdigits(&p, false, 0); + } + if (*p != ',' && *p != NUL) { + return false; + } + if (*p == ',') { + p++; + } + } + + wp->w_briopt_shift = bri_shift; + wp->w_briopt_min = bri_min; + wp->w_briopt_sbr = bri_sbr; + wp->w_briopt_list = bri_list; + wp->w_briopt_vcol = bri_vcol; + + return true; +} + // Return appropriate space number for breakindent, taking influencing // parameters into account. Window must be specified, since it is not // necessarily always the current one. -int get_breakindent_win(win_T *wp, char_u *line) +int get_breakindent_win(win_T *wp, char *line) FUNC_ATTR_NONNULL_ALL { static int prev_indent = 0; // Cached indent value. - static long prev_ts = 0; // Cached tabstop value. - static const char_u *prev_line = NULL; // cached pointer to line. + static long prev_ts = 0L; // Cached tabstop value. + static const char *prev_line = NULL; // cached pointer to line. static varnumber_T prev_tick = 0; // Changedtick of cached value. - static long *prev_vts = NULL; // Cached vartabs values. + static long *prev_vts = NULL; // Cached vartabs values. + static int prev_list = 0; // cached list value + static int prev_listopt = 0; // cached w_p_briopt_list value + static char *prev_flp = NULL; // cached formatlistpat value int bri = 0; // window width minus window margin space, i.e. what rests for text const int eff_wwidth = wp->w_width_inner - ((wp->w_p_nu || wp->w_p_rnu) && (vim_strchr(p_cpo, CPO_NUMCOL) == NULL) ? number_width(wp) + 1 : 0); - // used cached indent, unless pointer or 'tabstop' changed + // used cached indent, unless + // - line pointer changed + // - 'tabstop' changed + // - 'briopt_list changed' changed or + // - 'formatlistpattern' changed if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts || prev_tick != buf_get_changedtick(wp->w_buffer) + || prev_listopt != wp->w_briopt_list + || (prev_flp == NULL || (strcmp(prev_flp, get_flp_value(wp->w_buffer)) != 0)) || prev_vts != wp->w_buffer->b_p_vts_array) { prev_line = line; prev_ts = wp->w_buffer->b_p_ts; prev_tick = buf_get_changedtick(wp->w_buffer); prev_vts = wp->w_buffer->b_p_vts_array; - prev_indent = get_indent_str_vtab(line, - wp->w_buffer->b_p_ts, - wp->w_buffer->b_p_vts_array, - wp->w_p_list); + if (wp->w_briopt_vcol == 0) { + prev_indent = get_indent_str_vtab(line, + wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array, + wp->w_p_list); + } + prev_listopt = wp->w_briopt_list; + prev_list = 0; + xfree(prev_flp); + prev_flp = xstrdup(get_flp_value(wp->w_buffer)); + // add additional indent for numbered lists + if (wp->w_briopt_list != 0 && wp->w_briopt_vcol == 0) { + regmatch_T regmatch = { + .regprog = vim_regcomp(prev_flp, RE_MAGIC + RE_STRING + RE_AUTO + RE_STRICT), + }; + if (regmatch.regprog != NULL) { + regmatch.rm_ic = false; + if (vim_regexec(®match, line, 0)) { + if (wp->w_briopt_list > 0) { + prev_list += wp->w_briopt_list; + } else { + prev_indent = (int)(*regmatch.endp - *regmatch.startp); + } + } + vim_regfree(regmatch.regprog); + } + } + } + if (wp->w_briopt_vcol != 0) { + // column value has priority + bri = wp->w_briopt_vcol; + prev_list = 0; + } else { + bri = prev_indent + wp->w_briopt_shift; } - bri = prev_indent + wp->w_briopt_shift; // Add offset for number column, if 'n' is in 'cpoptions' bri += win_col_off2(wp); // add additional indent for numbered lists - if (wp->w_briopt_list != 0) { - regmatch_T regmatch = { - .regprog = vim_regcomp((char *)curbuf->b_p_flp, - RE_MAGIC + RE_STRING + RE_AUTO + RE_STRICT), - }; - - if (regmatch.regprog != NULL) { - regmatch.rm_ic = false; - if (vim_regexec(®match, (char *)line, 0)) { - if (wp->w_briopt_list > 0) { - bri += wp->w_briopt_list; - } else { - bri = (int)(*regmatch.endp - *regmatch.startp); - } - } - vim_regfree(regmatch.regprog); - } + if (wp->w_briopt_list > 0) { + bri += prev_list; } // indent minus the length of the showbreak string if (wp->w_briopt_sbr) { - bri -= vim_strsize((char *)get_showbreak_value(wp)); + bri -= vim_strsize(get_showbreak_value(wp)); } // never indent past left window margin @@ -811,7 +899,7 @@ int get_breakindent_win(win_T *wp, char_u *line) // the line. int inindent(int extra) { - char_u *ptr; + char *ptr; colnr_T col; for (col = 0, ptr = get_cursor_line_ptr(); ascii_iswhite(*ptr); col++) { @@ -820,9 +908,8 @@ int inindent(int extra) if (col >= curwin->w_cursor.col + extra) { return true; - } else { - return false; } + return false; } /// @return true if the conditions are OK for smart indenting. @@ -831,7 +918,198 @@ bool may_do_si(void) return curbuf->b_p_si && !curbuf->b_p_cin && *curbuf->b_p_inde == NUL && !p_paste; } -// Get indent level from 'indentexpr'. +/// Give a "resulting text too long" error and maybe set got_int. +static void emsg_text_too_long(void) +{ + emsg(_(e_resulting_text_too_long)); + // when not inside a try/catch set got_int to break out of any loop + if (trylevel == 0) { + got_int = true; + } +} + +/// ":retab". +void ex_retab(exarg_T *eap) +{ + linenr_T lnum; + bool got_tab = false; + long num_spaces = 0; + long num_tabs; + long len; + long col; + long vcol; + long start_col = 0; // For start of white-space string + long start_vcol = 0; // For start of white-space string + long old_len; + char *ptr; + char *new_line = (char *)1; // init to non-NULL + bool did_undo; // called u_save for current line + long *new_vts_array = NULL; + char *new_ts_str; // string value of tab argument + + int save_list; + linenr_T first_line = 0; // first changed line + linenr_T last_line = 0; // last changed line + + save_list = curwin->w_p_list; + curwin->w_p_list = 0; // don't want list mode here + + new_ts_str = eap->arg; + if (!tabstop_set(eap->arg, &new_vts_array)) { + return; + } + while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') { + (eap->arg)++; + } + + // This ensures that either new_vts_array and new_ts_str are freshly + // allocated, or new_vts_array points to an existing array and new_ts_str + // is null. + if (new_vts_array == NULL) { + new_vts_array = curbuf->b_p_vts_array; + new_ts_str = NULL; + } else { + new_ts_str = xstrnsave(new_ts_str, (size_t)(eap->arg - new_ts_str)); + } + for (lnum = eap->line1; !got_int && lnum <= eap->line2; lnum++) { + ptr = ml_get(lnum); + col = 0; + vcol = 0; + did_undo = false; + for (;;) { + if (ascii_iswhite(ptr[col])) { + if (!got_tab && num_spaces == 0) { + // First consecutive white-space + start_vcol = vcol; + start_col = col; + } + if (ptr[col] == ' ') { + num_spaces++; + } else { + got_tab = true; + } + } else { + if (got_tab || (eap->forceit && num_spaces > 1)) { + // Retabulate this string of white-space + + // len is virtual length of white string + len = num_spaces = vcol - start_vcol; + num_tabs = 0; + if (!curbuf->b_p_et) { + int t, s; + + tabstop_fromto((colnr_T)start_vcol, (colnr_T)vcol, + curbuf->b_p_ts, new_vts_array, &t, &s); + num_tabs = t; + num_spaces = s; + } + if (curbuf->b_p_et || got_tab + || (num_spaces + num_tabs < len)) { + if (did_undo == false) { + did_undo = true; + if (u_save((linenr_T)(lnum - 1), + (linenr_T)(lnum + 1)) == FAIL) { + new_line = NULL; // flag out-of-memory + break; + } + } + + // len is actual number of white characters used + len = num_spaces + num_tabs; + old_len = (long)strlen(ptr); + const long new_len = old_len - col + start_col + len + 1; + if (new_len <= 0 || new_len >= MAXCOL) { + emsg_text_too_long(); + break; + } + new_line = xmalloc((size_t)new_len); + + if (start_col > 0) { + memmove(new_line, ptr, (size_t)start_col); + } + memmove(new_line + start_col + len, + ptr + col, (size_t)(old_len - col + 1)); + ptr = new_line + start_col; + for (col = 0; col < len; col++) { + ptr[col] = (col < num_tabs) ? '\t' : ' '; + } + if (ml_replace(lnum, new_line, false) == OK) { + // "new_line" may have been copied + new_line = curbuf->b_ml.ml_line_ptr; + extmark_splice_cols(curbuf, lnum - 1, 0, (colnr_T)old_len, + (colnr_T)new_len - 1, kExtmarkUndo); + } + if (first_line == 0) { + first_line = lnum; + } + last_line = lnum; + ptr = new_line; + col = start_col + len; + } + } + got_tab = false; + num_spaces = 0; + } + if (ptr[col] == NUL) { + break; + } + vcol += win_chartabsize(curwin, ptr + col, (colnr_T)vcol); + if (vcol >= MAXCOL) { + emsg_text_too_long(); + break; + } + col += utfc_ptr2len(ptr + col); + } + if (new_line == NULL) { // out of memory + break; + } + line_breakcheck(); + } + if (got_int) { + emsg(_(e_interr)); + } + + // If a single value was given then it can be considered equal to + // either the value of 'tabstop' or the value of 'vartabstop'. + if (tabstop_count(curbuf->b_p_vts_array) == 0 + && tabstop_count(new_vts_array) == 1 + && curbuf->b_p_ts == tabstop_first(new_vts_array)) { + // not changed + } else if (tabstop_count(curbuf->b_p_vts_array) > 0 + && tabstop_eq(curbuf->b_p_vts_array, new_vts_array)) { + // not changed + } else { + redraw_curbuf_later(UPD_NOT_VALID); + } + if (first_line != 0) { + changed_lines(first_line, 0, last_line + 1, 0L, true); + } + + curwin->w_p_list = save_list; // restore 'list' + + if (new_ts_str != NULL) { // set the new tabstop + // If 'vartabstop' is in use or if the value given to retab has more + // than one tabstop then update 'vartabstop'. + long *old_vts_ary = curbuf->b_p_vts_array; + + if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1) { + set_string_option_direct("vts", -1, new_ts_str, OPT_FREE | OPT_LOCAL, 0); + curbuf->b_p_vts_array = new_vts_array; + xfree(old_vts_ary); + } else { + // 'vartabstop' wasn't in use and a single value was given to + // retab then update 'tabstop'. + curbuf->b_p_ts = tabstop_first(new_vts_array); + xfree(new_vts_array); + } + xfree(new_ts_str); + } + coladvance(curwin->w_curswant); + + u_clearline(); +} + +/// Get indent level from 'indentexpr'. int get_expr_indent(void) { int indent = -1; @@ -855,8 +1133,8 @@ int get_expr_indent(void) // Need to make a copy, the 'indentexpr' option could be changed while // evaluating it. - char_u *inde_copy = vim_strsave(curbuf->b_p_inde); - indent = (int)eval_to_number((char *)inde_copy); + char *inde_copy = xstrdup(curbuf->b_p_inde); + indent = (int)eval_to_number(inde_copy); xfree(inde_copy); if (use_sandbox) { @@ -875,6 +1153,12 @@ int get_expr_indent(void) check_cursor(); State = save_State; + // Reset did_throw, unless 'debug' has "throw" and inside a try/catch. + if (did_throw && (vim_strchr(p_debug, 't') == NULL || trylevel == 0)) { + handle_did_throw(); + did_throw = false; + } + // If there is an error, just keep the current indent. if (indent < 0) { indent = get_indent(); @@ -901,7 +1185,7 @@ int get_lisp_indent(void) { pos_T *pos, realpos, paren; int amount; - char_u *that; + char *that; colnr_T col; colnr_T firsttry; int parencount; @@ -991,14 +1275,16 @@ int get_lisp_indent(void) if (vi_lisp && (get_indent() == 0)) { amount = 2; } else { - char_u *line = that; - - amount = 0; - - while (*that && col) { - amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount); + 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); col--; } + amount = cts.cts_vcol; + that = cts.cts_ptr; + clear_chartabsize_arg(&cts); // Some keywords require "body" indenting rules (the // non-standard-lisp ones are Scheme special forms): @@ -1014,10 +1300,15 @@ int get_lisp_indent(void) } firsttry = amount; - while (ascii_iswhite(*that)) { - amount += lbr_chartabsize(line, that, (colnr_T)amount); - that++; + 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++; } + that = cts.cts_ptr; + amount = cts.cts_vcol; + clear_chartabsize_arg(&cts); if (*that && (*that != ';')) { // Not a comment line. @@ -1030,33 +1321,39 @@ int get_lisp_indent(void) parencount = 0; quotecount = 0; + init_chartabsize_arg(&cts, curwin, + (colnr_T)(that - line), amount, line, that); if (vi_lisp || ((*that != '"') && (*that != '\'') - && (*that != '#') && ((*that < '0') || (*that > '9')))) { - while (*that - && (!ascii_iswhite(*that) || quotecount || parencount) - && (!((*that == '(' || *that == '[') + && (*that != '#') + && (((uint8_t)(*that) < '0') || ((uint8_t)(*that) > '9')))) { + while (*cts.cts_ptr + && (!ascii_iswhite(*cts.cts_ptr) || quotecount || parencount) + && (!((*cts.cts_ptr == '(' || *cts.cts_ptr == '[') && !quotecount && !parencount && vi_lisp))) { - if (*that == '"') { + if (*cts.cts_ptr == '"') { quotecount = !quotecount; } - if (((*that == '(') || (*that == '[')) && !quotecount) { + if (((*cts.cts_ptr == '(') || (*cts.cts_ptr == '[')) && !quotecount) { parencount++; } - if (((*that == ')') || (*that == ']')) && !quotecount) { + if (((*cts.cts_ptr == ')') || (*cts.cts_ptr == ']')) && !quotecount) { parencount--; } - if ((*that == '\\') && (*(that + 1) != NUL)) { - amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount); + if ((*cts.cts_ptr == '\\') && (*(cts.cts_ptr + 1) != NUL)) { + cts.cts_vcol += lbr_chartabsize_adv(&cts); } - amount += lbr_chartabsize_adv(line, &that, (colnr_T)amount); + cts.cts_vcol += lbr_chartabsize_adv(&cts); } } - while (ascii_iswhite(*that)) { - amount += lbr_chartabsize(line, that, (colnr_T)amount); - that++; + while (ascii_iswhite(*cts.cts_ptr)) { + cts.cts_vcol += lbr_chartabsize(&cts); + cts.cts_ptr++; } + that = cts.cts_ptr; + amount = cts.cts_vcol; + clear_chartabsize_arg(&cts); if (!*that || (*that == ';')) { amount = firsttry; @@ -1073,19 +1370,63 @@ int get_lisp_indent(void) return amount; } -static int lisp_match(char_u *p) +static int lisp_match(char *p) { - char_u buf[LSIZE]; + char buf[LSIZE]; int len; - char *word = (char *)(*curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords); + char *word = *curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords; while (*word != NUL) { - (void)copy_option_part(&word, (char *)buf, LSIZE, ","); - len = (int)STRLEN(buf); + (void)copy_option_part(&word, buf, LSIZE, ","); + len = (int)strlen(buf); - if ((STRNCMP(buf, p, len) == 0) && (p[len] == ' ')) { + if ((strncmp(buf, p, (size_t)len) == 0) && ascii_iswhite_or_nul(p[len])) { return true; } } return false; } + +/// Re-indent the current line, based on the current contents of it and the +/// surrounding lines. Fixing the cursor position seems really easy -- I'm very +/// confused what all the part that handles Control-T is doing that I'm not. +/// "get_the_indent" should be get_c_indent, get_expr_indent or get_lisp_indent. +void fixthisline(IndentGetter get_the_indent) +{ + int amount = get_the_indent(); + + if (amount < 0) { + return; + } + + change_indent(INDENT_SET, amount, false, 0, true); + if (linewhite(curwin->w_cursor.lnum)) { + did_ai = true; // delete the indent if the line stays empty + } +} + +/// Return true if 'indentexpr' should be used for Lisp indenting. +/// Caller may want to check 'autoindent'. +bool use_indentexpr_for_lisp(void) +{ + return curbuf->b_p_lisp + && *curbuf->b_p_inde != NUL + && strcmp(curbuf->b_p_lop, "expr:1") == 0; +} + +/// Fix indent for 'lisp' and 'cindent'. +void fix_indent(void) +{ + if (p_paste) { + return; // no auto-indenting when 'paste' is set + } + if (curbuf->b_p_lisp && curbuf->b_p_ai) { + if (use_indentexpr_for_lisp()) { + do_c_expr_indent(); + } else { + fixthisline(get_lisp_indent); + } + } else if (cindent_on()) { + do_c_expr_indent(); + } +} diff --git a/src/nvim/indent.h b/src/nvim/indent.h index f96732bf1c..f807bbb42b 100644 --- a/src/nvim/indent.h +++ b/src/nvim/indent.h @@ -3,6 +3,8 @@ #include "nvim/vim.h" +typedef int (*IndentGetter)(void); + // flags for set_indent() #define SIN_CHANGED 1 // call changed_bytes() when line changed #define SIN_INSERT 2 // insert indent before existing text diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 34a3de4f78..1c771073b2 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -1,19 +1,26 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <assert.h> #include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/edit.h" +#include "nvim/globals.h" #include "nvim/indent.h" #include "nvim/indent_c.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/option.h" +#include "nvim/pos.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/vim.h" @@ -27,11 +34,9 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "indent_c.c.generated.h" #endif -/* - * Find the start of a comment, not knowing if we are in a comment right now. - * Search starts at w_cursor.lnum and goes backwards. - * Return NULL when not inside a comment. - */ +// Find the start of a comment, not knowing if we are in a comment right now. +// Search starts at w_cursor.lnum and goes backwards. +// Return NULL when not inside a comment. static pos_T *ind_find_start_comment(void) // XXX { return find_start_comment(curbuf->b_ind_maxcomment); @@ -48,10 +53,8 @@ pos_T *find_start_comment(int ind_maxcomment) // XXX break; } - /* - * Check if the comment start we found is inside a string. - * If it is then restrict the search to below this line and try again. - */ + // Check if the comment start we found is inside a string. + // If it is then restrict the search to below this line and try again. if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) { break; } @@ -97,11 +100,9 @@ static pos_T *ind_find_start_CORS(linenr_T *is_raw) return comment_pos; } -/* - * Find the start of a raw string, not knowing if we are in one right now. - * Search starts at w_cursor.lnum and goes backwards. - * Return NULL when not inside a raw string. - */ +// Find the start of a raw string, not knowing if we are in one right now. +// Search starts at w_cursor.lnum and goes backwards. +// Return NULL when not inside a raw string. static pos_T *find_start_rawstring(int ind_maxcomment) // XXX { pos_T *pos; @@ -127,17 +128,13 @@ static pos_T *find_start_rawstring(int ind_maxcomment) // XXX return pos; } -/* - * Skip to the end of a "string" and a 'c' character. - * If there is no string or character, return argument unmodified. - */ -static const char_u *skip_string(const char_u *p) +// Skip to the end of a "string" and a 'c' character. +// If there is no string or character, return argument unmodified. +static const char *skip_string(const char *p) { int i; - /* - * We loop, because strings may be concatenated: "date""time". - */ + // We loop, because strings may be concatenated: "date""time". for (;; p++) { if (p[0] == '\'') { // 'c' or '\n' or '\000' if (p[1] == NUL) { // ' at end of line @@ -167,14 +164,14 @@ static const char_u *skip_string(const char_u *p) } } else if (p[0] == 'R' && p[1] == '"') { // Raw string: R"[delim](...)[delim]" - const char *delim = (char *)p + 2; - const char *paren = vim_strchr((char *)delim, '('); + const char *delim = p + 2; + const char *paren = vim_strchr(delim, '('); if (paren != NULL) { const ptrdiff_t delim_len = paren - delim; for (p += 3; *p; p++) { - if (p[0] == ')' && STRNCMP(p + 1, delim, delim_len) == 0 + if (p[0] == ')' && strncmp(p + 1, delim, (size_t)delim_len) == 0 && p[delim_len + 1] == '"') { p += delim_len + 1; break; @@ -194,9 +191,9 @@ static const char_u *skip_string(const char_u *p) } /// @returns true if "line[col]" is inside a C string. -int is_pos_in_string(const char_u *line, colnr_T col) +int is_pos_in_string(const char *line, colnr_T col) { - const char_u *p; + const char *p; for (p = line; *p && (colnr_T)(p - line) < col; p++) { p = skip_string(p); @@ -204,29 +201,24 @@ int is_pos_in_string(const char_u *line, colnr_T col) return !((colnr_T)(p - line) <= col); } -/* - * Functions for C-indenting. - * Most of this originally comes from Eric Fischer. - */ -/* - * Below "XXX" means that this function may unlock the current line. - */ - -/* - * Return true if the string "line" starts with a word from 'cinwords'. - */ -bool cin_is_cinword(const char_u *line) +// Functions for C-indenting. +// Most of this originally comes from Eric Fischer. + +// Below "XXX" means that this function may unlock the current line. + +/// @return true if the string "line" starts with a word from 'cinwords'. +bool cin_is_cinword(const char *line) { bool retval = false; - size_t cinw_len = STRLEN(curbuf->b_p_cinw) + 1; - char_u *cinw_buf = xmalloc(cinw_len); - line = (char_u *)skipwhite((char *)line); + size_t cinw_len = strlen(curbuf->b_p_cinw) + 1; + char *cinw_buf = xmalloc(cinw_len); + line = skipwhite(line); - for (char *cinw = (char *)curbuf->b_p_cinw; *cinw;) { - size_t len = copy_option_part(&cinw, (char *)cinw_buf, cinw_len, ","); - if (STRNCMP(line, cinw_buf, len) == 0 - && (!vim_iswordc(line[len]) || !vim_iswordc(line[len - 1]))) { + for (char *cinw = curbuf->b_p_cinw; *cinw;) { + size_t len = copy_option_part(&cinw, cinw_buf, cinw_len, ","); + if (strncmp(line, cinw_buf, len) == 0 + && (!vim_iswordc((uint8_t)line[len]) || !vim_iswordc((uint8_t)line[len - 1]))) { retval = true; break; } @@ -237,21 +229,19 @@ bool cin_is_cinword(const char_u *line) return retval; } -/* - * Skip over white space and C comments within the line. - * Also skip over Perl/shell comments if desired. - */ -static const char_u *cin_skipcomment(const char_u *s) +// Skip over white space and C comments within the line. +// Also skip over Perl/shell comments if desired. +static const char *cin_skipcomment(const char *s) { while (*s) { - const char_u *prev_s = s; + const char *prev_s = s; - s = (char_u *)skipwhite((char *)s); + s = skipwhite(s); - /* Perl/shell # comment comment continues until eol. Require a space - * before # to avoid recognizing $#array. */ + // Perl/shell # comment comment continues until eol. Require a space + // before # to avoid recognizing $#array. if (curbuf->b_ind_hash_comment != 0 && s != prev_s && *s == '#') { - s += STRLEN(s); + s += strlen(s); break; } if (*s != '/') { @@ -259,7 +249,7 @@ static const char_u *cin_skipcomment(const char_u *s) } s++; if (*s == '/') { // slash-slash comment continues till eol - s += STRLEN(s); + s += strlen(s); break; } if (*s != '*') { @@ -275,28 +265,24 @@ static const char_u *cin_skipcomment(const char_u *s) return s; } -/* - * Return TRUE if there is no code at *s. White space and comments are - * not considered code. - */ -static int cin_nocode(const char_u *s) +/// Return true if there is no code at *s. White space and comments are +/// not considered code. +static int cin_nocode(const char *s) { return *cin_skipcomment(s) == NUL; } -/* - * Check previous lines for a "//" line comment, skipping over blank lines. - */ +// Check previous lines for a "//" line comment, skipping over blank lines. static pos_T *find_line_comment(void) // XXX { static pos_T pos; - char_u *line; - char_u *p; + char *line; + char *p; pos = curwin->w_cursor; while (--pos.lnum > 0) { line = ml_get(pos.lnum); - p = (char_u *)skipwhite((char *)line); + p = skipwhite(line); if (cin_islinecomment(p)) { pos.col = (int)(p - line); return &pos; @@ -309,21 +295,21 @@ static pos_T *find_line_comment(void) // XXX } /// Checks if `text` starts with "key:". -static bool cin_has_js_key(const char_u *text) +static bool cin_has_js_key(const char *text) { - const char_u *s = (char_u *)skipwhite((char *)text); + const char *s = skipwhite(text); - char_u quote = 0; + char quote = 0; if (*s == '\'' || *s == '"') { // can be 'key': or "key": quote = *s; s++; } - if (!vim_isIDc(*s)) { // need at least one ID character + if (!vim_isIDc((uint8_t)(*s))) { // need at least one ID character return false; } - while (vim_isIDc(*s)) { + while (vim_isIDc((uint8_t)(*s))) { s++; } if (*s && *s == quote) { @@ -338,14 +324,14 @@ static bool cin_has_js_key(const char_u *text) /// Checks if string matches "label:"; move to character after ':' if true. /// "*s" must point to the start of the label, if there is one. -static bool cin_islabel_skip(const char_u **s) +static bool cin_islabel_skip(const char **s) FUNC_ATTR_NONNULL_ALL { - if (!vim_isIDc(**s)) { // need at least one ID character + if (!vim_isIDc((uint8_t)(**s))) { // need at least one ID character return false; } - while (vim_isIDc(**s)) { + while (vim_isIDc((uint8_t)(**s))) { (*s)++; } @@ -359,7 +345,7 @@ static bool cin_islabel_skip(const char_u **s) // Note: curwin->w_cursor must be where we are looking for the label. bool cin_islabel(void) // XXX { - const char_u *s = cin_skipcomment(get_cursor_line_ptr()); + const char *s = cin_skipcomment(get_cursor_line_ptr()); // Exclude "default" from labels, since it should be indented // like a switch label. Same for C++ scope declarations. @@ -369,26 +355,23 @@ bool cin_islabel(void) // XXX if (cin_isscopedecl(s)) { return false; } + if (!cin_islabel_skip(&s)) { return false; } - /* - * Only accept a label if the previous line is terminated or is a case - * label. - */ + // Only accept a label if the previous line is terminated or is a case + // label. pos_T cursor_save; pos_T *trypos; - const char_u *line; + const char *line; cursor_save = curwin->w_cursor; while (curwin->w_cursor.lnum > 1) { - --curwin->w_cursor.lnum; + curwin->w_cursor.lnum--; - /* - * If we're in a comment or raw string now, skip to the start of - * it. - */ + // If we're in a comment or raw string now, skip to the start of + // it. curwin->w_cursor.col = 0; if ((trypos = ind_find_start_CORS(NULL)) != NULL) { // XXX curwin->w_cursor = *trypos; @@ -403,7 +386,7 @@ bool cin_islabel(void) // XXX } curwin->w_cursor = cursor_save; - if (cin_isterminated(line, TRUE, FALSE) + if (cin_isterminated(line, true, false) || cin_isscopedecl(line) || cin_iscase(line, true) || (cin_islabel_skip(&line) && cin_nocode(line))) { @@ -415,14 +398,12 @@ bool cin_islabel(void) // XXX return true; // label at start of file??? } -/* - * Recognize structure initialization and enumerations: - * "[typedef] [static|public|protected|private] enum" - * "[typedef] [static|public|protected|private] = {" - */ +// Recognize structure initialization and enumerations: +// "[typedef] [static|public|protected|private] enum" +// "[typedef] [static|public|protected|private] = {" static int cin_isinit(void) { - const char_u *s; + const char *s; static char *skip[] = { "static", "public", "protected", "private" }; s = cin_skipcomment(get_cursor_line_ptr()); @@ -434,7 +415,7 @@ static int cin_isinit(void) for (;;) { int i, l; - for (i = 0; i < (int)ARRAY_SIZE(skip); ++i) { + for (i = 0; i < (int)ARRAY_SIZE(skip); i++) { l = (int)strlen(skip[i]); if (cin_starts_with(s, skip[i])) { s = cin_skipcomment(s + l); @@ -451,21 +432,21 @@ static int cin_isinit(void) return true; } - if (cin_ends_in(s, (char_u *)"=", (char_u *)"{")) { + if (cin_ends_in(s, "=", "{")) { return true; } - return FALSE; + return false; } /// Recognize a switch label: "case .*:" or "default:". /// /// @param strict Allow relaxed check of case statement for JS -bool cin_iscase(const char_u *s, bool strict) +bool cin_iscase(const char *s, bool strict) { s = cin_skipcomment(s); if (cin_starts_with(s, "case")) { - for (s += 4; *s; ++s) { + for (s += 4; *s; s++) { s = cin_skipcomment(s); if (*s == NUL) { break; @@ -485,9 +466,8 @@ bool cin_iscase(const char_u *s, bool strict) // JS etc. if (strict) { return false; // stop at string - } else { - return true; } + return true; } } return false; @@ -499,30 +479,28 @@ bool cin_iscase(const char_u *s, bool strict) return false; } -/* - * Recognize a "default" switch label. - */ -static int cin_isdefault(const char_u *s) +// Recognize a "default" switch label. +static int cin_isdefault(const char *s) { - return STRNCMP(s, "default", 7) == 0 + return strncmp(s, "default", 7) == 0 && *(s = cin_skipcomment(s + 7)) == ':' && s[1] != ':'; } /// Recognize a scope declaration label set in 'cinscopedecls'. -bool cin_isscopedecl(const char_u *p) +bool cin_isscopedecl(const char *p) { - const char_u *s = cin_skipcomment(p); + const char *s = cin_skipcomment(p); - const size_t cinsd_len = STRLEN(curbuf->b_p_cinsd) + 1; - char_u *cinsd_buf = xmalloc(cinsd_len); + const size_t cinsd_len = strlen(curbuf->b_p_cinsd) + 1; + char *cinsd_buf = xmalloc(cinsd_len); bool found = false; - for (char *cinsd = (char *)curbuf->b_p_cinsd; *cinsd;) { - const size_t len = copy_option_part(&cinsd, (char *)cinsd_buf, cinsd_len, ","); - if (STRNCMP(s, cinsd_buf, len) == 0) { - const char_u *skip = cin_skipcomment(s + len); + for (char *cinsd = curbuf->b_p_cinsd; *cinsd;) { + const size_t len = copy_option_part(&cinsd, cinsd_buf, cinsd_len, ","); + if (strncmp(s, cinsd_buf, len) == 0) { + const char *skip = cin_skipcomment(s + len); if (*skip == ':' && skip[1] != ':') { found = true; break; @@ -539,33 +517,33 @@ bool cin_isscopedecl(const char_u *p) #define FIND_NAMESPACE_LIM 20 // Recognize a "namespace" scope declaration. -static bool cin_is_cpp_namespace(const char_u *s) +static bool cin_is_cpp_namespace(const char *s) { - const char_u *p; + const char *p; bool has_name = false; bool has_name_start = false; s = cin_skipcomment(s); - if (STRNCMP(s, "inline", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) { - s = cin_skipcomment((char_u *)skipwhite((char *)s + 6)); + if (strncmp(s, "inline", 6) == 0 && (s[6] == NUL || !vim_iswordc((uint8_t)s[6]))) { + s = cin_skipcomment(skipwhite(s + 6)); } - if (STRNCMP(s, "namespace", 9) == 0 && (s[9] == NUL || !vim_iswordc(s[9]))) { - p = cin_skipcomment((char_u *)skipwhite((char *)s + 9)); + if (strncmp(s, "namespace", 9) == 0 && (s[9] == NUL || !vim_iswordc((uint8_t)s[9]))) { + p = cin_skipcomment(skipwhite(s + 9)); while (*p != NUL) { if (ascii_iswhite(*p)) { has_name = true; // found end of a name - p = cin_skipcomment((char_u *)skipwhite((char *)p)); + p = cin_skipcomment(skipwhite(p)); } else if (*p == '{') { break; - } else if (vim_iswordc(*p)) { + } else if (vim_iswordc((uint8_t)(*p))) { has_name_start = true; if (has_name) { return false; // word character after skipping past name } p++; - } else if (p[0] == ':' && p[1] == ':' && vim_iswordc(p[2])) { + } else if (p[0] == ':' && p[1] == ':' && vim_iswordc((uint8_t)p[2])) { if (!has_name_start || has_name) { return false; } @@ -580,15 +558,13 @@ static bool cin_is_cpp_namespace(const char_u *s) return false; } -/* - * Return a pointer to the first non-empty non-comment character after a ':'. - * Return NULL if not found. - * case 234: a = b; - * ^ - */ -static const char_u *after_label(const char_u *l) +// Return a pointer to the first non-empty non-comment character after a ':'. +// Return NULL if not found. +// case 234: a = b; +// ^ +static const char *after_label(const char *l) { - for (; *l; ++l) { + for (; *l; l++) { if (*l == ':') { if (l[1] == ':') { // skip over "::" for C++ l++; @@ -609,16 +585,14 @@ static const char_u *after_label(const char_u *l) return l; } -/* - * Get indent of line "lnum", skipping a label. - * Return 0 if there is nothing after the label. - */ +// Get indent of line "lnum", skipping a label. +// Return 0 if there is nothing after the label. static int get_indent_nolabel(linenr_T lnum) // XXX { - const char_u *l; + const char *l; pos_T fp; colnr_T col; - const char_u *p; + const char *p; l = ml_get(lnum); p = after_label(l); @@ -632,15 +606,13 @@ static int get_indent_nolabel(linenr_T lnum) // XXX return (int)col; } -/* - * Find indent for line "lnum", ignoring any case or jump label. - * Also return a pointer to the text (after the label) in "pp". - * label: if (asdf && asdfasdf) - * ^ - */ -static int skip_label(linenr_T lnum, const char_u **pp) +// Find indent for line "lnum", ignoring any case or jump label. +// Also return a pointer to the text (after the label) in "pp". +// label: if (asdf && asdfasdf) +// ^ +static int skip_label(linenr_T lnum, const char **pp) { - const char_u *l; + const char *l; int amount; pos_T cursor_save; @@ -664,78 +636,75 @@ static int skip_label(linenr_T lnum, const char_u **pp) return amount; } -/* - * Return the indent of the first variable name after a type in a declaration. - * int a, indent of "a" - * static struct foo b, indent of "b" - * enum bla c, indent of "c" - * Returns zero when it doesn't look like a declaration. - */ +// Return the indent of the first variable name after a type in a declaration. +// int a, indent of "a" +// static struct foo b, indent of "b" +// enum bla c, indent of "c" +// Returns zero when it doesn't look like a declaration. static int cin_first_id_amount(void) { - char_u *line, *p, *s; + char *line, *p, *s; int len; pos_T fp; colnr_T col; line = get_cursor_line_ptr(); - p = (char_u *)skipwhite((char *)line); + p = skipwhite(line); len = (int)(skiptowhite(p) - p); - if (len == 6 && STRNCMP(p, "static", 6) == 0) { - p = (char_u *)skipwhite((char *)p + 6); + if (len == 6 && strncmp(p, "static", 6) == 0) { + p = skipwhite(p + 6); len = (int)(skiptowhite(p) - p); } - if (len == 6 && STRNCMP(p, "struct", 6) == 0) { - p = (char_u *)skipwhite((char *)p + 6); - } else if (len == 4 && STRNCMP(p, "enum", 4) == 0) { - p = (char_u *)skipwhite((char *)p + 4); - } else if ((len == 8 && STRNCMP(p, "unsigned", 8) == 0) - || (len == 6 && STRNCMP(p, "signed", 6) == 0)) { - s = (char_u *)skipwhite((char *)p + len); - if ((STRNCMP(s, "int", 3) == 0 && ascii_iswhite(s[3])) - || (STRNCMP(s, "long", 4) == 0 && ascii_iswhite(s[4])) - || (STRNCMP(s, "short", 5) == 0 && ascii_iswhite(s[5])) - || (STRNCMP(s, "char", 4) == 0 && ascii_iswhite(s[4]))) { + if (len == 6 && strncmp(p, "struct", 6) == 0) { + p = skipwhite(p + 6); + } else if (len == 4 && strncmp(p, "enum", 4) == 0) { + p = skipwhite(p + 4); + } else if ((len == 8 && strncmp(p, "unsigned", 8) == 0) + || (len == 6 && strncmp(p, "signed", 6) == 0)) { + s = skipwhite(p + len); + if ((strncmp(s, "int", 3) == 0 && ascii_iswhite(s[3])) + || (strncmp(s, "long", 4) == 0 && ascii_iswhite(s[4])) + || (strncmp(s, "short", 5) == 0 && ascii_iswhite(s[5])) + || (strncmp(s, "char", 4) == 0 && ascii_iswhite(s[4]))) { p = s; } } - for (len = 0; vim_isIDc(p[len]); len++) {} + for (len = 0; vim_isIDc((uint8_t)p[len]); len++) {} if (len == 0 || !ascii_iswhite(p[len]) || cin_nocode(p)) { return 0; } - p = (char_u *)skipwhite((char *)p + len); + p = skipwhite(p + len); fp.lnum = curwin->w_cursor.lnum; fp.col = (colnr_T)(p - line); getvcol(curwin, &fp, &col, NULL, NULL); return (int)col; } -/* - * Return the indent of the first non-blank after an equal sign. - * char *foo = "here"; - * Return zero if no (useful) equal sign found. - * Return -1 if the line above "lnum" ends in a backslash. - * foo = "asdf\ - * asdf\ - * here"; - */ +// Return the indent of the first non-blank after an equal sign. +// char *foo = "here"; +// Return zero if no (useful) equal sign found. +// Return -1 if the line above "lnum" ends in a backslash. +// foo = "asdf{backslash} +// asdf{backslash} +// here"; static int cin_get_equal_amount(linenr_T lnum) { - const char_u *line; - const char_u *s; + const char *line; + const char *s; colnr_T col; pos_T fp; if (lnum > 1) { line = ml_get(lnum - 1); - if (*line != NUL && line[STRLEN(line) - 1] == '\\') { + if (*line != NUL && line[strlen(line) - 1] == '\\') { return -1; } } - line = s = ml_get(lnum); - while (*s != NUL && vim_strchr("=;{}\"'", *s) == NULL) { + s = ml_get(lnum); + line = s; + while (*s != NUL && vim_strchr("=;{}\"'", (uint8_t)(*s)) == NULL) { if (cin_iscomment(s)) { // ignore comments s = cin_skipcomment(s); } else { @@ -746,7 +715,7 @@ static int cin_get_equal_amount(linenr_T lnum) return 0; } - s = (char_u *)skipwhite((char *)s + 1); + s = skipwhite(s + 1); if (cin_nocode(s)) { return 0; } @@ -761,35 +730,33 @@ static int cin_get_equal_amount(linenr_T lnum) return (int)col; } -/* - * Recognize a preprocessor statement: Any line that starts with '#'. - */ -static int cin_ispreproc(const char_u *s) +// Recognize a preprocessor statement: Any line that starts with '#'. +static int cin_ispreproc(const char *s) { - if (*skipwhite((char *)s) == '#') { + if (*skipwhite(s) == '#') { return true; } - return FALSE; + return false; } -/// Return TRUE if line "*pp" at "*lnump" is a preprocessor statement or a +/// Return true if line "*pp" at "*lnump" is a preprocessor statement or a /// continuation line of a preprocessor statement. Decrease "*lnump" to the /// start and return the line in "*pp". /// Put the amount of indent in "*amount". -static int cin_ispreproc_cont(const char_u **pp, linenr_T *lnump, int *amount) +static int cin_ispreproc_cont(const char **pp, linenr_T *lnump, int *amount) { - const char_u *line = *pp; + const char *line = *pp; linenr_T lnum = *lnump; int retval = false; int candidate_amount = *amount; - if (*line != NUL && line[STRLEN(line) - 1] == '\\') { + if (*line != NUL && line[strlen(line) - 1] == '\\') { candidate_amount = get_indent_lnum(lnum); } for (;;) { if (cin_ispreproc(line)) { - retval = TRUE; + retval = true; *lnump = lnum; break; } @@ -797,7 +764,7 @@ static int cin_ispreproc_cont(const char_u **pp, linenr_T *lnump, int *amount) break; } line = ml_get(--lnum); - if (*line == NUL || line[STRLEN(line) - 1] != '\\') { + if (*line == NUL || line[strlen(line) - 1] != '\\') { break; } } @@ -811,18 +778,14 @@ static int cin_ispreproc_cont(const char_u **pp, linenr_T *lnump, int *amount) return retval; } -/* - * Recognize the start of a C or C++ comment. - */ -static int cin_iscomment(const char_u *p) +// Recognize the start of a C or C++ comment. +static int cin_iscomment(const char *p) { return p[0] == '/' && (p[1] == '*' || p[1] == '/'); } -/* - * Recognize the start of a "//" comment. - */ -static int cin_islinecomment(const char_u *p) +// Recognize the start of a "//" comment. +static int cin_islinecomment(const char *p) { return p[0] == '/' && p[1] == '/'; } @@ -838,11 +801,11 @@ static int cin_islinecomment(const char_u *p) /// /// @return the character terminating the line (ending char's have precedence if /// both apply in order to determine initializations). -static char_u cin_isterminated(const char_u *s, int incl_open, int incl_comma) +static char cin_isterminated(const char *s, int incl_open, int incl_comma) { - char_u found_start = 0; + char found_start = 0; unsigned n_open = 0; - int is_else = FALSE; + int is_else = false; s = cin_skipcomment(s); @@ -889,9 +852,9 @@ static char_u cin_isterminated(const char_u *s, int incl_open, int incl_comma) /// lines here. /// @param[in] first_lnum Where to start looking. /// @param[in] min_lnum The line before which we will not be looking. -static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_lnum) +static int cin_isfuncdecl(const char **sp, linenr_T first_lnum, linenr_T min_lnum) { - const char_u *s; + const char *s; linenr_T lnum = first_lnum; linenr_T save_lnum = curwin->w_cursor.lnum; int retval = false; @@ -952,7 +915,7 @@ static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_l // defined(y) lnum = first_lnum - 1; s = ml_get(lnum); - if (*s == NUL || s[STRLEN(s) - 1] != '\\') { + if (*s == NUL || s[strlen(s) - 1] != '\\') { retval = true; } goto done; @@ -960,10 +923,10 @@ static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_l if ((*s == ',' && cin_nocode(s + 1)) || s[1] == NUL || cin_nocode(s)) { int comma = (*s == ','); - /* ',' at the end: continue looking in the next line. - * At the end: check for ',' in the next line, for this style: - * func(arg1 - * , arg2) */ + // ',' at the end: continue looking in the next line. + // At the end: check for ',' in the next line, for this style: + // func(arg1 + // , arg2) for (;;) { if (lnum >= curbuf->b_ml.ml_line_count) { break; @@ -978,7 +941,7 @@ static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_l } // Require a comma at end of the line or a comma or ')' at the // start of next line. - s = (char_u *)skipwhite((char *)s); + s = skipwhite(s); if (!just_started && (!comma && *s != ',' && *s != ')')) { break; } @@ -999,30 +962,28 @@ done: return retval; } -static int cin_isif(const char_u *p) +static int cin_isif(const char *p) { - return STRNCMP(p, "if", 2) == 0 && !vim_isIDc(p[2]); + return strncmp(p, "if", 2) == 0 && !vim_isIDc((uint8_t)p[2]); } -static int cin_iselse(const char_u *p) +static int cin_iselse(const char *p) { if (*p == '}') { // accept "} else" p = cin_skipcomment(p + 1); } - return STRNCMP(p, "else", 4) == 0 && !vim_isIDc(p[4]); + return strncmp(p, "else", 4) == 0 && !vim_isIDc((uint8_t)p[4]); } -static int cin_isdo(const char_u *p) +static int cin_isdo(const char *p) { - return STRNCMP(p, "do", 2) == 0 && !vim_isIDc(p[2]); + return strncmp(p, "do", 2) == 0 && !vim_isIDc((uint8_t)p[2]); } -/* - * Check if this is a "while" that should have a matching "do". - * We only accept a "while (condition) ;", with only white space between the - * ')' and ';'. The condition may be spread over several lines. - */ -static int cin_iswhileofdo(const char_u *p, linenr_T lnum) // XXX +// Check if this is a "while" that should have a matching "do". +// We only accept a "while (condition) ;", with only white space between the +// ')' and ';'. The condition may be spread over several lines. +static int cin_iswhileofdo(const char *p, linenr_T lnum) // XXX { pos_T cursor_save; pos_T *trypos; @@ -1050,13 +1011,11 @@ static int cin_iswhileofdo(const char_u *p, linenr_T lnum) // XXX return retval; } -/* - * Check whether in "p" there is an "if", "for" or "while" before "*poffset". - * Return 0 if there is none. - * Otherwise return !0 and update "*poffset" to point to the place where the - * string was found. - */ -static int cin_is_if_for_while_before_offset(const char_u *line, int *poffset) +// Check whether in "p" there is an "if", "for" or "while" before "*poffset". +// Return 0 if there is none. +// Otherwise return !0 and update "*poffset" to point to the place where the +// string was found. +static int cin_is_if_for_while_before_offset(const char *line, int *poffset) { int offset = *poffset; @@ -1068,19 +1027,19 @@ static int cin_is_if_for_while_before_offset(const char_u *line, int *poffset) } offset -= 1; - if (!STRNCMP(line + offset, "if", 2)) { + if (!strncmp(line + offset, "if", 2)) { goto probablyFound; } if (offset >= 1) { offset -= 1; - if (!STRNCMP(line + offset, "for", 3)) { + if (!strncmp(line + offset, "for", 3)) { goto probablyFound; } if (offset >= 2) { offset -= 2; - if (!STRNCMP(line + offset, "while", 5)) { + if (!strncmp(line + offset, "while", 5)) { goto probablyFound; } } @@ -1088,26 +1047,24 @@ static int cin_is_if_for_while_before_offset(const char_u *line, int *poffset) return 0; probablyFound: - if (!offset || !vim_isIDc(line[offset - 1])) { + if (!offset || !vim_isIDc((uint8_t)line[offset - 1])) { *poffset = offset; return 1; } return 0; } -/* - * Return TRUE if we are at the end of a do-while. - * do - * nothing; - * while (foo - * && bar); <-- here - * Adjust the cursor to the line with "while". - */ +/// Return true if we are at the end of a do-while. +/// do +/// nothing; +/// while (foo +/// && bar); <-- here +/// Adjust the cursor to the line with "while". static int cin_iswhileofdo_end(int terminated) { - const char_u *line; - const char_u *p; - const char_u *s; + const char *line; + const char *p; + const char *s; pos_T *trypos; int i; @@ -1119,10 +1076,10 @@ static int cin_iswhileofdo_end(int terminated) while (*p != NUL) { p = cin_skipcomment(p); if (*p == ')') { - s = (char_u *)skipwhite((char *)p + 1); + s = skipwhite(p + 1); if (*s == ';' && cin_nocode(s + 1)) { - /* Found ");" at end of the line, now check there is "while" - * before the matching '('. XXX */ + // Found ");" at end of the line, now check there is "while" + // before the matching '('. XXX i = (int)(p - line); curwin->w_cursor.col = i; trypos = find_match_paren(curbuf->b_ind_maxparen); @@ -1133,7 +1090,7 @@ static int cin_iswhileofdo_end(int terminated) } if (cin_starts_with(s, "while")) { curwin->w_cursor.lnum = trypos->lnum; - return TRUE; + return true; } } @@ -1146,34 +1103,32 @@ static int cin_iswhileofdo_end(int terminated) p++; } } - return FALSE; + return false; } -static int cin_isbreak(const char_u *p) +static int cin_isbreak(const char *p) { - return STRNCMP(p, "break", 5) == 0 && !vim_isIDc(p[5]); + return strncmp(p, "break", 5) == 0 && !vim_isIDc((uint8_t)p[5]); } -/* - * Find the position of a C++ base-class declaration or - * constructor-initialization. eg: - * - * class MyClass : - * baseClass <-- here - * class MyClass : public baseClass, - * anotherBaseClass <-- here (should probably lineup ??) - * MyClass::MyClass(...) : - * baseClass(...) <-- here (constructor-initialization) - * - * This is a lot of guessing. Watch out for "cond ? func() : foo". - */ +// Find the position of a C++ base-class declaration or +// constructor-initialization. eg: +// +// class MyClass : +// baseClass <-- here +// class MyClass : public baseClass, +// anotherBaseClass <-- here (should probably lineup ??) +// MyClass::MyClass(...) : +// baseClass(...) <-- here (constructor-initialization) +// +// This is a lot of guessing. Watch out for "cond ? func() : foo". static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) { lpos_T *pos = &cached->lpos; // find position - const char_u *s; + const char *s; int class_or_struct, lookfor_ctor_init, cpp_base_class; linenr_T lnum = curwin->w_cursor.lnum; - const char_u *line = get_cursor_line_ptr(); + const char *line = get_cursor_line_ptr(); if (pos->lnum <= lnum) { return cached->found; // Use the cached result @@ -1181,7 +1136,7 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) pos->col = 0; - s = (char_u *)skipwhite((char *)line); + s = skipwhite(line); if (*s == '#') { // skip #define FOO x ? (x) : x return false; } @@ -1190,24 +1145,23 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) return false; } - cpp_base_class = lookfor_ctor_init = class_or_struct = FALSE; - - /* Search for a line starting with '#', empty, ending in ';' or containing - * '{' or '}' and start below it. This handles the following situations: - * a = cond ? - * func() : - * asdf; - * func::foo() - * : something - * {} - * Foo::Foo (int one, int two) - * : something(4), - * somethingelse(3) - * {} - */ + cpp_base_class = lookfor_ctor_init = class_or_struct = false; + + // Search for a line starting with '#', empty, ending in ';' or containing + // '{' or '}' and start below it. This handles the following situations: + // a = cond ? + // func() : + // asdf; + // func::foo() + // : something + // {} + // Foo::Foo (int one, int two) + // : something(4), + // somethingelse(3) + // {} while (lnum > 1) { line = ml_get(lnum - 1); - s = (char_u *)skipwhite((char *)line); + s = skipwhite(line); if (*s == '#' || *s == NUL) { break; } @@ -1254,13 +1208,13 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) s = skip_string(s) + 1; } else if (s[0] == ':') { if (s[1] == ':') { - /* skip double colon. It can't be a constructor - * initialization any more */ - lookfor_ctor_init = FALSE; + // skip double colon. It can't be a constructor + // initialization any more + lookfor_ctor_init = false; s = cin_skipcomment(s + 2); } else if (lookfor_ctor_init || class_or_struct) { - /* we have something found, that looks like the start of - * cpp-base-class-declaration or constructor-initialization */ + // we have something found, that looks like the start of + // cpp-base-class-declaration or constructor-initialization cpp_base_class = true; lookfor_ctor_init = class_or_struct = false; pos->col = 0; @@ -1268,10 +1222,10 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) } else { s = cin_skipcomment(s + 1); } - } else if ((STRNCMP(s, "class", 5) == 0 && !vim_isIDc(s[5])) - || (STRNCMP(s, "struct", 6) == 0 && !vim_isIDc(s[6]))) { - class_or_struct = TRUE; - lookfor_ctor_init = FALSE; + } else if ((strncmp(s, "class", 5) == 0 && !vim_isIDc((uint8_t)s[5])) + || (strncmp(s, "struct", 6) == 0 && !vim_isIDc((uint8_t)s[6]))) { + class_or_struct = true; + lookfor_ctor_init = false; if (*s == 'c') { s = cin_skipcomment(s + 5); @@ -1280,16 +1234,16 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) } } else { if (s[0] == '{' || s[0] == '}' || s[0] == ';') { - cpp_base_class = lookfor_ctor_init = class_or_struct = FALSE; + cpp_base_class = lookfor_ctor_init = class_or_struct = false; } else if (s[0] == ')') { - /* Constructor-initialization is assumed if we come across - * something like "):" */ - class_or_struct = FALSE; - lookfor_ctor_init = TRUE; + // Constructor-initialization is assumed if we come across + // something like "):" + class_or_struct = false; + lookfor_ctor_init = true; } else if (s[0] == '?') { // Avoid seeing '() :' after '?' as constructor init. return false; - } else if (!vim_isIDc(s[0])) { + } else if (!vim_isIDc((uint8_t)s[0])) { // if it is not an identifier, we are wrong class_or_struct = false; lookfor_ctor_init = false; @@ -1331,7 +1285,7 @@ static int get_baseclass_amount(int col) && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) { amount = get_indent_lnum(trypos->lnum); // XXX } - if (!cin_ends_in(get_cursor_line_ptr(), (char_u *)",", NULL)) { + if (!cin_ends_in(get_cursor_line_ptr(), ",", NULL)) { amount += curbuf->b_ind_cpp_baseclass; } } else { @@ -1345,23 +1299,21 @@ static int get_baseclass_amount(int col) return amount; } -/* - * Return TRUE if string "s" ends with the string "find", possibly followed by - * white space and comments. Skip strings and comments. - * Ignore "ignore" after "find" if it's not NULL. - */ -static int cin_ends_in(const char_u *s, const char_u *find, const char_u *ignore) +/// Return true if string "s" ends with the string "find", possibly followed by +/// white space and comments. Skip strings and comments. +/// Ignore "ignore" after "find" if it's not NULL. +static int cin_ends_in(const char *s, const char *find, const char *ignore) { - const char_u *p = s; - const char_u *r; - int len = (int)STRLEN(find); + const char *p = s; + const char *r; + int len = (int)strlen(find); while (*p != NUL) { p = cin_skipcomment(p); - if (STRNCMP(p, find, len) == 0) { - r = (char_u *)skipwhite((char *)p + len); - if (ignore != NULL && STRNCMP(r, ignore, STRLEN(ignore)) == 0) { - r = (char_u *)skipwhite((char *)r + STRLEN(ignore)); + if (strncmp(p, find, (size_t)len) == 0) { + r = skipwhite(p + len); + if (ignore != NULL && strncmp(r, ignore, strlen(ignore)) == 0) { + r = skipwhite(r + strlen(ignore)); } if (cin_nocode(r)) { return true; @@ -1371,31 +1323,29 @@ static int cin_ends_in(const char_u *s, const char_u *find, const char_u *ignore p++; } } - return FALSE; + return false; } -/* - * Return TRUE when "s" starts with "word" and then a non-ID character. - */ -static int cin_starts_with(const char_u *s, const char *word) +/// Return true when "s" starts with "word" and then a non-ID character. +static int cin_starts_with(const char *s, const char *word) { - int l = (int)STRLEN(word); + size_t l = strlen(word); - return STRNCMP(s, word, l) == 0 && !vim_isIDc(s[l]); + return strncmp(s, word, l) == 0 && !vim_isIDc((uint8_t)s[l]); } /// Recognize a `extern "C"` or `extern "C++"` linkage specifications. -static int cin_is_cpp_extern_c(const char_u *s) +static int cin_is_cpp_extern_c(const char *s) { - const char_u *p; + const char *p; int has_string_literal = false; s = cin_skipcomment(s); - if (STRNCMP(s, "extern", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) { - p = cin_skipcomment((char_u *)skipwhite((char *)s + 6)); + if (strncmp(s, "extern", 6) == 0 && (s[6] == NUL || !vim_iswordc((uint8_t)s[6]))) { + p = cin_skipcomment(skipwhite(s + 6)); while (*p != NUL) { if (ascii_iswhite(*p)) { - p = cin_skipcomment((char_u *)skipwhite((char *)p)); + p = cin_skipcomment(skipwhite(p)); } else if (*p == '{') { break; } else if (p[0] == '"' && p[1] == 'C' && p[2] == '"') { @@ -1420,17 +1370,16 @@ static int cin_is_cpp_extern_c(const char_u *s) return false; } -/* - * Skip strings, chars and comments until at or past "trypos". - * Return the column found. - */ +// Skip strings, chars and comments until at or past "trypos". +// Return the column found. static int cin_skip2pos(pos_T *trypos) { - const char_u *line; - const char_u *p; - const char_u *new_p; + const char *line; + const char *p; + const char *new_p; - p = line = ml_get(trypos->lnum); + line = ml_get(trypos->lnum); + p = line; while (*p && (colnr_T)(p - line) < trypos->col) { if (cin_iscomment(p)) { p = cin_skipcomment(p); @@ -1446,11 +1395,10 @@ static int cin_skip2pos(pos_T *trypos) return (int)(p - line); } -/* - * Find the '{' at the start of the block we are in. - * Return NULL if no match found. - * Ignore a '{' that is in a comment, makes indenting the next three lines - * work. */ +// Find the '{' at the start of the block we are in. +// Return NULL if no match found. +// Ignore a '{' that is in a comment, makes indenting the next three lines +// work. // foo() // { // } @@ -1488,7 +1436,7 @@ static pos_T *find_match_paren(int ind_maxparen) return find_match_char('(', ind_maxparen); } -static pos_T *find_match_char(char_u c, int ind_maxparen) +static pos_T *find_match_char(char c, int ind_maxparen) { pos_T cursor_save; pos_T *trypos; @@ -1498,7 +1446,7 @@ static pos_T *find_match_char(char_u c, int ind_maxparen) cursor_save = curwin->w_cursor; ind_maxp_wk = ind_maxparen; retry: - if ((trypos = findmatchlimit(NULL, c, 0, ind_maxp_wk)) != NULL) { + if ((trypos = findmatchlimit(NULL, (uint8_t)c, 0, ind_maxp_wk)) != NULL) { // check if the ( is in a // comment if ((colnr_T)cin_skip2pos(trypos) > trypos->col) { ind_maxp_wk = ind_maxparen - (cursor_save.lnum - trypos->lnum); @@ -1550,12 +1498,10 @@ static pos_T *find_match_paren_after_brace(int ind_maxparen) return trypos; } -/* - * Return ind_maxparen corrected for the difference in line number between the - * cursor position and "startpos". This makes sure that searching for a - * matching paren above the cursor line doesn't find a match because of - * looking a few lines further. - */ +// Return ind_maxparen corrected for the difference in line number between the +// cursor position and "startpos". This makes sure that searching for a +// matching paren above the cursor line doesn't find a match because of +// looking a few lines further. static int corr_ind_maxparen(pos_T *startpos) { long n = (long)startpos->lnum - (long)curwin->w_cursor.lnum; @@ -1566,14 +1512,12 @@ static int corr_ind_maxparen(pos_T *startpos) return curbuf->b_ind_maxparen; } -/* - * Set w_cursor.col to the column number of the last unmatched ')' or '{' in - * line "l". "l" must point to the start of the line. - */ -static int find_last_paren(const char_u *l, int start, int end) +// Set w_cursor.col to the column number of the last unmatched ')' or '{' in +// line "l". "l" must point to the start of the line. +static int find_last_paren(const char *l, char start, char end) { int i; - int retval = FALSE; + int retval = false; int open_count = 0; curwin->w_cursor.col = 0; // default is start of line @@ -1588,17 +1532,15 @@ static int find_last_paren(const char_u *l, int start, int end) open_count--; } else { curwin->w_cursor.col = i; - retval = TRUE; + retval = true; } } } return retval; } -/* - * Parse 'cinoptions' and set the values in "curbuf". - * Must be called when 'cinoptions', 'shiftwidth' and/or 'tabstop' changes. - */ +// Parse 'cinoptions' and set the values in "curbuf". +// Must be called when 'cinoptions', 'shiftwidth' and/or 'tabstop' changes. void parse_cino(buf_T *buf) { char *p; @@ -1607,39 +1549,37 @@ void parse_cino(buf_T *buf) int fraction = 0; int sw = get_sw_value(buf); - /* - * Set the default values. - */ - /* Spaces from a block's opening brace the prevailing indent for that - * block should be. */ + // Set the default values. + // Spaces from a block's opening brace the prevailing indent for that + // block should be. buf->b_ind_level = sw; - /* Spaces from the edge of the line an open brace that's at the end of a - * line is imagined to be. */ + // Spaces from the edge of the line an open brace that's at the end of a + // line is imagined to be. buf->b_ind_open_imag = 0; - /* Spaces from the prevailing indent for a line that is not preceded by - * an opening brace. */ + // Spaces from the prevailing indent for a line that is not preceded by + // an opening brace. buf->b_ind_no_brace = 0; // Column where the first { of a function should be located }. buf->b_ind_first_open = 0; - /* Spaces from the prevailing indent a leftmost open brace should be - * located. */ + // Spaces from the prevailing indent a leftmost open brace should be + // located. buf->b_ind_open_extra = 0; - /* Spaces from the matching open brace (real location for one at the left - * edge; imaginary location from one that ends a line) the matching close - * brace should be located. */ + // Spaces from the matching open brace (real location for one at the left + // edge; imaginary location from one that ends a line) the matching close + // brace should be located. buf->b_ind_close_extra = 0; - /* Spaces from the edge of the line an open brace sitting in the leftmost - * column is imagined to be. */ + // Spaces from the edge of the line an open brace sitting in the leftmost + // column is imagined to be. buf->b_ind_open_left_imag = 0; - /* Spaces jump labels should be shifted to the left if N is non-negative, - * otherwise the jump label will be put to column 1. */ + // Spaces jump labels should be shifted to the left if N is non-negative, + // otherwise the jump label will be put to column 1. buf->b_ind_jump_label = -1; // Spaces from the switch() indent a "case xx" label should be located. @@ -1651,8 +1591,8 @@ void parse_cino(buf_T *buf) // Lineup break at end of case in switch() with case label. buf->b_ind_case_break = 0; - /* Spaces from the class declaration indent a scope declaration label - * should be located. */ + // Spaces from the class declaration indent a scope declaration label + // should be located. buf->b_ind_scopedecl = sw; // Spaces from the scope declaration label code should be located. @@ -1664,32 +1604,32 @@ void parse_cino(buf_T *buf) // Amount a function type spec should be indented. buf->b_ind_func_type = sw; - /* Amount a cpp base class declaration or constructor initialization - * should be indented. */ + // Amount a cpp base class declaration or constructor initialization + // should be indented. buf->b_ind_cpp_baseclass = sw; - /* additional spaces beyond the prevailing indent a continuation line - * should be located. */ + // additional spaces beyond the prevailing indent a continuation line + // should be located. buf->b_ind_continuation = sw; // Spaces from the indent of the line with an unclosed parentheses. buf->b_ind_unclosed = sw * 2; - /* Spaces from the indent of the line with an unclosed parentheses, which - * itself is also unclosed. */ + // Spaces from the indent of the line with an unclosed parentheses, which + // itself is also unclosed. buf->b_ind_unclosed2 = sw; // Suppress ignoring spaces from the indent of a line starting with an // unclosed parenthesis. buf->b_ind_unclosed_noignore = 0; - /* If the opening paren is the last nonwhite character on the line, and - * b_ind_unclosed_wrapped is nonzero, use this indent relative to the outer - * context (for very long lines). */ + // If the opening paren is the last nonwhite character on the line, and + // b_ind_unclosed_wrapped is nonzero, use this indent relative to the outer + // context (for very long lines). buf->b_ind_unclosed_wrapped = 0; - /* Suppress ignoring white space when lining up with the character after - * an unclosed parentheses. */ + // Suppress ignoring white space when lining up with the character after + // an unclosed parentheses. buf->b_ind_unclosed_whiteok = 0; // Indent a closing parenthesis under the line start of the matching @@ -1705,8 +1645,8 @@ void parse_cino(buf_T *buf) // Spaces from the comment opener when there is nothing after it. buf->b_ind_in_comment = 3; - /* Boolean: if non-zero, use b_ind_in_comment even if there is something - * after the comment opener. */ + // Boolean: if non-zero, use b_ind_in_comment even if there is something + // after the comment opener. buf->b_ind_in_comment2 = 0; // Max lines to search for an open paren. @@ -1727,8 +1667,8 @@ void parse_cino(buf_T *buf) // Handle C++ namespace. buf->b_ind_cpp_namespace = 0; - /* Handle continuation lines containing conditions of if(), for() and - * while(). */ + // Handle continuation lines containing conditions of if(), for() and + // while(). buf->b_ind_if_for_while = 0; // indentation for # comments @@ -1740,7 +1680,7 @@ void parse_cino(buf_T *buf) // Handle C #pragma directives buf->b_ind_pragma = 0; - for (p = (char *)buf->b_p_cino; *p;) { + for (p = buf->b_p_cino; *p;) { l = p++; if (*p == '-') { p++; @@ -1774,8 +1714,8 @@ void parse_cino(buf_T *buf) n = -n; } - /* When adding an entry here, also update the default 'cinoptions' in - * doc/indent.txt, and add explanation for it! */ + // When adding an entry here, also update the default 'cinoptions' in + // doc/indent.txt, and add explanation for it! switch (*l) { case '>': buf->b_ind_level = n; @@ -1895,10 +1835,8 @@ void parse_cino(buf_T *buf) } } -/* - * Return the desired indent for C code. - * Return -1 if the indent should be left alone (inside a raw string). - */ +// Return the desired indent for C code. +// Return -1 if the indent should be left alone (inside a raw string). int get_c_indent(void) { pos_T cur_curpos; @@ -1906,22 +1844,22 @@ int get_c_indent(void) int scope_amount; int cur_amount = MAXCOL; colnr_T col; - char_u *theline; - char_u *linecopy; + char *theline; + char *linecopy; pos_T *trypos; pos_T *comment_pos; pos_T *tryposBrace = NULL; pos_T tryposCopy; pos_T our_paren_pos; - char_u *start; + char *start; int start_brace; #define BRACE_IN_COL0 1 // '{' is in column 0 #define BRACE_AT_START 2 // '{' is at start of line #define BRACE_AT_END 3 // '{' is at end of line linenr_T ourscope; - const char_u *l; - const char_u *look; - char_u terminated; + const char *l; + const char *look; + char terminated; int lookfor; #define LOOKFOR_INITIAL 0 #define LOOKFOR_IF 1 @@ -1959,25 +1897,23 @@ int get_c_indent(void) return 0; } - /* Get a copy of the current contents of the line. - * This is required, because only the most recent line obtained with - * ml_get is valid! */ - linecopy = vim_strsave(ml_get(cur_curpos.lnum)); + // Get a copy of the current contents of the line. + // This is required, because only the most recent line obtained with + // ml_get is valid! + linecopy = xstrdup(ml_get(cur_curpos.lnum)); - /* - * In insert mode and the cursor is on a ')' truncate the line at the - * cursor position. We don't want to line up with the matching '(' when - * inserting new stuff. - * For unknown reasons the cursor might be past the end of the line, thus - * check for that. - */ + // In insert mode and the cursor is on a ')' truncate the line at the + // cursor position. We don't want to line up with the matching '(' when + // inserting new stuff. + // For unknown reasons the cursor might be past the end of the line, thus + // check for that. if ((State & MODE_INSERT) - && curwin->w_cursor.col < (colnr_T)STRLEN(linecopy) + && curwin->w_cursor.col < (colnr_T)strlen(linecopy) && linecopy[curwin->w_cursor.col] == ')') { linecopy[curwin->w_cursor.col] = NUL; } - theline = (char_u *)skipwhite((char *)linecopy); + theline = skipwhite(linecopy); // move the cursor to the start of the line @@ -1985,10 +1921,8 @@ int get_c_indent(void) original_line_islabel = cin_islabel(); // XXX - /* - * If we are inside a raw string don't change the indent. - * Ignore a raw string inside a comment. - */ + // If we are inside a raw string don't change the indent. + // Ignore a raw string inside a comment. comment_pos = ind_find_start_comment(); if (comment_pos != NULL) { // findmatchlimit() static pos is overwritten, make a copy @@ -2004,8 +1938,8 @@ int get_c_indent(void) // #defines and so on go at the left when included in 'cinkeys', // excluding pragmas when customized in 'cinoptions' if (*theline == '#' && (*linecopy == '#' || in_cinkeys('#', ' ', true))) { - const char_u *const directive = (char_u *)skipwhite((char *)theline + 1); - if (curbuf->b_ind_pragma == 0 || STRNCMP(directive, "pragma", 6) != 0) { + const char *const directive = skipwhite(theline + 1); + if (curbuf->b_ind_pragma == 0 || strncmp(directive, "pragma", 6) != 0) { amount = curbuf->b_ind_hash_comment; goto theend; } @@ -2018,10 +1952,8 @@ int get_c_indent(void) amount = 0; goto theend; } - /* - * If we're inside a "//" comment and there is a "//" comment in a - * previous line, lineup with that one. - */ + // If we're inside a "//" comment and there is a "//" comment in a + // previous line, lineup with that one. if (cin_islinecomment(theline)) { pos_T linecomment_pos; @@ -2042,10 +1974,8 @@ int get_c_indent(void) goto theend; } } - /* - * If we're inside a comment and not looking at the start of the - * comment, try using the 'comments' option. - */ + // If we're inside a comment and not looking at the start of the + // comment, try using the 'comments' option. if (!cin_iscomment(theline) && comment_pos != NULL) { // XXX int lead_start_len = 2; int lead_middle_len = 1; @@ -2055,7 +1985,7 @@ int get_c_indent(void) char *p; int start_align = 0; int start_off = 0; - int done = FALSE; + int done = false; // find how indented the line beginning the comment is getvcol(curwin, comment_pos, &col, NULL, NULL); @@ -2063,7 +1993,7 @@ int get_c_indent(void) *lead_start = NUL; *lead_middle = NUL; - p = (char *)curbuf->b_p_com; + p = curbuf->b_p_com; while (*p != NUL) { int align = 0; int off = 0; @@ -2087,31 +2017,31 @@ int get_c_indent(void) (void)copy_option_part(&p, lead_end, COM_MAX_LEN, ","); if (what == COM_START) { STRCPY(lead_start, lead_end); - lead_start_len = (int)STRLEN(lead_start); + lead_start_len = (int)strlen(lead_start); start_off = off; start_align = align; } else if (what == COM_MIDDLE) { STRCPY(lead_middle, lead_end); - lead_middle_len = (int)STRLEN(lead_middle); + lead_middle_len = (int)strlen(lead_middle); } else if (what == COM_END) { - /* If our line starts with the middle comment string, line it - * up with the comment opener per the 'comments' option. */ - if (STRNCMP(theline, lead_middle, lead_middle_len) == 0 - && STRNCMP(theline, lead_end, STRLEN(lead_end)) != 0) { - done = TRUE; + // If our line starts with the middle comment string, line it + // up with the comment opener per the 'comments' option. + if (strncmp(theline, lead_middle, (size_t)lead_middle_len) == 0 + && strncmp(theline, lead_end, strlen(lead_end)) != 0) { + done = true; if (curwin->w_cursor.lnum > 1) { - /* If the start comment string matches in the previous - * line, use the indent of that line plus offset. If - * the middle comment string matches in the previous - * line, use the indent of that line. XXX */ - look = (char_u *)skipwhite((char *)ml_get(curwin->w_cursor.lnum - 1)); - if (STRNCMP(look, lead_start, lead_start_len) == 0) { + // If the start comment string matches in the previous + // line, use the indent of that line plus offset. If + // the middle comment string matches in the previous + // line, use the indent of that line. XXX + look = skipwhite(ml_get(curwin->w_cursor.lnum - 1)); + if (strncmp(look, lead_start, (size_t)lead_start_len) == 0) { amount = get_indent_lnum(curwin->w_cursor.lnum - 1); - } else if (STRNCMP(look, lead_middle, lead_middle_len) == 0) { + } else if (strncmp(look, lead_middle, (size_t)lead_middle_len) == 0) { amount = get_indent_lnum(curwin->w_cursor.lnum - 1); break; - } else if (STRNCMP(ml_get(comment_pos->lnum) + comment_pos->col, - lead_start, lead_start_len) != 0) { + } else if (strncmp(ml_get(comment_pos->lnum) + comment_pos->col, + lead_start, (size_t)lead_start_len) != 0) { // If the start comment string doesn't match with the // start of the comment, skip this entry. XXX continue; @@ -2125,10 +2055,10 @@ int get_c_indent(void) break; } - /* If our line starts with the end comment string, line it up - * with the middle comment */ - if (STRNCMP(theline, lead_middle, lead_middle_len) != 0 - && STRNCMP(theline, lead_end, STRLEN(lead_end)) == 0) { + // If our line starts with the end comment string, line it up + // with the middle comment + if (strncmp(theline, lead_middle, (size_t)lead_middle_len) != 0 + && strncmp(theline, lead_end, strlen(lead_end)) == 0) { amount = get_indent_lnum(curwin->w_cursor.lnum - 1); // XXX if (off != 0) { @@ -2142,10 +2072,9 @@ int get_c_indent(void) } } - /* If our line starts with an asterisk, line up with the - * asterisk in the comment opener; otherwise, line up - * with the first character of the comment text. - */ + // If our line starts with an asterisk, line up with the + // asterisk in the comment opener; otherwise, line up + // with the first character of the comment text. if (done) { // skip } else if (theline[0] == '*') { @@ -2169,7 +2098,7 @@ int get_c_indent(void) start = ml_get(comment_pos->lnum); look = start + comment_pos->col + 2; // skip / and * if (*look != NUL) { // if something after it - comment_pos->col = (colnr_T)((char_u *)skipwhite((char *)look) - start); + comment_pos->col = (colnr_T)(skipwhite(look) - start); } } getvcol(curwin, comment_pos, &col, NULL, NULL); @@ -2182,7 +2111,7 @@ int get_c_indent(void) goto theend; } // Are we looking at a ']' that has a match? - if (*skipwhite((char *)theline) == ']' + if (*skipwhite(theline) == ']' && (trypos = find_match_char('[', curbuf->b_ind_maxparen)) != NULL) { // align with the line containing the '['. amount = get_indent_lnum(trypos->lnum); @@ -2195,8 +2124,8 @@ int get_c_indent(void) || (tryposBrace = find_start_brace()) != NULL || trypos != NULL) { if (trypos != NULL && tryposBrace != NULL) { - /* Both an unmatched '(' and '{' is found. Use the one which is - * closer to the current cursor position, set the other to NULL. */ + // Both an unmatched '(' and '{' is found. Use the one which is + // closer to the current cursor position, set the other to NULL. if (trypos->lnum != tryposBrace->lnum ? trypos->lnum < tryposBrace->lnum : trypos->col < tryposBrace->col) { @@ -2208,17 +2137,15 @@ int get_c_indent(void) if (trypos != NULL) { our_paren_pos = *trypos; - /* - * If the matching paren is more than one line away, use the indent of - * a previous non-empty line that matches the same paren. - */ + // If the matching paren is more than one line away, use the indent of + // a previous non-empty line that matches the same paren. if (theline[0] == ')' && curbuf->b_ind_paren_prev) { // Line up with the start of the matching paren line. amount = get_indent_lnum(curwin->w_cursor.lnum - 1); // XXX } else { amount = -1; for (lnum = cur_curpos.lnum - 1; lnum > our_paren_pos.lnum; lnum--) { - l = (char_u *)skipwhite((char *)ml_get(lnum)); + l = skipwhite(ml_get(lnum)); if (cin_nocode(l)) { // skip comment lines continue; } @@ -2251,22 +2178,20 @@ int get_c_indent(void) } } - /* - * Line up with line where the matching paren is. XXX - * If the line starts with a '(' or the indent for unclosed - * parentheses is zero, line up with the unclosed parentheses. - */ + // Line up with line where the matching paren is. XXX + // If the line starts with a '(' or the indent for unclosed + // parentheses is zero, line up with the unclosed parentheses. if (amount == -1) { int ignore_paren_col = 0; int is_if_for_while = 0; if (curbuf->b_ind_if_for_while) { - /* Look for the outermost opening parenthesis on this line - * and check whether it belongs to an "if", "for" or "while". */ + // Look for the outermost opening parenthesis on this line + // and check whether it belongs to an "if", "for" or "while". pos_T cursor_save = curwin->w_cursor; pos_T outermost; - char_u *line; + char *line; trypos = &our_paren_pos; do { @@ -2286,14 +2211,14 @@ int get_c_indent(void) } amount = skip_label(our_paren_pos.lnum, &look); - look = (char_u *)skipwhite((char *)look); + look = skipwhite(look); if (*look == '(') { linenr_T save_lnum = curwin->w_cursor.lnum; - char_u *line; + char *line; int look_col; - /* Ignore a '(' in front of the line that has a match before - * our matching '('. */ + // Ignore a '(' in front of the line that has a match before + // our matching '('. curwin->w_cursor.lnum = our_paren_pos.lnum; line = get_cursor_line_ptr(); look_col = (int)(look - line); @@ -2313,24 +2238,22 @@ int get_c_indent(void) && is_if_for_while == 0) || (!curbuf->b_ind_unclosed_noignore && *look == '(' && ignore_paren_col == 0)) { - /* - * If we're looking at a close paren, line up right there; - * otherwise, line up with the next (non-white) character. - * When b_ind_unclosed_wrapped is set and the matching paren is - * the last nonwhite character of the line, use either the - * indent of the current line or the indentation of the next - * outer paren and add b_ind_unclosed_wrapped (for very long - * lines). - */ + // If we're looking at a close paren, line up right there; + // otherwise, line up with the next (non-white) character. + // When b_ind_unclosed_wrapped is set and the matching paren is + // the last nonwhite character of the line, use either the + // indent of the current line or the indentation of the next + // outer paren and add b_ind_unclosed_wrapped (for very long + // lines). if (theline[0] != ')') { cur_amount = MAXCOL; l = ml_get(our_paren_pos.lnum); if (curbuf->b_ind_unclosed_wrapped - && cin_ends_in(l, (char_u *)"(", NULL)) { - /* look for opening unmatched paren, indent one level - * for each additional level */ + && cin_ends_in(l, "(", NULL)) { + // look for opening unmatched paren, indent one level + // for each additional level n = 1; - for (col = 0; col < our_paren_pos.col; ++col) { + for (col = 0; col < our_paren_pos.col; col++) { switch (l[col]) { case '(': case '{': @@ -2363,10 +2286,8 @@ int get_c_indent(void) } } - /* - * Find how indented the paren is, or the character after it - * if we did the above "if". - */ + // Find how indented the paren is, or the character after it + // if we did the above "if". if (our_paren_pos.col > 0) { getvcol(curwin, &our_paren_pos, &col, NULL, NULL); if (cur_amount > (int)col) { @@ -2384,11 +2305,11 @@ int get_c_indent(void) amount = cur_amount; } } else { - /* Add b_ind_unclosed2 for each '(' before our matching one, - * but ignore (void) before the line (ignore_paren_col). */ + // Add b_ind_unclosed2 for each '(' before our matching one, + // but ignore (void) before the line (ignore_paren_col). col = our_paren_pos.col; while ((int)our_paren_pos.col > ignore_paren_col) { - --our_paren_pos.col; + our_paren_pos.col--; switch (*ml_get_pos(&our_paren_pos)) { case '(': amount += curbuf->b_ind_unclosed2; @@ -2401,8 +2322,8 @@ int get_c_indent(void) } } - /* Use b_ind_unclosed once, when the first '(' is not inside - * braces */ + // Use b_ind_unclosed once, when the first '(' is not inside + // braces if (col == MAXCOL) { amount += curbuf->b_ind_unclosed; } else { @@ -2418,14 +2339,12 @@ int get_c_indent(void) } } } - /* - * For a line starting with ')' use the minimum of the two - * positions, to avoid giving it more indent than the previous - * lines: - * func_long_name( if (x - * arg && yy - * ) ^ not here ) ^ not here - */ + // For a line starting with ')' use the minimum of the two + // positions, to avoid giving it more indent than the previous + // lines: + // func_long_name( if (x + // arg && yy + // ) ^ not here ) ^ not here if (cur_amount < amount) { amount = cur_amount; } @@ -2447,13 +2366,11 @@ int get_c_indent(void) ourscope = trypos->lnum; start = ml_get(ourscope); - /* - * Now figure out how indented the line is in general. - * If the brace was at the start of the line, we use that; - * otherwise, check out the indentation of the line as - * a whole and then add the "imaginary indent" to that. - */ - look = (char_u *)skipwhite((char *)start); + // Now figure out how indented the line is in general. + // If the brace was at the start of the line, we use that; + // otherwise, check out the indentation of the line as + // a whole and then add the "imaginary indent" to that. + look = skipwhite(start); if (*look == '{') { getvcol(curwin, trypos, &col, NULL, NULL); amount = col; @@ -2480,7 +2397,7 @@ int get_c_indent(void) // ldfd) { // } if ((curbuf->b_ind_js || curbuf->b_ind_keep_case_label) - && cin_iscase((char_u *)skipwhite((char *)get_cursor_line_ptr()), false)) { + && cin_iscase(skipwhite(get_cursor_line_ptr()), false)) { amount = get_indent(); } else if (curbuf->b_ind_js) { amount = get_indent_lnum(lnum); @@ -2498,18 +2415,14 @@ int get_c_indent(void) // we want to be. Otherwise, add the amount of room // that an indent is supposed to be. if (theline[0] == '}') { - /* - * they may want closing braces to line up with something - * other than the open brace. indulge them, if so. - */ + // they may want closing braces to line up with something + // other than the open brace. indulge them, if so. amount += curbuf->b_ind_close_extra; } else { - /* - * If we're looking at an "else", try to find an "if" - * to match it with. - * If we're looking at a "while", try to find a "do" - * to match it with. - */ + // If we're looking at an "else", try to find an "if" + // to match it with. + // If we're looking at a "while", try to find a "do" + // to match it with. lookfor = LOOKFOR_INITIAL; if (cin_iselse(theline)) { lookfor = LOOKFOR_IF; @@ -2524,18 +2437,14 @@ int get_c_indent(void) } } - /* - * We get here if we are not on an "while-of-do" or "else" (or - * failed to find a matching "if"). - * Search backwards for something to line up with. - * First set amount for when we don't find anything. - */ + // We get here if we are not on an "while-of-do" or "else" (or + // failed to find a matching "if"). + // Search backwards for something to line up with. + // First set amount for when we don't find anything. - /* - * if the '{' is _really_ at the left margin, use the imaginary - * location of a left-margin brace. Otherwise, correct the - * location for b_ind_open_extra. - */ + // if the '{' is _really_ at the left margin, use the imaginary + // location of a left-margin brace. Otherwise, correct the + // location for b_ind_open_extra. if (start_brace == BRACE_IN_COL0) { // '{' is in column 0 amount = curbuf->b_ind_open_left_imag; @@ -2547,7 +2456,7 @@ int get_c_indent(void) if (start_brace == BRACE_AT_END) { // '{' is at end of line amount += curbuf->b_ind_open_imag; - l = (char_u *)skipwhite((char *)get_cursor_line_ptr()); + l = skipwhite(get_cursor_line_ptr()); if (cin_is_cpp_namespace(l)) { amount += curbuf->b_ind_cpp_namespace; } else if (cin_is_cpp_extern_c(l)) { @@ -2562,7 +2471,7 @@ int get_c_indent(void) } } - lookfor_break = FALSE; + lookfor_break = false; if (cin_iscase(theline, false)) { // it's a switch() label lookfor = LOOKFOR_CASE; // find a previous switch() label @@ -2594,10 +2503,8 @@ int get_c_indent(void) curwin->w_cursor.lnum--; curwin->w_cursor.col = 0; - /* - * If we went all the way back to the start of our scope, line - * up with it. - */ + // If we went all the way back to the start of our scope, line + // up with it. if (curwin->w_cursor.lnum <= ourscope) { // We reached end of scope: // If looking for a enum or structure initialization @@ -2611,9 +2518,9 @@ int get_c_indent(void) if (curwin->w_cursor.lnum == 0 || curwin->w_cursor.lnum < ourscope - curbuf->b_ind_maxparen) { - /* nothing found (abuse curbuf->b_ind_maxparen as - * limit) assume terminated line (i.e. a variable - * initialization) */ + // nothing found (abuse curbuf->b_ind_maxparen as + // limit) assume terminated line (i.e. a variable + // initialization) if (cont_amount > 0) { amount = cont_amount; } else if (!curbuf->b_ind_js) { @@ -2624,10 +2531,8 @@ int get_c_indent(void) l = get_cursor_line_ptr(); - /* - * If we're in a comment or raw string now, skip to - * the start of it. - */ + // If we're in a comment or raw string now, skip to + // the start of it. trypos = ind_find_start_CORS(NULL); if (trypos != NULL) { curwin->w_cursor.lnum = trypos->lnum + 1; @@ -2646,29 +2551,25 @@ int get_c_indent(void) continue; } - terminated = cin_isterminated(l, FALSE, TRUE); + terminated = cin_isterminated(l, false, true); - /* - * If we are at top level and the line looks like a - * function declaration, we are done - * (it's a variable declaration). - */ + // If we are at top level and the line looks like a + // function declaration, we are done + // (it's a variable declaration). if (start_brace != BRACE_IN_COL0 || !cin_isfuncdecl(&l, curwin->w_cursor.lnum, 0)) { - /* if the line is terminated with another ',' - * it is a continued variable initialization. - * don't add extra indent. - * TODO: does not work, if a function - * declaration is split over multiple lines: - * cin_isfuncdecl returns FALSE then. - */ + // if the line is terminated with another ',' + // it is a continued variable initialization. + // don't add extra indent. + // TODO(vim): does not work, if a function + // declaration is split over multiple lines: + // cin_isfuncdecl returns false then. if (terminated == ',') { break; } - /* if it is an enum declaration or an assignment, - * we are done. - */ + // if it is an enum declaration or an assignment, + // we are done. if (terminated != ';' && cin_isinit()) { break; } @@ -2700,11 +2601,10 @@ int get_c_indent(void) } } - /* it's a variable declaration, add indentation - * like in - * int a, - * b; - */ + // it's a variable declaration, add indentation + // like in + // int a, + // b; if (cont_amount > 0) { amount = cont_amount; } else { @@ -2728,10 +2628,8 @@ int get_c_indent(void) } if (lookfor_cpp_namespace) { - /* - * Looking for C++ namespace, need to look further - * back. - */ + // Looking for C++ namespace, need to look further + // back. if (curwin->w_cursor.lnum == ourscope) { continue; } @@ -2744,8 +2642,8 @@ int get_c_indent(void) l = get_cursor_line_ptr(); - /* If we're in a comment or raw string now, skip - * to the start of it. */ + // If we're in a comment or raw string now, skip + // to the start of it. trypos = ind_find_start_CORS(NULL); if (trypos != NULL) { curwin->w_cursor.lnum = trypos->lnum + 1; @@ -2787,20 +2685,18 @@ int get_c_indent(void) l = get_cursor_line_ptr(); - /* - * If this is a switch() label, may line up relative to that. - * If this is a C++ scope declaration, do the same. - */ + // If this is a switch() label, may line up relative to that. + // If this is a C++ scope declaration, do the same. bool iscase = cin_iscase(l, false); if (iscase || cin_isscopedecl(l)) { - /* we are only looking for cpp base class - * declaration/initialization any longer */ + // we are only looking for cpp base class + // declaration/initialization any longer if (lookfor == LOOKFOR_CPP_BASECLASS) { break; } - /* When looking for a "do" we are not interested in - * labels. */ + // When looking for a "do" we are not interested in + // labels. if (whilelevel > 0) { continue; } @@ -2873,14 +2769,12 @@ int get_c_indent(void) break; } - /* - * Try to get the indent of a statement before the switch - * label. If nothing is found, line up relative to the - * switch label. - * break; <- may line up with this line - * case xx: - * -> y = 1; - */ + // Try to get the indent of a statement before the switch + // label. If nothing is found, line up relative to the + // switch label. + // break; <- may line up with this line + // case xx: + // -> y = 1; scope_amount = get_indent() + (iscase // XXX ? curbuf->b_ind_case_code : curbuf->b_ind_scopedecl_code); @@ -2889,10 +2783,8 @@ int get_c_indent(void) continue; } - /* - * Looking for a switch() label or C++ scope declaration, - * ignore other lines, skip {}-blocks. - */ + // Looking for a switch() label or C++ scope declaration, + // ignore other lines, skip {}-blocks. if (lookfor == LOOKFOR_CASE || lookfor == LOOKFOR_SCOPEDECL) { if (find_last_paren(l, '{', '}') && (trypos = find_start_brace()) != NULL) { @@ -2902,9 +2794,7 @@ int get_c_indent(void) continue; } - /* - * Ignore jump labels with nothing after them. - */ + // Ignore jump labels with nothing after them. if (!curbuf->b_ind_js && cin_islabel()) { l = after_label(get_cursor_line_ptr()); if (l == NULL || cin_nocode(l)) { @@ -2912,12 +2802,10 @@ int get_c_indent(void) } } - /* - * Ignore #defines, #if, etc. - * Ignore comment and empty lines. - * (need to get the line again, cin_islabel() may have - * unlocked it) - */ + // Ignore #defines, #if, etc. + // Ignore comment and empty lines. + // (need to get the line again, cin_islabel() may have + // unlocked it) l = get_cursor_line_ptr(); if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount) || cin_nocode(l)) { @@ -2950,9 +2838,8 @@ int get_c_indent(void) } break; } else if (lookfor == LOOKFOR_CPP_BASECLASS) { - /* only look, whether there is a cpp base class - * declaration or initialization before the opening brace. - */ + // only look, whether there is a cpp base class + // declaration or initialization before the opening brace. if (cin_isterminated(l, true, false)) { break; } else { @@ -2960,18 +2847,16 @@ int get_c_indent(void) } } - /* - * What happens next depends on the line being terminated. - * If terminated with a ',' only consider it terminating if - * there is another unterminated statement behind, eg: - * 123, - * sizeof - * here - * Otherwise check whether it is an enumeration or structure - * initialisation (not indented) or a variable declaration - * (indented). - */ - terminated = cin_isterminated(l, FALSE, TRUE); + // What happens next depends on the line being terminated. + // If terminated with a ',' only consider it terminating if + // there is another unterminated statement behind, eg: + // 123, + // sizeof + // here + // Otherwise check whether it is an enumeration or structure + // initialisation (not indented) or a variable declaration + // (indented). + terminated = cin_isterminated(l, false, true); if (js_cur_has_key) { js_cur_has_key = false; // only check the first line @@ -2999,21 +2884,20 @@ int get_c_indent(void) // Line below current line is the one that starts a // (possibly broken) line ending in a comma. break; - } else { - amount = get_indent(); - if (curwin->w_cursor.lnum - 1 == ourscope) { - // line above is start of the scope, thus current - // line is the one that stars a (possibly broken) - // line ending in a comma. - break; - } + } + amount = get_indent(); + if (curwin->w_cursor.lnum - 1 == ourscope) { + // line above is start of the scope, thus current + // line is the one that stars a (possibly broken) + // line ending in a comma. + break; } } if (terminated == 0 || (lookfor != LOOKFOR_UNTERM && terminated == ',')) { if (lookfor != LOOKFOR_ENUM_OR_INIT - && (*skipwhite((char *)l) == '[' || l[STRLEN(l) - 1] == '[')) { + && (*skipwhite(l) == '[' || l[strlen(l) - 1] == '[')) { amount += ind_continuation; } // If we're in the middle of a paren thing, Go back to the line @@ -3040,12 +2924,10 @@ int get_c_indent(void) } if (trypos != NULL) { - /* - * Check if we are on a case label now. This is - * handled above. - * case xx: if ( asdf && - * asdf) - */ + // Check if we are on a case label now. This is + // handled above. + // case xx: if ( asdf && + // asdf) curwin->w_cursor = *trypos; l = get_cursor_line_ptr(); if (cin_iscase(l, false) || cin_isscopedecl(l)) { @@ -3055,17 +2937,15 @@ int get_c_indent(void) } } - /* - * Skip over continuation lines to find the one to get the - * indent from - * char *usethis = "bla\ - * bla", - * here; - */ + // Skip over continuation lines to find the one to get the + // indent from + // char *usethis = "bla{backslash} + // bla", + // here; if (terminated == ',') { while (curwin->w_cursor.lnum > 1) { l = ml_get(curwin->w_cursor.lnum - 1); - if (*l == NUL || l[STRLEN(l) - 1] != '\\') { + if (*l == NUL || l[strlen(l) - 1] != '\\') { break; } curwin->w_cursor.lnum--; @@ -3073,50 +2953,42 @@ int get_c_indent(void) } } - /* - * Get indent and pointer to text for current line, - * ignoring any jump label. XXX - */ + // Get indent and pointer to text for current line, + // ignoring any jump label. XXX if (curbuf->b_ind_js) { cur_amount = get_indent(); } else { cur_amount = skip_label(curwin->w_cursor.lnum, &l); } - /* - * If this is just above the line we are indenting, and it - * starts with a '{', line it up with this line. - * while (not) - * -> { - * } - */ + // If this is just above the line we are indenting, and it + // starts with a '{', line it up with this line. + // while (not) + // -> { + // } if (terminated != ',' && lookfor != LOOKFOR_TERM && theline[0] == '{') { amount = cur_amount; - /* - * Only add b_ind_open_extra when the current line - * doesn't start with a '{', which must have a match - * in the same line (scope is the same). Probably: - * { 1, 2 }, - * -> { 3, 4 } - */ - if (*skipwhite((char *)l) != '{') { + // Only add b_ind_open_extra when the current line + // doesn't start with a '{', which must have a match + // in the same line (scope is the same). Probably: + // { 1, 2 }, + // -> { 3, 4 } + if (*skipwhite(l) != '{') { amount += curbuf->b_ind_open_extra; } if (curbuf->b_ind_cpp_baseclass && !curbuf->b_ind_js) { - /* have to look back, whether it is a cpp base - * class declaration or initialization */ + // have to look back, whether it is a cpp base + // class declaration or initialization lookfor = LOOKFOR_CPP_BASECLASS; continue; } break; } - /* - * Check if we are after an "if", "while", etc. - * Also allow " } else". - */ - if (cin_is_cinword(l) || cin_iselse((char_u *)skipwhite((char *)l))) { + // Check if we are after an "if", "while", etc. + // Also allow " } else". + if (cin_is_cinword(l) || cin_iselse(skipwhite(l))) { // Found an unterminated line after an if (), line up // with the last one. // if (cond) @@ -3132,19 +3004,17 @@ int get_c_indent(void) break; } - /* - * If this is just above the line we are indenting, we - * are finished. - * while (not) - * -> here; - * Otherwise this indent can be used when the line - * before this is terminated. - * yyy; - * if (stat) - * while (not) - * xxx; - * -> here; - */ + // If this is just above the line we are indenting, we + // are finished. + // while (not) + // -> here; + // Otherwise this indent can be used when the line + // before this is terminated. + // yyy; + // if (stat) + // while (not) + // xxx; + // -> here; amount = cur_amount; if (theline[0] == '{') { amount += curbuf->b_ind_open_extra; @@ -3155,14 +3025,12 @@ int get_c_indent(void) break; } - /* - * Special trick: when expecting the while () after a - * do, line up with the while() - * do - * x = 1; - * -> here - */ - l = (char_u *)skipwhite((char *)get_cursor_line_ptr()); + // Special trick: when expecting the while () after a + // do, line up with the while() + // do + // x = 1; + // -> here + l = skipwhite(get_cursor_line_ptr()); if (cin_isdo(l)) { if (whilelevel == 0) { break; @@ -3170,16 +3038,14 @@ int get_c_indent(void) whilelevel--; } - /* - * When searching for a terminated line, don't use the - * one between the "if" and the matching "else". - * Need to use the scope of this "else". XXX - * If whilelevel != 0 continue looking for a "do {". - */ + // When searching for a terminated line, don't use the + // one between the "if" and the matching "else". + // Need to use the scope of this "else". XXX + // If whilelevel != 0 continue looking for a "do {". if (cin_iselse(l) && whilelevel == 0) { - /* If we're looking at "} else", let's make sure we - * find the opening brace of the enclosing scope, - * not the one from "if () {". */ + // If we're looking at "} else", let's make sure we + // find the opening brace of the enclosing scope, + // not the one from "if () {". if (*l == '}') { curwin->w_cursor.col = (colnr_T)(l - get_cursor_line_ptr()) + 1; @@ -3191,21 +3057,17 @@ int get_c_indent(void) break; } } - } - /* - * If we're below an unterminated line that is not an - * "if" or something, we may line up with this line or - * add something for a continuation line, depending on - * the line before this one. - */ - else { - /* - * Found two unterminated lines on a row, line up with - * the last one. - * c = 99 + - * 100 + - * -> here; - */ + } else { + // If we're below an unterminated line that is not an + // "if" or something, we may line up with this line or + // add something for a continuation line, depending on + // the line before this one. + + // Found two unterminated lines on a row, line up with + // the last one. + // c = 99 + + // 100 + + // -> here; if (lookfor == LOOKFOR_UNTERM) { // When line ends in a comma add extra indent if (terminated == ',') { @@ -3215,11 +3077,11 @@ int get_c_indent(void) } if (lookfor == LOOKFOR_ENUM_OR_INIT) { - /* Found two lines ending in ',', lineup with the - * lowest one, but check for cpp base class - * declaration/initialization, if it is an - * opening brace or we are looking just for - * enumerations/initializations. */ + // Found two lines ending in ',', lineup with the + // lowest one, but check for cpp base class + // declaration/initialization, if it is an + // opening brace or we are looking just for + // enumerations/initializations. if (terminated == ',') { if (curbuf->b_ind_cpp_baseclass == 0) { break; @@ -3242,9 +3104,9 @@ int get_c_indent(void) l = get_cursor_line_ptr(); amount = cur_amount; - n = (int)STRLEN(l); + n = (int)strlen(l); if (terminated == ',' - && (*skipwhite((char *)l) == ']' + && (*skipwhite(l) == ']' || (n >= 2 && l[n - 2] == ']'))) { break; } @@ -3271,7 +3133,7 @@ int get_c_indent(void) // 4 * // 5, // 6, - if (cin_iscomment((char_u *)skipwhite((char *)l))) { + if (cin_iscomment(skipwhite(l))) { break; } lookfor = LOOKFOR_COMMA; @@ -3291,7 +3153,7 @@ int get_c_indent(void) } else { if (lookfor == LOOKFOR_INITIAL && *l != NUL - && l[STRLEN(l) - 1] == '\\') { + && l[strlen(l) - 1] == '\\') { // XXX cont_amount = cin_get_equal_amount(curwin->w_cursor.lnum); } @@ -3304,19 +3166,14 @@ int get_c_indent(void) } } } - } - /* - * Check if we are after a while (cond); - * If so: Ignore until the matching "do". - */ - else if (cin_iswhileofdo_end(terminated)) { // XXX - /* - * Found an unterminated line after a while ();, line up - * with the last one. - * while (cond); - * 100 + <- line up with this one - * -> here; - */ + // Check if we are after a while (cond); + // If so: Ignore until the matching "do". + } else if (cin_iswhileofdo_end((uint8_t)terminated)) { // XXX + // Found an unterminated line after a while ();, line up + // with the last one. + // while (cond); + // 100 + <- line up with this one + // -> here; if (lookfor == LOOKFOR_UNTERM || lookfor == LOOKFOR_ENUM_OR_INIT) { if (cont_amount > 0) { @@ -3335,28 +3192,22 @@ int get_c_indent(void) } } whilelevel++; - } - /* - * We are after a "normal" statement. - * If we had another statement we can stop now and use the - * indent of that other statement. - * Otherwise the indent of the current statement may be used, - * search backwards for the next "normal" statement. - */ - else { - /* - * Skip single break line, if before a switch label. It - * may be lined up with the case label. - */ + } else { + // We are after a "normal" statement. + // If we had another statement we can stop now and use the + // indent of that other statement. + // Otherwise the indent of the current statement may be used, + // search backwards for the next "normal" statement. + + // Skip single break line, if before a switch label. It + // may be lined up with the case label. if (lookfor == LOOKFOR_NOBREAK - && cin_isbreak((char_u *)skipwhite((char *)get_cursor_line_ptr()))) { + && cin_isbreak(skipwhite(get_cursor_line_ptr()))) { lookfor = LOOKFOR_ANY; continue; } - /* - * Handle "do {" line. - */ + // Handle "do {" line. if (whilelevel > 0) { l = cin_skipcomment(get_cursor_line_ptr()); if (cin_isdo(l)) { @@ -3366,17 +3217,15 @@ int get_c_indent(void) } } - /* - * Found a terminated line above an unterminated line. Add - * the amount for a continuation line. - * x = 1; - * y = foo + - * -> here; - * or - * int x = 1; - * int foo, - * -> here; - */ + // Found a terminated line above an unterminated line. Add + // the amount for a continuation line. + // x = 1; + // y = foo + + // -> here; + // or + // int x = 1; + // int foo, + // -> here; if (lookfor == LOOKFOR_UNTERM || lookfor == LOOKFOR_ENUM_OR_INIT) { if (cont_amount > 0) { @@ -3387,34 +3236,28 @@ int get_c_indent(void) break; } - /* - * Found a terminated line above a terminated line or "if" - * etc. line. Use the amount of the line below us. - * x = 1; x = 1; - * if (asdf) y = 2; - * while (asdf) ->here; - * here; - * ->foo; - */ + // Found a terminated line above a terminated line or "if" + // etc. line. Use the amount of the line below us. + // x = 1; x = 1; + // if (asdf) y = 2; + // while (asdf) ->here; + // here; + // ->foo; if (lookfor == LOOKFOR_TERM) { if (!lookfor_break && whilelevel == 0) { break; } - } - /* - * First line above the one we're indenting is terminated. - * To know what needs to be done look further backward for - * a terminated line. - */ - else { - /* - * position the cursor over the rightmost paren, so - * that matching it will take us back to the start of - * the line. Helps for: - * func(asdr, - * asdfasdf); - * here; - */ + } else { + // First line above the one we're indenting is terminated. + // To know what needs to be done look further backward for + // a terminated line. + + // position the cursor over the rightmost paren, so + // that matching it will take us back to the start of + // the line. Helps for: + // func(asdr, + // asdfasdf); + // here; term_again: l = get_cursor_line_ptr(); if (find_last_paren(l, '(', ')') @@ -3432,41 +3275,36 @@ term_again: } } - /* When aligning with the case statement, don't align - * with a statement after it. - * case 1: { <-- don't use this { position - * stat; - * } - * case 2: - * stat; - * } - */ + // When aligning with the case statement, don't align + // with a statement after it. + // case 1: { <-- don't use this { position + // stat; + // } + // case 2: + // stat; + // } iscase = curbuf->b_ind_keep_case_label && cin_iscase(l, false); - /* - * Get indent and pointer to text for current line, - * ignoring any jump label. - */ + // Get indent and pointer to text for current line, + // ignoring any jump label. amount = skip_label(curwin->w_cursor.lnum, &l); if (theline[0] == '{') { amount += curbuf->b_ind_open_extra; } // See remark above: "Only add b_ind_open_extra.." - l = (char_u *)skipwhite((char *)l); + l = skipwhite(l); if (*l == '{') { amount -= curbuf->b_ind_open_extra; } lookfor = iscase ? LOOKFOR_ANY : LOOKFOR_TERM; - /* - * When a terminated line starts with "else" skip to - * the matching "if": - * else 3; - * indent this; - * Need to use the scope of this "else". XXX - * If whilelevel != 0 continue looking for a "do {". - */ + // When a terminated line starts with "else" skip to + // the matching "if": + // else 3; + // indent this; + // Need to use the scope of this "else". XXX + // If whilelevel != 0 continue looking for a "do {". if (lookfor == LOOKFOR_TERM && *l != '}' && cin_iselse(l) @@ -3479,10 +3317,8 @@ term_again: continue; } - /* - * If we're at the end of a block, skip to the start of - * that block. - */ + // If we're at the end of a block, skip to the start of + // that block. l = get_cursor_line_ptr(); if (find_last_paren(l, '{', '}') // XXX && (trypos = find_start_brace()) != NULL) { @@ -3529,19 +3365,17 @@ term_again: amount = curbuf->b_ind_first_open; goto theend; } - /* - * If the NEXT line is a function declaration, the current - * line needs to be indented as a function type spec. - * Don't do this if the current line looks like a comment or if the - * current line is terminated, ie. ends in ';', or if the current line - * contains { or }: "void f() {\n if (1)" - */ + // If the NEXT line is a function declaration, the current + // line needs to be indented as a function type spec. + // Don't do this if the current line looks like a comment or if the + // current line is terminated, ie. ends in ';', or if the current line + // contains { or }: "void f() {\n if (1)" if (cur_curpos.lnum < curbuf->b_ml.ml_line_count && !cin_nocode(theline) - && vim_strchr((char *)theline, '{') == NULL - && vim_strchr((char *)theline, '}') == NULL - && !cin_ends_in(theline, (char_u *)":", NULL) - && !cin_ends_in(theline, (char_u *)",", NULL) + && vim_strchr(theline, '{') == NULL + && vim_strchr(theline, '}') == NULL + && !cin_ends_in(theline, ":", NULL) + && !cin_ends_in(theline, ",", NULL) && cin_isfuncdecl(NULL, cur_curpos.lnum + 1, cur_curpos.lnum + 1) && !cin_isterminated(theline, false, true)) { amount = curbuf->b_ind_func_type; @@ -3590,35 +3424,32 @@ term_again: continue; } - /* - * If the previous line ends in ',', use one level of - * indentation: - * int foo, - * bar; - * do this before checking for '}' in case of eg. - * enum foobar - * { - * ... - * } foo, - * bar; - */ - if (cin_ends_in(l, (char_u *)",", NULL) - || (*l != NUL && (n = l[STRLEN(l) - 1]) == '\\')) { + // If the previous line ends in ',', use one level of + // indentation: + // int foo, + // bar; + // do this before checking for '}' in case of eg. + // enum foobar + // { + // ... + // } foo, + // bar; + if (cin_ends_in(l, ",", NULL) + || (*l != NUL && (n = (uint8_t)l[strlen(l) - 1]) == '\\')) { // take us back to opening paren if (find_last_paren(l, '(', ')') && (trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) { curwin->w_cursor = *trypos; } - /* For a line ending in ',' that is a continuation line go - * back to the first line with a backslash: - * char *foo = "bla\ - * bla", - * here; - */ + // For a line ending in ',' that is a continuation line go + // back to the first line with a backslash: + // char *foo = "bla{backslash} + // bla", + // here; while (n == 0 && curwin->w_cursor.lnum > 1) { l = ml_get(curwin->w_cursor.lnum - 1); - if (*l == NUL || l[STRLEN(l) - 1] != '\\') { + if (*l == NUL || l[strlen(l) - 1] != '\\') { break; } curwin->w_cursor.lnum--; @@ -3636,20 +3467,16 @@ term_again: break; } - /* - * If the line looks like a function declaration, and we're - * not in a comment, put it the left margin. - */ + // If the line looks like a function declaration, and we're + // not in a comment, put it the left margin. if (cin_isfuncdecl(NULL, cur_curpos.lnum, 0)) { // XXX break; } l = get_cursor_line_ptr(); - /* - * Finding the closing '}' of a previous function. Put - * current line at the left margin. For when 'cino' has "fs". - */ - if (*skipwhite((char *)l) == '}') { + // Finding the closing '}' of a previous function. Put + // current line at the left margin. For when 'cino' has "fs". + if (*skipwhite(l) == '}') { break; } @@ -3658,7 +3485,7 @@ term_again: // comments) align at column 0. For example: // char *string_array[] = { "foo", // / * x * / "b};ar" }; / * foobar * / - if (cin_ends_in(l, (char_u *)"};", NULL)) { + if (cin_ends_in(l, "};", NULL)) { break; } @@ -3666,17 +3493,15 @@ term_again: // array constant: // something = [ // 234, <- extra indent - if (cin_ends_in(l, (char_u *)"[", NULL)) { + if (cin_ends_in(l, "[", NULL)) { amount = get_indent() + ind_continuation; break; } - /* - * Find a line only has a semicolon that belongs to a previous - * line ending in '}', e.g. before an #endif. Don't increase - * indent then. - */ - if (*(look = (char_u *)skipwhite((char *)l)) == ';' && cin_nocode(look + 1)) { + // Find a line only has a semicolon that belongs to a previous + // line ending in '}', e.g. before an #endif. Don't increase + // indent then. + if (*(look = skipwhite(l)) == ';' && cin_nocode(look + 1)) { pos_T curpos_save = curwin->w_cursor; while (curwin->w_cursor.lnum > 1) { @@ -3687,46 +3512,40 @@ term_again: } } if (curwin->w_cursor.lnum > 0 - && cin_ends_in(look, (char_u *)"}", NULL)) { + && cin_ends_in(look, "}", NULL)) { break; } curwin->w_cursor = curpos_save; } - /* - * If the PREVIOUS line is a function declaration, the current - * line (and the ones that follow) needs to be indented as - * parameters. - */ + // If the PREVIOUS line is a function declaration, the current + // line (and the ones that follow) needs to be indented as + // parameters. if (cin_isfuncdecl(&l, curwin->w_cursor.lnum, 0)) { amount = curbuf->b_ind_param; break; } - /* - * If the previous line ends in ';' and the line before the - * previous line ends in ',' or '\', ident to column zero: - * int foo, - * bar; - * indent_to_0 here; - */ - if (cin_ends_in(l, (char_u *)";", NULL)) { + // If the previous line ends in ';' and the line before the + // previous line ends in ',' or '\', ident to column zero: + // int foo, + // bar; + // indent_to_0 here; + if (cin_ends_in(l, ";", NULL)) { l = ml_get(curwin->w_cursor.lnum - 1); - if (cin_ends_in(l, (char_u *)",", NULL) - || (*l != NUL && l[STRLEN(l) - 1] == '\\')) { + if (cin_ends_in(l, ",", NULL) + || (*l != NUL && l[strlen(l) - 1] == '\\')) { break; } l = get_cursor_line_ptr(); } - /* - * Doesn't look like anything interesting -- so just - * use the indent of this line. - * - * Position the cursor over the rightmost paren, so that - * matching it will take us back to the start of the line. - */ + // Doesn't look like anything interesting -- so just + // use the indent of this line. + // + // Position the cursor over the rightmost paren, so that + // matching it will take us back to the start of the line. (void)find_last_paren(l, '(', ')'); if ((trypos = find_match_paren(curbuf->b_ind_maxparen)) != NULL) { @@ -3748,7 +3567,7 @@ term_again: // here"; if (cur_curpos.lnum > 1) { l = ml_get(cur_curpos.lnum - 1); - if (*l != NUL && l[STRLEN(l) - 1] == '\\') { + if (*l != NUL && l[strlen(l) - 1] == '\\') { cur_amount = cin_get_equal_amount(cur_curpos.lnum - 1); if (cur_amount > 0) { amount = cur_amount; @@ -3774,9 +3593,9 @@ laterend: static int find_match(int lookfor, linenr_T ourscope) { - const char_u *look; + const char *look; pos_T *theirscope; - const char_u *mightbeif; + const char *mightbeif; int elselevel; int whilelevel; @@ -3802,38 +3621,30 @@ static int find_match(int lookfor, linenr_T ourscope) continue; } - /* - * if we've gone outside the braces entirely, - * we must be out of scope... - */ + // if we've gone outside the braces entirely, + // we must be out of scope... theirscope = find_start_brace(); // XXX if (theirscope == NULL) { break; } - /* - * and if the brace enclosing this is further - * back than the one enclosing the else, we're - * out of luck too. - */ + // and if the brace enclosing this is further + // back than the one enclosing the else, we're + // out of luck too. if (theirscope->lnum < ourscope) { break; } - /* - * and if they're enclosed in a *deeper* brace, - * then we can ignore it because it's in a - * different scope... - */ + // and if they're enclosed in a *deeper* brace, + // then we can ignore it because it's in a + // different scope... if (theirscope->lnum > ourscope) { continue; } - /* - * if it was an "else" (that's not an "else if") - * then we need to go back to another if, so - * increment elselevel - */ + // if it was an "else" (that's not an "else if") + // then we need to go back to another if, so + // increment elselevel look = cin_skipcomment(get_cursor_line_ptr()); if (cin_iselse(look)) { mightbeif = cin_skipcomment(look + 4); @@ -3843,10 +3654,8 @@ static int find_match(int lookfor, linenr_T ourscope) continue; } - /* - * if it was a "while" then we need to go back to - * another "do", so increment whilelevel. XXX - */ + // if it was a "while" then we need to go back to + // another "do", so increment whilelevel. XXX if (cin_iswhileofdo(look, curwin->w_cursor.lnum)) { whilelevel++; continue; @@ -3855,11 +3664,9 @@ static int find_match(int lookfor, linenr_T ourscope) // If it's an "if" decrement elselevel look = cin_skipcomment(get_cursor_line_ptr()); if (cin_isif(look)) { - elselevel--; - /* - * When looking for an "if" ignore "while"s that - * get in the way. - */ + elselevel--; // NOLINT(readability/braces) + // When looking for an "if" ignore "while"s that + // get in the way. if (elselevel == 0 && lookfor == LOOKFOR_IF) { whilelevel = 0; } @@ -3870,11 +3677,9 @@ static int find_match(int lookfor, linenr_T ourscope) whilelevel--; } - /* - * if we've used up all the elses, then - * this must be the if that we want! - * match the indent level of that if. - */ + // if we've used up all the elses, then + // this must be the if that we want! + // match the indent level of that if. if (elselevel <= 0 && whilelevel <= 0) { return OK; } @@ -3882,9 +3687,7 @@ static int find_match(int lookfor, linenr_T ourscope) return FAIL; } -/* - * Do C or expression indenting on the current line. - */ +// Do C or expression indenting on the current line. void do_c_expr_indent(void) { if (*curbuf->b_p_inde != NUL) { diff --git a/src/nvim/input.c b/src/nvim/input.c index 37c2903cfd..96214d45c2 100644 --- a/src/nvim/input.c +++ b/src/nvim/input.c @@ -4,24 +4,33 @@ // input.c: high level functions for prompting the user or input // like yes/no or number prompts. -#include <inttypes.h> #include <stdbool.h> +#include <string.h> +#include "nvim/ascii.h" +#include "nvim/event/multiqueue.h" #include "nvim/func_attr.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/input.h" +#include "nvim/keycodes.h" #include "nvim/mbyte.h" #include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/os/input.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "input.c.generated.h" +# include "input.c.generated.h" // IWYU pragma: export #endif -/// Ask for a reply from the user, 'y' or 'n' +/// Ask for a reply from the user, a 'y' or a 'n', with prompt "str" (which +/// should have been translated already). /// /// No other characters are accepted, the message is repeated until a valid /// reply is entered or <C-c> is hit. @@ -46,7 +55,7 @@ int ask_yesno(const char *const str, const bool direct) int r = ' '; while (r != 'y' && r != 'n') { - // Same highlighting as for wait_return. + // same highlighting as for wait_return() smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str); if (direct) { r = get_keystroke(NULL); @@ -83,7 +92,6 @@ int get_keystroke(MultiQueue *events) int len = 0; int n; int save_mapped_ctrl_c = mapped_ctrl_c; - int waited = 0; mapped_ctrl_c = 0; // mappings are not used here for (;;) { @@ -110,10 +118,8 @@ int get_keystroke(MultiQueue *events) // Replace zero and K_SPECIAL by a special key code. n = fix_input_buffer(buf + len, n); len += n; - waited = 0; - } else if (len > 0) { - waited++; // keep track of the waiting time } + if (n > 0) { // found a termcode: adjust length len = n; } diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 938189f343..6de3b0a9d0 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -4,11 +4,16 @@ // insexpand.c: functions for Insert mode completion #include <assert.h> -#include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" @@ -18,35 +23,48 @@ #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/userfunc.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" +#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" +#include "nvim/macros.h" +#include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/os/fs.h" #include "nvim/os/input.h" #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/regexp.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/spell.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/tag.h" +#include "nvim/textformat.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" -#include "nvim/window.h" // Definitions used for CTRL-X submode. // Note: If you change CTRL-X submode, you must also maintain ctrl_x_msgs[] @@ -54,30 +72,31 @@ #define CTRL_X_WANT_IDENT 0x100 -#define CTRL_X_NORMAL 0 ///< CTRL-N CTRL-P completion, default -#define CTRL_X_NOT_DEFINED_YET 1 -#define CTRL_X_SCROLL 2 -#define CTRL_X_WHOLE_LINE 3 -#define CTRL_X_FILES 4 -#define CTRL_X_TAGS (5 + CTRL_X_WANT_IDENT) -#define CTRL_X_PATH_PATTERNS (6 + CTRL_X_WANT_IDENT) -#define CTRL_X_PATH_DEFINES (7 + CTRL_X_WANT_IDENT) -#define CTRL_X_FINISHED 8 -#define CTRL_X_DICTIONARY (9 + CTRL_X_WANT_IDENT) -#define CTRL_X_THESAURUS (10 + CTRL_X_WANT_IDENT) -#define CTRL_X_CMDLINE 11 -#define CTRL_X_FUNCTION 12 -#define CTRL_X_OMNI 13 -#define CTRL_X_SPELL 14 -#define CTRL_X_LOCAL_MSG 15 ///< only used in "ctrl_x_msgs" -#define CTRL_X_EVAL 16 ///< for builtin function complete() -#define CTRL_X_CMDLINE_CTRL_X 17 ///< CTRL-X typed in CTRL_X_CMDLINE +enum { + CTRL_X_NORMAL = 0, ///< CTRL-N CTRL-P completion, default + CTRL_X_NOT_DEFINED_YET = 1, + CTRL_X_SCROLL = 2, + CTRL_X_WHOLE_LINE = 3, + CTRL_X_FILES = 4, + CTRL_X_TAGS = (5 + CTRL_X_WANT_IDENT), + CTRL_X_PATH_PATTERNS = (6 + CTRL_X_WANT_IDENT), + CTRL_X_PATH_DEFINES = (7 + CTRL_X_WANT_IDENT), + CTRL_X_FINISHED = 8, + CTRL_X_DICTIONARY = (9 + CTRL_X_WANT_IDENT), + CTRL_X_THESAURUS = (10 + CTRL_X_WANT_IDENT), + CTRL_X_CMDLINE = 11, + CTRL_X_FUNCTION = 12, + CTRL_X_OMNI = 13, + CTRL_X_SPELL = 14, + CTRL_X_LOCAL_MSG = 15, ///< only used in "ctrl_x_msgs" + CTRL_X_EVAL = 16, ///< for builtin function complete() + CTRL_X_CMDLINE_CTRL_X = 17, ///< CTRL-X typed in CTRL_X_CMDLINE +}; #define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT] /// Message for CTRL-X mode, index is ctrl_x_mode. -static char *ctrl_x_msgs[] = -{ +static char *ctrl_x_msgs[] = { N_(" Keyword completion (^N^P)"), // CTRL_X_NORMAL, ^P/^N compl. N_(" ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"), NULL, // CTRL_X_SCROLL: depends on state @@ -131,15 +150,30 @@ typedef struct compl_S compl_T; struct compl_S { compl_T *cp_next; compl_T *cp_prev; - char_u *cp_str; ///< matched text - char_u *(cp_text[CPT_COUNT]); ///< text for the menu + char *cp_str; ///< matched text + char *(cp_text[CPT_COUNT]); ///< text for the menu typval_T cp_user_data; - char_u *cp_fname; ///< file containing the match, allocated when + char *cp_fname; ///< file containing the match, allocated when ///< cp_flags has CP_FREE_FNAME int cp_flags; ///< CP_ values int cp_number; ///< sequence number }; +/// state information used for getting the next set of insert completion +/// matches. +typedef struct { + char *e_cpt; ///< current entry in 'complete' + buf_T *ins_buf; ///< buffer being scanned + pos_T *cur_match_pos; ///< current match position + pos_T prev_match_pos; ///< previous match position + bool set_match_pos; ///< save first_match_pos/last_match_pos + pos_T first_match_pos; ///< first match position + pos_T last_match_pos; ///< last match position + bool found_all; ///< found all matches of a certain type. + char *dict; ///< dictionary file to search + int dict_f; ///< "dict" is an exact file name or not +} ins_compl_next_state_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "insexpand.c.generated.h" #endif @@ -162,6 +196,7 @@ static char e_compldel[] = N_("E840: Completion function deleted text"); // "compl_curr_match" points to the currently selected entry. // "compl_shown_match" is different from compl_curr_match during // ins_compl_get_exp(). +// "compl_old_match" points to previous "compl_curr_match". static compl_T *compl_first_match = NULL; static compl_T *compl_curr_match = NULL; @@ -174,7 +209,7 @@ static bool compl_enter_selects = false; /// When "compl_leader" is not NULL only matches that start with this string /// are used. -static char_u *compl_leader = NULL; +static char *compl_leader = NULL; static bool compl_get_longest = false; ///< put longest common string in compl_leader @@ -182,6 +217,8 @@ static bool compl_no_insert = false; ///< false: select & insert ///< true: noinsert static bool compl_no_select = false; ///< false: select & insert ///< true: noselect +static bool compl_longest = false; ///< false: insert full match + ///< true: insert longest prefix /// Selected one of the matches. When false the match was edited or using the /// longest common string. @@ -196,26 +233,42 @@ static bool compl_interrupted = false; static bool compl_restarting = false; ///< don't insert match -///< When the first completion is done "compl_started" is set. When it's -///< false the word to be completed must be located. +/// When the first completion is done "compl_started" is set. When it's +/// false the word to be completed must be located. static bool compl_started = false; -///< Which Ctrl-X mode are we in? +/// Which Ctrl-X mode are we in? static int ctrl_x_mode = CTRL_X_NORMAL; -static int compl_matches = 0; +static int compl_matches = 0; ///< number of completion matches static char *compl_pattern = NULL; static Direction compl_direction = FORWARD; static Direction compl_shows_dir = FORWARD; static int compl_pending = 0; ///< > 1 for postponed CTRL-N static pos_T compl_startpos; +/// Length in bytes of the text being completed (this is deleted to be replaced +/// by the match.) +static int compl_length = 0; static colnr_T compl_col = 0; ///< column where the text starts ///< that is being completed -static char_u *compl_orig_text = NULL; ///< text as it was before +static char *compl_orig_text = NULL; ///< text as it was before ///< completion started static int compl_cont_mode = 0; static expand_T compl_xp; +// List of flags for method of completion. +static int compl_cont_status = 0; +#define CONT_ADDING 1 ///< "normal" or "adding" expansion +#define CONT_INTRPT (2 + 4) ///< a ^X interrupted the current expansion + ///< it's set only iff N_ADDS is set +#define CONT_N_ADDS 4 ///< next ^X<> will add-new or expand-current +#define CONT_S_IPOS 8 ///< next ^X<> will set initial_pos? + ///< if so, word-wise-expansion will set SOL +#define CONT_SOL 16 ///< pattern includes start of line, just for + ///< word-wise expansion, not set for ^X^L +#define CONT_LOCAL 32 ///< for ctrl_x_mode 0, ^X^P/^X^N do a local + ///< expansion, (eg use complete=.) + static bool compl_opt_refresh_always = false; static size_t spell_bad_len = 0; // length of located bad word @@ -234,7 +287,7 @@ void ins_ctrl_x(void) } // We're not sure which CTRL-X mode it will be yet ctrl_x_mode = CTRL_X_NOT_DEFINED_YET; - edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode)); + edit_submode = _(CTRL_X_MSG(ctrl_x_mode)); edit_submode_pre = NULL; showmode(); } else { @@ -359,9 +412,52 @@ bool ctrl_x_mode_not_defined_yet(void) return ctrl_x_mode == CTRL_X_NOT_DEFINED_YET; } -/// Check that the "dict" or "tsr" option can be used. +/// @return true if currently in "normal" or "adding" insert completion matches state +bool compl_status_adding(void) +{ + return compl_cont_status & CONT_ADDING; +} + +/// @return true if the completion pattern includes start of line, just for +/// word-wise expansion. +bool compl_status_sol(void) +{ + return compl_cont_status & CONT_SOL; +} + +/// @return true if ^X^P/^X^N will do a local completion (i.e. use complete=.) +bool compl_status_local(void) +{ + return compl_cont_status & CONT_LOCAL; +} + +/// Clear the completion status flags +void compl_status_clear(void) +{ + compl_cont_status = 0; +} + +/// @return true if completion is using the forward direction matches +static bool compl_dir_forward(void) +{ + return compl_direction == FORWARD; +} + +/// @return true if currently showing forward completion matches +static bool compl_shows_dir_forward(void) +{ + return compl_shows_dir == FORWARD; +} + +/// @return true if currently showing backward completion matches +static bool compl_shows_dir_backward(void) +{ + return compl_shows_dir == BACKWARD; +} + +/// Check that the 'dictionary' or 'thesaurus' option can be used. /// -/// @param dict_opt check "dict" when true, "tsr" when false. +/// @param dict_opt check 'dictionary' when true, 'thesaurus' when false. bool check_compl_option(bool dict_opt) { if (dict_opt @@ -373,7 +469,7 @@ bool check_compl_option(bool dict_opt) msg_attr((dict_opt ? _("'dictionary' option is empty") : _("'thesaurus' option is empty")), HL_ATTR(HLF_E)); - if (emsg_silent == 0) { + if (emsg_silent == 0 && !in_assert_fails) { vim_beep(BO_COMPL); setcursor(); ui_flush(); @@ -445,6 +541,18 @@ bool vim_is_ctrl_x_key(int c) return false; } +/// @return true if "match" is the original text when the completion began. +static bool match_at_original_text(const compl_T *const match) +{ + return match->cp_flags & CP_ORIGINAL_TEXT; +} + +/// @return true if "match" is the first match in the completion list. +static bool is_first_match(const compl_T *const match) +{ + return match == compl_first_match; +} + /// Check that character "c" is part of the item currently being /// completed. Used to decide whether to abandon complete mode when the menu /// is visible. @@ -479,126 +587,150 @@ bool ins_compl_accept_char(int c) return vim_iswordc(c); } +/// Get the completed text by inferring the case of the originally typed text. +/// If the result is in allocated memory "tofree" is set to it. +static char *ins_compl_infercase_gettext(const char *str, int char_len, int compl_char_len, + int min_len, char **tofree) +{ + bool has_lower = false; + bool was_letter = false; + + // Allocate wide character array for the completion and fill it. + int *const wca = xmalloc((size_t)char_len * sizeof(*wca)); + { + const char *p = str; + for (int i = 0; i < char_len; i++) { + wca[i] = mb_ptr2char_adv(&p); + } + } + + // Rule 1: Were any chars converted to lower? + { + const char *p = compl_orig_text; + for (int i = 0; i < min_len; i++) { + const int c = mb_ptr2char_adv(&p); + if (mb_islower(c)) { + has_lower = true; + if (mb_isupper(wca[i])) { + // Rule 1 is satisfied. + for (i = compl_char_len; i < char_len; i++) { + wca[i] = mb_tolower(wca[i]); + } + break; + } + } + } + } + + // Rule 2: No lower case, 2nd consecutive letter converted to + // upper case. + if (!has_lower) { + const char *p = compl_orig_text; + for (int i = 0; i < min_len; i++) { + const int c = mb_ptr2char_adv(&p); + if (was_letter && mb_isupper(c) && mb_islower(wca[i])) { + // Rule 2 is satisfied. + for (i = compl_char_len; i < char_len; i++) { + wca[i] = mb_toupper(wca[i]); + } + break; + } + was_letter = mb_islower(c) || mb_isupper(c); + } + } + + // Copy the original case of the part we typed. + { + const char *p = compl_orig_text; + for (int i = 0; i < min_len; i++) { + const int c = mb_ptr2char_adv(&p); + if (mb_islower(c)) { + wca[i] = mb_tolower(wca[i]); + } else if (mb_isupper(c)) { + wca[i] = mb_toupper(wca[i]); + } + } + } + + // Generate encoding specific output from wide character array. + garray_T gap; + char *p = IObuff; + int i = 0; + ga_init(&gap, 1, 500); + while (i < char_len) { + if (gap.ga_data != NULL) { + ga_grow(&gap, 10); + assert(gap.ga_data != NULL); // suppress clang "Dereference of NULL pointer" + p = (char *)gap.ga_data + gap.ga_len; + gap.ga_len += utf_char2bytes(wca[i++], p); + } else if ((p - IObuff) + 6 >= IOSIZE) { + // Multi-byte characters can occupy up to five bytes more than + // ASCII characters, and we also need one byte for NUL, so when + // getting to six bytes from the edge of IObuff switch to using a + // growarray. Add the character in the next round. + ga_grow(&gap, IOSIZE); + *p = NUL; + STRCPY(gap.ga_data, IObuff); + gap.ga_len = (int)strlen(IObuff); + } else { + p += utf_char2bytes(wca[i++], p); + } + } + xfree(wca); + + if (gap.ga_data != NULL) { + *tofree = gap.ga_data; + return gap.ga_data; + } + + *p = NUL; + return IObuff; +} + /// This is like ins_compl_add(), but if 'ic' and 'inf' are set, then the /// case of the originally typed text is used, and the case of the completed /// text is inferred, ie this tries to work out what case you probably wanted /// the rest of the word to be in -- webb /// /// @param[in] cont_s_ipos next ^X<> will set initial_pos -int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, Direction dir, +int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Direction dir, bool cont_s_ipos) FUNC_ATTR_NONNULL_ARG(1) { - char_u *str = str_arg; - int i, c; - int actual_len; // Take multi-byte characters - int actual_compl_length; // into account. - int min_len; - bool has_lower = false; - bool was_letter = false; + char *str = str_arg; + int char_len; // count multi-byte characters + int compl_char_len; int flags = 0; + char *tofree = NULL; if (p_ic && curbuf->b_p_inf && len > 0) { // Infer case of completed part. // Find actual length of completion. { - const char_u *p = str; - actual_len = 0; + const char *p = str; + char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); - actual_len++; + char_len++; } } // Find actual length of original text. { - const char_u *p = compl_orig_text; - actual_compl_length = 0; + const char *p = compl_orig_text; + compl_char_len = 0; while (*p != NUL) { MB_PTR_ADV(p); - actual_compl_length++; + compl_char_len++; } } - // "actual_len" may be smaller than "actual_compl_length" when using + // "char_len" may be smaller than "compl_char_len" when using // thesaurus, only use the minimum when comparing. - min_len = actual_len < actual_compl_length - ? actual_len : actual_compl_length; - - // Allocate wide character array for the completion and fill it. - int *const wca = xmalloc((size_t)actual_len * sizeof(*wca)); - { - const char_u *p = str; - for (i = 0; i < actual_len; i++) { - wca[i] = mb_ptr2char_adv(&p); - } - } - - // Rule 1: Were any chars converted to lower? - { - const char_u *p = compl_orig_text; - for (i = 0; i < min_len; i++) { - c = mb_ptr2char_adv(&p); - if (mb_islower(c)) { - has_lower = true; - if (mb_isupper(wca[i])) { - // Rule 1 is satisfied. - for (i = actual_compl_length; i < actual_len; i++) { - wca[i] = mb_tolower(wca[i]); - } - break; - } - } - } - } - - // Rule 2: No lower case, 2nd consecutive letter converted to - // upper case. - if (!has_lower) { - const char_u *p = compl_orig_text; - for (i = 0; i < min_len; i++) { - c = mb_ptr2char_adv(&p); - if (was_letter && mb_isupper(c) && mb_islower(wca[i])) { - // Rule 2 is satisfied. - for (i = actual_compl_length; i < actual_len; i++) { - wca[i] = mb_toupper(wca[i]); - } - break; - } - was_letter = mb_islower(c) || mb_isupper(c); - } - } - - // Copy the original case of the part we typed. - { - const char_u *p = compl_orig_text; - for (i = 0; i < min_len; i++) { - c = mb_ptr2char_adv(&p); - if (mb_islower(c)) { - wca[i] = mb_tolower(wca[i]); - } else if (mb_isupper(c)) { - wca[i] = mb_toupper(wca[i]); - } - } - } + int min_len = char_len < compl_char_len ? char_len : compl_char_len; - // Generate encoding specific output from wide character array. - // Multi-byte characters can occupy up to five bytes more than - // ASCII characters, and we also need one byte for NUL, so stay - // six bytes away from the edge of IObuff. - { - char_u *p = IObuff; - i = 0; - while (i < actual_len && (p - IObuff + 6) < IOSIZE) { - p += utf_char2bytes(wca[i++], (char *)p); - } - *p = NUL; - } - - xfree(wca); - - str = IObuff; + str = ins_compl_infercase_gettext(str, char_len, compl_char_len, min_len, &tofree); } if (cont_s_ipos) { flags |= CP_CONT_S_IPOS; @@ -607,29 +739,37 @@ int ins_compl_add_infercase(char_u *str_arg, int len, bool icase, char_u *fname, flags |= CP_ICASE; } - return ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false); + int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false); + xfree(tofree); + return res; } /// Add a match to the list of matches /// -/// @param[in] str Match to add. -/// @param[in] len Match length, -1 to use #STRLEN. -/// @param[in] fname File name match comes from. May be NULL. -/// @param[in] cptext Extra text for popup menu. May be NULL. If not NULL, -/// must have exactly #CPT_COUNT items. +/// @param[in] str text of the match to add +/// @param[in] len length of "str". If -1, then the length of "str" is computed. +/// @param[in] fname file name to associate with this match. May be NULL. +/// @param[in] cptext list of strings to use with this match (for abbr, menu, info +/// and kind). May be NULL. +/// If not NULL, must have exactly #CPT_COUNT items. /// @param[in] cptext_allocated If true, will not copy cptext strings. /// /// @note Will free strings in case of error. /// cptext itself will not be freed. -/// @param[in] cdir Completion direction. -/// @param[in] adup True if duplicate matches are to be accepted. +/// @param[in] user_data user supplied data (any vim type) for this match +/// @param[in] cdir match direction. If 0, use "compl_direction". +/// @param[in] flags_arg match flags (cp_flags) +/// @param[in] adup accept this match even if it is already present. +/// +/// If "cdir" is FORWARD, then the match is added after the current match. +/// Otherwise, it is added before the current match. /// /// @return NOTDONE if the given string is already in the list of completions, /// otherwise it is added to the list and OK is returned. FAIL will be /// returned in case of error. -static int ins_compl_add(char_u *const str, int len, char_u *const fname, - char_u *const *const cptext, const bool cptext_allocated, - typval_T *user_data, const Direction cdir, int flags_arg, const bool adup) +static int ins_compl_add(char *const str, int len, char *const fname, char *const *const cptext, + const bool cptext_allocated, typval_T *user_data, const Direction cdir, + int flags_arg, const bool adup) FUNC_ATTR_NONNULL_ARG(1) { compl_T *match; @@ -654,21 +794,21 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, return FAIL; } if (len < 0) { - len = (int)STRLEN(str); + len = (int)strlen(str); } // If the same match is already present, don't add it. if (compl_first_match != NULL && !adup) { match = compl_first_match; do { - if (!(match->cp_flags & CP_ORIGINAL_TEXT) - && STRNCMP(match->cp_str, str, len) == 0 - && match->cp_str[len] == NUL) { + if (!match_at_original_text(match) + && strncmp(match->cp_str, str, (size_t)len) == 0 + && ((int)strlen(match->cp_str) <= len || match->cp_str[len] == NUL)) { FREE_CPTEXT(cptext, cptext_allocated); return NOTDONE; } match = match->cp_next; - } while (match != NULL && match != compl_first_match); + } while (match != NULL && !is_first_match(match)); } // Remove any popup menu before changing the list of matches. @@ -681,7 +821,7 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, if (flags & CP_ORIGINAL_TEXT) { match->cp_number = 0; } - match->cp_str = vim_strnsave(str, (size_t)len); + match->cp_str = xstrnsave(str, (size_t)len); // match-fname is: // - compl_curr_match->cp_fname if it is a string equal to fname. @@ -690,10 +830,10 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, if (fname != NULL && compl_curr_match != NULL && compl_curr_match->cp_fname != NULL - && STRCMP(fname, compl_curr_match->cp_fname) == 0) { + && strcmp(fname, compl_curr_match->cp_fname) == 0) { match->cp_fname = compl_curr_match->cp_fname; } else if (fname != NULL) { - match->cp_fname = vim_strsave(fname); + match->cp_fname = xstrdup(fname); flags |= CP_FREE_FNAME; } else { match->cp_fname = NULL; @@ -708,9 +848,7 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, continue; } if (*cptext[i] != NUL) { - match->cp_text[i] = (cptext_allocated - ? cptext[i] - : (char_u *)xstrdup((char *)cptext[i])); + match->cp_text[i] = (cptext_allocated ? cptext[i] : xstrdup(cptext[i])); } else if (cptext_allocated) { xfree(cptext[i]); } @@ -721,7 +859,8 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, match->cp_user_data = *user_data; } - // Link the new match structure in the list of matches. + // Link the new match structure after (FORWARD) or before (BACKWARD) the + // current match in the list of matches . if (compl_first_match == NULL) { match->cp_next = match->cp_prev = NULL; } else if (dir == FORWARD) { @@ -755,7 +894,7 @@ static int ins_compl_add(char_u *const str, int len, char_u *const fname, /// @param match completion match /// @param str character string to check /// @param len length of "str" -static bool ins_compl_equal(compl_T *match, char_u *str, size_t len) +static bool ins_compl_equal(compl_T *match, char *str, size_t len) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { if (match->cp_flags & CP_EQUAL) { @@ -764,19 +903,20 @@ static bool ins_compl_equal(compl_T *match, char_u *str, size_t len) if (match->cp_flags & CP_ICASE) { return STRNICMP(match->cp_str, str, len) == 0; } - return STRNCMP(match->cp_str, str, len) == 0; + return strncmp(match->cp_str, str, len) == 0; } /// Reduce the longest common string for match "match". static void ins_compl_longest_match(compl_T *match) { - char_u *p, *s; + char *p, *s; int c1, c2; int had_match; if (compl_leader == NULL) { // First match, use it as a whole. - compl_leader = vim_strsave(match->cp_str); + compl_leader = xstrdup(match->cp_str); + had_match = (curwin->w_cursor.col > compl_col); ins_compl_delete(); ins_bytes(compl_leader + get_compl_len()); @@ -788,40 +928,42 @@ static void ins_compl_longest_match(compl_T *match) ins_compl_delete(); } compl_used_match = false; - } else { - // Reduce the text if this match differs from compl_leader. - p = compl_leader; - s = match->cp_str; - while (*p != NUL) { - c1 = utf_ptr2char((char *)p); - c2 = utf_ptr2char((char *)s); - - if ((match->cp_flags & CP_ICASE) - ? (mb_tolower(c1) != mb_tolower(c2)) - : (c1 != c2)) { - break; - } - MB_PTR_ADV(p); - MB_PTR_ADV(s); - } - if (*p != NUL) { - // Leader was shortened, need to change the inserted text. - *p = NUL; - had_match = (curwin->w_cursor.col > compl_col); - ins_compl_delete(); - ins_bytes(compl_leader + get_compl_len()); - ins_redraw(false); + return; + } - // When the match isn't there (to avoid matching itself) remove it - // again after redrawing. - if (!had_match) { - ins_compl_delete(); - } + // Reduce the text if this match differs from compl_leader. + p = compl_leader; + s = match->cp_str; + while (*p != NUL) { + c1 = utf_ptr2char(p); + c2 = utf_ptr2char(s); + + if ((match->cp_flags & CP_ICASE) + ? (mb_tolower(c1) != mb_tolower(c2)) + : (c1 != c2)) { + break; } + MB_PTR_ADV(p); + MB_PTR_ADV(s); + } - compl_used_match = false; + if (*p != NUL) { + // Leader was shortened, need to change the inserted text. + *p = NUL; + had_match = (curwin->w_cursor.col > compl_col); + ins_compl_delete(); + ins_bytes(compl_leader + get_compl_len()); + ins_redraw(false); + + // When the match isn't there (to avoid matching itself) remove it + // again after redrawing. + if (!had_match) { + ins_compl_delete(); + } } + + compl_used_match = false; } /// Add an array of matches to the list of matches. @@ -832,7 +974,7 @@ static void ins_compl_add_matches(int num_matches, char **matches, int icase) Direction dir = compl_direction; for (int i = 0; i < num_matches && add_r != FAIL; i++) { - if ((add_r = ins_compl_add((char_u *)matches[i], -1, NULL, NULL, false, NULL, dir, + if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir, CP_FAST | (icase ? CP_ICASE : 0), false)) == OK) { // If dir was BACKWARD then honor it just once. @@ -846,20 +988,21 @@ static void ins_compl_add_matches(int num_matches, char **matches, int icase) /// Return the number of matches (excluding the original). static int ins_compl_make_cyclic(void) { - compl_T *match; - int count = 0; + if (compl_first_match == NULL) { + return 0; + } - if (compl_first_match != NULL) { - // Find the end of the list. - match = compl_first_match; - // there's always an entry for the compl_orig_text, it doesn't count. - while (match->cp_next != NULL && match->cp_next != compl_first_match) { - match = match->cp_next; - count++; - } - match->cp_next = compl_first_match; - compl_first_match->cp_prev = match; + // Find the end of the list. + compl_T *match = compl_first_match; + int count = 0; + // there's always an entry for the compl_orig_text, it doesn't count. + while (match->cp_next != NULL && !is_first_match(match->cp_next)) { + match = match->cp_next; + count++; } + match->cp_next = compl_first_match; + compl_first_match->cp_prev = match; + return count; } @@ -873,7 +1016,7 @@ bool ins_compl_has_shown_match(void) bool ins_compl_long_shown_match(void) { return compl_shown_match != NULL && compl_shown_match->cp_str != NULL - && (colnr_T)STRLEN(compl_shown_match->cp_str) > curwin->w_cursor.col - compl_col; + && (colnr_T)strlen(compl_shown_match->cp_str) > curwin->w_cursor.col - compl_col; } /// Set variables that store noselect and noinsert behavior from the @@ -882,12 +1025,16 @@ void completeopt_was_set(void) { compl_no_insert = false; compl_no_select = false; - if (strstr((char *)p_cot, "noselect") != NULL) { + compl_longest = false; + if (strstr(p_cot, "noselect") != NULL) { compl_no_select = true; } - if (strstr((char *)p_cot, "noinsert") != NULL) { + if (strstr(p_cot, "noinsert") != NULL) { compl_no_insert = true; } + if (strstr(p_cot, "longest") != NULL) { + compl_longest = true; + } } /// "compl_match_array" points the currently displayed list of entries in the @@ -898,10 +1045,12 @@ static int compl_match_arraysize; /// Remove any popup menu. static void ins_compl_del_pum(void) { - if (compl_match_array != NULL) { - pum_undisplay(false); - XFREE_CLEAR(compl_match_array); + if (compl_match_array == NULL) { + return; } + + pum_undisplay(false); + XFREE_CLEAR(compl_match_array); } /// Check if the popup menu should be displayed. @@ -909,7 +1058,7 @@ bool pum_wanted(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // "completeopt" must contain "menu" or "menuone" - return vim_strchr((char *)p_cot, 'm') != NULL; + return vim_strchr(p_cot, 'm') != NULL; } /// Check that there are two or more matches to be shown in the popup menu. @@ -919,17 +1068,16 @@ static bool pum_enough_matches(void) { // Don't display the popup menu if there are no matches or there is only // one (ignoring the original text). - compl_T *comp = compl_first_match; + compl_T *compl = compl_first_match; int i = 0; do { - if (comp == NULL - || ((comp->cp_flags & CP_ORIGINAL_TEXT) == 0 && ++i == 2)) { + if (compl == NULL || (!match_at_original_text(compl) && ++i == 2)) { break; } - comp = comp->cp_next; - } while (comp != compl_first_match); + compl = compl->cp_next; + } while (!is_first_match(compl)); - if (strstr((char *)p_cot, "menuone") != NULL) { + if (strstr(p_cot, "menuone") != NULL) { return i >= 1; } return i >= 2; @@ -953,6 +1101,8 @@ static dict_T *ins_compl_dict_alloc(compl_T *match) return dict; } +/// Trigger the CompleteChanged autocmd event. Invoked each time the Insert mode +/// completion menu is changed. static void trigger_complete_changed_event(int cur) { static bool recursive = false; @@ -981,141 +1131,151 @@ static void trigger_complete_changed_event(int cur) restore_v_event(v_event, &save_v_event); } -/// Show the popup menu for the list of matches. -/// Also adjusts "compl_shown_match" to an entry that is actually displayed. -void ins_compl_show_pum(void) +/// Build a popup menu to show the completion matches. +/// +/// @return the popup menu entry that should be selected, +/// -1 if nothing should be selected. +static int ins_compl_build_pum(void) { - compl_T *compl; - compl_T *shown_compl = NULL; - bool did_find_shown_match = false; - bool shown_match_ok = false; - int i; - int cur = -1; - colnr_T col; - int lead_len = 0; - bool array_changed = false; + // Need to build the popup menu list. + compl_match_arraysize = 0; + compl_T *compl = compl_first_match; - if (!pum_wanted() || !pum_enough_matches()) { - return; + // If it's user complete function and refresh_always, + // do not use "compl_leader" as prefix filter. + if (ins_compl_need_restart()) { + XFREE_CLEAR(compl_leader); } - // Dirty hard-coded hack: remove any matchparen highlighting. - do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif"); - - // Update the screen before drawing the popup menu over it. - update_screen(0); + const int lead_len = compl_leader != NULL ? (int)strlen(compl_leader) : 0; - if (compl_match_array == NULL) { - array_changed = true; - // Need to build the popup menu list. - compl_match_arraysize = 0; - compl = compl_first_match; - // If it's user complete function and refresh_always, - // do not use "compl_leader" as prefix filter. - if (ins_compl_need_restart()) { - XFREE_CLEAR(compl_leader); - } - if (compl_leader != NULL) { - lead_len = (int)STRLEN(compl_leader); - } - do { - if ((compl->cp_flags & CP_ORIGINAL_TEXT) == 0 - && (compl_leader == NULL - || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { - compl_match_arraysize++; - } - compl = compl->cp_next; - } while (compl != NULL && compl != compl_first_match); - if (compl_match_arraysize == 0) { - return; + do { + if (!match_at_original_text(compl) + && (compl_leader == NULL + || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { + compl_match_arraysize++; } + compl = compl->cp_next; + } while (compl != NULL && !is_first_match(compl)); - assert(compl_match_arraysize >= 0); - compl_match_array = xcalloc((size_t)compl_match_arraysize, sizeof(pumitem_T)); - // If the current match is the original text don't find the first - // match after it, don't highlight anything. - if (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) { - shown_match_ok = true; - } + if (compl_match_arraysize == 0) { + return -1; + } - i = 0; - compl = compl_first_match; - do { - if ((compl->cp_flags & CP_ORIGINAL_TEXT) == 0 - && (compl_leader == NULL - || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { - if (!shown_match_ok) { - if (compl == compl_shown_match || did_find_shown_match) { - // This item is the shown match or this is the - // first displayed item after the shown match. - compl_shown_match = compl; - did_find_shown_match = true; - shown_match_ok = true; - } else { - // Remember this displayed match for when the - // shown match is just below it. - shown_compl = compl; - } - cur = i; - } + assert(compl_match_arraysize >= 0); + compl_match_array = xcalloc((size_t)compl_match_arraysize, sizeof(pumitem_T)); - if (compl->cp_text[CPT_ABBR] != NULL) { - compl_match_array[i].pum_text = - compl->cp_text[CPT_ABBR]; - } else { - compl_match_array[i].pum_text = compl->cp_str; - } - compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND]; - compl_match_array[i].pum_info = compl->cp_text[CPT_INFO]; - if (compl->cp_text[CPT_MENU] != NULL) { - compl_match_array[i++].pum_extra = - compl->cp_text[CPT_MENU]; + // If the current match is the original text don't find the first + // match after it, don't highlight anything. + bool shown_match_ok = match_at_original_text(compl_shown_match); + + compl_T *shown_compl = NULL; + bool did_find_shown_match = false; + int cur = -1; + int i = 0; + compl = compl_first_match; + do { + if (!match_at_original_text(compl) + && (compl_leader == NULL + || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) { + if (!shown_match_ok) { + if (compl == compl_shown_match || did_find_shown_match) { + // This item is the shown match or this is the + // first displayed item after the shown match. + compl_shown_match = compl; + did_find_shown_match = true; + shown_match_ok = true; } else { - compl_match_array[i++].pum_extra = compl->cp_fname; + // Remember this displayed match for when the + // shown match is just below it. + shown_compl = compl; } + cur = i; } - if (compl == compl_shown_match) { - did_find_shown_match = true; + if (compl->cp_text[CPT_ABBR] != NULL) { + compl_match_array[i].pum_text = compl->cp_text[CPT_ABBR]; + } else { + compl_match_array[i].pum_text = compl->cp_str; + } + compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND]; + compl_match_array[i].pum_info = compl->cp_text[CPT_INFO]; + if (compl->cp_text[CPT_MENU] != NULL) { + compl_match_array[i++].pum_extra = compl->cp_text[CPT_MENU]; + } else { + compl_match_array[i++].pum_extra = compl->cp_fname; + } + } - // When the original text is the shown match don't set - // compl_shown_match. - if (compl->cp_flags & CP_ORIGINAL_TEXT) { - shown_match_ok = true; - } + if (compl == compl_shown_match) { + did_find_shown_match = true; - if (!shown_match_ok && shown_compl != NULL) { - // The shown match isn't displayed, set it to the - // previously displayed match. - compl_shown_match = shown_compl; - shown_match_ok = true; - } + // When the original text is the shown match don't set + // compl_shown_match. + if (match_at_original_text(compl)) { + shown_match_ok = true; } - compl = compl->cp_next; - } while (compl != NULL && compl != compl_first_match); - if (!shown_match_ok) { // no displayed match at all - cur = -1; + if (!shown_match_ok && shown_compl != NULL) { + // The shown match isn't displayed, set it to the + // previously displayed match. + compl_shown_match = shown_compl; + shown_match_ok = true; + } } + compl = compl->cp_next; + } while (compl != NULL && !is_first_match(compl)); + + if (!shown_match_ok) { // no displayed match at all + cur = -1; + } + + return cur; +} + +/// Show the popup menu for the list of matches. +/// Also adjusts "compl_shown_match" to an entry that is actually displayed. +void ins_compl_show_pum(void) +{ + if (!pum_wanted() || !pum_enough_matches()) { + return; + } + + // Dirty hard-coded hack: remove any matchparen highlighting. + do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif"); + + // Update the screen before drawing the popup menu over it. + update_screen(); + + int cur = -1; + bool array_changed = false; + + if (compl_match_array == NULL) { + array_changed = true; + // Need to build the popup menu list. + cur = ins_compl_build_pum(); } else { // popup menu already exists, only need to find the current item. - for (i = 0; i < compl_match_arraysize; i++) { + for (int i = 0; i < compl_match_arraysize; i++) { if (compl_match_array[i].pum_text == compl_shown_match->cp_str - || compl_match_array[i].pum_text - == compl_shown_match->cp_text[CPT_ABBR]) { + || compl_match_array[i].pum_text == compl_shown_match->cp_text[CPT_ABBR]) { cur = i; break; } } } + if (compl_match_array == NULL) { + return; + } + // In Replace mode when a $ is displayed at the end of the line only // part of the screen would be updated. We do need to redraw here. dollar_vcol = -1; // Compute the screen column of the start of the completed text. // Use the cursor to get all wrapping and other settings right. - col = curwin->w_cursor.col; + const colnr_T col = curwin->w_cursor.col; curwin->w_cursor.col = compl_col; pum_selected_item = cur; pum_display(compl_match_array, compl_match_arraysize, cur, array_changed, 0); @@ -1129,15 +1289,15 @@ void ins_compl_show_pum(void) #define DICT_FIRST (1) ///< use just first element in "dict" #define DICT_EXACT (2) ///< "dict" is the exact name of a file -/// Add any identifiers that match the given pattern in the list of dictionary -/// files "dict_start" to the list of completions. +/// Add any identifiers that match the given pattern "pat" in the list of +/// dictionary files "dict_start" to the list of completions. /// /// @param flags DICT_FIRST and/or DICT_EXACT /// @param thesaurus Thesaurus completion -static void ins_compl_dictionaries(char_u *dict_start, char_u *pat, int flags, int thesaurus) +static void ins_compl_dictionaries(char *dict_start, char *pat, int flags, int thesaurus) { - char *dict = (char *)dict_start; - char_u *ptr; + char *dict = dict_start; + char *ptr; char *buf; regmatch_T regmatch; char **files; @@ -1168,16 +1328,16 @@ static void ins_compl_dictionaries(char_u *dict_start, char_u *pat, int flags, i // to only match at the start of a line. Otherwise just match the // pattern. Also need to double backslashes. if (ctrl_x_mode_line_or_eval()) { - char_u *pat_esc = vim_strsave_escaped(pat, (char_u *)"\\"); + char *pat_esc = vim_strsave_escaped(pat, "\\"); - size_t len = STRLEN(pat_esc) + 10; + size_t len = strlen(pat_esc) + 10; ptr = xmalloc(len); - vim_snprintf((char *)ptr, len, "^\\s*\\zs\\V%s", pat_esc); - regmatch.regprog = vim_regcomp((char *)ptr, RE_MAGIC); + vim_snprintf(ptr, len, "^\\s*\\zs\\V%s", pat_esc); + regmatch.regprog = vim_regcomp(ptr, RE_MAGIC); xfree(pat_esc); xfree(ptr); } else { - regmatch.regprog = vim_regcomp((char *)pat, p_magic ? RE_MAGIC : 0); + regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0); if (regmatch.regprog == NULL) { goto theend; } @@ -1195,7 +1355,7 @@ static void ins_compl_dictionaries(char_u *dict_start, char_u *pat, int flags, i // backticks (for security, the 'dict' option may have been set in // a modeline). copy_option_part(&dict, buf, LSIZE, ","); - if (!thesaurus && STRCMP(buf, "spell") == 0) { + if (!thesaurus && strcmp(buf, "spell") == 0) { count = -1; } else if (vim_strchr(buf, '`') != NULL || expand_wildcards(1, &buf, &count, &files, @@ -1215,7 +1375,7 @@ static void ins_compl_dictionaries(char_u *dict_start, char_u *pat, int flags, i spell_dump_compl(ptr, regmatch.rm_ic, &dir, 0); } else if (count > 0) { // avoid warning for using "files" uninit ins_compl_files(count, files, thesaurus, flags, - ®match, (char_u *)buf, &dir); + ®match, buf, &dir); if (flags != DICT_EXACT) { FreeWild(count, files); } @@ -1231,22 +1391,70 @@ theend: xfree(buf); } +/// Add all the words in the line "*buf_arg" from the thesaurus file "fname" +/// skipping the word at 'skip_word'. +/// +/// @return OK on success. +static int thesaurus_add_words_in_line(char *fname, char **buf_arg, int dir, const char *skip_word) +{ + int status = OK; + + // Add the other matches on the line + char *ptr = *buf_arg; + while (!got_int) { + // Find start of the next word. Skip white + // space and punctuation. + ptr = find_word_start(ptr); + if (*ptr == NUL || *ptr == NL) { + break; + } + char *wstart = ptr; + + // Find end of the word. + // Japanese words may have characters in + // different classes, only separate words + // with single-byte non-word characters. + while (*ptr != NUL) { + const int l = utfc_ptr2len((const char *)ptr); + + if (l < 2 && !vim_iswordc((uint8_t)(*ptr))) { + break; + } + ptr += l; + } + + // Add the word. Skip the regexp match. + if (wstart != skip_word) { + status = ins_compl_add_infercase(wstart, (int)(ptr - wstart), p_ic, + fname, dir, false); + if (status == FAIL) { + break; + } + } + } + + *buf_arg = ptr; + return status; +} + +/// Process "count" dictionary/thesaurus "files" and add the text matching +/// "regmatch". static void ins_compl_files(int count, char **files, int thesaurus, int flags, regmatch_T *regmatch, - char_u *buf, Direction *dir) + char *buf, Direction *dir) FUNC_ATTR_NONNULL_ARG(2, 7) { - char_u *ptr; + char *ptr; int i; FILE *fp; int add_r; for (i = 0; i < count && !got_int && !compl_interrupted; i++) { fp = os_fopen(files[i], "r"); // open dictionary file - if (flags != DICT_EXACT) { + if (flags != DICT_EXACT && !shortmess(SHM_COMPLETIONSCAN)) { msg_hist_off = true; // reset in msg_trunc_attr() - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Scanning dictionary: %s"), files[i]); - (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); + (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); } if (fp == NULL) { @@ -1255,10 +1463,9 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r // Read dictionary file line by line. // Check each line for a match. - while (!got_int && !compl_interrupted - && !vim_fgets(buf, LSIZE, fp)) { + while (!got_int && !compl_interrupted && !vim_fgets(buf, LSIZE, fp)) { ptr = buf; - while (vim_regexec(regmatch, (char *)buf, (colnr_T)(ptr - buf))) { + while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf))) { ptr = regmatch->startp[0]; if (ctrl_x_mode_line_or_eval()) { ptr = find_line_end(ptr); @@ -1267,40 +1474,11 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r } add_r = ins_compl_add_infercase(regmatch->startp[0], (int)(ptr - regmatch->startp[0]), - p_ic, (char_u *)files[i], *dir, false); + p_ic, files[i], *dir, false); if (thesaurus) { - char_u *wstart; - - // Add the other matches on the line + // For a thesaurus, add all the words in the line ptr = buf; - while (!got_int) { - // Find start of the next word. Skip white - // space and punctuation. - ptr = find_word_start(ptr); - if (*ptr == NUL || *ptr == NL) { - break; - } - wstart = ptr; - - // Find end of the word. - // Japanese words may have characters in - // different classes, only separate words - // with single-byte non-word characters. - while (*ptr != NUL) { - const int l = utfc_ptr2len((char *)ptr); - - if (l < 2 && !vim_iswordc(*ptr)) { - break; - } - ptr += l; - } - - // Add the word. Skip the regexp match. - if (wstart != regmatch->startp[0]) { - add_r = ins_compl_add_infercase(wstart, (int)(ptr - wstart), - p_ic, (char_u *)files[i], *dir, false); - } - } + add_r = thesaurus_add_words_in_line(files[i], &ptr, *dir, regmatch->startp[0]); } if (add_r == OK) { // if dir was BACKWARD then honor it just once @@ -1323,24 +1501,24 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r /// Find the start of the next word. /// Returns a pointer to the first char of the word. Also stops at a NUL. -char_u *find_word_start(char_u *ptr) +char *find_word_start(char *ptr) FUNC_ATTR_PURE { while (*ptr != NUL && *ptr != '\n' && mb_get_class(ptr) <= 1) { - ptr += utfc_ptr2len((char *)ptr); + ptr += utfc_ptr2len(ptr); } return ptr; } /// Find the end of the word. Assumes it starts inside a word. /// Returns a pointer to just after the word. -char_u *find_word_end(char_u *ptr) +char *find_word_end(char *ptr) FUNC_ATTR_PURE { const int start_class = mb_get_class(ptr); if (start_class > 1) { while (*ptr != NUL) { - ptr += utfc_ptr2len((char *)ptr); + ptr += utfc_ptr2len(ptr); if (mb_get_class(ptr) != start_class) { break; } @@ -1350,12 +1528,13 @@ char_u *find_word_end(char_u *ptr) } /// Find the end of the line, omitting CR and NL at the end. -/// Returns a pointer to just after the line. -static char_u *find_line_end(char_u *ptr) +/// +/// @return a pointer to just after the line. +static char *find_line_end(char *ptr) { - char_u *s; + char *s; - s = ptr + STRLEN(ptr); + s = ptr + strlen(ptr); while (s > ptr && (s[-1] == CAR || s[-1] == NL)) { s--; } @@ -1391,12 +1570,13 @@ static void ins_compl_free(void) } tv_clear(&match->cp_user_data); xfree(match); - } while (compl_curr_match != NULL && compl_curr_match != compl_first_match); + } while (compl_curr_match != NULL && !is_first_match(compl_curr_match)); compl_first_match = compl_curr_match = NULL; compl_shown_match = NULL; compl_old_match = NULL; } +/// Reset/clear the completion state. void ins_compl_clear(void) { compl_cont_status = 0; @@ -1450,14 +1630,20 @@ colnr_T ins_compl_col(void) return compl_col; } +/// Return the length in bytes of the text being completed +int ins_compl_len(void) +{ + return compl_length; +} + /// Delete one character before the cursor and show the subset of the matches /// that match the word that is now before the cursor. /// Returns the character to be used, NUL if the work is done and another char /// to be got from the user. int ins_compl_bs(void) { - char_u *line = get_cursor_line_ptr(); - char_u *p = line + curwin->w_cursor.col; + char *line = get_cursor_line_ptr(); + char *p = line + curwin->w_cursor.col; MB_PTR_BACK(line, p); ptrdiff_t p_off = p - line; @@ -1479,18 +1665,18 @@ int ins_compl_bs(void) ins_compl_restart(); } - // ins_compl_restart() calls update_screen(0) which may invalidate the pointer + // ins_compl_restart() calls update_screen() which may invalidate the pointer // TODO(bfredl): get rid of random update_screen() calls deep inside completion logic line = get_cursor_line_ptr(); xfree(compl_leader); - compl_leader = vim_strnsave(line + compl_col, (size_t)(p_off - (ptrdiff_t)compl_col)); + compl_leader = xstrnsave(line + compl_col, (size_t)(p_off - (ptrdiff_t)compl_col)); + ins_compl_new_leader(); if (compl_shown_match != NULL) { // Make sure current match is not a hidden item. compl_curr_match = compl_shown_match; } - return NUL; } @@ -1567,7 +1753,7 @@ void ins_compl_addleader(int c) utf_char2bytes(c, (char *)buf); buf[cc] = NUL; - ins_char_bytes((char_u *)buf, (size_t)cc); + ins_char_bytes(buf, (size_t)cc); } else { ins_char(c); } @@ -1578,8 +1764,8 @@ void ins_compl_addleader(int c) } xfree(compl_leader); - compl_leader = vim_strnsave(get_cursor_line_ptr() + compl_col, - (size_t)(curwin->w_cursor.col - compl_col)); + compl_leader = xstrnsave(get_cursor_line_ptr() + compl_col, + (size_t)(curwin->w_cursor.col - compl_col)); ins_compl_new_leader(); } @@ -1590,7 +1776,7 @@ static void ins_compl_restart(void) // update screen before restart. // so if complete is blocked, // will stay to the last popup menu and reduce flicker - update_screen(0); + update_screen(); // TODO(bfredl): no. ins_compl_free(); compl_started = false; compl_matches = 0; @@ -1599,19 +1785,19 @@ static void ins_compl_restart(void) } /// Set the first match, the original text. -static void ins_compl_set_original_text(char_u *str) +static void ins_compl_set_original_text(char *str) FUNC_ATTR_NONNULL_ALL { // Replace the original text entry. - // The CP_ORIGINAL_TEXT flag is either at the first item or might possibly be - // at the last item for backward completion - if (compl_first_match->cp_flags & CP_ORIGINAL_TEXT) { // safety check + // The CP_ORIGINAL_TEXT flag is either at the first item or might possibly + // be at the last item for backward completion + if (match_at_original_text(compl_first_match)) { // safety check xfree(compl_first_match->cp_str); - compl_first_match->cp_str = vim_strsave(str); + compl_first_match->cp_str = xstrdup(str); } else if (compl_first_match->cp_prev != NULL - && (compl_first_match->cp_prev->cp_flags & CP_ORIGINAL_TEXT)) { + && match_at_original_text(compl_first_match->cp_prev)) { xfree(compl_first_match->cp_prev->cp_str); - compl_first_match->cp_prev->cp_str = vim_strsave(str); + compl_first_match->cp_prev->cp_str = xstrdup(str); } } @@ -1619,37 +1805,280 @@ static void ins_compl_set_original_text(char_u *str) /// matches. void ins_compl_addfrommatch(void) { - char_u *p; + char *p; int len = (int)curwin->w_cursor.col - (int)compl_col; int c; compl_T *cp; assert(compl_shown_match != NULL); p = compl_shown_match->cp_str; - if ((int)STRLEN(p) <= len) { // the match is too short + if ((int)strlen(p) <= len) { // the match is too short // When still at the original match use the first entry that matches // the leader. - if (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) { - p = NULL; - for (cp = compl_shown_match->cp_next; cp != NULL - && cp != compl_first_match; cp = cp->cp_next) { - if (compl_leader == NULL - || ins_compl_equal(cp, compl_leader, STRLEN(compl_leader))) { - p = cp->cp_str; - break; - } - } - if (p == NULL || (int)STRLEN(p) <= len) { - return; + if (!match_at_original_text(compl_shown_match)) { + return; + } + + p = NULL; + for (cp = compl_shown_match->cp_next; cp != NULL + && !is_first_match(cp); cp = cp->cp_next) { + if (compl_leader == NULL + || ins_compl_equal(cp, compl_leader, strlen(compl_leader))) { + p = cp->cp_str; + break; } - } else { + } + if (p == NULL || (int)strlen(p) <= len) { return; } } p += len; - c = utf_ptr2char((char *)p); + c = utf_ptr2char(p); ins_compl_addleader(c); } +/// Set the CTRL-X completion mode based on the key "c" typed after a CTRL-X. +/// Uses the global variables: ctrl_x_mode, edit_submode, edit_submode_pre, +/// compl_cont_mode and compl_cont_status. +/// +/// @return true when the character is not to be inserted. +static bool set_ctrl_x_mode(const int c) +{ + bool retval = false; + + switch (c) { + case Ctrl_E: + case Ctrl_Y: + // scroll the window one line up or down + ctrl_x_mode = CTRL_X_SCROLL; + if (!(State & REPLACE_FLAG)) { + edit_submode = _(" (insert) Scroll (^E/^Y)"); + } else { + edit_submode = _(" (replace) Scroll (^E/^Y)"); + } + edit_submode_pre = NULL; + showmode(); + break; + case Ctrl_L: + // complete whole line + ctrl_x_mode = CTRL_X_WHOLE_LINE; + break; + case Ctrl_F: + // complete filenames + ctrl_x_mode = CTRL_X_FILES; + break; + case Ctrl_K: + // complete words from a dictionary + ctrl_x_mode = CTRL_X_DICTIONARY; + break; + case Ctrl_R: + // Register insertion without exiting CTRL-X mode + // Simply allow ^R to happen without affecting ^X mode + break; + case Ctrl_T: + // complete words from a thesaurus + ctrl_x_mode = CTRL_X_THESAURUS; + break; + case Ctrl_U: + // user defined completion + ctrl_x_mode = CTRL_X_FUNCTION; + break; + case Ctrl_O: + // omni completion + ctrl_x_mode = CTRL_X_OMNI; + break; + case 's': + case Ctrl_S: + // complete spelling suggestions + ctrl_x_mode = CTRL_X_SPELL; + emsg_off++; // Avoid getting the E756 error twice. + spell_back_to_badword(); + emsg_off--; + break; + case Ctrl_RSB: + // complete tag names + ctrl_x_mode = CTRL_X_TAGS; + break; + case Ctrl_I: + case K_S_TAB: + // complete keywords from included files + ctrl_x_mode = CTRL_X_PATH_PATTERNS; + break; + case Ctrl_D: + // complete definitions from included files + ctrl_x_mode = CTRL_X_PATH_DEFINES; + break; + case Ctrl_V: + case Ctrl_Q: + // complete vim commands + ctrl_x_mode = CTRL_X_CMDLINE; + break; + case Ctrl_Z: + // stop completion + ctrl_x_mode = CTRL_X_NORMAL; + edit_submode = NULL; + showmode(); + retval = true; + break; + case Ctrl_P: + case Ctrl_N: + // ^X^P means LOCAL expansion if nothing interrupted (eg we + // just started ^X mode, or there were enough ^X's to cancel + // the previous mode, say ^X^F^X^X^P or ^P^X^X^X^P, see below) + // do normal expansion when interrupting a different mode (say + // ^X^F^X^P or ^P^X^X^P, see below) + // nothing changes if interrupting mode 0, (eg, the flag + // doesn't change when going to ADDING mode -- Acevedo + if (!(compl_cont_status & CONT_INTRPT)) { + compl_cont_status |= CONT_LOCAL; + } else if (compl_cont_mode != 0) { + compl_cont_status &= ~CONT_LOCAL; + } + FALLTHROUGH; + default: + // If we have typed at least 2 ^X's... for modes != 0, we set + // compl_cont_status = 0 (eg, as if we had just started ^X + // mode). + // For mode 0, we set "compl_cont_mode" to an impossible + // value, in both cases ^X^X can be used to restart the same + // mode (avoiding ADDING mode). + // Undocumented feature: In a mode != 0 ^X^P and ^X^X^P start + // 'complete' and local ^P expansions respectively. + // In mode 0 an extra ^X is needed since ^X^P goes to ADDING + // mode -- Acevedo + if (c == Ctrl_X) { + if (compl_cont_mode != 0) { + compl_cont_status = 0; + } else { + compl_cont_mode = CTRL_X_NOT_DEFINED_YET; + } + } + ctrl_x_mode = CTRL_X_NORMAL; + edit_submode = NULL; + showmode(); + break; + } + + return retval; +} + +/// Stop insert completion mode +static bool ins_compl_stop(const int c, const int prev_mode, bool retval) +{ + // Get here when we have finished typing a sequence of ^N and + // ^P or other completion characters in CTRL-X mode. Free up + // memory that was used, and make sure we can redo the insert. + if (compl_curr_match != NULL || compl_leader != NULL || c == Ctrl_E) { + // If any of the original typed text has been changed, eg when + // ignorecase is set, we must add back-spaces to the redo + // buffer. We add as few as necessary to delete just the part + // of the original text that has changed. + // When using the longest match, edited the match or used + // CTRL-E then don't use the current match. + char *ptr; + if (compl_curr_match != NULL && compl_used_match && c != Ctrl_E) { + ptr = compl_curr_match->cp_str; + } else { + ptr = NULL; + } + ins_compl_fixRedoBufForLeader(ptr); + } + + bool want_cindent = (get_can_cindent() && cindent_on()); + + // When completing whole lines: fix indent for 'cindent'. + // Otherwise, break line if it's too long. + if (compl_cont_mode == CTRL_X_WHOLE_LINE) { + // re-indent the current line + if (want_cindent) { + do_c_expr_indent(); + want_cindent = false; // don't do it again + } + } else { + const int prev_col = curwin->w_cursor.col; + + // put the cursor on the last char, for 'tw' formatting + if (prev_col > 0) { + dec_cursor(); + } + + // only format when something was inserted + if (!arrow_used && !ins_need_undo_get() && c != Ctrl_E) { + insertchar(NUL, 0, -1); + } + + if (prev_col > 0 + && get_cursor_line_ptr()[curwin->w_cursor.col] != NUL) { + inc_cursor(); + } + } + + // If the popup menu is displayed pressing CTRL-Y means accepting + // the selection without inserting anything. When + // compl_enter_selects is set the Enter key does the same. + if ((c == Ctrl_Y || (compl_enter_selects + && (c == CAR || c == K_KENTER || c == NL))) + && pum_visible()) { + retval = true; + } + + // CTRL-E means completion is Ended, go back to the typed text. + // but only do this, if the Popup is still visible + if (c == Ctrl_E) { + ins_compl_delete(); + char *p = NULL; + if (compl_leader != NULL) { + p = compl_leader; + } else if (compl_first_match != NULL) { + p = compl_orig_text; + } + if (p != NULL) { + const int compl_len = get_compl_len(); + const int len = (int)strlen(p); + if (len > compl_len) { + ins_bytes_len(p + compl_len, (size_t)(len - compl_len)); + } + } + retval = true; + } + + auto_format(false, true); + + // Trigger the CompleteDonePre event to give scripts a chance to + // act upon the completion before clearing the info, and restore + // ctrl_x_mode, so that complete_info() can be used. + ctrl_x_mode = prev_mode; + ins_apply_autocmds(EVENT_COMPLETEDONEPRE); + + ins_compl_free(); + compl_started = false; + compl_matches = 0; + if (!shortmess(SHM_COMPLETIONMENU)) { + msg_clr_cmdline(); // necessary for "noshowmode" + } + ctrl_x_mode = CTRL_X_NORMAL; + compl_enter_selects = false; + if (edit_submode != NULL) { + edit_submode = NULL; + showmode(); + } + + if (c == Ctrl_C && cmdwin_type != 0) { + // Avoid the popup menu remains displayed when leaving the + // command line window. + update_screen(); + } + + // Indent now if a key was typed that is in 'cinkeys'. + if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0))) { + do_c_expr_indent(); + } + // Trigger the CompleteDone event to give scripts a chance to act + // upon the end of completion. + ins_apply_autocmds(EVENT_COMPLETEDONE); + + return retval; +} + /// Prepare for Insert mode completion, or stop it. /// Called just after typing a character in Insert mode. /// @@ -1658,7 +2087,6 @@ void ins_compl_addfrommatch(void) /// @return true when the character is not to be inserted; bool ins_compl_prep(int c) { - char_u *ptr; bool retval = false; const int prev_mode = ctrl_x_mode; @@ -1698,111 +2126,14 @@ bool ins_compl_prep(int c) // Set "compl_get_longest" when finding the first matches. if (ctrl_x_mode_not_defined_yet() || (ctrl_x_mode_normal() && !compl_started)) { - compl_get_longest = (strstr((char *)p_cot, "longest") != NULL); + compl_get_longest = compl_longest; compl_used_match = true; } if (ctrl_x_mode_not_defined_yet()) { // We have just typed CTRL-X and aren't quite sure which CTRL-X mode // it will be yet. Now we decide. - switch (c) { - case Ctrl_E: - case Ctrl_Y: - ctrl_x_mode = CTRL_X_SCROLL; - if (!(State & REPLACE_FLAG)) { - edit_submode = (char_u *)_(" (insert) Scroll (^E/^Y)"); - } else { - edit_submode = (char_u *)_(" (replace) Scroll (^E/^Y)"); - } - edit_submode_pre = NULL; - showmode(); - break; - case Ctrl_L: - ctrl_x_mode = CTRL_X_WHOLE_LINE; - break; - case Ctrl_F: - ctrl_x_mode = CTRL_X_FILES; - break; - case Ctrl_K: - ctrl_x_mode = CTRL_X_DICTIONARY; - break; - case Ctrl_R: - // Simply allow ^R to happen without affecting ^X mode - break; - case Ctrl_T: - ctrl_x_mode = CTRL_X_THESAURUS; - break; - case Ctrl_U: - ctrl_x_mode = CTRL_X_FUNCTION; - break; - case Ctrl_O: - ctrl_x_mode = CTRL_X_OMNI; - break; - case 's': - case Ctrl_S: - ctrl_x_mode = CTRL_X_SPELL; - emsg_off++; // Avoid getting the E756 error twice. - spell_back_to_badword(); - emsg_off--; - break; - case Ctrl_RSB: - ctrl_x_mode = CTRL_X_TAGS; - break; - case Ctrl_I: - case K_S_TAB: - ctrl_x_mode = CTRL_X_PATH_PATTERNS; - break; - case Ctrl_D: - ctrl_x_mode = CTRL_X_PATH_DEFINES; - break; - case Ctrl_V: - case Ctrl_Q: - ctrl_x_mode = CTRL_X_CMDLINE; - break; - case Ctrl_Z: - ctrl_x_mode = CTRL_X_NORMAL; - edit_submode = NULL; - showmode(); - retval = true; - break; - case Ctrl_P: - case Ctrl_N: - // ^X^P means LOCAL expansion if nothing interrupted (eg we - // just started ^X mode, or there were enough ^X's to cancel - // the previous mode, say ^X^F^X^X^P or ^P^X^X^X^P, see below) - // do normal expansion when interrupting a different mode (say - // ^X^F^X^P or ^P^X^X^P, see below) - // nothing changes if interrupting mode 0, (eg, the flag - // doesn't change when going to ADDING mode -- Acevedo - if (!(compl_cont_status & CONT_INTRPT)) { - compl_cont_status |= CONT_LOCAL; - } else if (compl_cont_mode != 0) { - compl_cont_status &= ~CONT_LOCAL; - } - FALLTHROUGH; - default: - // If we have typed at least 2 ^X's... for modes != 0, we set - // compl_cont_status = 0 (eg, as if we had just started ^X - // mode). - // For mode 0, we set "compl_cont_mode" to an impossible - // value, in both cases ^X^X can be used to restart the same - // mode (avoiding ADDING mode). - // Undocumented feature: In a mode != 0 ^X^P and ^X^X^P start - // 'complete' and local ^P expansions respectively. - // In mode 0 an extra ^X is needed since ^X^P goes to ADDING - // mode -- Acevedo - if (c == Ctrl_X) { - if (compl_cont_mode != 0) { - compl_cont_status = 0; - } else { - compl_cont_mode = CTRL_X_NOT_DEFINED_YET; - } - } - ctrl_x_mode = CTRL_X_NORMAL; - edit_submode = NULL; - showmode(); - break; - } + retval = set_ctrl_x_mode(c); } else if (ctrl_x_mode_not_default()) { // We're already in CTRL-X mode, do we stay in it? if (!vim_is_ctrl_x_key(c)) { @@ -1827,107 +2158,7 @@ bool ins_compl_prep(int c) && c != Ctrl_R && !ins_compl_pum_key(c)) || ctrl_x_mode == CTRL_X_FINISHED) { - // Get here when we have finished typing a sequence of ^N and - // ^P or other completion characters in CTRL-X mode. Free up - // memory that was used, and make sure we can redo the insert. - if (compl_curr_match != NULL || compl_leader != NULL || c == Ctrl_E) { - // If any of the original typed text has been changed, eg when - // ignorecase is set, we must add back-spaces to the redo - // buffer. We add as few as necessary to delete just the part - // of the original text that has changed. - // When using the longest match, edited the match or used - // CTRL-E then don't use the current match. - if (compl_curr_match != NULL && compl_used_match && c != Ctrl_E) { - ptr = compl_curr_match->cp_str; - } else { - ptr = NULL; - } - ins_compl_fixRedoBufForLeader(ptr); - } - - bool want_cindent = (can_cindent_get() && cindent_on()); - - // When completing whole lines: fix indent for 'cindent'. - // Otherwise, break line if it's too long. - if (compl_cont_mode == CTRL_X_WHOLE_LINE) { - // re-indent the current line - if (want_cindent) { - do_c_expr_indent(); - want_cindent = false; // don't do it again - } - } else { - int prev_col = curwin->w_cursor.col; - - // put the cursor on the last char, for 'tw' formatting - if (prev_col > 0) { - dec_cursor(); - } - - if (!arrow_used && !ins_need_undo_get() && c != Ctrl_E) { - insertchar(NUL, 0, -1); - } - - if (prev_col > 0 - && get_cursor_line_ptr()[curwin->w_cursor.col] != NUL) { - inc_cursor(); - } - } - - // If the popup menu is displayed pressing CTRL-Y means accepting - // the selection without inserting anything. When - // compl_enter_selects is set the Enter key does the same. - if ((c == Ctrl_Y || (compl_enter_selects - && (c == CAR || c == K_KENTER || c == NL))) - && pum_visible()) { - retval = true; - } - - // CTRL-E means completion is Ended, go back to the typed text. - // but only do this, if the Popup is still visible - if (c == Ctrl_E) { - ins_compl_delete(); - if (compl_leader != NULL) { - ins_bytes(compl_leader + get_compl_len()); - } else if (compl_first_match != NULL) { - ins_bytes(compl_orig_text + get_compl_len()); - } - retval = true; - } - - auto_format(false, true); - - // Trigger the CompleteDonePre event to give scripts a chance to - // act upon the completion before clearing the info, and restore - // ctrl_x_mode, so that complete_info() can be used. - ctrl_x_mode = prev_mode; - ins_apply_autocmds(EVENT_COMPLETEDONEPRE); - - ins_compl_free(); - compl_started = false; - compl_matches = 0; - if (!shortmess(SHM_COMPLETIONMENU)) { - msg_clr_cmdline(); // necessary for "noshowmode" - } - ctrl_x_mode = CTRL_X_NORMAL; - compl_enter_selects = false; - if (edit_submode != NULL) { - edit_submode = NULL; - showmode(); - } - - // Avoid the popup menu remains displayed when leaving the - // command line window. - if (c == Ctrl_C && cmdwin_type != 0) { - update_screen(0); - } - - // Indent now if a key was typed that is in 'cinkeys'. - if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0))) { - do_c_expr_indent(); - } - // Trigger the CompleteDone event to give scripts a chance to act - // upon the end of completion. - ins_apply_autocmds(EVENT_COMPLETEDONE); + retval = ins_compl_stop(c, prev_mode, retval); } } else if (ctrl_x_mode == CTRL_X_LOCAL_MSG) { // Trigger the CompleteDone event to give scripts a chance to act @@ -1950,11 +2181,11 @@ bool ins_compl_prep(int c) /// Fix the redo buffer for the completion leader replacing some of the typed /// text. This inserts backspaces and appends the changed text. /// "ptr" is the known leader text or NUL. -static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg) +static void ins_compl_fixRedoBufForLeader(char *ptr_arg) { int len; - char_u *p; - char_u *ptr = ptr_arg; + char *p; + char *ptr = ptr_arg; if (ptr == NULL) { if (compl_leader != NULL) { @@ -1975,7 +2206,7 @@ static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg) } else { len = 0; } - AppendToRedobuffLit((char *)ptr + len, -1); + AppendToRedobuffLit(ptr + len, -1); } /// Loops through the list of windows, loaded-buffers or non-loaded-buffers @@ -2010,8 +2241,94 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag) return buf; } -/// Get the user-defined completion function name for completion 'type' -static char_u *get_complete_funcname(int type) +static Callback cfu_cb; ///< 'completefunc' callback function +static Callback ofu_cb; ///< 'omnifunc' callback function +static Callback tsrfu_cb; ///< 'thesaurusfunc' callback function + +/// Copy a global callback function to a buffer local callback. +static void copy_global_to_buflocal_cb(Callback *globcb, Callback *bufcb) +{ + callback_free(bufcb); + if (globcb->type != kCallbackNone) { + callback_copy(bufcb, globcb); + } +} + +/// Parse the 'completefunc' option value and set the callback function. +/// Invoked when the 'completefunc' option is set. The option value can be a +/// name of a function (string), or function(<name>) or funcref(<name>) or a +/// lambda expression. +void set_completefunc_option(char **errmsg) +{ + if (option_set_callback_func(curbuf->b_p_cfu, &cfu_cb) == FAIL) { + *errmsg = e_invarg; + return; + } + + set_buflocal_cfu_callback(curbuf); +} + +/// Copy the global 'completefunc' callback function to the buffer-local +/// 'completefunc' callback for "buf". +void set_buflocal_cfu_callback(buf_T *buf) +{ + copy_global_to_buflocal_cb(&cfu_cb, &buf->b_cfu_cb); +} + +/// Parse the 'omnifunc' option value and set the callback function. +/// Invoked when the 'omnifunc' option is set. The option value can be a +/// name of a function (string), or function(<name>) or funcref(<name>) or a +/// lambda expression. +void set_omnifunc_option(buf_T *buf, char **errmsg) +{ + if (option_set_callback_func(buf->b_p_ofu, &ofu_cb) == FAIL) { + *errmsg = e_invarg; + return; + } + set_buflocal_ofu_callback(buf); +} + +/// Copy the global 'omnifunc' callback function to the buffer-local 'omnifunc' +/// callback for "buf". +void set_buflocal_ofu_callback(buf_T *buf) +{ + copy_global_to_buflocal_cb(&ofu_cb, &buf->b_ofu_cb); +} + +/// Parse the 'thesaurusfunc' option value and set the callback function. +/// Invoked when the 'thesaurusfunc' option is set. The option value can be a +/// name of a function (string), or function(<name>) or funcref(<name>) or a +/// lambda expression. +void set_thesaurusfunc_option(char **errmsg) +{ + int retval; + + if (*curbuf->b_p_tsrfu != NUL) { + // buffer-local option set + retval = option_set_callback_func(curbuf->b_p_tsrfu, &curbuf->b_tsrfu_cb); + } else { + // global option set + retval = option_set_callback_func(p_tsrfu, &tsrfu_cb); + } + + if (retval == FAIL) { + *errmsg = e_invarg; + } +} + +/// Mark the global 'completefunc' 'omnifunc' and 'thesaurusfunc' callbacks with +/// "copyID" so that they are not garbage collected. +bool set_ref_in_insexpand_funcs(int copyID) +{ + bool abort = set_ref_in_callback(&cfu_cb, copyID, NULL, NULL); + abort = abort || set_ref_in_callback(&ofu_cb, copyID, NULL, NULL); + abort = abort || set_ref_in_callback(&tsrfu_cb, copyID, NULL, NULL); + + return abort; +} + +/// Get the user-defined completion function name for completion "type" +static char *get_complete_funcname(int type) { switch (type) { case CTRL_X_FUNCTION: @@ -2021,19 +2338,32 @@ static char_u *get_complete_funcname(int type) case CTRL_X_THESAURUS: return *curbuf->b_p_tsrfu == NUL ? p_tsrfu : curbuf->b_p_tsrfu; default: - return (char_u *)""; + return ""; + } +} + +/// Get the callback to use for insert mode completion. +static Callback *get_insert_callback(int type) +{ + if (type == CTRL_X_FUNCTION) { + return &curbuf->b_cfu_cb; + } + if (type == CTRL_X_OMNI) { + return &curbuf->b_ofu_cb; } + // CTRL_X_THESAURUS + return (*curbuf->b_p_tsrfu != NUL) ? &curbuf->b_tsrfu_cb : &tsrfu_cb; } -/// Execute user defined complete function 'completefunc' or 'omnifunc', and -/// get matches in "matches". +/// Execute user defined complete function 'completefunc', 'omnifunc' or +/// 'thesaurusfunc', and get matches in "matches". /// -/// @param type CTRL_X_OMNI or CTRL_X_FUNCTION -static void expand_by_function(int type, char_u *base) +/// @param type either CTRL_X_OMNI or CTRL_X_FUNCTION or CTRL_X_THESAURUS +static void expand_by_function(int type, char *base) { list_T *matchlist = NULL; dict_T *matchdict = NULL; - char_u *funcname; + char *funcname; pos_T pos; typval_T rettv; const int save_State = State; @@ -2050,7 +2380,7 @@ static void expand_by_function(int type, char_u *base) args[1].v_type = VAR_STRING; args[2].v_type = VAR_UNKNOWN; args[0].vval.v_number = 0; - args[1].vval.v_string = base != NULL ? (char *)base : ""; + args[1].vval.v_string = base != NULL ? base : ""; pos = curwin->w_cursor; // Lock the text to avoid weird things from happening. Also disallow @@ -2058,8 +2388,10 @@ static void expand_by_function(int type, char_u *base) // Insert mode in another buffer. textlock++; + Callback *cb = get_insert_callback(type); + // Call a function, which returns a list or dict. - if (call_vim_function((char *)funcname, 2, args, &rettv) == OK) { + if (callback_call(cb, 2, args, &rettv)) { switch (rettv.v_type) { case VAR_LIST: matchlist = rettv.vval.v_list; @@ -2147,10 +2479,15 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast) for (size_t i = 0; i < CPT_COUNT; i++) { xfree(cptext[i]); } + tv_clear(&user_data); return FAIL; } - return ins_compl_add((char_u *)word, -1, NULL, - (char_u **)cptext, true, &user_data, dir, flags, dup); + int status = ins_compl_add((char *)word, -1, NULL, cptext, true, + &user_data, dir, flags, dup); + if (status != OK) { + tv_clear(&user_data); + } + return status; } /// Add completions from a list. @@ -2207,6 +2544,7 @@ static void set_completion(colnr_T startcol, list_T *list) } ins_compl_clear(); ins_compl_free(); + compl_get_longest = compl_longest; compl_direction = FORWARD; if (startcol > curwin->w_cursor.col) { @@ -2215,8 +2553,8 @@ static void set_completion(colnr_T startcol, list_T *list) compl_col = startcol; compl_length = (int)curwin->w_cursor.col - (int)startcol; // compl_pattern doesn't need to be set - compl_orig_text = vim_strnsave(get_cursor_line_ptr() + compl_col, - (size_t)compl_length); + compl_orig_text = xstrnsave(get_cursor_line_ptr() + compl_col, + (size_t)compl_length); if (p_ic) { flags |= CP_ICASE; } @@ -2236,9 +2574,10 @@ static void set_completion(colnr_T startcol, list_T *list) int save_w_leftcol = curwin->w_leftcol; compl_curr_match = compl_first_match; - if (compl_no_insert || compl_no_select) { + bool no_select = compl_no_select || compl_longest; + if (compl_no_insert || no_select) { ins_complete(K_DOWN, false); - if (compl_no_select) { + if (no_select) { ins_complete(K_UP, false); } } else { @@ -2256,7 +2595,7 @@ static void set_completion(colnr_T startcol, list_T *list) } /// "complete()" function -void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_complete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if ((State & MODE_INSERT) == 0) { emsg(_("E785: complete() can only be used in Insert mode")); @@ -2280,13 +2619,13 @@ void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "complete_add()" function -void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_complete_add(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0, false); } /// "complete_check()" function -void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_complete_check(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int saved = RedrawingDisabled; @@ -2297,26 +2636,27 @@ void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// Return Insert completion mode name string -static char_u *ins_compl_mode(void) +static char *ins_compl_mode(void) { if (ctrl_x_mode_not_defined_yet() || ctrl_x_mode_scroll() || compl_started) { - return (char_u *)ctrl_x_mode_names[ctrl_x_mode & ~CTRL_X_WANT_IDENT]; + return ctrl_x_mode_names[ctrl_x_mode & ~CTRL_X_WANT_IDENT]; } - return (char_u *)""; + return ""; } +/// Assign the sequence number to all the completion matches which don't have +/// one assigned yet. static void ins_compl_update_sequence_numbers(void) { int number = 0; compl_T *match; - if (compl_direction == FORWARD) { + if (compl_dir_forward()) { // search backwards for the first valid (!= -1) number. // This should normally succeed already at the first loop // cycle, so it's fast! for (match = compl_curr_match->cp_prev; - match != NULL && match != compl_first_match; - match = match->cp_prev) { + match != NULL && !is_first_match(match); match = match->cp_prev) { if (match->cp_number != -1) { number = match->cp_number; break; @@ -2336,8 +2676,7 @@ static void ins_compl_update_sequence_numbers(void) // number. This should normally succeed already at the // first loop cycle, so it's fast! for (match = compl_curr_match->cp_next; - match != NULL && match != compl_first_match; - match = match->cp_next) { + match != NULL && !is_first_match(match); match = match->cp_next) { if (match->cp_number != -1) { number = match->cp_number; break; @@ -2375,15 +2714,15 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) ; item = TV_LIST_ITEM_NEXT(what_list, item)) { const char *what = tv_get_string(TV_LIST_ITEM_TV(item)); - if (STRCMP(what, "mode") == 0) { + if (strcmp(what, "mode") == 0) { what_flag |= CI_WHAT_MODE; - } else if (STRCMP(what, "pum_visible") == 0) { + } else if (strcmp(what, "pum_visible") == 0) { what_flag |= CI_WHAT_PUM_VISIBLE; - } else if (STRCMP(what, "items") == 0) { + } else if (strcmp(what, "items") == 0) { what_flag |= CI_WHAT_ITEMS; - } else if (STRCMP(what, "selected") == 0) { + } else if (strcmp(what, "selected") == 0) { what_flag |= CI_WHAT_SELECTED; - } else if (STRCMP(what, "inserted") == 0) { + } else if (strcmp(what, "inserted") == 0) { what_flag |= CI_WHAT_INSERTED; } } @@ -2391,8 +2730,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) int ret = OK; if (what_flag & CI_WHAT_MODE) { - ret = tv_dict_add_str(retdict, S_LEN("mode"), - (char *)ins_compl_mode()); + ret = tv_dict_add_str(retdict, S_LEN("mode"), ins_compl_mode()); } if (ret == OK && (what_flag & CI_WHAT_PUM_VISIBLE)) { @@ -2406,7 +2744,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) if (ret == OK && compl_first_match != NULL) { compl_T *match = compl_first_match; do { - if (!(match->cp_flags & CP_ORIGINAL_TEXT)) { + if (!match_at_original_text(match)) { dict_T *di = tv_dict_alloc(); tv_list_append_dict(li, di); @@ -2416,13 +2754,14 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) tv_dict_add_str(di, S_LEN("kind"), EMPTY_IF_NULL(match->cp_text[CPT_KIND])); tv_dict_add_str(di, S_LEN("info"), EMPTY_IF_NULL(match->cp_text[CPT_INFO])); if (match->cp_user_data.v_type == VAR_UNKNOWN) { + // Add an empty string for backwards compatibility tv_dict_add_str(di, S_LEN("user_data"), ""); } else { tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data); } } match = match->cp_next; - } while (match != NULL && match != compl_first_match); + } while (match != NULL && !is_first_match(match)); } } @@ -2441,7 +2780,7 @@ static void get_complete_info(list_T *what_list, dict_T *retdict) } /// "complete_info()" function -void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_complete_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); @@ -2464,6 +2803,485 @@ static bool thesaurus_func_complete(int type) && (*curbuf->b_p_tsrfu != NUL || *p_tsrfu != NUL); } +/// Return value of process_next_cpt_value() +enum { + INS_COMPL_CPT_OK = 1, + INS_COMPL_CPT_CONT, + INS_COMPL_CPT_END, +}; + +/// Process the next 'complete' option value in st->e_cpt. +/// +/// If successful, the arguments are set as below: +/// st->cpt - pointer to the next option value in "st->cpt" +/// compl_type_arg - type of insert mode completion to use +/// st->found_all - all matches of this type are found +/// st->ins_buf - search for completions in this buffer +/// st->first_match_pos - position of the first completion match +/// st->last_match_pos - position of the last completion match +/// st->set_match_pos - true if the first match position should be saved to +/// avoid loops after the search wraps around. +/// st->dict - name of the dictionary or thesaurus file to search +/// st->dict_f - flag specifying whether "dict" is an exact file name or not +/// +/// @return INS_COMPL_CPT_OK if the next value is processed successfully. +/// INS_COMPL_CPT_CONT to skip the current completion source matching +/// the "st->e_cpt" option value and process the next matching source. +/// INS_COMPL_CPT_END if all the values in "st->e_cpt" are processed. +static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_arg, + pos_T *start_match_pos) +{ + int compl_type = -1; + int status = INS_COMPL_CPT_OK; + + st->found_all = false; + + while (*st->e_cpt == ',' || *st->e_cpt == ' ') { + st->e_cpt++; + } + + if (*st->e_cpt == '.' && !curbuf->b_scanned) { + st->ins_buf = curbuf; + st->first_match_pos = *start_match_pos; + // Move the cursor back one character so that ^N can match the + // word immediately after the cursor. + if (ctrl_x_mode_normal() && dec(&st->first_match_pos) < 0) { + // Move the cursor to after the last character in the + // buffer, so that word at start of buffer is found + // correctly. + st->first_match_pos.lnum = st->ins_buf->b_ml.ml_line_count; + st->first_match_pos.col = (colnr_T)strlen(ml_get(st->first_match_pos.lnum)); + } + st->last_match_pos = st->first_match_pos; + compl_type = 0; + + // Remember the first match so that the loop stops when we + // wrap and come back there a second time. + st->set_match_pos = true; + } else if (vim_strchr("buwU", (uint8_t)(*st->e_cpt)) != NULL + && (st->ins_buf = ins_compl_next_buf(st->ins_buf, *st->e_cpt)) != curbuf) { + // Scan a buffer, but not the current one. + if (st->ins_buf->b_ml.ml_mfp != NULL) { // loaded buffer + compl_started = true; + st->first_match_pos.col = st->last_match_pos.col = 0; + st->first_match_pos.lnum = st->ins_buf->b_ml.ml_line_count + 1; + st->last_match_pos.lnum = 0; + compl_type = 0; + } else { // unloaded buffer, scan like dictionary + st->found_all = true; + if (st->ins_buf->b_fname == NULL) { + status = INS_COMPL_CPT_CONT; + goto done; + } + compl_type = CTRL_X_DICTIONARY; + st->dict = st->ins_buf->b_fname; + st->dict_f = DICT_EXACT; + } + if (!shortmess(SHM_COMPLETIONSCAN)) { + msg_hist_off = true; // reset in msg_trunc_attr() + vim_snprintf(IObuff, IOSIZE, _("Scanning: %s"), + st->ins_buf->b_fname == NULL + ? buf_spname(st->ins_buf) + : st->ins_buf->b_sfname == NULL + ? st->ins_buf->b_fname + : st->ins_buf->b_sfname); + (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); + } + } else if (*st->e_cpt == NUL) { + status = INS_COMPL_CPT_END; + } else { + if (ctrl_x_mode_line_or_eval()) { + compl_type = -1; + } else if (*st->e_cpt == 'k' || *st->e_cpt == 's') { + if (*st->e_cpt == 'k') { + compl_type = CTRL_X_DICTIONARY; + } else { + compl_type = CTRL_X_THESAURUS; + } + if (*++st->e_cpt != ',' && *st->e_cpt != NUL) { + st->dict = st->e_cpt; + st->dict_f = DICT_FIRST; + } + } else if (*st->e_cpt == 'i') { + compl_type = CTRL_X_PATH_PATTERNS; + } else if (*st->e_cpt == 'd') { + compl_type = CTRL_X_PATH_DEFINES; + } else if (*st->e_cpt == ']' || *st->e_cpt == 't') { + compl_type = CTRL_X_TAGS; + if (!shortmess(SHM_COMPLETIONSCAN)) { + msg_hist_off = true; // reset in msg_trunc_attr() + vim_snprintf(IObuff, IOSIZE, "%s", _("Scanning tags.")); + (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); + } + } else { + compl_type = -1; + } + + // in any case e_cpt is advanced to the next entry + (void)copy_option_part(&st->e_cpt, IObuff, IOSIZE, ","); + + st->found_all = true; + if (compl_type == -1) { + status = INS_COMPL_CPT_CONT; + } + } + +done: + *compl_type_arg = compl_type; + return status; +} + +/// Get the next set of identifiers or defines matching "compl_pattern" in +/// included files. +static void get_next_include_file_completion(int compl_type) +{ + find_pattern_in_path(compl_pattern, compl_direction, + strlen(compl_pattern), false, false, + ((compl_type == CTRL_X_PATH_DEFINES + && !(compl_cont_status & CONT_SOL)) + ? FIND_DEFINE : FIND_ANY), + 1L, ACTION_EXPAND, 1, MAXLNUM); +} + +/// Get the next set of words matching "compl_pattern" in dictionary or +/// thesaurus files. +static void get_next_dict_tsr_completion(int compl_type, char *dict, int dict_f) +{ + if (thesaurus_func_complete(compl_type)) { + expand_by_function(compl_type, compl_pattern); + } else { + ins_compl_dictionaries(dict != NULL ? dict + : (compl_type == CTRL_X_THESAURUS + ? (*curbuf->b_p_tsr == NUL ? p_tsr : curbuf->b_p_tsr) + : (*curbuf->b_p_dict == + NUL ? p_dict : curbuf->b_p_dict)), + compl_pattern, + dict != NULL ? dict_f : 0, + compl_type == CTRL_X_THESAURUS); + } +} + +/// Get the next set of tag names matching "compl_pattern". +static void get_next_tag_completion(void) +{ + // set p_ic according to p_ic, p_scs and pat for find_tags(). + const int save_p_ic = p_ic; + p_ic = ignorecase(compl_pattern); + + // Find up to TAG_MANY matches. Avoids that an enormous number + // of matches is found when compl_pattern is empty + g_tag_at_cursor = true; + char **matches; + int num_matches; + if (find_tags(compl_pattern, &num_matches, &matches, + TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP + | (ctrl_x_mode_not_default() ? TAG_VERBOSE : 0), + TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) { + ins_compl_add_matches(num_matches, matches, p_ic); + } + g_tag_at_cursor = false; + p_ic = save_p_ic; +} + +/// Get the next set of filename matching "compl_pattern". +static void get_next_filename_completion(void) +{ + char **matches; + int num_matches; + if (expand_wildcards(1, &compl_pattern, &num_matches, &matches, + EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) != OK) { + return; + } + + // May change home directory back to "~". + tilde_replace(compl_pattern, num_matches, matches); +#ifdef BACKSLASH_IN_FILENAME + if (curbuf->b_p_csl[0] != NUL) { + for (int i = 0; i < num_matches; i++) { + char *ptr = matches[i]; + while (*ptr != NUL) { + if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') { + *ptr = '/'; + } else if (curbuf->b_p_csl[0] == 'b' && *ptr == '/') { + *ptr = '\\'; + } + ptr += utfc_ptr2len(ptr); + } + } + } +#endif + ins_compl_add_matches(num_matches, matches, p_fic || p_wic); +} + +/// Get the next set of command-line completions matching "compl_pattern". +static void get_next_cmdline_completion(void) +{ + char **matches; + int num_matches; + if (expand_cmdline(&compl_xp, compl_pattern, + (int)strlen(compl_pattern), + &num_matches, &matches) == EXPAND_OK) { + ins_compl_add_matches(num_matches, matches, false); + } +} + +/// Get the next set of spell suggestions matching "compl_pattern". +static void get_next_spell_completion(linenr_T lnum) +{ + char **matches; + int num_matches = expand_spelling(lnum, compl_pattern, &matches); + if (num_matches > 0) { + ins_compl_add_matches(num_matches, matches, p_ic); + } else { + xfree(matches); + } +} + +/// Return the next word or line from buffer "ins_buf" at position +/// "cur_match_pos" for completion. The length of the match is set in "len". +/// @param ins_buf buffer being scanned +/// @param cur_match_pos current match position +/// @param match_len +/// @param cont_s_ipos next ^X<> will set initial_pos +static char *ins_comp_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_pos, int *match_len, + bool *cont_s_ipos) +{ + *match_len = 0; + char *ptr = ml_get_buf(ins_buf, cur_match_pos->lnum, false) + cur_match_pos->col; + int len; + if (ctrl_x_mode_line_or_eval()) { + if (compl_status_adding()) { + if (cur_match_pos->lnum >= ins_buf->b_ml.ml_line_count) { + return NULL; + } + ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false); + if (!p_paste) { + ptr = skipwhite(ptr); + } + } + len = (int)strlen(ptr); + } else { + char *tmp_ptr = ptr; + + if (compl_status_adding() && compl_length <= (int)strlen(tmp_ptr)) { + tmp_ptr += compl_length; + // Skip if already inside a word. + if (vim_iswordp(tmp_ptr)) { + return NULL; + } + // Find start of next word. + tmp_ptr = find_word_start(tmp_ptr); + } + // Find end of this word. + tmp_ptr = find_word_end(tmp_ptr); + len = (int)(tmp_ptr - ptr); + + if (compl_status_adding() && len == compl_length) { + if (cur_match_pos->lnum < ins_buf->b_ml.ml_line_count) { + // Try next line, if any. the new word will be "join" as if the + // normal command "J" was used. IOSIZE is always greater than + // compl_length, so the next strncpy always works -- Acevedo + strncpy(IObuff, ptr, (size_t)len); // NOLINT(runtime/printf) + ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false); + tmp_ptr = ptr = skipwhite(ptr); + // Find start of next word. + tmp_ptr = find_word_start(tmp_ptr); + // Find end of next word. + tmp_ptr = find_word_end(tmp_ptr); + if (tmp_ptr > ptr) { + if (*ptr != ')' && IObuff[len - 1] != TAB) { + if (IObuff[len - 1] != ' ') { + IObuff[len++] = ' '; + } + // IObuf =~ "\k.* ", thus len >= 2 + if (p_js + && (IObuff[len - 2] == '.' + || IObuff[len - 2] == '?' + || IObuff[len - 2] == '!')) { + IObuff[len++] = ' '; + } + } + // copy as much as possible of the new word + if (tmp_ptr - ptr >= IOSIZE - len) { + tmp_ptr = ptr + IOSIZE - len - 1; + } + xstrlcpy(IObuff + len, ptr, (size_t)(IOSIZE - len)); + len += (int)(tmp_ptr - ptr); + *cont_s_ipos = true; + } + IObuff[len] = NUL; + ptr = IObuff; + } + if (len == compl_length) { + return NULL; + } + } + } + + *match_len = len; + return ptr; +} + +/// Get the next set of words matching "compl_pattern" for default completion(s) +/// (normal ^P/^N and ^X^L). +/// Search for "compl_pattern" in the buffer "st->ins_buf" starting from the +/// position "st->start_pos" in the "compl_direction" direction. If +/// "st->set_match_pos" is true, then set the "st->first_match_pos" and +/// "st->last_match_pos". +/// +/// @return OK if a new next match is found, otherwise FAIL. +static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos) +{ + // If 'infercase' is set, don't use 'smartcase' here + const int save_p_scs = p_scs; + assert(st->ins_buf); + if (st->ins_buf->b_p_inf) { + p_scs = false; + } + + // Buffers other than curbuf are scanned from the beginning or the + // end but never from the middle, thus setting nowrapscan in this + // buffers is a good idea, on the other hand, we always set + // wrapscan for curbuf to avoid missing matches -- Acevedo,Webb + const int save_p_ws = p_ws; + if (st->ins_buf != curbuf) { + p_ws = false; + } else if (*st->e_cpt == '.') { + p_ws = true; + } + bool looped_around = false; + int found_new_match = FAIL; + for (;;) { + bool cont_s_ipos = false; + + msg_silent++; // Don't want messages for wrapscan. + // ctrl_x_mode_line_or_eval() || word-wise search that + // has added a word that was at the beginning of the line. + if (ctrl_x_mode_line_or_eval() || (compl_cont_status & CONT_SOL)) { + found_new_match = search_for_exact_line(st->ins_buf, st->cur_match_pos, + compl_direction, compl_pattern); + } else { + found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos, + NULL, compl_direction, compl_pattern, 1L, + SEARCH_KEEP + SEARCH_NFMSG, RE_LAST, NULL); + } + msg_silent--; + if (!compl_started || st->set_match_pos) { + // set "compl_started" even on fail + compl_started = true; + st->first_match_pos = *st->cur_match_pos; + st->last_match_pos = *st->cur_match_pos; + st->set_match_pos = false; + } else if (st->first_match_pos.lnum == st->last_match_pos.lnum + && st->first_match_pos.col == st->last_match_pos.col) { + found_new_match = FAIL; + } else if (compl_dir_forward() + && (st->prev_match_pos.lnum > st->cur_match_pos->lnum + || (st->prev_match_pos.lnum == st->cur_match_pos->lnum + && st->prev_match_pos.col >= st->cur_match_pos->col))) { + if (looped_around) { + found_new_match = FAIL; + } else { + looped_around = true; + } + } else if (!compl_dir_forward() + && (st->prev_match_pos.lnum < st->cur_match_pos->lnum + || (st->prev_match_pos.lnum == st->cur_match_pos->lnum + && st->prev_match_pos.col <= st->cur_match_pos->col))) { + if (looped_around) { + found_new_match = FAIL; + } else { + looped_around = true; + } + } + st->prev_match_pos = *st->cur_match_pos; + if (found_new_match == FAIL) { + break; + } + + // when ADDING, the text before the cursor matches, skip it + if (compl_status_adding() && st->ins_buf == curbuf + && start_pos->lnum == st->cur_match_pos->lnum + && start_pos->col == st->cur_match_pos->col) { + continue; + } + int len; + char *ptr = ins_comp_get_next_word_or_line(st->ins_buf, st->cur_match_pos, + &len, &cont_s_ipos); + if (ptr == NULL) { + continue; + } + if (ins_compl_add_infercase(ptr, len, p_ic, + st->ins_buf == curbuf ? NULL : st->ins_buf->b_sfname, + 0, cont_s_ipos) != NOTDONE) { + found_new_match = OK; + break; + } + } + p_scs = save_p_scs; + p_ws = save_p_ws; + + return found_new_match; +} + +/// get the next set of completion matches for "type". +/// @return true if a new match is found, otherwise false. +static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_T *ini) +{ + int found_new_match = FAIL; + + switch (type) { + case -1: + break; + case CTRL_X_PATH_PATTERNS: + case CTRL_X_PATH_DEFINES: + get_next_include_file_completion(type); + break; + + case CTRL_X_DICTIONARY: + case CTRL_X_THESAURUS: + get_next_dict_tsr_completion(type, st->dict, st->dict_f); + st->dict = NULL; + break; + + case CTRL_X_TAGS: + get_next_tag_completion(); + break; + + case CTRL_X_FILES: + get_next_filename_completion(); + break; + + case CTRL_X_CMDLINE: + case CTRL_X_CMDLINE_CTRL_X: + get_next_cmdline_completion(); + break; + + case CTRL_X_FUNCTION: + case CTRL_X_OMNI: + expand_by_function(type, compl_pattern); + break; + + case CTRL_X_SPELL: + get_next_spell_completion(st->first_match_pos.lnum); + break; + + default: // normal ^P/^N and ^X^L + found_new_match = get_next_default_completion(st, ini); + if (found_new_match == FAIL && st->ins_buf == curbuf) { + st->found_all = true; + } + } + + // check if compl_curr_match has changed, (e.g. other type of + // expansion added something) + if (type != 0 && compl_curr_match != compl_old_match) { + found_new_match = OK; + } + + return found_new_match; +} + /// Get the next expansion(s), using "compl_pattern". /// The search starts at position "ini" in curbuf and in the direction /// compl_direction. @@ -2473,28 +3291,10 @@ static bool thesaurus_func_complete(int type) /// Return the total number of matches or -1 if still unknown -- Acevedo static int ins_compl_get_exp(pos_T *ini) { - static pos_T first_match_pos; - static pos_T last_match_pos; - static char *e_cpt = ""; // curr. entry in 'complete' - static bool found_all = false; // Found all matches of a - // certain type. - static buf_T *ins_buf = NULL; // buffer being scanned - - pos_T *pos; - char **matches; - int save_p_scs; - bool save_p_ws; - int save_p_ic; + static ins_compl_next_state_T st; int i; - int num_matches; - int len; int found_new_match; int type = ctrl_x_mode; - char_u *ptr; - char_u *dict = NULL; - int dict_f = 0; - bool set_match_pos; - pos_T prev_pos = { 0, 0, 0 }; assert(curbuf != NULL); @@ -2502,111 +3302,34 @@ static int ins_compl_get_exp(pos_T *ini) FOR_ALL_BUFFERS(buf) { buf->b_scanned = false; } - found_all = false; - ins_buf = curbuf; - e_cpt = (compl_cont_status & CONT_LOCAL) ? "." : (char *)curbuf->b_p_cpt; - last_match_pos = first_match_pos = *ini; - } else if (ins_buf != curbuf && !buf_valid(ins_buf)) { - ins_buf = curbuf; // In case the buffer was wiped out. + st.found_all = false; + st.ins_buf = curbuf; + st.e_cpt = (compl_cont_status & CONT_LOCAL) ? "." : curbuf->b_p_cpt; + st.last_match_pos = st.first_match_pos = *ini; + } else if (st.ins_buf != curbuf && !buf_valid(st.ins_buf)) { + st.ins_buf = curbuf; // In case the buffer was wiped out. } - assert(ins_buf != NULL); + assert(st.ins_buf != NULL); compl_old_match = compl_curr_match; // remember the last current match - pos = (compl_direction == FORWARD) ? &last_match_pos : &first_match_pos; + st.cur_match_pos = compl_dir_forward() ? &st.last_match_pos : &st.first_match_pos; // For ^N/^P loop over all the flags/windows/buffers in 'complete' for (;;) { found_new_match = FAIL; - set_match_pos = false; + st.set_match_pos = false; // For ^N/^P pick a new entry from e_cpt if compl_started is off, // or if found_all says this entry is done. For ^X^L only use the // entries from 'complete' that look in loaded buffers. if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) - && (!compl_started || found_all)) { - found_all = false; - while (*e_cpt == ',' || *e_cpt == ' ') { - e_cpt++; - } - if (*e_cpt == '.' && !curbuf->b_scanned) { - ins_buf = curbuf; - first_match_pos = *ini; - // Move the cursor back one character so that ^N can match the - // word immediately after the cursor. - if (ctrl_x_mode_normal() && dec(&first_match_pos) < 0) { - // Move the cursor to after the last character in the - // buffer, so that word at start of buffer is found - // correctly. - first_match_pos.lnum = ins_buf->b_ml.ml_line_count; - first_match_pos.col = (colnr_T)STRLEN(ml_get(first_match_pos.lnum)); - } - last_match_pos = first_match_pos; - type = 0; - - // Remember the first match so that the loop stops when we - // wrap and come back there a second time. - set_match_pos = true; - } else if (vim_strchr("buwU", *e_cpt) != NULL - && (ins_buf = ins_compl_next_buf(ins_buf, *e_cpt)) != curbuf) { - // Scan a buffer, but not the current one. - if (ins_buf->b_ml.ml_mfp != NULL) { // loaded buffer - compl_started = true; - first_match_pos.col = last_match_pos.col = 0; - first_match_pos.lnum = ins_buf->b_ml.ml_line_count + 1; - last_match_pos.lnum = 0; - type = 0; - } else { // unloaded buffer, scan like dictionary - found_all = true; - if (ins_buf->b_fname == NULL) { - continue; - } - type = CTRL_X_DICTIONARY; - dict = (char_u *)ins_buf->b_fname; - dict_f = DICT_EXACT; - } - msg_hist_off = true; // reset in msg_trunc_attr() - vim_snprintf((char *)IObuff, IOSIZE, _("Scanning: %s"), - ins_buf->b_fname == NULL - ? buf_spname(ins_buf) - : ins_buf->b_sfname == NULL - ? ins_buf->b_fname - : ins_buf->b_sfname); - (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); - } else if (*e_cpt == NUL) { + && (!compl_started || st.found_all)) { + int status = process_next_cpt_value(&st, &type, ini); + if (status == INS_COMPL_CPT_END) { break; - } else { - if (ctrl_x_mode_line_or_eval()) { - type = -1; - } else if (*e_cpt == 'k' || *e_cpt == 's') { - if (*e_cpt == 'k') { - type = CTRL_X_DICTIONARY; - } else { - type = CTRL_X_THESAURUS; - } - if (*++e_cpt != ',' && *e_cpt != NUL) { - dict = (char_u *)e_cpt; - dict_f = DICT_FIRST; - } - } else if (*e_cpt == 'i') { - type = CTRL_X_PATH_PATTERNS; - } else if (*e_cpt == 'd') { - type = CTRL_X_PATH_DEFINES; - } else if (*e_cpt == ']' || *e_cpt == 't') { - msg_hist_off = true; // reset in msg_trunc_attr() - type = CTRL_X_TAGS; - vim_snprintf((char *)IObuff, IOSIZE, "%s", _("Scanning tags.")); - (void)msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); - } else { - type = -1; - } - - // in any case e_cpt is advanced to the next entry - (void)copy_option_part(&e_cpt, (char *)IObuff, IOSIZE, ","); - - found_all = true; - if (type == -1) { - continue; - } + } + if (status == INS_COMPL_CPT_CONT) { + continue; } } @@ -2616,271 +3339,12 @@ static int ins_compl_get_exp(pos_T *ini) break; } - switch (type) { - case -1: - break; - case CTRL_X_PATH_PATTERNS: - case CTRL_X_PATH_DEFINES: - find_pattern_in_path((char_u *)compl_pattern, compl_direction, - STRLEN(compl_pattern), false, false, - ((type == CTRL_X_PATH_DEFINES - && !(compl_cont_status & CONT_SOL)) - ? FIND_DEFINE - : FIND_ANY), - 1L, ACTION_EXPAND, 1, MAXLNUM); - break; - - case CTRL_X_DICTIONARY: - case CTRL_X_THESAURUS: - if (thesaurus_func_complete(type)) { - expand_by_function(type, (char_u *)compl_pattern); - } else { - ins_compl_dictionaries(dict != NULL ? dict - : (type == CTRL_X_THESAURUS - ? (*curbuf->b_p_tsr == NUL ? p_tsr : curbuf->b_p_tsr) - : (*curbuf->b_p_dict == - NUL ? p_dict : curbuf->b_p_dict)), - (char_u *)compl_pattern, - dict != NULL ? dict_f : 0, type == CTRL_X_THESAURUS); - } - dict = NULL; - break; - - case CTRL_X_TAGS: - // set p_ic according to p_ic, p_scs and pat for find_tags(). - save_p_ic = p_ic; - p_ic = ignorecase((char_u *)compl_pattern); - - // Find up to TAG_MANY matches. Avoids that an enormous number - // of matches is found when compl_pattern is empty - g_tag_at_cursor = true; - if (find_tags((char_u *)compl_pattern, &num_matches, &matches, - TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP - | (ctrl_x_mode_not_default() ? TAG_VERBOSE : 0), - TAG_MANY, (char_u *)curbuf->b_ffname) == OK && num_matches > 0) { - ins_compl_add_matches(num_matches, matches, p_ic); - } - g_tag_at_cursor = false; - p_ic = save_p_ic; - break; - - case CTRL_X_FILES: - if (expand_wildcards(1, &compl_pattern, &num_matches, &matches, - EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) == OK) { - // May change home directory back to "~". - tilde_replace((char_u *)compl_pattern, num_matches, matches); -#ifdef BACKSLASH_IN_FILENAME - if (curbuf->b_p_csl[0] != NUL) { - for (int i = 0; i < num_matches; i++) { - char_u *ptr = matches[i]; - while (*ptr != NUL) { - if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') { - *ptr = '/'; - } else if (curbuf->b_p_csl[0] == 'b' && *ptr == '/') { - *ptr = '\\'; - } - ptr += utfc_ptr2len(ptr); - } - } - } -#endif - ins_compl_add_matches(num_matches, matches, p_fic || p_wic); - } - break; - - case CTRL_X_CMDLINE: - case CTRL_X_CMDLINE_CTRL_X: - if (expand_cmdline(&compl_xp, (char_u *)compl_pattern, - (int)STRLEN(compl_pattern), - &num_matches, &matches) == EXPAND_OK) { - ins_compl_add_matches(num_matches, matches, false); - } - break; - - case CTRL_X_FUNCTION: - case CTRL_X_OMNI: - expand_by_function(type, (char_u *)compl_pattern); - break; - - case CTRL_X_SPELL: - num_matches = expand_spelling(first_match_pos.lnum, - (char_u *)compl_pattern, &matches); - if (num_matches > 0) { - ins_compl_add_matches(num_matches, matches, p_ic); - } - break; - - default: // normal ^P/^N and ^X^L - // If 'infercase' is set, don't use 'smartcase' here - save_p_scs = p_scs; - assert(ins_buf); - if (ins_buf->b_p_inf) { - p_scs = false; - } - - // Buffers other than curbuf are scanned from the beginning or the - // end but never from the middle, thus setting nowrapscan in this - // buffers is a good idea, on the other hand, we always set - // wrapscan for curbuf to avoid missing matches -- Acevedo,Webb - save_p_ws = p_ws; - if (ins_buf != curbuf) { - p_ws = false; - } else if (*e_cpt == '.') { - p_ws = true; - } - bool looped_around = false; - for (;;) { - bool cont_s_ipos = false; - - msg_silent++; // Don't want messages for wrapscan. - // ctrl_x_mode_line_or_eval() || word-wise search that - // has added a word that was at the beginning of the line. - if (ctrl_x_mode_line_or_eval() - || (compl_cont_status & CONT_SOL)) { - found_new_match = search_for_exact_line(ins_buf, pos, - compl_direction, - (char_u *)compl_pattern); - } else { - found_new_match = searchit(NULL, ins_buf, pos, NULL, - compl_direction, - (char_u *)compl_pattern, 1L, - SEARCH_KEEP + SEARCH_NFMSG, - RE_LAST, NULL); - } - msg_silent--; - if (!compl_started || set_match_pos) { - // set "compl_started" even on fail - compl_started = true; - first_match_pos = *pos; - last_match_pos = *pos; - set_match_pos = false; - } else if (first_match_pos.lnum == last_match_pos.lnum - && first_match_pos.col == last_match_pos.col) { - found_new_match = FAIL; - } else if ((compl_direction == FORWARD) - && (prev_pos.lnum > pos->lnum - || (prev_pos.lnum == pos->lnum - && prev_pos.col >= pos->col))) { - if (looped_around) { - found_new_match = FAIL; - } else { - looped_around = true; - } - } else if ((compl_direction != FORWARD) - && (prev_pos.lnum < pos->lnum - || (prev_pos.lnum == pos->lnum - && prev_pos.col <= pos->col))) { - if (looped_around) { - found_new_match = FAIL; - } else { - looped_around = true; - } - } - prev_pos = *pos; - if (found_new_match == FAIL) { - if (ins_buf == curbuf) { - found_all = true; - } - break; - } - - // when ADDING, the text before the cursor matches, skip it - if ((compl_cont_status & CONT_ADDING) && ins_buf == curbuf - && ini->lnum == pos->lnum - && ini->col == pos->col) { - continue; - } - ptr = ml_get_buf(ins_buf, pos->lnum, false) + pos->col; - if (ctrl_x_mode_line_or_eval()) { - if (compl_cont_status & CONT_ADDING) { - if (pos->lnum >= ins_buf->b_ml.ml_line_count) { - continue; - } - ptr = ml_get_buf(ins_buf, pos->lnum + 1, false); - if (!p_paste) { - ptr = (char_u *)skipwhite((char *)ptr); - } - } - len = (int)STRLEN(ptr); - } else { - char_u *tmp_ptr = ptr; - - if (compl_cont_status & CONT_ADDING) { - tmp_ptr += compl_length; - // Skip if already inside a word. - if (vim_iswordp(tmp_ptr)) { - continue; - } - // Find start of next word. - tmp_ptr = find_word_start(tmp_ptr); - } - // Find end of this word. - tmp_ptr = find_word_end(tmp_ptr); - len = (int)(tmp_ptr - ptr); - - if ((compl_cont_status & CONT_ADDING) - && len == compl_length) { - if (pos->lnum < ins_buf->b_ml.ml_line_count) { - // Try next line, if any. the new word will be "join" as if the - // normal command "J" was used. IOSIZE is always greater than - // compl_length, so the next STRNCPY always works -- Acevedo - STRNCPY(IObuff, ptr, len); // NOLINT(runtime/printf) - ptr = ml_get_buf(ins_buf, pos->lnum + 1, false); - tmp_ptr = ptr = (char_u *)skipwhite((char *)ptr); - // Find start of next word. - tmp_ptr = find_word_start(tmp_ptr); - // Find end of next word. - tmp_ptr = find_word_end(tmp_ptr); - if (tmp_ptr > ptr) { - if (*ptr != ')' && IObuff[len - 1] != TAB) { - if (IObuff[len - 1] != ' ') { - IObuff[len++] = ' '; - } - // IObuf =~ "\k.* ", thus len >= 2 - if (p_js - && (IObuff[len - 2] == '.' - || IObuff[len - 2] == '?' - || IObuff[len - 2] == '!')) { - IObuff[len++] = ' '; - } - } - // copy as much as possible of the new word - if (tmp_ptr - ptr >= IOSIZE - len) { - tmp_ptr = ptr + IOSIZE - len - 1; - } - STRLCPY(IObuff + len, ptr, IOSIZE - len); - len += (int)(tmp_ptr - ptr); - cont_s_ipos = true; - } - IObuff[len] = NUL; - ptr = IObuff; - } - if (len == compl_length) { - continue; - } - } - } - if (ins_compl_add_infercase(ptr, len, p_ic, - ins_buf == curbuf ? NULL : (char_u *)ins_buf->b_sfname, - 0, cont_s_ipos) != NOTDONE) { - found_new_match = OK; - break; - } - } - p_scs = save_p_scs; - p_ws = save_p_ws; - } - - // check if compl_curr_match has changed, (e.g. other type of - // expansion added something) - if (type != 0 && compl_curr_match != compl_old_match) { - found_new_match = OK; - } + // get the next set of completion matches + found_new_match = get_next_completion_match(type, &st, ini); // break the loop for specialized modes (use 'complete' just for the // generic ctrl_x_mode == CTRL_X_NORMAL) or when we've found a new match - if ((ctrl_x_mode_not_default() - && !ctrl_x_mode_line_or_eval()) + if ((ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval()) || found_new_match != FAIL) { if (got_int) { break; @@ -2890,8 +3354,7 @@ static int ins_compl_get_exp(pos_T *ini) ins_compl_check_keys(0, false); } - if ((ctrl_x_mode_not_default() - && !ctrl_x_mode_line_or_eval()) + if ((ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval()) || compl_interrupted) { break; } @@ -2899,8 +3362,8 @@ static int ins_compl_get_exp(pos_T *ini) } else { // Mark a buffer scanned when it has been scanned completely if (type == 0 || type == CTRL_X_PATH_PATTERNS) { - assert(ins_buf); - ins_buf->b_scanned = true; + assert(st.ins_buf); + st.ins_buf->b_scanned = true; } compl_started = false; @@ -2908,16 +3371,14 @@ static int ins_compl_get_exp(pos_T *ini) } compl_started = true; - if ((ctrl_x_mode_normal() - || ctrl_x_mode_line_or_eval()) - && *e_cpt == NUL) { // Got to end of 'complete' + if ((ctrl_x_mode_normal() || ctrl_x_mode_line_or_eval()) + && *st.e_cpt == NUL) { // Got to end of 'complete' found_new_match = FAIL; } i = -1; // total of matches, unknown if (found_new_match == FAIL - || (ctrl_x_mode_not_default() - && !ctrl_x_mode_line_or_eval())) { + || (ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval())) { i = ins_compl_make_cyclic(); } @@ -2925,7 +3386,7 @@ static int ins_compl_get_exp(pos_T *ini) // If several matches were added (FORWARD) or the search failed and has // just been made cyclic then we have to move compl_curr_match to the // next or previous entry (if any) -- Acevedo - compl_curr_match = compl_direction == FORWARD + compl_curr_match = compl_dir_forward() ? compl_old_match->cp_next : compl_old_match->cp_prev; if (compl_curr_match == NULL) { @@ -2937,6 +3398,31 @@ static int ins_compl_get_exp(pos_T *ini) return i; } +/// Update "compl_shown_match" to the actually shown match, it may differ when +/// "compl_leader" is used to omit some of the matches. +static void ins_compl_update_shown_match(void) +{ + while (!ins_compl_equal(compl_shown_match, + compl_leader, strlen(compl_leader)) + && compl_shown_match->cp_next != NULL + && !is_first_match(compl_shown_match->cp_next)) { + compl_shown_match = compl_shown_match->cp_next; + } + + // If we didn't find it searching forward, and compl_shows_dir is + // backward, find the last match. + if (compl_shows_dir_backward() + && !ins_compl_equal(compl_shown_match, compl_leader, strlen(compl_leader)) + && (compl_shown_match->cp_next == NULL + || is_first_match(compl_shown_match->cp_next))) { + while (!ins_compl_equal(compl_shown_match, compl_leader, strlen(compl_leader)) + && compl_shown_match->cp_prev != NULL + && !is_first_match(compl_shown_match->cp_prev)) { + compl_shown_match = compl_shown_match->cp_prev; + } + } +} + /// Delete the old text being completed. void ins_compl_delete(void) { @@ -2944,7 +3430,7 @@ void ins_compl_delete(void) // In insert mode: Delete the typed part. // In replace mode: Put the old characters back, if any. - col = compl_col + (compl_cont_status & CONT_ADDING ? compl_length : 0); + col = compl_col + (compl_status_adding() ? compl_length : 0); if ((int)curwin->w_cursor.col > col) { if (stop_arrow() == FAIL) { return; @@ -2964,7 +3450,7 @@ void ins_compl_delete(void) void ins_compl_insert(bool in_compl_func) { ins_bytes(compl_shown_match->cp_str + get_compl_len()); - compl_used_match = !(compl_shown_match->cp_flags & CP_ORIGINAL_TEXT); + compl_used_match = !match_at_original_text(compl_shown_match); dict_T *dict = ins_compl_dict_alloc(compl_shown_match); set_vim_var_dict(VV_COMPLETED_ITEM, dict); @@ -2973,97 +3459,69 @@ void ins_compl_insert(bool in_compl_func) } } -/// Fill in the next completion in the current direction. -/// If "allow_get_expansion" is true, then we may call ins_compl_get_exp() to -/// get more completions. If it is false, then we just do nothing when there -/// are no more completions in a given direction. The latter case is used when -/// we are still in the middle of finding completions, to allow browsing -/// through the ones found so far. -/// @return the total number of matches, or -1 if still unknown -- webb. -/// -/// compl_curr_match is currently being used by ins_compl_get_exp(), so we use -/// compl_shown_match here. -/// -/// Note that this function may be called recursively once only. First with -/// "allow_get_expansion" true, which calls ins_compl_get_exp(), which in turn -/// calls this function with "allow_get_expansion" false. -/// -/// @param count Repeat completion this many times; should be at least 1 -/// @param insert_match Insert the newly selected match -/// @param in_compl_func Called from complete_check() -static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match, - bool in_compl_func) +/// show the file name for the completion match (if any). Truncate the file +/// name to avoid a wait for return. +static void ins_compl_show_filename(void) { - int num_matches = -1; - int todo = count; - compl_T *found_compl = NULL; - bool found_end = false; - const bool started = compl_started; - - // When user complete function return -1 for findstart which is next - // time of 'always', compl_shown_match become NULL. - if (compl_shown_match == NULL) { - return -1; + char *const lead = _("match in file"); + int space = sc_col - vim_strsize(lead) - 2; + if (space <= 0) { + return; } - if (compl_leader != NULL - && (compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) == 0) { - // Set "compl_shown_match" to the actually shown match, it may differ - // when "compl_leader" is used to omit some of the matches. - while (!ins_compl_equal(compl_shown_match, - compl_leader, STRLEN(compl_leader)) - && compl_shown_match->cp_next != NULL - && compl_shown_match->cp_next != compl_first_match) { - compl_shown_match = compl_shown_match->cp_next; - } - - // If we didn't find it searching forward, and compl_shows_dir is - // backward, find the last match. - if (compl_shows_dir == BACKWARD - && !ins_compl_equal(compl_shown_match, compl_leader, STRLEN(compl_leader)) - && (compl_shown_match->cp_next == NULL - || compl_shown_match->cp_next == compl_first_match)) { - while (!ins_compl_equal(compl_shown_match, compl_leader, STRLEN(compl_leader)) - && compl_shown_match->cp_prev != NULL - && compl_shown_match->cp_prev != compl_first_match) { - compl_shown_match = compl_shown_match->cp_prev; - } + // We need the tail that fits. With double-byte encoding going + // back from the end is very slow, thus go from the start and keep + // the text that fits in "space" between "s" and "e". + char *s; + char *e; + for (s = e = compl_shown_match->cp_fname; *e != NUL; MB_PTR_ADV(e)) { + space -= ptr2cells(e); + while (space < 0) { + space += ptr2cells(s); + MB_PTR_ADV(s); } } + msg_hist_off = true; + vim_snprintf(IObuff, IOSIZE, "%s %s%s", lead, + s > compl_shown_match->cp_fname ? "<" : "", s); + msg(IObuff); + msg_hist_off = false; + redraw_cmdline = false; // don't overwrite! +} - if (allow_get_expansion && insert_match - && (!(compl_get_longest || compl_restarting) || compl_used_match)) { - // Delete old text to be replaced - ins_compl_delete(); - } - - // When finding the longest common text we stick at the original text, - // don't let CTRL-N or CTRL-P move to the first match. - bool advance = count != 1 || !allow_get_expansion || !compl_get_longest; - - // When restarting the search don't insert the first match either. - if (compl_restarting) { - advance = false; - compl_restarting = false; - } +/// Find the next set of matches for completion. Repeat the completion "todo" +/// times. The number of matches found is returned in 'num_matches'. +/// +/// @param allow_get_expansion If true, then ins_compl_get_exp() may be called to +/// get more completions. +/// If false, then do nothing when there are no more +/// completions in the given direction. +/// @param todo repeat completion this many times +/// @param advance If true, then completion will move to the first match. +/// Otherwise, the original text will be shown. +/// +/// @return OK on success and -1 if the number of matches are unknown. +static int find_next_completion_match(bool allow_get_expansion, int todo, bool advance, + int *num_matches) +{ + bool found_end = false; + compl_T *found_compl = NULL; - // Repeat this for when <PageUp> or <PageDown> is typed. But don't wrap - // around. while (--todo >= 0) { - if (compl_shows_dir == FORWARD && compl_shown_match->cp_next != NULL) { + if (compl_shows_dir_forward() && compl_shown_match->cp_next != NULL) { compl_shown_match = compl_shown_match->cp_next; found_end = (compl_first_match != NULL - && (compl_shown_match->cp_next == compl_first_match - || compl_shown_match == compl_first_match)); - } else if (compl_shows_dir == BACKWARD + && (is_first_match(compl_shown_match->cp_next) + || is_first_match(compl_shown_match))); + } else if (compl_shows_dir_backward() && compl_shown_match->cp_prev != NULL) { - found_end = (compl_shown_match == compl_first_match); + found_end = is_first_match(compl_shown_match); compl_shown_match = compl_shown_match->cp_prev; - found_end |= (compl_shown_match == compl_first_match); + found_end |= is_first_match(compl_shown_match); } else { if (!allow_get_expansion) { if (advance) { - if (compl_shows_dir == BACKWARD) { + if (compl_shows_dir_backward()) { compl_pending -= todo + 1; } else { compl_pending += todo + 1; @@ -3073,7 +3531,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } if (!compl_no_select && advance) { - if (compl_shows_dir == BACKWARD) { + if (compl_shows_dir_backward()) { compl_pending--; } else { compl_pending++; @@ -3081,7 +3539,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } // Find matches. - num_matches = ins_compl_get_exp(&compl_startpos); + *num_matches = ins_compl_get_exp(&compl_startpos); // handle any pending completions while (compl_pending != 0 && compl_direction == compl_shows_dir @@ -3099,10 +3557,10 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } found_end = false; } - if ((compl_shown_match->cp_flags & CP_ORIGINAL_TEXT) == 0 + if (!match_at_original_text(compl_shown_match) && compl_leader != NULL && !ins_compl_equal(compl_shown_match, - compl_leader, STRLEN(compl_leader))) { + compl_leader, strlen(compl_leader))) { todo++; } else { // Remember a matching item. @@ -3119,6 +3577,68 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } } + return OK; +} + +/// Fill in the next completion in the current direction. +/// If "allow_get_expansion" is true, then we may call ins_compl_get_exp() to +/// get more completions. If it is false, then we just do nothing when there +/// are no more completions in a given direction. The latter case is used when +/// we are still in the middle of finding completions, to allow browsing +/// through the ones found so far. +/// @return the total number of matches, or -1 if still unknown -- webb. +/// +/// compl_curr_match is currently being used by ins_compl_get_exp(), so we use +/// compl_shown_match here. +/// +/// Note that this function may be called recursively once only. First with +/// "allow_get_expansion" true, which calls ins_compl_get_exp(), which in turn +/// calls this function with "allow_get_expansion" false. +/// +/// @param count Repeat completion this many times; should be at least 1 +/// @param insert_match Insert the newly selected match +/// @param in_compl_func Called from complete_check() +static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match, + bool in_compl_func) +{ + int num_matches = -1; + int todo = count; + const bool started = compl_started; + + // When user complete function return -1 for findstart which is next + // time of 'always', compl_shown_match become NULL. + if (compl_shown_match == NULL) { + return -1; + } + + if (compl_leader != NULL && !match_at_original_text(compl_shown_match)) { + // Update "compl_shown_match" to the actually shown match + ins_compl_update_shown_match(); + } + + if (allow_get_expansion && insert_match + && (!(compl_get_longest || compl_restarting) || compl_used_match)) { + // Delete old text to be replaced + ins_compl_delete(); + } + + // When finding the longest common text we stick at the original text, + // don't let CTRL-N or CTRL-P move to the first match. + bool advance = count != 1 || !allow_get_expansion || !compl_get_longest; + + // When restarting the search don't insert the first match either. + if (compl_restarting) { + advance = false; + compl_restarting = false; + } + + // Repeat this for when <PageUp> or <PageDown> is typed. But don't wrap + // around. + if (find_next_completion_match(allow_get_expansion, todo, advance, + &num_matches) == -1) { + return -1; + } + // Insert the text of the new completion, or the compl_leader. if (compl_no_insert && !started) { ins_bytes(compl_orig_text + get_compl_len()); @@ -3135,7 +3655,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match if (!allow_get_expansion) { // redraw to show the user what was inserted - update_screen(0); + update_screen(); // TODO(bfredl): no! // display the updated popup menu ins_compl_show_pum(); @@ -3154,47 +3674,13 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match } // Show the file name for the match (if any) - // Truncate the file name to avoid a wait for return. if (compl_shown_match->cp_fname != NULL) { - char *lead = _("match in file"); - int space = sc_col - vim_strsize(lead) - 2; - char *s; - char *e; - - if (space > 0) { - // We need the tail that fits. With double-byte encoding going - // back from the end is very slow, thus go from the start and keep - // the text that fits in "space" between "s" and "e". - for (s = e = (char *)compl_shown_match->cp_fname; *e != NUL; MB_PTR_ADV(e)) { - space -= ptr2cells(e); - while (space < 0) { - space += ptr2cells(s); - MB_PTR_ADV(s); - } - } - msg_hist_off = true; - vim_snprintf((char *)IObuff, IOSIZE, "%s %s%s", lead, - (char_u *)s > compl_shown_match->cp_fname ? "<" : "", s); - msg((char *)IObuff); - msg_hist_off = false; - redraw_cmdline = false; // don't overwrite! - } + ins_compl_show_filename(); } return num_matches; } -void pum_ext_select_item(int item, bool insert, bool finish) -{ - if (!pum_visible() || item < -1 || item >= compl_match_arraysize) { - return; - } - pum_want.active = true; - pum_want.item = item; - pum_want.insert = insert; - pum_want.finish = finish; -} - /// Call this while finding completions, to check whether the user has hit a key /// that should change the currently displayed completion, or exit completion /// mode. Also, when compl_pending is not zero, show a completion as soon as @@ -3328,36 +3814,36 @@ static bool ins_compl_use_match(int c) /// completion) /// Sets the global variables: compl_col, compl_length and compl_pattern. /// Uses the global variables: compl_cont_status and ctrl_x_mode -static int get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) +static int get_normal_compl_info(char *line, int startcol, colnr_T curs_col) { if ((compl_cont_status & CONT_SOL) || ctrl_x_mode_path_defines()) { - if (!(compl_cont_status & CONT_ADDING)) { - while (--startcol >= 0 && vim_isIDc(line[startcol])) {} + if (!compl_status_adding()) { + while (--startcol >= 0 && vim_isIDc((uint8_t)line[startcol])) {} compl_col += ++startcol; compl_length = curs_col - startcol; } if (p_ic) { - compl_pattern = (char *)str_foldcase(line + compl_col, compl_length, NULL, 0); + compl_pattern = str_foldcase(line + compl_col, compl_length, NULL, 0); } else { - compl_pattern = (char *)vim_strnsave(line + compl_col, (size_t)compl_length); + compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); } - } else if (compl_cont_status & CONT_ADDING) { - char_u *prefix = (char_u *)"\\<"; + } else if (compl_status_adding()) { + char *prefix = "\\<"; // we need up to 2 extra chars for the prefix - compl_pattern = xmalloc(quote_meta(NULL, line + compl_col, - compl_length) + 2); + compl_pattern = xmalloc(quote_meta(NULL, line + compl_col, compl_length) + 2); if (!vim_iswordp(line + compl_col) - || (compl_col > 0 && (vim_iswordp(mb_prevptr(line, line + compl_col))))) { - prefix = (char_u *)""; + || (compl_col > 0 + && (vim_iswordp(mb_prevptr(line, line + compl_col))))) { + prefix = ""; } STRCPY(compl_pattern, prefix); - (void)quote_meta((char_u *)compl_pattern + STRLEN(prefix), + (void)quote_meta(compl_pattern + strlen(prefix), line + compl_col, compl_length); } else if (--startcol < 0 || !vim_iswordp(mb_prevptr(line, line + startcol + 1))) { // Match any word of at least two chars - compl_pattern = (char *)vim_strsave((char_u *)"\\<\\k\\k"); + compl_pattern = xstrdup("\\<\\k\\k"); compl_col += curs_col; compl_length = 0; } else { @@ -3380,13 +3866,12 @@ static int get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) // xmalloc(7) is enough -- Acevedo compl_pattern = xmalloc(7); STRCPY(compl_pattern, "\\<"); - (void)quote_meta((char_u *)compl_pattern + 2, line + compl_col, 1); + (void)quote_meta(compl_pattern + 2, line + compl_col, 1); STRCAT(compl_pattern, "\\k"); } else { - compl_pattern = xmalloc(quote_meta(NULL, line + compl_col, - compl_length) + 2); + compl_pattern = xmalloc(quote_meta(NULL, line + compl_col, compl_length) + 2); STRCPY(compl_pattern, "\\<"); - (void)quote_meta((char_u *)compl_pattern + 2, line + compl_col, compl_length); + (void)quote_meta(compl_pattern + 2, line + compl_col, compl_length); } } @@ -3396,7 +3881,7 @@ static int get_normal_compl_info(char_u *line, int startcol, colnr_T curs_col) /// Get the pattern, column and length for whole line completion or for the /// complete() function. /// Sets the global variables: compl_col, compl_length and compl_pattern. -static int get_wholeline_compl_info(char_u *line, colnr_T curs_col) +static int get_wholeline_compl_info(char *line, colnr_T curs_col) { compl_col = (colnr_T)getwhitecols(line); compl_length = (int)curs_col - (int)compl_col; @@ -3404,9 +3889,9 @@ static int get_wholeline_compl_info(char_u *line, colnr_T curs_col) compl_length = 0; } if (p_ic) { - compl_pattern = (char *)str_foldcase(line + compl_col, compl_length, NULL, 0); + compl_pattern = str_foldcase(line + compl_col, compl_length, NULL, 0); } else { - compl_pattern = (char *)vim_strnsave(line + compl_col, (size_t)compl_length); + compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); } return OK; @@ -3414,17 +3899,17 @@ static int get_wholeline_compl_info(char_u *line, colnr_T curs_col) /// Get the pattern, column and length for filename completion. /// Sets the global variables: compl_col, compl_length and compl_pattern. -static int get_filename_compl_info(char_u *line, int startcol, colnr_T curs_col) +static int get_filename_compl_info(char *line, int startcol, colnr_T curs_col) { // Go back to just before the first filename character. if (startcol > 0) { - char_u *p = line + startcol; + char *p = line + startcol; MB_PTR_BACK(line, p); - while (p > line && vim_isfilec(utf_ptr2char((char *)p))) { + while (p > line && vim_isfilec(utf_ptr2char(p))) { MB_PTR_BACK(line, p); } - if (p == line && vim_isfilec(utf_ptr2char((char *)p))) { + if (p == line && vim_isfilec(utf_ptr2char(p))) { startcol = 0; } else { startcol = (int)(p - line) + 1; @@ -3433,17 +3918,17 @@ static int get_filename_compl_info(char_u *line, int startcol, colnr_T curs_col) compl_col += startcol; compl_length = (int)curs_col - startcol; - compl_pattern = (char *)addstar(line + compl_col, (size_t)compl_length, EXPAND_FILES); + compl_pattern = addstar(line + compl_col, (size_t)compl_length, EXPAND_FILES); return OK; } /// Get the pattern, column and length for command-line completion. /// Sets the global variables: compl_col, compl_length and compl_pattern. -static int get_cmdline_compl_info(char_u *line, colnr_T curs_col) +static int get_cmdline_compl_info(char *line, colnr_T curs_col) { - compl_pattern = (char *)vim_strnsave(line, (size_t)curs_col); - set_cmd_context(&compl_xp, (char_u *)compl_pattern, (int)STRLEN(compl_pattern), curs_col, false); + compl_pattern = xstrnsave(line, (size_t)curs_col); + set_cmd_context(&compl_xp, compl_pattern, (int)strlen(compl_pattern), curs_col, false); if (compl_xp.xp_context == EXPAND_UNSUCCESSFUL || compl_xp.xp_context == EXPAND_NOTHING) { // No completion possible, use an empty pattern to get a @@ -3468,7 +3953,7 @@ static int get_userdefined_compl_info(colnr_T curs_col) const int save_State = State; // Call 'completefunc' or 'omnifunc' and get pattern length as a string - char_u *funcname = get_complete_funcname(ctrl_x_mode); + char *funcname = get_complete_funcname(ctrl_x_mode); if (*funcname == NUL) { semsg(_(e_notset), ctrl_x_mode_function() ? "completefunc" : "omnifunc"); return FAIL; @@ -3483,7 +3968,8 @@ static int get_userdefined_compl_info(colnr_T curs_col) pos_T pos = curwin->w_cursor; textlock++; - colnr_T col = (colnr_T)call_func_retnr((char *)funcname, 2, args); + Callback *cb = get_insert_callback(ctrl_x_mode); + colnr_T col = (colnr_T)callback_call_retnr(cb, 2, args); textlock--; State = save_State; @@ -3510,7 +3996,7 @@ static int get_userdefined_compl_info(colnr_T curs_col) return FAIL; } - // Reset extended parameters of completion, when start new + // Reset extended parameters of completion, when starting new // completion. compl_opt_refresh_always = false; @@ -3524,9 +4010,9 @@ static int get_userdefined_compl_info(colnr_T curs_col) // Setup variables for completion. Need to obtain "line" again, // it may have become invalid. - char_u *line = ml_get(curwin->w_cursor.lnum); + char *line = ml_get(curwin->w_cursor.lnum); compl_length = curs_col - compl_col; - compl_pattern = (char *)vim_strnsave(line + compl_col, (size_t)compl_length); + compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); return OK; } @@ -3550,14 +4036,22 @@ static int get_spell_compl_info(int startcol, colnr_T curs_col) compl_length = (int)curs_col - compl_col; } // Need to obtain "line" again, it may have become invalid. - char_u *line = ml_get(curwin->w_cursor.lnum); - compl_pattern = (char *)vim_strnsave(line + compl_col, (size_t)compl_length); + char *line = ml_get(curwin->w_cursor.lnum); + compl_pattern = xstrnsave(line + compl_col, (size_t)compl_length); return OK; } /// Get the completion pattern, column and length. -static int compl_get_info(char_u *line, int startcol, colnr_T curs_col, bool *line_invalid) +/// +/// @param startcol start column number of the completion pattern/text +/// @param cur_col current cursor column +/// +/// On return, "line_invalid" is set to true, if the current line may have +/// become invalid and needs to be fetched again. +/// +/// @return OK on success. +static int compl_get_info(char *line, int startcol, colnr_T curs_col, bool *line_invalid) { if (ctrl_x_mode_normal() || ((ctrl_x_mode & CTRL_X_WANT_IDENT) @@ -3574,12 +4068,12 @@ static int compl_get_info(char_u *line, int startcol, colnr_T curs_col, bool *li if (get_userdefined_compl_info(curs_col) == FAIL) { return FAIL; } - *line_invalid = true; // 'line' may have become invalid + *line_invalid = true; // "line" may have become invalid } else if (ctrl_x_mode_spell()) { if (get_spell_compl_info(startcol, curs_col) == FAIL) { return FAIL; } - *line_invalid = true; // 'line' may have become invalid + *line_invalid = true; // "line" may have become invalid } else { internal_error("ins_complete()"); return FAIL; @@ -3588,232 +4082,189 @@ static int compl_get_info(char_u *line, int startcol, colnr_T curs_col, bool *li return OK; } -/// Do Insert mode completion. -/// Called when character "c" was typed, which has a meaning for completion. -/// Returns OK if completion was done, FAIL if something failed. -int ins_complete(int c, bool enable_pum) +/// Continue an interrupted completion mode search in "line". +/// +/// If this same ctrl_x_mode has been interrupted use the text from +/// "compl_startpos" to the cursor as a pattern to add a new word instead of +/// expand the one before the cursor, in word-wise if "compl_startpos" is not in +/// the same line as the cursor then fix it (the line has been split because it +/// was longer than 'tw'). if SOL is set then skip the previous pattern, a word +/// at the beginning of the line has been inserted, we'll look for that. +static void ins_compl_continue_search(char *line) { - char_u *line; - int startcol = 0; // column where searched text starts - colnr_T curs_col; // cursor column - int n; - int save_w_wrow; - int save_w_leftcol; - int insert_match; - const bool save_did_ai = did_ai; - int flags = CP_ORIGINAL_TEXT; - bool line_invalid = false; - - compl_direction = ins_compl_key2dir(c); - insert_match = ins_compl_use_match(c); - - if (!compl_started) { - // First time we hit ^N or ^P (in a row, I mean) - - did_ai = false; - did_si = false; - can_si = false; - can_si_back = false; - if (stop_arrow() == FAIL) { - return FAIL; - } - - line = ml_get(curwin->w_cursor.lnum); - curs_col = curwin->w_cursor.col; - compl_pending = 0; - - // If this same ctrl_x_mode has been interrupted use the text from - // "compl_startpos" to the cursor as a pattern to add a new word - // instead of expand the one before the cursor, in word-wise if - // "compl_startpos" is not in the same line as the cursor then fix it - // (the line has been split because it was longer than 'tw'). if SOL - // is set then skip the previous pattern, a word at the beginning of - // the line has been inserted, we'll look for that -- Acevedo. - if ((compl_cont_status & CONT_INTRPT) == CONT_INTRPT - && compl_cont_mode == ctrl_x_mode) { - // it is a continued search - compl_cont_status &= ~CONT_INTRPT; // remove INTRPT - if (ctrl_x_mode_normal() - || ctrl_x_mode_path_patterns() - || ctrl_x_mode_path_defines()) { - if (compl_startpos.lnum != curwin->w_cursor.lnum) { - // line (probably) wrapped, set compl_startpos to the - // first non_blank in the line, if it is not a wordchar - // include it to get a better pattern, but then we don't - // want the "\\<" prefix, check it below. - compl_col = (colnr_T)getwhitecols(line); - compl_startpos.col = compl_col; - compl_startpos.lnum = curwin->w_cursor.lnum; - compl_cont_status &= ~CONT_SOL; // clear SOL if present - } else { - // S_IPOS was set when we inserted a word that was at the - // beginning of the line, which means that we'll go to SOL - // mode but first we need to redefine compl_startpos - if (compl_cont_status & CONT_S_IPOS) { - compl_cont_status |= CONT_SOL; - compl_startpos.col = (colnr_T)((char_u *)skipwhite((char *)line - + compl_length - + compl_startpos.col) - line); - } - compl_col = compl_startpos.col; - } - compl_length = curwin->w_cursor.col - (int)compl_col; - // IObuff is used to add a "word from the next line" would we - // have enough space? just being paranoid -#define MIN_SPACE 75 - if (compl_length > (IOSIZE - MIN_SPACE)) { - compl_cont_status &= ~CONT_SOL; - compl_length = (IOSIZE - MIN_SPACE); - compl_col = curwin->w_cursor.col - compl_length; - } - compl_cont_status |= CONT_ADDING | CONT_N_ADDS; - if (compl_length < 1) { - compl_cont_status &= CONT_LOCAL; - } - } else if (ctrl_x_mode_line_or_eval()) { - compl_cont_status = CONT_ADDING | CONT_N_ADDS; - } else { - compl_cont_status = 0; - } + // it is a continued search + compl_cont_status &= ~CONT_INTRPT; // remove INTRPT + if (ctrl_x_mode_normal() + || ctrl_x_mode_path_patterns() + || ctrl_x_mode_path_defines()) { + if (compl_startpos.lnum != curwin->w_cursor.lnum) { + // line (probably) wrapped, set compl_startpos to the + // first non_blank in the line, if it is not a wordchar + // include it to get a better pattern, but then we don't + // want the "\\<" prefix, check it below. + compl_col = (colnr_T)getwhitecols(line); + compl_startpos.col = compl_col; + compl_startpos.lnum = curwin->w_cursor.lnum; + compl_cont_status &= ~CONT_SOL; // clear SOL if present } else { - compl_cont_status &= CONT_LOCAL; - } - - if (!(compl_cont_status & CONT_ADDING)) { // normal expansion - compl_cont_mode = ctrl_x_mode; - if (ctrl_x_mode_not_default()) { - // Remove LOCAL if ctrl_x_mode != CTRL_X_NORMAL - compl_cont_status = 0; + // S_IPOS was set when we inserted a word that was at the + // beginning of the line, which means that we'll go to SOL + // mode but first we need to redefine compl_startpos + if (compl_cont_status & CONT_S_IPOS) { + compl_cont_status |= CONT_SOL; + compl_startpos.col = (colnr_T)(skipwhite(line + compl_length + compl_startpos.col) - line); } - compl_cont_status |= CONT_N_ADDS; - compl_startpos = curwin->w_cursor; - startcol = (int)curs_col; - compl_col = 0; + compl_col = compl_startpos.col; } - - // Work out completion pattern and original text -- webb - if (compl_get_info(line, startcol, curs_col, &line_invalid) == FAIL) { - if (ctrl_x_mode_function() || ctrl_x_mode_omni() - || thesaurus_func_complete(ctrl_x_mode)) { - // restore did_ai, so that adding comment leader works - did_ai = save_did_ai; - } - return FAIL; + compl_length = curwin->w_cursor.col - (int)compl_col; + // IObuff is used to add a "word from the next line" would we + // have enough space? just being paranoid +#define MIN_SPACE 75 + if (compl_length > (IOSIZE - MIN_SPACE)) { + compl_cont_status &= ~CONT_SOL; + compl_length = (IOSIZE - MIN_SPACE); + compl_col = curwin->w_cursor.col - compl_length; } - // If "line" was changed while getting completion info get it again. - if (line_invalid) { - line = ml_get(curwin->w_cursor.lnum); + compl_cont_status |= CONT_ADDING | CONT_N_ADDS; + if (compl_length < 1) { + compl_cont_status &= CONT_LOCAL; } + } else if (ctrl_x_mode_line_or_eval()) { + compl_cont_status = CONT_ADDING | CONT_N_ADDS; + } else { + compl_cont_status = 0; + } +} - if (compl_cont_status & CONT_ADDING) { - edit_submode_pre = (char_u *)_(" Adding"); - if (ctrl_x_mode_line_or_eval()) { - // Insert a new line, keep indentation but ignore 'comments'. - char_u *old = curbuf->b_p_com; +/// start insert mode completion +static int ins_compl_start(void) +{ + const bool save_did_ai = did_ai; - curbuf->b_p_com = (char_u *)""; - compl_startpos.lnum = curwin->w_cursor.lnum; - compl_startpos.col = compl_col; - ins_eol('\r'); - curbuf->b_p_com = old; - compl_length = 0; - compl_col = curwin->w_cursor.col; - } - } else { - edit_submode_pre = NULL; - compl_startpos.col = compl_col; - } + // First time we hit ^N or ^P (in a row, I mean) - if (compl_cont_status & CONT_LOCAL) { - edit_submode = (char_u *)_(ctrl_x_msgs[CTRL_X_LOCAL_MSG]); - } else { - edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode)); - } + did_ai = false; + did_si = false; + can_si = false; + can_si_back = false; + if (stop_arrow() == FAIL) { + return FAIL; + } - // If any of the original typed text has been changed we need to fix - // the redo buffer. - ins_compl_fixRedoBufForLeader(NULL); + char *line = ml_get(curwin->w_cursor.lnum); + colnr_T curs_col = curwin->w_cursor.col; + compl_pending = 0; - // Always add completion for the original text. - xfree(compl_orig_text); - compl_orig_text = vim_strnsave(line + compl_col, (size_t)compl_length); - if (p_ic) { - flags |= CP_ICASE; - } - if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, - flags, false) != OK) { - XFREE_CLEAR(compl_pattern); - XFREE_CLEAR(compl_orig_text); - return FAIL; + if ((compl_cont_status & CONT_INTRPT) == CONT_INTRPT + && compl_cont_mode == ctrl_x_mode) { + // this same ctrl-x_mode was interrupted previously. Continue the + // completion. + ins_compl_continue_search(line); + } else { + compl_cont_status &= CONT_LOCAL; + } + + int startcol = 0; // column where searched text starts + if (!compl_status_adding()) { // normal expansion + compl_cont_mode = ctrl_x_mode; + if (ctrl_x_mode_not_default()) { + // Remove LOCAL if ctrl_x_mode != CTRL_X_NORMAL + compl_cont_status = 0; } + compl_cont_status |= CONT_N_ADDS; + compl_startpos = curwin->w_cursor; + startcol = (int)curs_col; + compl_col = 0; + } - // showmode might reset the internal line pointers, so it must - // be called before line = ml_get(), or when this address is no - // longer needed. -- Acevedo. - edit_submode_extra = (char_u *)_("-- Searching..."); - edit_submode_highl = HLF_COUNT; - showmode(); - edit_submode_extra = NULL; - ui_flush(); - } else if (insert_match && stop_arrow() == FAIL) { + // Work out completion pattern and original text -- webb + bool line_invalid = false; + if (compl_get_info(line, startcol, curs_col, &line_invalid) == FAIL) { + if (ctrl_x_mode_function() || ctrl_x_mode_omni() + || thesaurus_func_complete(ctrl_x_mode)) { + // restore did_ai, so that adding comment leader works + did_ai = save_did_ai; + } return FAIL; } + // If "line" was changed while getting completion info get it again. + if (line_invalid) { + line = ml_get(curwin->w_cursor.lnum); + } - compl_shown_match = compl_curr_match; - compl_shows_dir = compl_direction; + if (compl_status_adding()) { + edit_submode_pre = _(" Adding"); + if (ctrl_x_mode_line_or_eval()) { + // Insert a new line, keep indentation but ignore 'comments'. + char *old = curbuf->b_p_com; - // Find next match (and following matches). - save_w_wrow = curwin->w_wrow; - save_w_leftcol = curwin->w_leftcol; - n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false); + curbuf->b_p_com = ""; + compl_startpos.lnum = curwin->w_cursor.lnum; + compl_startpos.col = compl_col; + ins_eol('\r'); + curbuf->b_p_com = old; + compl_length = 0; + compl_col = curwin->w_cursor.col; + } + } else { + edit_submode_pre = NULL; + compl_startpos.col = compl_col; + } - if (n > 1) { // all matches have been found - compl_matches = n; + if (compl_cont_status & CONT_LOCAL) { + edit_submode = _(ctrl_x_msgs[CTRL_X_LOCAL_MSG]); + } else { + edit_submode = _(CTRL_X_MSG(ctrl_x_mode)); } - compl_curr_match = compl_shown_match; - compl_direction = compl_shows_dir; - // Eat the ESC that vgetc() returns after a CTRL-C to avoid leaving Insert - // mode. - if (got_int && !global_busy) { - (void)vgetc(); - got_int = false; + // If any of the original typed text has been changed we need to fix + // the redo buffer. + ins_compl_fixRedoBufForLeader(NULL); + + // Always add completion for the original text. + xfree(compl_orig_text); + compl_orig_text = xstrnsave(line + compl_col, (size_t)compl_length); + int flags = CP_ORIGINAL_TEXT; + if (p_ic) { + flags |= CP_ICASE; + } + if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0, + flags, false) != OK) { + XFREE_CLEAR(compl_pattern); + XFREE_CLEAR(compl_orig_text); + return FAIL; } + // showmode might reset the internal line pointers, so it must + // be called before line = ml_get(), or when this address is no + // longer needed. -- Acevedo. + edit_submode_extra = _("-- Searching..."); + edit_submode_highl = HLF_COUNT; + showmode(); + edit_submode_extra = NULL; + ui_flush(); + + return OK; +} + +/// display the completion status message +static void ins_compl_show_statusmsg(void) +{ // we found no match if the list has only the "compl_orig_text"-entry - if (compl_first_match == compl_first_match->cp_next) { - edit_submode_extra = (compl_cont_status & CONT_ADDING) - && compl_length > 1 - ? (char_u *)_(e_hitend) : (char_u *)_(e_patnotf); + if (is_first_match(compl_first_match->cp_next)) { + edit_submode_extra = compl_status_adding() && compl_length > 1 ? _(e_hitend) : _(e_patnotf); edit_submode_highl = HLF_E; - // remove N_ADDS flag, so next ^X<> won't try to go to ADDING mode, - // because we couldn't expand anything at first place, but if we used - // ^P, ^N, ^X^I or ^X^D we might want to add-expand a single-char-word - // (such as M in M'exico) if not tried already. -- Acevedo - if (compl_length > 1 - || (compl_cont_status & CONT_ADDING) - || (ctrl_x_mode_not_default() - && !ctrl_x_mode_path_patterns() - && !ctrl_x_mode_path_defines())) { - compl_cont_status &= ~CONT_N_ADDS; - } - } - - if (compl_curr_match->cp_flags & CP_CONT_S_IPOS) { - compl_cont_status |= CONT_S_IPOS; - } else { - compl_cont_status &= ~CONT_S_IPOS; } if (edit_submode_extra == NULL) { - if (compl_curr_match->cp_flags & CP_ORIGINAL_TEXT) { - edit_submode_extra = (char_u *)_("Back at original"); + if (match_at_original_text(compl_curr_match)) { + edit_submode_extra = _("Back at original"); edit_submode_highl = HLF_W; } else if (compl_cont_status & CONT_S_IPOS) { - edit_submode_extra = (char_u *)_("Word from other line"); + edit_submode_extra = _("Word from other line"); edit_submode_highl = HLF_COUNT; } else if (compl_curr_match->cp_next == compl_curr_match->cp_prev) { - edit_submode_extra = (char_u *)_("The only match"); + edit_submode_extra = _("The only match"); edit_submode_highl = HLF_COUNT; compl_curr_match->cp_number = 1; } else { @@ -3827,14 +4278,14 @@ int ins_complete(int c, bool enable_pum) if (compl_curr_match->cp_number != -1) { // Space for 10 text chars. + 2x10-digit no.s = 31. // Translations may need more than twice that. - static char_u match_ref[81]; + static char match_ref[81]; if (compl_matches > 0) { - vim_snprintf((char *)match_ref, sizeof(match_ref), + vim_snprintf(match_ref, sizeof(match_ref), _("match %d of %d"), compl_curr_match->cp_number, compl_matches); } else { - vim_snprintf((char *)match_ref, sizeof(match_ref), + vim_snprintf(match_ref, sizeof(match_ref), _("match %d"), compl_curr_match->cp_number); } @@ -3862,6 +4313,72 @@ int ins_complete(int c, bool enable_pum) msg_clr_cmdline(); // necessary for "noshowmode" } } +} + +/// Do Insert mode completion. +/// Called when character "c" was typed, which has a meaning for completion. +/// Returns OK if completion was done, FAIL if something failed. +int ins_complete(int c, bool enable_pum) +{ + int n; + int save_w_wrow; + int save_w_leftcol; + int insert_match; + + compl_direction = ins_compl_key2dir(c); + insert_match = ins_compl_use_match(c); + + if (!compl_started) { + if (ins_compl_start() == FAIL) { + return FAIL; + } + } else if (insert_match && stop_arrow() == FAIL) { + return FAIL; + } + + compl_shown_match = compl_curr_match; + compl_shows_dir = compl_direction; + + // Find next match (and following matches). + save_w_wrow = curwin->w_wrow; + save_w_leftcol = curwin->w_leftcol; + n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false); + + if (n > 1) { // all matches have been found + compl_matches = n; + } + compl_curr_match = compl_shown_match; + compl_direction = compl_shows_dir; + + // Eat the ESC that vgetc() returns after a CTRL-C to avoid leaving Insert + // mode. + if (got_int && !global_busy) { + (void)vgetc(); + got_int = false; + } + + // we found no match if the list has only the "compl_orig_text"-entry + if (is_first_match(compl_first_match->cp_next)) { + // remove N_ADDS flag, so next ^X<> won't try to go to ADDING mode, + // because we couldn't expand anything at first place, but if we used + // ^P, ^N, ^X^I or ^X^D we might want to add-expand a single-char-word + // (such as M in M'exico) if not tried already. -- Acevedo + if (compl_length > 1 + || compl_status_adding() + || (ctrl_x_mode_not_default() + && !ctrl_x_mode_path_patterns() + && !ctrl_x_mode_path_defines())) { + compl_cont_status &= ~CONT_N_ADDS; + } + } + + if (compl_curr_match->cp_flags & CP_CONT_S_IPOS) { + compl_cont_status |= CONT_S_IPOS; + } else { + compl_cont_status &= ~CONT_S_IPOS; + } + + ins_compl_show_statusmsg(); // Show the popup menu, unless we got interrupted. if (enable_pum && !compl_interrupted) { @@ -3873,6 +4390,7 @@ int ins_complete(int c, bool enable_pum) return OK; } +/// Remove (if needed) and show the popup menu static void show_pum(int prev_w_wrow, int prev_w_leftcol) { // RedrawingDisabled may be set when invoked through complete(). @@ -3895,7 +4413,7 @@ static void show_pum(int prev_w_wrow, int prev_w_leftcol) // If dest is not NULL the chars. are copied there quoting (with // a backslash) the metachars, and dest would be NUL terminated. // Returns the length (needed) of dest -static unsigned quote_meta(char_u *dest, char_u *src, int len) +static unsigned quote_meta(char *dest, char *src, int len) { unsigned m = (unsigned)len + 1; // one extra for the NUL @@ -3909,7 +4427,7 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len) } FALLTHROUGH; case '~': - if (!p_magic) { // quote these only if magic is set + if (!magic_isset()) { // quote these only if magic is set break; } FALLTHROUGH; @@ -3930,7 +4448,7 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len) *dest++ = *src; } // Copy remaining bytes of a multibyte character. - const int mb_len = utfc_ptr2len((char *)src) - 1; + const int mb_len = utfc_ptr2len(src) - 1; if (mb_len > 0 && len >= mb_len) { for (int i = 0; i < mb_len; i++) { len--; @@ -3952,6 +4470,9 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len) void free_insexpand_stuff(void) { XFREE_CLEAR(compl_orig_text); + callback_free(&cfu_cb); + callback_free(&ofu_cb); + callback_free(&tsrfu_cb); } #endif diff --git a/src/nvim/insexpand.h b/src/nvim/insexpand.h index 8e183455ca..83ba14e0d2 100644 --- a/src/nvim/insexpand.h +++ b/src/nvim/insexpand.h @@ -1,15 +1,10 @@ #ifndef NVIM_INSEXPAND_H #define NVIM_INSEXPAND_H -#include "nvim/vim.h" +#include <stdbool.h> -/// state for pum_ext_select_item. -EXTERN struct { - bool active; - int item; - bool insert; - bool finish; -} pum_want; +#include "nvim/macros.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "insexpand.h.generated.h" diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c index 5a5257efb2..e19806e464 100644 --- a/src/nvim/keycodes.c +++ b/src/nvim/keycodes.c @@ -4,16 +4,25 @@ #include <assert.h> #include <inttypes.h> #include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/charset.h" -#include "nvim/edit.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/keycodes.h" +#include "nvim/log.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -46,8 +55,7 @@ static const struct modmasktable { #define MOD_KEYS_ENTRY_SIZE 5 -static char_u modifier_keys_table[] = -{ +static uint8_t modifier_keys_table[] = { // mod mask with modifier without modifier MOD_MASK_SHIFT, '&', '9', '@', '1', // begin MOD_MASK_SHIFT, '&', '0', '@', '2', // cancel @@ -57,15 +65,15 @@ static char_u modifier_keys_table[] = MOD_MASK_SHIFT, '*', '4', 'k', 'D', // delete char MOD_MASK_SHIFT, '*', '5', 'k', 'L', // delete line MOD_MASK_SHIFT, '*', '7', '@', '7', // end - MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_END, '@', '7', // end + MOD_MASK_CTRL, KS_EXTRA, KE_C_END, '@', '7', // end MOD_MASK_SHIFT, '*', '9', '@', '9', // exit MOD_MASK_SHIFT, '*', '0', '@', '0', // find MOD_MASK_SHIFT, '#', '1', '%', '1', // help MOD_MASK_SHIFT, '#', '2', 'k', 'h', // home - MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_HOME, 'k', 'h', // home + MOD_MASK_CTRL, KS_EXTRA, KE_C_HOME, 'k', 'h', // home MOD_MASK_SHIFT, '#', '3', 'k', 'I', // insert MOD_MASK_SHIFT, '#', '4', 'k', 'l', // left arrow - MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_LEFT, 'k', 'l', // left arrow + MOD_MASK_CTRL, KS_EXTRA, KE_C_LEFT, 'k', 'l', // left arrow MOD_MASK_SHIFT, '%', 'a', '%', '3', // message MOD_MASK_SHIFT, '%', 'b', '%', '4', // move MOD_MASK_SHIFT, '%', 'c', '%', '5', // next @@ -75,63 +83,63 @@ static char_u modifier_keys_table[] = MOD_MASK_SHIFT, '%', 'g', '%', '0', // redo MOD_MASK_SHIFT, '%', 'h', '&', '3', // replace MOD_MASK_SHIFT, '%', 'i', 'k', 'r', // right arr. - MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_RIGHT, 'k', 'r', // right arr. + MOD_MASK_CTRL, KS_EXTRA, KE_C_RIGHT, 'k', 'r', // right arr. MOD_MASK_SHIFT, '%', 'j', '&', '5', // resume MOD_MASK_SHIFT, '!', '1', '&', '6', // save MOD_MASK_SHIFT, '!', '2', '&', '7', // suspend MOD_MASK_SHIFT, '!', '3', '&', '8', // undo - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_UP, 'k', 'u', // up arrow - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_DOWN, 'k', 'd', // down arrow + MOD_MASK_SHIFT, KS_EXTRA, KE_S_UP, 'k', 'u', // up arrow + MOD_MASK_SHIFT, KS_EXTRA, KE_S_DOWN, 'k', 'd', // down arrow // vt100 F1 - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF1, KS_EXTRA, (int)KE_XF1, - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF2, KS_EXTRA, (int)KE_XF2, - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF3, KS_EXTRA, (int)KE_XF3, - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF4, KS_EXTRA, (int)KE_XF4, - - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F1, 'k', '1', // F1 - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F2, 'k', '2', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F3, 'k', '3', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F4, 'k', '4', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F5, 'k', '5', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F6, 'k', '6', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F7, 'k', '7', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F8, 'k', '8', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F9, 'k', '9', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F10, 'k', ';', // F10 - - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F11, 'F', '1', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F12, 'F', '2', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F13, 'F', '3', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F14, 'F', '4', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F15, 'F', '5', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F16, 'F', '6', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F17, 'F', '7', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F18, 'F', '8', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F19, 'F', '9', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F20, 'F', 'A', - - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F21, 'F', 'B', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F22, 'F', 'C', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F23, 'F', 'D', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F24, 'F', 'E', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F25, 'F', 'F', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F26, 'F', 'G', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F27, 'F', 'H', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F28, 'F', 'I', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F29, 'F', 'J', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F30, 'F', 'K', - - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F31, 'F', 'L', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F32, 'F', 'M', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F33, 'F', 'N', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F34, 'F', 'O', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F35, 'F', 'P', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F36, 'F', 'Q', - MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F37, 'F', 'R', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_XF1, KS_EXTRA, KE_XF1, + MOD_MASK_SHIFT, KS_EXTRA, KE_S_XF2, KS_EXTRA, KE_XF2, + MOD_MASK_SHIFT, KS_EXTRA, KE_S_XF3, KS_EXTRA, KE_XF3, + MOD_MASK_SHIFT, KS_EXTRA, KE_S_XF4, KS_EXTRA, KE_XF4, + + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F1, 'k', '1', // F1 + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F2, 'k', '2', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F3, 'k', '3', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F4, 'k', '4', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F5, 'k', '5', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F6, 'k', '6', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F7, 'k', '7', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F8, 'k', '8', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F9, 'k', '9', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F10, 'k', ';', // F10 + + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F11, 'F', '1', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F12, 'F', '2', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F13, 'F', '3', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F14, 'F', '4', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F15, 'F', '5', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F16, 'F', '6', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F17, 'F', '7', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F18, 'F', '8', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F19, 'F', '9', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F20, 'F', 'A', + + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F21, 'F', 'B', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F22, 'F', 'C', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F23, 'F', 'D', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F24, 'F', 'E', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F25, 'F', 'F', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F26, 'F', 'G', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F27, 'F', 'H', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F28, 'F', 'I', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F29, 'F', 'J', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F30, 'F', 'K', + + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F31, 'F', 'L', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F32, 'F', 'M', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F33, 'F', 'N', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F34, 'F', 'O', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F35, 'F', 'P', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F36, 'F', 'Q', + MOD_MASK_SHIFT, KS_EXTRA, KE_S_F37, 'F', 'R', // TAB pseudo code - MOD_MASK_SHIFT, 'k', 'B', KS_EXTRA, (int)KE_TAB, + MOD_MASK_SHIFT, 'k', 'B', KS_EXTRA, KE_TAB, NUL }; @@ -347,28 +355,27 @@ static struct mousetable { int button; // Which mouse button is it? bool is_click; // Is it a mouse button click event? bool is_drag; // Is it a mouse drag event? -} mouse_table[] = -{ - { (int)KE_LEFTMOUSE, MOUSE_LEFT, true, false }, - { (int)KE_LEFTDRAG, MOUSE_LEFT, false, true }, - { (int)KE_LEFTRELEASE, MOUSE_LEFT, false, false }, - { (int)KE_MIDDLEMOUSE, MOUSE_MIDDLE, true, false }, - { (int)KE_MIDDLEDRAG, MOUSE_MIDDLE, false, true }, - { (int)KE_MIDDLERELEASE, MOUSE_MIDDLE, false, false }, - { (int)KE_RIGHTMOUSE, MOUSE_RIGHT, true, false }, - { (int)KE_RIGHTDRAG, MOUSE_RIGHT, false, true }, - { (int)KE_RIGHTRELEASE, MOUSE_RIGHT, false, false }, - { (int)KE_X1MOUSE, MOUSE_X1, true, false }, - { (int)KE_X1DRAG, MOUSE_X1, false, true }, - { (int)KE_X1RELEASE, MOUSE_X1, false, false }, - { (int)KE_X2MOUSE, MOUSE_X2, true, false }, - { (int)KE_X2DRAG, MOUSE_X2, false, true }, - { (int)KE_X2RELEASE, MOUSE_X2, false, false }, +} mouse_table[] = { + { KE_LEFTMOUSE, MOUSE_LEFT, true, false }, + { KE_LEFTDRAG, MOUSE_LEFT, false, true }, + { KE_LEFTRELEASE, MOUSE_LEFT, false, false }, + { KE_MIDDLEMOUSE, MOUSE_MIDDLE, true, false }, + { KE_MIDDLEDRAG, MOUSE_MIDDLE, false, true }, + { KE_MIDDLERELEASE, MOUSE_MIDDLE, false, false }, + { KE_RIGHTMOUSE, MOUSE_RIGHT, true, false }, + { KE_RIGHTDRAG, MOUSE_RIGHT, false, true }, + { KE_RIGHTRELEASE, MOUSE_RIGHT, false, false }, + { KE_X1MOUSE, MOUSE_X1, true, false }, + { KE_X1DRAG, MOUSE_X1, false, true }, + { KE_X1RELEASE, MOUSE_X1, false, false }, + { KE_X2MOUSE, MOUSE_X2, true, false }, + { KE_X2DRAG, MOUSE_X2, false, true }, + { KE_X2RELEASE, MOUSE_X2, false, false }, // DRAG without CLICK - { (int)KE_MOUSEMOVE, MOUSE_RELEASE, false, true }, + { KE_MOUSEMOVE, MOUSE_RELEASE, false, true }, // RELEASE without CLICK - { (int)KE_IGNORE, MOUSE_RELEASE, false, false }, - { 0, 0, 0, 0 }, + { KE_IGNORE, MOUSE_RELEASE, false, false }, + { 0, 0, 0, 0 }, }; /// Return the modifier mask bit (#MOD_MASK_*) corresponding to mod name @@ -396,22 +403,24 @@ int name_to_mod_mask(int c) int simplify_key(const int key, int *modifiers) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - if (*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT)) { - // TAB is a special case. - if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) { - *modifiers &= ~MOD_MASK_SHIFT; - return K_S_TAB; - } - const int key0 = KEY2TERMCAP0(key); - const int key1 = KEY2TERMCAP1(key); - for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) { - if (key0 == modifier_keys_table[i + 3] - && key1 == modifier_keys_table[i + 4] - && (*modifiers & modifier_keys_table[i])) { - *modifiers &= ~modifier_keys_table[i]; - return TERMCAP2KEY(modifier_keys_table[i + 1], - modifier_keys_table[i + 2]); - } + if (!(*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT))) { + return key; + } + + // TAB is a special case. + if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) { + *modifiers &= ~MOD_MASK_SHIFT; + return K_S_TAB; + } + const int key0 = KEY2TERMCAP0(key); + const int key1 = KEY2TERMCAP1(key); + for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) { + if (key0 == modifier_keys_table[i + 3] + && key1 == modifier_keys_table[i + 4] + && (*modifiers & modifier_keys_table[i])) { + *modifiers &= ~modifier_keys_table[i]; + return TERMCAP2KEY(modifier_keys_table[i + 1], + modifier_keys_table[i + 2]); } } return key; @@ -465,7 +474,7 @@ char_u *get_special_key_name(int c, int modifiers) int i, idx; int table_idx; - char_u *s; + char *s; string[0] = '<'; idx = 1; @@ -534,12 +543,12 @@ char_u *get_special_key_name(int c, int modifiers) } else { s = transchar(c); while (*s) { - string[idx++] = *s++; + string[idx++] = (uint8_t)(*s++); } } } } else { // use name of special key - size_t len = STRLEN(key_names_table[table_idx].name); + size_t len = strlen(key_names_table[table_idx].name); if ((int)len + idx + 2 <= MAX_KEY_NAME_LEN) { STRCPY(string + idx, key_names_table[table_idx].name); @@ -563,7 +572,7 @@ char_u *get_special_key_name(int c, int modifiers) /// @param[out] did_simplify found <C-H>, etc. /// /// @return Number of characters added to dst, zero for no match. -unsigned int trans_special(const char_u **const srcp, const size_t src_len, char_u *const dst, +unsigned int trans_special(const char **const srcp, const size_t src_len, char *const dst, const int flags, const bool escape_ks, bool *const did_simplify) FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -573,7 +582,7 @@ unsigned int trans_special(const char_u **const srcp, const size_t src_len, char return 0; } - return special_to_buf(key, modifiers, escape_ks, dst); + return special_to_buf(key, modifiers, escape_ks, (char_u *)dst); } /// Put the character sequence for "key" with "modifiers" into "dst" and return @@ -616,15 +625,15 @@ unsigned int special_to_buf(int key, int modifiers, bool escape_ks, char_u *dst) /// @param[out] did_simplify FSK_SIMPLIFY and found <C-H>, etc. /// /// @return Key and modifiers or 0 if there is no match. -int find_special_key(const char_u **const srcp, const size_t src_len, int *const modp, +int find_special_key(const char **const srcp, const size_t src_len, int *const modp, const int flags, bool *const did_simplify) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 3) { - const char_u *last_dash; - const char_u *end_of_name; - const char_u *src; - const char_u *bp; - const char_u *const end = *srcp + src_len - 1; + const char *last_dash; + const char *end_of_name; + const char *src; + const char *bp; + const char *const end = *srcp + src_len - 1; const bool in_string = flags & FSK_IN_STRING; int modifiers; int bit; @@ -682,7 +691,7 @@ int find_special_key(const char_u **const srcp, const size_t src_len, int *const modifiers = 0x0; for (bp = src + 1; bp < last_dash; bp++) { if (*bp != '-') { - bit = name_to_mod_mask(*bp); + bit = name_to_mod_mask((uint8_t)(*bp)); if (bit == 0x0) { break; // Illegal modifier name } @@ -709,12 +718,12 @@ int find_special_key(const char_u **const srcp, const size_t src_len, int *const // Special case for a double-quoted string off = l = 2; } else { - l = utfc_ptr2len((char *)last_dash + 1); + l = utfc_ptr2len(last_dash + 1); } if (modifiers != 0 && last_dash[l + 1] == '>') { - key = utf_ptr2char((char *)last_dash + off); + key = utf_ptr2char(last_dash + off); } else { - key = get_special_key_code(last_dash + off); + key = get_special_key_code((char_u *)last_dash + off); if (!(flags & FSK_KEEP_X_KEY)) { key = handle_x_keys(key); } @@ -861,10 +870,9 @@ int get_mouse_button(int code, bool *is_click, bool *is_drag) /// @param[in] from What characters to replace. /// @param[in] from_len Length of the "from" argument. /// @param[out] bufp Location where results were saved in case of success (allocated). -/// if *bufp is non-NULL, it will be used directly. it is -/// assumed to be 128 bytes long (enough for transcoding LHS -/// of mapping) -/// Will be set to NULL in case of failure. +/// If `*bufp` is non-NULL, it will be used directly, +/// and is assumed to be 128 bytes long (enough for transcoding LHS of mapping), +/// and will be set to NULL in case of failure. /// @param[in] flags REPTERM_FROM_PART see above /// REPTERM_DO_LT also translate <lt> /// REPTERM_NO_SPECIAL do not accept <key> notation @@ -872,18 +880,18 @@ int get_mouse_button(int code, bool *is_click, bool *is_drag) /// @param[out] did_simplify set when some <C-H> code was simplied, unless it is NULL. /// @param[in] cpo_flags Relevant flags derived from p_cpo, see CPO_TO_CPO_FLAGS. /// -/// @return Pointer to an allocated memory, which is also saved to "bufp". +/// @return The same as what `*bufp` is set to. char *replace_termcodes(const char *const from, const size_t from_len, char **const bufp, const int flags, bool *const did_simplify, const int cpo_flags) FUNC_ATTR_NONNULL_ARG(1, 3) { ssize_t i; size_t slen; - char_u key; + char key; size_t dlen = 0; - const char_u *src; - const char_u *const end = (char_u *)from + from_len - 1; - char_u *result; // buffer for resulting string + const char *src; + const char *const end = from + from_len - 1; + char *result; // buffer for resulting string const bool do_backslash = !(cpo_flags & FLAG_CPO_BSLASH); // backslash is a special character const bool do_special = !(flags & REPTERM_NO_SPECIAL); @@ -895,12 +903,12 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co const size_t buf_len = allocated ? from_len * 6 + 1 : 128; result = allocated ? xmalloc(buf_len) : *bufp; - src = (char_u *)from; + src = from; // Check for #n at start only: function key n if ((flags & REPTERM_FROM_PART) && from_len > 1 && src[0] == '#' && ascii_isdigit(src[1])) { // function key - result[dlen++] = K_SPECIAL; + result[dlen++] = (char)K_SPECIAL; result[dlen++] = 'k'; if (src[1] == '0') { result[dlen++] = ';'; // #0 is F10 is "k;" @@ -917,7 +925,7 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co } // Check for special <> keycodes, like "<C-S-LeftMouse>" if (do_special && ((flags & REPTERM_DO_LT) || ((end - src) >= 3 - && STRNCMP(src, "<lt>", 4) != 0))) { + && strncmp(src, "<lt>", 4) != 0))) { // Replace <SID> by K_SNR <script-nr> _. // (room: 5 * 6 = 30 bytes; needed: 3 + <nr> + 1 <= 14) if (end - src >= 4 && STRNICMP(src, "<SID>", 5) == 0) { @@ -925,12 +933,12 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co emsg(_(e_usingsid)); } else { src += 5; - result[dlen++] = K_SPECIAL; - result[dlen++] = (int)KS_EXTRA; - result[dlen++] = (int)KE_SNR; - snprintf((char *)result + dlen, buf_len - dlen, "%" PRId64, + result[dlen++] = (char)K_SPECIAL; + result[dlen++] = (char)KS_EXTRA; + result[dlen++] = KE_SNR; + snprintf(result + dlen, buf_len - dlen, "%" PRId64, (int64_t)current_sctx.sc_sid); - dlen += STRLEN(result + dlen); + dlen += strlen(result + dlen); result[dlen++] = '_'; continue; } @@ -946,7 +954,8 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co } if (do_special) { - char_u *p, *s, len; + char *p, *s; + int len; // Replace <Leader> by the value of "mapleader". // Replace <LocalLeader> by the value of "maplocalleader". @@ -964,8 +973,8 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co if (len != 0) { // Allow up to 8 * 6 characters for "mapleader". - if (p == NULL || *p == NUL || STRLEN(p) > 8 * 6) { - s = (char_u *)"\\"; + if (p == NULL || *p == NUL || strlen(p) > 8 * 6) { + s = "\\"; } else { s = p; } @@ -993,12 +1002,12 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co } // skip multibyte char correctly - for (i = utfc_ptr2len_len(src, (int)(end - src) + 1); i > 0; i--) { + for (i = utfc_ptr2len_len((char *)src, (int)(end - src) + 1); i > 0; i--) { // If the character is K_SPECIAL, replace it with K_SPECIAL // KS_SPECIAL KE_FILLER. - if (*src == K_SPECIAL) { - result[dlen++] = K_SPECIAL; - result[dlen++] = KS_SPECIAL; + if (*src == (char)K_SPECIAL) { + result[dlen++] = (char)K_SPECIAL; + result[dlen++] = (char)KS_SPECIAL; result[dlen++] = KE_FILLER; } else { result[dlen++] = *src; @@ -1050,8 +1059,8 @@ char *vim_strsave_escape_ks(char *p) // Need a buffer to hold up to three times as much. Four in case of an // illegal utf-8 byte: // 0xc0 -> 0xc3 - 0x80 -> 0xc3 K_SPECIAL KS_SPECIAL KE_FILLER - char_u *res = xmalloc(STRLEN(p) * 4 + 1); - char_u *d = res; + char *res = xmalloc(strlen(p) * 4 + 1); + char_u *d = (char_u *)res; for (char_u *s = (char_u *)p; *s != NUL;) { if (s[0] == K_SPECIAL && s[1] != NUL && s[2] != NUL) { // Copy special key unmodified. @@ -1067,7 +1076,7 @@ char *vim_strsave_escape_ks(char *p) } *d = NUL; - return (char *)res; + return res; } /// Remove escaping from K_SPECIAL characters. Reverse of diff --git a/src/nvim/keycodes.h b/src/nvim/keycodes.h index c4d984ee17..7842dee92c 100644 --- a/src/nvim/keycodes.h +++ b/src/nvim/keycodes.h @@ -1,6 +1,10 @@ #ifndef NVIM_KEYCODES_H #define NVIM_KEYCODES_H +#include <stddef.h> + +#include "nvim/ascii.h" +#include "nvim/option_defs.h" #include "nvim/strings.h" // Keycode definitions for special keys. diff --git a/src/nvim/lib/ringbuf.h b/src/nvim/lib/ringbuf.h index 4fd110a531..907018a06e 100644 --- a/src/nvim/lib/ringbuf.h +++ b/src/nvim/lib/ringbuf.h @@ -25,14 +25,9 @@ #define _RINGBUF_LENGTH(rb) \ ((rb)->first == NULL ? 0 \ - : ((rb)->next == (rb)->first) ? (size_t)((rb)->buf_end - (rb)->buf) + 1 \ - : ((rb)->next > \ - (rb)->first) ? (size_t)((rb)->next - \ - (rb)->first) \ - : (size_t)((rb)-> \ - next - (rb)->buf + \ - (rb)->buf_end - \ - (rb)->first + 1)) + : ((rb)->next == (rb)->first) ? (size_t)((rb)->buf_end - (rb)->buf) + 1 \ + : ((rb)->next > (rb)->first) ? (size_t)((rb)->next - (rb)->first) \ + : (size_t)((rb)->next - (rb)->buf + (rb)->buf_end - (rb)->first + 1)) #define _RINGBUF_NEXT(rb, var) \ ((var) == (rb)->buf_end ? (rb)->buf : (var) + 1) diff --git a/src/nvim/linematch.c b/src/nvim/linematch.c new file mode 100644 index 0000000000..a9dac40731 --- /dev/null +++ b/src/nvim/linematch.c @@ -0,0 +1,384 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <string.h> + +#include "nvim/linematch.h" +#include "nvim/macros.h" +#include "nvim/memory.h" + +// struct for running the diff linematch algorithm +typedef struct { + int *df_decision; // to keep track of this path traveled + int df_lev_score; // to keep track of the total score of this path + size_t df_path_idx; // current index of this path +} diffcmppath_T; + +#define LN_MAX_BUFS 8 + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "linematch.c.generated.h" +#endif + +static size_t line_len(const char *s) +{ + char *end = strchr(s, '\n'); + if (end) { + return (size_t)(end - s); + } + return strlen(s); +} + +/// Same as matching_chars but ignore whitespace +/// +/// @param s1 +/// @param s2 +static int matching_chars_iwhite(const char *s1, const char *s2) +{ + // the newly processed strings that will be compared + // delete the white space characters, and/or replace all upper case with lower + char *strsproc[2]; + const char *strsorig[2] = { s1, s2 }; + for (int k = 0; k < 2; k++) { + size_t d = 0; + size_t i = 0; + size_t slen = line_len(strsorig[k]); + strsproc[k] = xmalloc((slen + 1) * sizeof(char)); + while (d + i < slen) { + char e = strsorig[k][i + d]; + if (e != ' ' && e != '\t') { + strsproc[k][i] = e; + i++; + } else { + d++; + } + } + strsproc[k][i] = '\0'; + } + int matching = matching_chars(strsproc[0], strsproc[1]); + xfree(strsproc[0]); + xfree(strsproc[1]); + return matching; +} + +/// update the path of a point in the diff linematch algorithm +/// @param diffcmppath +/// @param score +/// @param to +/// @param from +/// @param choice +static void update_path_flat(diffcmppath_T *diffcmppath, int score, size_t to, size_t from, + const int choice) +{ + size_t path_idx = diffcmppath[from].df_path_idx; + + for (size_t k = 0; k < path_idx; k++) { + diffcmppath[to].df_decision[k] = diffcmppath[from].df_decision[k]; + } + + diffcmppath[to].df_decision[path_idx] = choice; + diffcmppath[to].df_lev_score = score; + diffcmppath[to].df_path_idx = path_idx + 1; +} + +#define MATCH_CHAR_MAX_LEN 800 + +/// Return matching characters between "s1" and "s2" whilst respecting sequence order. +/// Consider the case of two strings 'AAACCC' and 'CCCAAA', the +/// return value from this function will be 3, either to match +/// the 3 C's, or the 3 A's. +/// +/// Examples: +/// matching_chars("aabc", "acba") -> 2 // 'a' and 'b' in common +/// matching_chars("123hello567", "he123ll567o") -> 8 // '123', 'll' and '567' in common +/// matching_chars("abcdefg", "gfedcba") -> 1 // all characters in common, +/// // but only at most 1 in sequence +/// +/// @param s1 +/// @param s2 +static int matching_chars(const char *s1, const char *s2) +{ + size_t s1len = MIN(MATCH_CHAR_MAX_LEN - 1, line_len(s1)); + size_t s2len = MIN(MATCH_CHAR_MAX_LEN - 1, line_len(s2)); + int matrix[2][MATCH_CHAR_MAX_LEN] = { 0 }; + bool icur = 1; // save space by storing only two rows for i axis + for (size_t i = 0; i < s1len; i++) { + icur = !icur; + int *e1 = matrix[icur]; + int *e2 = matrix[!icur]; + for (size_t j = 0; j < s2len; j++) { + // skip char in s1 + if (e2[j + 1] > e1[j + 1]) { + e1[j + 1] = e2[j + 1]; + } + // skip char in s2 + if (e1[j] > e1[j + 1]) { + e1[j + 1] = e1[j]; + } + // compare char in s1 and s2 + if ((s1[i] == s2[j]) && (e2[j] + 1) > e1[j + 1]) { + e1[j + 1] = e2[j] + 1; + } + } + } + return matrix[icur][s2len]; +} + +/// count the matching characters between a variable number of strings "sp" +/// mark the strings that have already been compared to extract them later +/// without re-running the character match counting. +/// @param sp +/// @param fomvals +/// @param n +static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite) +{ + int matched_chars = 0; + int matched = 0; + for (size_t i = 0; i < n; i++) { + for (size_t j = i + 1; j < n; j++) { + if (sp[i] != NULL && sp[j] != NULL) { + matched++; + // TODO(lewis6991): handle whitespace ignoring higher up in the stack + matched_chars += iwhite ? matching_chars_iwhite(sp[i], sp[j]) + : matching_chars(sp[i], sp[j]); + } + } + } + + // prioritize a match of 3 (or more lines) equally to a match of 2 lines + if (matched >= 2) { + matched_chars *= 2; + matched_chars /= matched; + } + + return matched_chars; +} + +void fastforward_buf_to_lnum(const char **s, long lnum) +{ + for (long i = 0; i < lnum - 1; i++) { + *s = strchr(*s, '\n'); + (*s)++; + } +} + +/// try all the different ways to compare these lines and use the one that +/// results in the most matching characters +/// @param df_iters +/// @param paths +/// @param npaths +/// @param path_idx +/// @param choice +/// @param diffcmppath +/// @param diff_len +/// @param ndiffs +/// @param diff_blk +static void try_possible_paths(const int *df_iters, const size_t *paths, const int npaths, + const int path_idx, int *choice, diffcmppath_T *diffcmppath, + const int *diff_len, const size_t ndiffs, const char **diff_blk, + bool iwhite) +{ + if (path_idx == npaths) { + if ((*choice) > 0) { + int from_vals[LN_MAX_BUFS]; + const int *to_vals = df_iters; + const char *current_lines[LN_MAX_BUFS]; + for (size_t k = 0; k < ndiffs; k++) { + from_vals[k] = df_iters[k]; + // get the index at all of the places + if ((*choice) & (1 << k)) { + from_vals[k]--; + const char *p = diff_blk[k]; + fastforward_buf_to_lnum(&p, df_iters[k]); + current_lines[k] = p; + } else { + current_lines[k] = NULL; + } + } + size_t unwrapped_idx_from = unwrap_indexes(from_vals, diff_len, ndiffs); + size_t unwrapped_idx_to = unwrap_indexes(to_vals, diff_len, ndiffs); + int matched_chars = count_n_matched_chars(current_lines, ndiffs, iwhite); + int score = diffcmppath[unwrapped_idx_from].df_lev_score + matched_chars; + if (score > diffcmppath[unwrapped_idx_to].df_lev_score) { + update_path_flat(diffcmppath, score, unwrapped_idx_to, unwrapped_idx_from, *choice); + } + } else { + // initialize the 0, 0, 0 ... choice + size_t i = 0; + while (i < ndiffs && df_iters[i] == 0) { + i++; + if (i == ndiffs) { + diffcmppath[0].df_lev_score = 0; + diffcmppath[0].df_path_idx = 0; + } + } + } + return; + } + size_t bit_place = paths[path_idx]; + *(choice) |= (1 << bit_place); // set it to 1 + try_possible_paths(df_iters, paths, npaths, path_idx + 1, choice, + diffcmppath, diff_len, ndiffs, diff_blk, iwhite); + *(choice) &= ~(1 << bit_place); // set it to 0 + try_possible_paths(df_iters, paths, npaths, path_idx + 1, choice, + diffcmppath, diff_len, ndiffs, diff_blk, iwhite); +} + +/// unwrap indexes to access n dimensional tensor +/// @param values +/// @param diff_len +/// @param ndiffs +static size_t unwrap_indexes(const int *values, const int *diff_len, const size_t ndiffs) +{ + size_t num_unwrap_scalar = 1; + for (size_t k = 0; k < ndiffs; k++) { + num_unwrap_scalar *= (size_t)diff_len[k] + 1; + } + + size_t path_idx = 0; + for (size_t k = 0; k < ndiffs; k++) { + num_unwrap_scalar /= (size_t)diff_len[k] + 1; + + // (k == 0) space optimization + int n = k == 0 ? values[k] % 2 : values[k]; + path_idx += num_unwrap_scalar * (size_t)n; + } + return path_idx; +} + +/// populate the values of the linematch algorithm tensor, and find the best +/// decision for how to compare the relevant lines from each of the buffers at +/// each point in the tensor +/// @param df_iters +/// @param ch_dim +/// @param diffcmppath +/// @param diff_len +/// @param ndiffs +/// @param diff_blk +static void populate_tensor(int *df_iters, const size_t ch_dim, diffcmppath_T *diffcmppath, + const int *diff_len, const size_t ndiffs, const char **diff_blk, + bool iwhite) +{ + if (ch_dim == ndiffs) { + int npaths = 0; + size_t paths[LN_MAX_BUFS]; + + for (size_t j = 0; j < ndiffs; j++) { + if (df_iters[j] > 0) { + paths[npaths] = j; + npaths++; + } + } + int choice = 0; + size_t unwrapper_idx_to = unwrap_indexes(df_iters, diff_len, ndiffs); + diffcmppath[unwrapper_idx_to].df_lev_score = -1; + try_possible_paths(df_iters, paths, npaths, 0, &choice, diffcmppath, + diff_len, ndiffs, diff_blk, iwhite); + return; + } + + for (int i = 0; i <= diff_len[ch_dim]; i++) { + df_iters[ch_dim] = i; + populate_tensor(df_iters, ch_dim + 1, diffcmppath, diff_len, + ndiffs, diff_blk, iwhite); + } +} + +/// algorithm to find an optimal alignment of lines of a diff block with 2 or +/// more files. The algorithm is generalized to work for any number of files +/// which corresponds to another dimension added to the tensor used in the +/// algorithm +/// +/// for questions and information about the linematch algorithm please contact +/// Jonathon White (jonathonwhite@protonmail.com) +/// +/// for explanation, a summary of the algorithm in 3 dimensions (3 files +/// compared) follows +/// +/// The 3d case (for 3 buffers) of the algorithm implemented when diffopt +/// 'linematch' is enabled. The algorithm constructs a 3d tensor to +/// compare a diff between 3 buffers. The dimensions of the tensor are +/// the length of the diff in each buffer plus 1 A path is constructed by +/// moving from one edge of the cube/3d tensor to the opposite edge. +/// Motions from one cell of the cube to the next represent decisions. In +/// a 3d cube, there are a total of 7 decisions that can be made, +/// represented by the enum df_path3_choice which is defined in +/// buffer_defs.h a comparison of buffer 0 and 1 represents a motion +/// toward the opposite edge of the cube with components along the 0 and +/// 1 axes. a comparison of buffer 0, 1, and 2 represents a motion +/// toward the opposite edge of the cube with components along the 0, 1, +/// and 2 axes. A skip of buffer 0 represents a motion along only the 0 +/// axis. For each action, a point value is awarded, and the path is +/// saved for reference later, if it is found to have been the optimal +/// path. The optimal path has the highest score. The score is +/// calculated as the summation of the total characters matching between +/// all of the lines which were compared. The structure of the algorithm +/// is that of a dynamic programming problem. We can calculate a point +/// i,j,k in the cube as a function of i-1, j-1, and k-1. To find the +/// score and path at point i,j,k, we must determine which path we want +/// to use, this is done by looking at the possibilities and choosing +/// the one which results in the local highest score. The total highest +/// scored path is, then in the end represented by the cell in the +/// opposite corner from the start location. The entire algorithm +/// consists of populating the 3d cube with the optimal paths from which +/// it may have came. +/// +/// Optimizations: +/// As the function to calculate the cell of a tensor at point i,j,k is a +/// function of the cells at i-1, j-1, k-1, the whole tensor doesn't need +/// to be stored in memory at once. In the case of the 3d cube, only two +/// slices (along k and j axis) are stored in memory. For the 2d matrix +/// (for 2 files), only two rows are stored at a time. The next/previous +/// slice (or row) is always calculated from the other, and they alternate +/// at each iteration. +/// In the 3d case, 3 arrays are populated to memorize the score (matched +/// characters) of the 3 buffers, so a redundant calculation of the +/// scores does not occur +/// @param diff_blk +/// @param diff_len +/// @param ndiffs +/// @param [out] [allocated] decisions +/// @return the length of decisions +size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size_t ndiffs, + int **decisions, bool iwhite) +{ + assert(ndiffs <= LN_MAX_BUFS); + + size_t memsize = 1; + size_t memsize_decisions = 0; + for (size_t i = 0; i < ndiffs; i++) { + assert(diff_len[i] >= 0); + memsize *= i == 0 ? 2 : (size_t)(diff_len[i] + 1); + memsize_decisions += (size_t)diff_len[i]; + } + + // create the flattened path matrix + diffcmppath_T *diffcmppath = xmalloc(sizeof(diffcmppath_T) * memsize); + // allocate memory here + for (size_t i = 0; i < memsize; i++) { + diffcmppath[i].df_decision = xmalloc(memsize_decisions * sizeof(int)); + } + + // memory for avoiding repetitive calculations of score + int df_iters[LN_MAX_BUFS]; + populate_tensor(df_iters, 0, diffcmppath, diff_len, ndiffs, diff_blk, iwhite); + + const size_t u = unwrap_indexes(diff_len, diff_len, ndiffs); + const size_t best_path_idx = diffcmppath[u].df_path_idx; + const int *best_path_decisions = diffcmppath[u].df_decision; + + *decisions = xmalloc(sizeof(int) * best_path_idx); + for (size_t i = 0; i < best_path_idx; i++) { + (*decisions)[i] = best_path_decisions[i]; + } + + for (size_t i = 0; i < memsize; i++) { + xfree(diffcmppath[i].df_decision); + } + xfree(diffcmppath); + + return best_path_idx; +} diff --git a/src/nvim/linematch.h b/src/nvim/linematch.h new file mode 100644 index 0000000000..052d438617 --- /dev/null +++ b/src/nvim/linematch.h @@ -0,0 +1,10 @@ +#ifndef NVIM_LINEMATCH_H +#define NVIM_LINEMATCH_H + +#include <stddef.h> + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "linematch.h.generated.h" +#endif + +#endif // NVIM_LINEMATCH_H diff --git a/src/nvim/locale.c b/src/nvim/locale.c new file mode 100644 index 0000000000..c3cfd3bedb --- /dev/null +++ b/src/nvim/locale.c @@ -0,0 +1,377 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// locale.c: functions for language/locale configuration + +#include <stdbool.h> +#include <stdio.h> + +#include "auto/config.h" +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif + +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/charset.h" +#include "nvim/eval.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/locale.h" +#include "nvim/macros.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option.h" +#include "nvim/os/os.h" +#include "nvim/os/shell.h" +#include "nvim/path.h" +#include "nvim/profile.h" +#include "nvim/types.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "locale.c.generated.h" +#endif + +#if defined(HAVE_LOCALE_H) +# define HAVE_GET_LOCALE_VAL + +static char *get_locale_val(int what) +{ + // Obtain the locale value from the libraries. + char *loc = setlocale(what, NULL); + + return loc; +} +#endif + +/// @return true when "lang" starts with a valid language name. +/// Rejects NULL, empty string, "C", "C.UTF-8" and others. +static bool is_valid_mess_lang(const char *lang) +{ + return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); +} + +/// Obtain the current messages language. Used to set the default for +/// 'helplang'. May return NULL or an empty string. +char *get_mess_lang(void) +{ + char *p; + +#ifdef HAVE_GET_LOCALE_VAL +# if defined(LC_MESSAGES) + p = get_locale_val(LC_MESSAGES); +# else + // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG + // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME + // and LC_MONETARY may be set differently for a Japanese working in the + // US. + p = get_locale_val(LC_COLLATE); +# endif +#else + p = os_getenv("LC_ALL"); + if (!is_valid_mess_lang(p)) { + p = os_getenv("LC_MESSAGES"); + if (!is_valid_mess_lang(p)) { + p = os_getenv("LANG"); + } + } +#endif + return is_valid_mess_lang(p) ? p : NULL; +} + +// Complicated #if; matches with where get_mess_env() is used below. +#ifdef HAVE_WORKING_LIBINTL +/// Get the language used for messages from the environment. +static char *get_mess_env(void) +{ + char *p; + + p = (char *)os_getenv("LC_ALL"); + if (p == NULL) { + p = (char *)os_getenv("LC_MESSAGES"); + if (p == NULL) { + p = (char *)os_getenv("LANG"); + if (p != NULL && ascii_isdigit(*p)) { + p = NULL; // ignore something like "1043" + } +# ifdef HAVE_GET_LOCALE_VAL + if (p == NULL) { + p = get_locale_val(LC_CTYPE); + } +# endif + } + } + return p; +} +#endif + +/// Set the "v:lang" variable according to the current locale setting. +/// Also do "v:lc_time"and "v:ctype". +void set_lang_var(void) +{ + const char *loc; + +#ifdef HAVE_GET_LOCALE_VAL + loc = get_locale_val(LC_CTYPE); +#else + // setlocale() not supported: use the default value + loc = "C"; +#endif + set_vim_var_string(VV_CTYPE, loc, -1); + + // When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall + // back to LC_CTYPE if it's empty. +#ifdef HAVE_WORKING_LIBINTL + loc = get_mess_env(); +#elif defined(LC_MESSAGES) + loc = get_locale_val(LC_MESSAGES); +#else + // In Windows LC_MESSAGES is not defined fallback to LC_CTYPE + loc = get_locale_val(LC_CTYPE); +#endif + set_vim_var_string(VV_LANG, loc, -1); + +#ifdef HAVE_GET_LOCALE_VAL + loc = get_locale_val(LC_TIME); +#endif + set_vim_var_string(VV_LC_TIME, loc, -1); + +#ifdef HAVE_GET_LOCALE_VAL + loc = get_locale_val(LC_COLLATE); +#else + // setlocale() not supported: use the default value + loc = "C"; +#endif + set_vim_var_string(VV_COLLATE, loc, -1); +} + +#if defined(HAVE_LOCALE_H) +/// Setup to use the current locale (for ctype() and many other things). +void init_locale(void) +{ + setlocale(LC_ALL, ""); + +# ifdef LC_NUMERIC + // Make sure strtod() uses a decimal point, not a comma. + setlocale(LC_NUMERIC, "C"); +# endif + + char localepath[MAXPATHL] = { 0 }; + snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH)); + char *tail = path_tail_with_sep(localepath); + *tail = NUL; + tail = path_tail(localepath); + xstrlcpy(tail, "share/locale", + sizeof(localepath) - (size_t)(tail - localepath)); + bindtextdomain(PROJECT_NAME, localepath); + textdomain(PROJECT_NAME); + TIME_MSG("locale set"); +} +#endif + +#ifdef HAVE_WORKING_LIBINTL + +/// ":language": Set the language (locale). +/// +/// @param eap +void ex_language(exarg_T *eap) +{ + char *loc; + char *p; + char *name; + int what = LC_ALL; + char *whatstr = ""; +# ifdef LC_MESSAGES +# define VIM_LC_MESSAGES LC_MESSAGES +# else +# define VIM_LC_MESSAGES 6789 +# endif + + name = eap->arg; + + // Check for "messages {name}", "ctype {name}" or "time {name}" argument. + // Allow abbreviation, but require at least 3 characters to avoid + // confusion with a two letter language name "me" or "ct". + p = skiptowhite(eap->arg); + if ((*p == NUL || ascii_iswhite(*p)) && p - eap->arg >= 3) { + if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) { + what = VIM_LC_MESSAGES; + name = skipwhite(p); + whatstr = "messages "; + } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) { + what = LC_CTYPE; + name = skipwhite(p); + whatstr = "ctype "; + } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) { + what = LC_TIME; + name = skipwhite(p); + whatstr = "time "; + } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) { + what = LC_COLLATE; + name = skipwhite(p); + whatstr = "collate "; + } + } + + if (*name == NUL) { + if (what == VIM_LC_MESSAGES) { + p = get_mess_env(); + } else { + p = setlocale(what, NULL); + } + if (p == NULL || *p == NUL) { + p = "Unknown"; + } + smsg(_("Current %slanguage: \"%s\""), whatstr, p); + } else { +# ifndef LC_MESSAGES + if (what == VIM_LC_MESSAGES) { + loc = ""; + } else { +# endif + loc = setlocale(what, name); +# ifdef LC_NUMERIC + // Make sure strtod() uses a decimal point, not a comma. + setlocale(LC_NUMERIC, "C"); +# endif +# ifndef LC_MESSAGES + } +# endif + if (loc == NULL) { + semsg(_("E197: Cannot set language to \"%s\""), name); + } else { +# ifdef HAVE_NL_MSG_CAT_CNTR + // Need to do this for GNU gettext, otherwise cached translations + // will be used again. + extern int _nl_msg_cat_cntr; + + _nl_msg_cat_cntr++; +# endif + // Reset $LC_ALL, otherwise it would overrule everything. + os_setenv("LC_ALL", "", 1); + + if (what != LC_TIME && what != LC_COLLATE) { + // Tell gettext() what to translate to. It apparently doesn't + // use the currently effective locale. + if (what == LC_ALL) { + os_setenv("LANG", name, 1); + + // Clear $LANGUAGE because GNU gettext uses it. + os_setenv("LANGUAGE", "", 1); + } + if (what != LC_CTYPE) { + os_setenv("LC_MESSAGES", name, 1); + set_helplang_default(name); + } + } + + // Set v:lang, v:lc_time, v:collate and v:ctype to the final result. + set_lang_var(); + maketitle(); + } + } +} + +static char **locales = NULL; // Array of all available locales + +# ifndef MSWIN +static bool did_init_locales = false; + +/// @return an array of strings for all available locales + NULL for the +/// last element or, +/// NULL in case of error. +static char **find_locales(void) +{ + garray_T locales_ga; + char *loc; + char *saveptr = NULL; + + // Find all available locales by running command "locale -a". If this + // doesn't work we won't have completion. + char *locale_a = get_cmd_output("locale -a", NULL, kShellOptSilent, NULL); + if (locale_a == NULL) { + return NULL; + } + ga_init(&locales_ga, sizeof(char *), 20); + + // Transform locale_a string where each locale is separated by "\n" + // into an array of locale strings. + loc = os_strtok(locale_a, "\n", &saveptr); + + while (loc != NULL) { + loc = xstrdup(loc); + GA_APPEND(char *, &locales_ga, loc); + loc = os_strtok(NULL, "\n", &saveptr); + } + xfree(locale_a); + // Guarantee that .ga_data is NULL terminated + ga_grow(&locales_ga, 1); + ((char **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; + return locales_ga.ga_data; +} +# endif + +/// Lazy initialization of all available locales. +static void init_locales(void) +{ +# ifndef MSWIN + if (did_init_locales) { + return; + } + + did_init_locales = true; + locales = find_locales(); +# endif +} + +# if defined(EXITFREE) +void free_locales(void) +{ + if (locales == NULL) { + return; + } + + for (int i = 0; locales[i] != NULL; i++) { + xfree(locales[i]); + } + XFREE_CLEAR(locales); +} +# endif + +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":language" command. +char *get_lang_arg(expand_T *xp, int idx) +{ + if (idx == 0) { + return "messages"; + } + if (idx == 1) { + return "ctype"; + } + if (idx == 2) { + return "time"; + } + if (idx == 3) { + return "collate"; + } + + init_locales(); + if (locales == NULL) { + return NULL; + } + return locales[idx - 4]; +} + +/// Function given to ExpandGeneric() to obtain the available locales. +char *get_locales(expand_T *xp, int idx) +{ + init_locales(); + if (locales == NULL) { + return NULL; + } + return locales[idx]; +} + +#endif diff --git a/src/nvim/locale.h b/src/nvim/locale.h new file mode 100644 index 0000000000..39735d371f --- /dev/null +++ b/src/nvim/locale.h @@ -0,0 +1,10 @@ +#ifndef NVIM_LOCALE_H +#define NVIM_LOCALE_H + +#include "nvim/ex_cmds_defs.h" +#include "nvim/types.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "locale.h.generated.h" +#endif +#endif // NVIM_LOCALE_H diff --git a/src/nvim/log.c b/src/nvim/log.c index 99b17a612b..2c214aa32d 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -9,21 +9,27 @@ // #include <assert.h> +#include <errno.h> #include <inttypes.h> #include <stdarg.h> #include <stdbool.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> #include <uv.h> #include "auto/config.h" +#include "nvim/ascii.h" #include "nvim/eval.h" +#include "nvim/globals.h" #include "nvim/log.h" -#include "nvim/main.h" +#include "nvim/memory.h" #include "nvim/message.h" #include "nvim/os/os.h" +#include "nvim/os/stdpaths_defs.h" #include "nvim/os/time.h" #include "nvim/path.h" -#include "nvim/types.h" /// Cached location of the expanded log file path decided by log_path_init(). static char log_file_path[MAXPATHL + 1] = { 0 }; @@ -60,16 +66,16 @@ static bool log_try_create(char *fname) static void log_path_init(void) { size_t size = sizeof(log_file_path); - expand_env((char_u *)"$" ENV_LOGFILE, (char_u *)log_file_path, (int)size - 1); + expand_env("$" ENV_LOGFILE, log_file_path, (int)size - 1); if (strequal("$" ENV_LOGFILE, log_file_path) || log_file_path[0] == '\0' - || os_isdir((char_u *)log_file_path) + || os_isdir(log_file_path) || !log_try_create(log_file_path)) { // Make $XDG_STATE_HOME if it does not exist. char *loghome = get_xdg_home(kXDGStateHome); char *failed_dir = NULL; bool log_dir_failure = false; - if (!os_isdir((char_u *)loghome)) { + if (!os_isdir(loghome)) { log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir) != 0); } XFREE_CLEAR(loghome); diff --git a/src/nvim/log.h b/src/nvim/log.h index cbee0e0f81..14d46c2ea7 100644 --- a/src/nvim/log.h +++ b/src/nvim/log.h @@ -11,6 +11,7 @@ // NVIM_PROBE(nvim_foo_bar, 1, string.data); #if defined(HAVE_SYS_SDT_H) # include <sys/sdt.h> // NOLINT + # define NVIM_PROBE(name, n, ...) STAP_PROBE##n(neovim, name, __VA_ARGS__) #else # define NVIM_PROBE(name, n, ...) diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 49d49f76b9..6160b84485 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -4,27 +4,31 @@ #include <assert.h> #include <lauxlib.h> #include <lua.h> -#include <lualib.h> #include <stdbool.h> +#include <stddef.h> #include <stdint.h> +#include <stdlib.h> +#include <string.h> #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/assert.h" -#include "nvim/func_attr.h" #include "nvim/memory.h" // FIXME: vim.h is not actually needed, but otherwise it states MAXPATHL is // redefined +#include "klib/kvec.h" #include "nvim/ascii.h" #include "nvim/eval/decode.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/typval_encode.h" #include "nvim/eval/userfunc.h" -#include "nvim/globals.h" -#include "nvim/lib/kvec.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/message.h" +#include "nvim/types.h" #include "nvim/vim.h" /// Determine, which keys lua table contains @@ -385,15 +389,12 @@ nlua_pop_typval_table_processing_end: break; } case LUA_TFUNCTION: { - LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); - state->lua_callable.func_ref = nlua_ref_global(lstate, -1); + LuaRef func = nlua_ref_global(lstate, -1); - char_u *name = register_cfunc(&nlua_CFunction_func_call, - &nlua_CFunction_func_free, - state); + char *name = register_luafunc(func); cur.tv->v_type = VAR_FUNC; - cur.tv->vval.v_string = (char *)vim_strsave(name); + cur.tv->vval.v_string = xstrdup(name); break; } case LUA_TUSERDATA: { @@ -476,8 +477,8 @@ static bool typval_conv_special = false; #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ ufunc_T *fp = find_func(fun); \ - if (fp != NULL && fp->uf_cb == nlua_CFunction_func_call) { \ - nlua_pushref(lstate, ((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); \ + if (fp != NULL && fp->uf_flags & FC_LUAREF) { \ + nlua_pushref(lstate, fp->uf_luaref); \ } else { \ TYPVAL_ENCODE_CONV_NIL(tv); \ } \ @@ -568,6 +569,7 @@ static bool typval_conv_special = false; #define TYPVAL_ENCODE_FIRST_ARG_TYPE lua_State *const #define TYPVAL_ENCODE_FIRST_ARG_NAME lstate #include "nvim/eval/typval_encode.c.h" + #undef TYPVAL_ENCODE_SCOPE #undef TYPVAL_ENCODE_NAME #undef TYPVAL_ENCODE_FIRST_ARG_TYPE @@ -909,9 +911,8 @@ Float nlua_pop_Float(lua_State *lstate, Error *err) lua_pop(lstate, 1); if (table_props.type != kObjectTypeFloat) { return 0; - } else { - return (Float)table_props.val; } + return (Float)table_props.val; } /// Convert lua table to array without determining whether it is array diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h index f6a85900ba..ddc0acfbfa 100644 --- a/src/nvim/lua/converter.h +++ b/src/nvim/lua/converter.h @@ -9,14 +9,6 @@ #include "nvim/eval/typval.h" #include "nvim/func_attr.h" -typedef struct { - LuaRef func_ref; -} LuaCallable; - -typedef struct { - LuaCallable lua_callable; -} LuaCFunctionState; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/converter.h.generated.h" #endif diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index d1d1480696..5ffd90fddd 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1,17 +1,22 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> +#include <inttypes.h> #include <lauxlib.h> #include <lua.h> #include <lualib.h> +#include <stddef.h> +#include <string.h> #include <tree_sitter/api.h> +#include <uv.h> +#include "klib/kvec.h" #include "luv/luv.h" +#include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/ascii.h" -#include "nvim/assert.h" #include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/cursor.h" @@ -19,27 +24,39 @@ #include "nvim/eval.h" #include "nvim/eval/funcs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" +#include "nvim/event/defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/time.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" -#include "nvim/extmark.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/keycodes.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" #include "nvim/lua/stdlib.h" #include "nvim/lua/treesitter.h" #include "nvim/macros.h" -#include "nvim/map.h" +#include "nvim/main.h" #include "nvim/memline.h" +#include "nvim/memory.h" #include "nvim/message.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/option_defs.h" +#include "nvim/os/fileio.h" #include "nvim/os/os.h" +#include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/runtime.h" +#include "nvim/strings.h" +#include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/usercmd.h" #include "nvim/version.h" @@ -188,8 +205,8 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags if (status) { if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) { // consider out of memory errors unrecoverable, just like xmalloc() - mch_errmsg(e_outofmem); - mch_errmsg("\n"); + os_errmsg(e_outofmem); + os_errmsg("\n"); preserve_exit(); } const char *error = lua_tostring(lstate, -1); @@ -241,10 +258,10 @@ static int nlua_luv_thread_common_cfpcall(lua_State *lstate, int nargs, int nres if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) { // Terminate this thread, as the main thread may be able to continue // execution. - mch_errmsg(e_outofmem); - mch_errmsg("\n"); + os_errmsg(e_outofmem); + os_errmsg("\n"); lua_close(lstate); -#ifdef WIN32 +#ifdef MSWIN ExitThread(0); #else pthread_exit(0); @@ -306,6 +323,36 @@ static int nlua_thr_api_nvim__get_runtime(lua_State *lstate) return 1; } +/// Copies args starting at `lua_arg0` to Lua `_G.arg`, and sets `_G.arg[0]` to the scriptname. +/// +/// Example (arg[0] => "foo.lua", arg[1] => "--arg1", …): +/// nvim -l foo.lua --arg1 --arg2 +/// +/// @note Lua CLI sets args before "-e" as _negative_ `_G.arg` indices, but we currently don't. +/// +/// @see https://www.lua.org/pil/1.4.html +/// @see https://github.com/premake/premake-core/blob/1c1304637f4f5e50ba8c57aae8d1d80ec3b7aaf2/src/host/premake.c#L563-L594 +/// +/// @returns number of args +static int nlua_init_argv(lua_State *const L, char **argv, int argc, int lua_arg0) +{ + int i = 0; + lua_newtable(L); // _G.arg + + if (lua_arg0 > 0) { + lua_pushstring(L, argv[lua_arg0 - 1]); + lua_rawseti(L, -2, 0); // _G.arg[0] = "foo.lua" + + for (; lua_arg0 >= 0 && i + lua_arg0 < argc; i++) { + lua_pushstring(L, argv[i + lua_arg0]); + lua_rawseti(L, -2, i + 1); // _G.arg[i+1] = "--foo" + } + } + + lua_setglobal(L, "arg"); + return i; +} + static void nlua_schedule_event(void **argv) { LuaRef cb = (LuaRef)(ptrdiff_t)argv[0]; @@ -396,7 +443,7 @@ static int nlua_wait(lua_State *lstate) bool fast_only = false; if (lua_top >= 4) { - fast_only = lua_toboolean(lstate, 4); + fast_only = lua_toboolean(lstate, 4); } MultiQueue *loop_events = fast_only || in_fast_callback > 0 @@ -581,14 +628,79 @@ static bool nlua_init_packages(lua_State *lstate) lua_getglobal(lstate, "require"); lua_pushstring(lstate, "vim._init_packages"); if (nlua_pcall(lstate, 1, 0)) { - mch_errmsg(lua_tostring(lstate, -1)); - mch_errmsg("\n"); + os_errmsg((char *)lua_tostring(lstate, -1)); + os_errmsg("\n"); return false; } return true; } +/// "vim.ui_attach(ns_id, {ext_foo=true}, cb)" function +static int nlua_ui_attach(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + uint32_t ns_id = (uint32_t)luaL_checkinteger(lstate, 1); + + if (!ns_initialized(ns_id)) { + return luaL_error(lstate, "invalid ns_id"); + } + if (!lua_istable(lstate, 2)) { + return luaL_error(lstate, "ext_widgets must be a table"); + } + if (!lua_isfunction(lstate, 3)) { + return luaL_error(lstate, "callback must be a Lua function"); + } + + bool ext_widgets[kUIGlobalCount] = { false }; + bool tbl_has_true_val = false; + + lua_pushvalue(lstate, 2); + lua_pushnil(lstate); + while (lua_next(lstate, -2)) { + // [dict, key, val] + size_t len; + const char *s = lua_tolstring(lstate, -2, &len); + bool val = lua_toboolean(lstate, -1); + + for (size_t i = 0; i < kUIGlobalCount; i++) { + if (strequal(s, ui_ext_names[i])) { + if (val) { + tbl_has_true_val = true; + } + ext_widgets[i] = val; + goto ok; + } + } + + return luaL_error(lstate, "Unexpected key: %s", s); +ok: + lua_pop(lstate, 1); + } + + if (!tbl_has_true_val) { + return luaL_error(lstate, "ext_widgets table must contain at least one 'true' value"); + } + + LuaRef ui_event_cb = nlua_ref_global(lstate, 3); + ui_add_cb(ns_id, ui_event_cb, ext_widgets); + return 0; +} + +/// "vim.ui_detach(ns_id)" function +static int nlua_ui_detach(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + uint32_t ns_id = (uint32_t)luaL_checkinteger(lstate, 1); + + if (!ns_initialized(ns_id)) { + return luaL_error(lstate, "invalid ns_id"); + } + + ui_remove_cb(ns_id); + return 0; +} + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -604,7 +716,7 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "debug"); lua_pop(lstate, 1); -#ifdef WIN32 +#ifdef MSWIN // os.getenv lua_getglobal(lstate, "os"); lua_pushcfunction(lstate, &nlua_getenv); @@ -649,6 +761,14 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, &nlua_wait); lua_setfield(lstate, -2, "wait"); + // ui_attach + lua_pushcfunction(lstate, &nlua_ui_attach); + lua_setfield(lstate, -2, "ui_attach"); + + // ui_detach + lua_pushcfunction(lstate, &nlua_ui_detach); + lua_setfield(lstate, -2, "ui_detach"); + nlua_common_vim_init(lstate, false); // patch require() (only for --startuptime) @@ -675,10 +795,8 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return true; } -/// Initialize global lua interpreter -/// -/// Crashes Nvim if initialization fails. -void nlua_init(void) +/// Initializes global Lua interpreter, or exits Nvim on failure. +void nlua_init(char **argv, int argc, int lua_arg0) { #ifdef NLUA_TRACK_REFS const char *env = os_getenv("NVIM_LUA_NOTRACK"); @@ -689,20 +807,19 @@ void nlua_init(void) lua_State *lstate = luaL_newstate(); if (lstate == NULL) { - mch_errmsg(_("E970: Failed to initialize lua interpreter\n")); + os_errmsg(_("E970: Failed to initialize lua interpreter\n")); os_exit(1); } luaL_openlibs(lstate); if (!nlua_state_init(lstate)) { - mch_errmsg(_("E970: Failed to initialize builtin lua modules\n")); + os_errmsg(_("E970: Failed to initialize builtin lua modules\n")); os_exit(1); } luv_set_thread_cb(nlua_thread_acquire_vm, nlua_common_free_all_mem); - global_lstate = lstate; - main_thread = uv_thread_self(); + nlua_init_argv(lstate, argv, argc, lua_arg0); } static lua_State *nlua_thread_acquire_vm(void) @@ -935,8 +1052,8 @@ static int nlua_require(lua_State *const lstate) time_push(&rel_time, &start_time); int status = lua_pcall(lstate, 1, 1, 0); if (status == 0) { - vim_snprintf((char *)IObuff, IOSIZE, "require('%s')", name); - time_msg((char *)IObuff, &start_time); + vim_snprintf(IObuff, IOSIZE, "require('%s')", name); + time_msg(IObuff, &start_time); } time_pop(rel_time); @@ -967,12 +1084,12 @@ static int nlua_debug(lua_State *lstate) if (input.v_type != VAR_STRING || input.vval.v_string == NULL || *input.vval.v_string == NUL - || STRCMP(input.vval.v_string, "cont") == 0) { + || strcmp(input.vval.v_string, "cont") == 0) { tv_clear(&input); return 0; } if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string, - STRLEN(input.vval.v_string), "=(debug command)")) { + strlen(input.vval.v_string), "=(debug command)")) { nlua_error(lstate, _("E5115: Error while loading debug string: %.*s")); } else if (nlua_pcall(lstate, 0, 0)) { nlua_error(lstate, _("E5116: Error while calling debug string: %.*s")); @@ -990,7 +1107,7 @@ int nlua_in_fast_event(lua_State *lstate) static bool viml_func_is_fast(const char *name) { - const EvalFuncDef *const fdef = find_internal_func((const char *)name); + const EvalFuncDef *const fdef = find_internal_func(name); if (fdef) { return fdef->fast; } @@ -1027,15 +1144,15 @@ int nlua_call(lua_State *lstate) // TODO(bfredl): this should be simplified in error handling refactor force_abort = false; suppress_errthrow = false; - current_exception = NULL; + did_throw = false; did_emsg = false; try_start(); typval_T rettv; funcexe_T funcexe = FUNCEXE_INIT; - funcexe.firstline = curwin->w_cursor.lnum; - funcexe.lastline = curwin->w_cursor.lnum; - funcexe.evaluate = true; + funcexe.fe_firstline = curwin->w_cursor.lnum; + funcexe.fe_lastline = curwin->w_cursor.lnum; + funcexe.fe_evaluate = true; // call_func() retval is deceptive, ignore it. Instead we set `msg_list` // (TRY_WRAP) to capture abort-causing non-exception errors. (void)call_func((char *)name, (int)name_len, &rettv, nargs, vim_args, &funcexe); @@ -1093,13 +1210,14 @@ static int nlua_rpc(lua_State *lstate, bool request) Object result = rpc_send_call(chan_id, name, args, &res_mem, &err); if (!ERROR_SET(&err)) { nlua_push_Object(lstate, result, false); - arena_mem_free(res_mem, NULL); + arena_mem_free(res_mem); } } else { if (!rpc_send_event(chan_id, name, args)) { api_set_error(&err, kErrorTypeValidation, "Invalid channel: %" PRIu64, chan_id); } + api_free_array(args); // TODO(bfredl): no } check_err: @@ -1124,7 +1242,7 @@ static int nlua_empty_dict_tostring(lua_State *lstate) return 1; } -#ifdef WIN32 +#ifdef MSWIN /// os.getenv: override os.getenv to maintain coherency. #9681 /// /// uv_os_setenv uses SetEnvironmentVariableW which does not update _environ. @@ -1224,7 +1342,7 @@ void nlua_typval_eval(const String str, typval_T *const arg, typval_T *const ret const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1; char *lcmd; if (lcmd_len < IOSIZE) { - lcmd = (char *)IObuff; + lcmd = IObuff; } else { lcmd = xmalloc(lcmd_len); } @@ -1234,7 +1352,7 @@ void nlua_typval_eval(const String str, typval_T *const arg, typval_T *const ret #undef EVALHEADER nlua_typval_exec(lcmd, lcmd_len, "luaeval()", arg, 1, true, ret_tv); - if (lcmd != (char *)IObuff) { + if (lcmd != IObuff) { xfree(lcmd); } } @@ -1248,7 +1366,7 @@ void nlua_typval_call(const char *str, size_t len, typval_T *const args, int arg const size_t lcmd_len = sizeof(CALLHEADER) - 1 + len + sizeof(CALLSUFFIX) - 1; char *lcmd; if (lcmd_len < IOSIZE) { - lcmd = (char *)IObuff; + lcmd = IObuff; } else { lcmd = xmalloc(lcmd_len); } @@ -1261,7 +1379,7 @@ void nlua_typval_call(const char *str, size_t len, typval_T *const args, int arg nlua_typval_exec(lcmd, lcmd_len, "v:lua", args, argcount, false, ret_tv); - if (lcmd != (char *)IObuff) { + if (lcmd != IObuff) { xfree(lcmd); } } @@ -1322,11 +1440,11 @@ int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name) estack_push(ETYPE_SCRIPT, name, 0); garray_T ga; - char_u *line = NULL; + char *line = NULL; - ga_init(&ga, (int)sizeof(char_u *), 10); - while ((line = (char_u *)fgetline(0, cookie, 0, false)) != NULL) { - GA_APPEND(char_u *, &ga, line); + ga_init(&ga, (int)sizeof(char *), 10); + while ((line = fgetline(0, cookie, 0, false)) != NULL) { + GA_APPEND(char *, &ga, line); } char *code = ga_concat_strings_sep(&ga, "\n"); size_t len = strlen(code); @@ -1348,23 +1466,22 @@ int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name) /// @param[in] argcount Count of typval arguments /// @param[in] argvars Typval Arguments /// @param[out] rettv The return value from the called function. -int typval_exec_lua_callable(lua_State *lstate, LuaCallable lua_cb, int argcount, typval_T *argvars, - typval_T *rettv) +int typval_exec_lua_callable(LuaRef lua_cb, int argcount, typval_T *argvars, typval_T *rettv) { - LuaRef cb = lua_cb.func_ref; + lua_State *lstate = global_lstate; - nlua_pushref(lstate, cb); + nlua_pushref(lstate, lua_cb); PUSH_ALL_TYPVALS(lstate, argvars, argcount, false); if (nlua_pcall(lstate, argcount, 1)) { nlua_print(lstate); - return ERROR_OTHER; + return FCERR_OTHER; } nlua_pop_typval(lstate, rettv); - return ERROR_NONE; + return FCERR_NONE; } /// Execute Lua string @@ -1422,9 +1539,10 @@ bool nlua_ref_is_function(LuaRef ref) /// @param name if non-NULL, sent to callback as first arg /// if NULL, only args are used /// @param retval if true, convert return value to Object -/// if false, discard return value +/// if false, only check if return value is truthy /// @param err Error details, if any (if NULL, errors are echoed) -/// @return Return value of function, if retval was set. Otherwise NIL. +/// @return Return value of function, if retval was set. Otherwise +/// BOOLEAN_OBJ(true) or NIL. Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Error *err) { lua_State *const lstate = global_lstate; @@ -1438,7 +1556,7 @@ Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Erro nlua_push_Object(lstate, args.items[i], false); } - if (nlua_pcall(lstate, nargs, retval ? 1 : 0)) { + if (nlua_pcall(lstate, nargs, 1)) { // if err is passed, the caller will deal with the error. if (err) { size_t len; @@ -1458,7 +1576,10 @@ Object nlua_call_ref(LuaRef ref, const char *name, Array args, bool retval, Erro } return nlua_pop_Object(lstate, false, err); } else { - return NIL; + bool value = lua_toboolean(lstate, -1); + lua_pop(lstate, 1); + + return value ? BOOLEAN_OBJ(true) : NIL; } } @@ -1524,7 +1645,7 @@ void ex_luado(exarg_T *const eap) + (sizeof(DOEND) - 1)); char *lcmd; if (lcmd_len < IOSIZE) { - lcmd = (char *)IObuff; + lcmd = IObuff; } else { lcmd = xmalloc(lcmd_len + 1); } @@ -1561,7 +1682,7 @@ void ex_luado(exarg_T *const eap) break; } if (lua_isstring(lstate, -1)) { - size_t old_line_len = STRLEN(old_line); + size_t old_line_len = strlen(old_line); size_t new_line_len; const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); @@ -1578,7 +1699,7 @@ void ex_luado(exarg_T *const eap) } lua_pop(lstate, 1); check_cursor(); - update_screen(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); } /// Run lua file @@ -1592,21 +1713,51 @@ void ex_luafile(exarg_T *const eap) nlua_exec_file((const char *)eap->arg); } -/// execute lua code from a file. +/// Executes Lua code from a file or "-" (stdin). /// -/// Note: we call the lua global loadfile as opposed to calling luaL_loadfile -/// in case loadfile has been overridden in the users environment. +/// Calls the Lua `loadfile` global as opposed to `luaL_loadfile` in case `loadfile` was overridden +/// in the user environment. /// -/// @param path path of the file +/// @param path Path to the file, may be "-" (stdin) during startup. /// -/// @return true if everything ok, false if there was an error (echoed) +/// @return true on success, false on error (echoed) or user canceled (CTRL-c) while reading "-" +/// (stdin). bool nlua_exec_file(const char *path) FUNC_ATTR_NONNULL_ALL { lua_State *const lstate = global_lstate; + if (!strequal(path, "-")) { + lua_getglobal(lstate, "loadfile"); + lua_pushstring(lstate, path); + } else { + FileDescriptor *stdin_dup = file_open_stdin(); + + StringBuilder sb = KV_INITIAL_VALUE; + kv_resize(sb, 64); + ptrdiff_t read_size = -1; + // Read all input from stdin, unless interrupted (ctrl-c). + while (true) { + if (got_int) { // User canceled. + return false; + } + read_size = file_read(stdin_dup, IObuff, 64); + if (read_size < 0) { // Error. + return false; + } + if (read_size > 0) { + kv_concat_len(sb, IObuff, (size_t)read_size); + } + if (read_size < 64) { // EOF. + break; + } + } + kv_push(sb, NUL); + file_free(stdin_dup, false); - lua_getglobal(lstate, "loadfile"); - lua_pushstring(lstate, path); + lua_getglobal(lstate, "loadstring"); + lua_pushstring(lstate, sb.items); + kv_destroy(sb); + } if (nlua_pcall(lstate, 1, 2)) { nlua_error(lstate, _("E5111: Error calling lua: %.*s")); @@ -1662,6 +1813,9 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, tslua_has_language); lua_setfield(lstate, -2, "_ts_has_language"); + lua_pushcfunction(lstate, tslua_remove_lang); + lua_setfield(lstate, -2, "_ts_remove_language"); + lua_pushcfunction(lstate, tslua_inspect_lang); lua_setfield(lstate, -2, "_ts_inspect_language"); @@ -1675,7 +1829,7 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_setfield(lstate, -2, "_ts_get_minimum_language_version"); } -int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char ***results) +int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results) { lua_State *const lstate = global_lstate; int ret = OK; @@ -1688,7 +1842,7 @@ int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char ***results luaL_checktype(lstate, -1, LUA_TFUNCTION); // [ vim, vim._expand_pat, buf ] - lua_pushlstring(lstate, (const char *)pat, STRLEN(pat)); + lua_pushlstring(lstate, (const char *)pat, strlen(pat)); if (nlua_pcall(lstate, 1, 2) != 0) { nlua_error(lstate, @@ -1723,7 +1877,7 @@ int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char ***results goto cleanup_array; } - GA_APPEND(char_u *, &result_array, (char_u *)string_to_cstr(v.data.string)); + GA_APPEND(char *, &result_array, string_to_cstr(v.data.string)); } xp->xp_pattern += prefix_len; @@ -1749,27 +1903,7 @@ static int nlua_is_thread(lua_State *lstate) return 1; } -// Required functions for lua c functions as VimL callbacks - -int nlua_CFunction_func_call(int argcount, typval_T *argvars, typval_T *rettv, void *state) -{ - lua_State *const lstate = global_lstate; - LuaCFunctionState *funcstate = (LuaCFunctionState *)state; - - return typval_exec_lua_callable(lstate, funcstate->lua_callable, - argcount, argvars, rettv); -} - -void nlua_CFunction_func_free(void *state) -{ - lua_State *const lstate = global_lstate; - LuaCFunctionState *funcstate = (LuaCFunctionState *)state; - - nlua_unref_global(lstate, funcstate->lua_callable.func_ref); - xfree(funcstate); -} - -bool nlua_is_table_from_lua(typval_T *const arg) +bool nlua_is_table_from_lua(const typval_T *const arg) { if (arg->v_type == VAR_DICT) { return arg->vval.v_dict->lua_table_ref != LUA_NOREF; @@ -1780,7 +1914,7 @@ bool nlua_is_table_from_lua(typval_T *const arg) } } -char_u *nlua_register_table_as_callable(typval_T *const arg) +char *nlua_register_table_as_callable(const typval_T *const arg) { LuaRef table_ref = LUA_NOREF; if (arg->v_type == VAR_DICT) { @@ -1814,11 +1948,9 @@ char_u *nlua_register_table_as_callable(typval_T *const arg) } lua_pop(lstate, 2); // [table] - LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); - state->lua_callable.func_ref = nlua_ref_global(lstate, -1); + LuaRef func = nlua_ref_global(lstate, -1); - char_u *name = register_cfunc(&nlua_CFunction_func_call, - &nlua_CFunction_func_free, state); + char *name = register_luafunc(func); lua_pop(lstate, 1); // [] assert(top == lua_gettop(lstate)); @@ -1828,8 +1960,8 @@ char_u *nlua_register_table_as_callable(typval_T *const arg) void nlua_execute_on_key(int c) { - char_u buf[NUMBUFLEN]; - size_t buf_len = special_to_buf(c, mod_mask, false, buf); + char buf[NUMBUFLEN]; + size_t buf_len = special_to_buf(c, mod_mask, false, (char_u *)buf); lua_State *const lstate = global_lstate; @@ -1845,7 +1977,7 @@ void nlua_execute_on_key(int c) luaL_checktype(lstate, -1, LUA_TFUNCTION); // [ vim, vim._on_key, buf ] - lua_pushlstring(lstate, (const char *)buf, buf_len); + lua_pushlstring(lstate, buf, buf_len); int save_got_int = got_int; got_int = false; // avoid interrupts when the key typed is Ctrl-C @@ -1924,6 +2056,9 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) nlua_pushref(lstate, preview ? cmd->uc_preview_luaref : cmd->uc_luaref); lua_newtable(lstate); + lua_pushstring(lstate, cmd->uc_name); + lua_setfield(lstate, -2, "name"); + lua_pushboolean(lstate, eap->forceit == 1); lua_setfield(lstate, -2, "bang"); @@ -1940,7 +2075,7 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) // Split args by unescaped whitespace |<f-args>| (nargs dependent) if (cmd->uc_argt & EX_NOSPC) { - if ((cmd->uc_argt & EX_NEEDARG) || STRLEN(eap->arg)) { + if ((cmd->uc_argt & EX_NEEDARG) || strlen(eap->arg)) { // For commands where nargs is 1 or "?" and argument is passed, fargs = { args } lua_rawseti(lstate, -2, 1); } else { @@ -1950,7 +2085,7 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) } else if (eap->args == NULL) { // For commands with more than one possible argument, split if argument list isn't available. lua_pop(lstate, 1); // Pop the reference of opts.args - size_t length = STRLEN(eap->arg); + size_t length = strlen(eap->arg); size_t end = 0; size_t len = 0; int i = 1; @@ -1975,7 +2110,8 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) } lua_setfield(lstate, -2, "fargs"); - lua_pushstring(lstate, (const char *)&eap->regname); + char reg[2] = { (char)eap->regname, NUL }; + lua_pushstring(lstate, reg); lua_setfield(lstate, -2, "reg"); lua_pushinteger(lstate, eap->addr_count); @@ -1998,7 +2134,7 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) lua_newtable(lstate); // smods table - lua_pushinteger(lstate, cmdmod.cmod_tab); + lua_pushinteger(lstate, cmdmod.cmod_tab - 1); lua_setfield(lstate, -2, "tab"); lua_pushinteger(lstate, cmdmod.cmod_verbose - 1); @@ -2019,6 +2155,8 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview) lua_pushboolean(lstate, cmdmod.cmod_split & WSP_VERT); lua_setfield(lstate, -2, "vertical"); + lua_pushboolean(lstate, cmdmod.cmod_split & WSP_HOR); + lua_setfield(lstate, -2, "horizontal"); lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_SILENT); lua_setfield(lstate, -2, "silent"); lua_pushboolean(lstate, cmdmod.cmod_flags & CMOD_ERRSILENT); @@ -2113,3 +2251,80 @@ plain: kv_printf(str, "<Lua %d>", ref); return str.items; } + +char *nlua_read_secure(const char *path) +{ + lua_State *const lstate = global_lstate; + const int top = lua_gettop(lstate); + + lua_getglobal(lstate, "vim"); + lua_getfield(lstate, -1, "secure"); + lua_getfield(lstate, -1, "read"); + lua_pushstring(lstate, path); + if (nlua_pcall(lstate, 1, 1)) { + nlua_error(lstate, _("Error executing vim.secure.read: %.*s")); + lua_settop(lstate, top); + return NULL; + } + + size_t len = 0; + const char *contents = lua_tolstring(lstate, -1, &len); + char *buf = NULL; + if (contents != NULL) { + // Add one to include trailing null byte + buf = xcalloc(len + 1, sizeof(char)); + memcpy(buf, contents, len + 1); + } + + lua_settop(lstate, top); + return buf; +} + +bool nlua_trust(const char *action, const char *path) +{ + lua_State *const lstate = global_lstate; + const int top = lua_gettop(lstate); + + lua_getglobal(lstate, "vim"); + lua_getfield(lstate, -1, "secure"); + lua_getfield(lstate, -1, "trust"); + + lua_newtable(lstate); + lua_pushstring(lstate, "action"); + lua_pushstring(lstate, action); + lua_settable(lstate, -3); + if (path == NULL) { + lua_pushstring(lstate, "bufnr"); + lua_pushnumber(lstate, 0); + lua_settable(lstate, -3); + } else { + lua_pushstring(lstate, "path"); + lua_pushstring(lstate, path); + lua_settable(lstate, -3); + } + + if (nlua_pcall(lstate, 1, 2)) { + nlua_error(lstate, _("Error executing vim.secure.trust: %.*s")); + lua_settop(lstate, top); + return false; + } + + bool success = lua_toboolean(lstate, -2); + const char *msg = lua_tostring(lstate, -1); + if (msg != NULL) { + if (success) { + if (strcmp(action, "allow") == 0) { + smsg("Allowed \"%s\" in trust database.", msg); + } else if (strcmp(action, "deny") == 0) { + smsg("Denied \"%s\" in trust database.", msg); + } else if (strcmp(action, "remove") == 0) { + smsg("Removed \"%s\" from trust database.", msg); + } + } else { + semsg(e_trustfile, msg); + } + } + + lua_settop(lstate, top); + return success; +} diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index 78346fd81f..c6747833e5 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -3,13 +3,17 @@ #include <lauxlib.h> #include <lua.h> +#include <stdbool.h> #include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" #include "nvim/assert.h" #include "nvim/eval/typval.h" #include "nvim/ex_cmds_defs.h" #include "nvim/func_attr.h" #include "nvim/lua/converter.h" +#include "nvim/macros.h" +#include "nvim/types.h" #include "nvim/usercmd.h" // Generated by msgpack-gen.lua diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c index 31a2b2d19f..d510d25e90 100644 --- a/src/nvim/lua/spell.c +++ b/src/nvim/lua/spell.c @@ -1,15 +1,25 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> #include <lauxlib.h> +#include <limits.h> #include <lua.h> - +#include <stdbool.h> +#include <stddef.h> + +#include "nvim/ascii.h" +#include "nvim/buffer_defs.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/lua/spell.h" +#include "nvim/message.h" #include "nvim/spell.h" -#include "nvim/vim.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "lua/spell.c.generated.h" +# include "lua/spell.c.generated.h" // IWYU pragma: export #endif int nlua_spell_check(lua_State *lstate) @@ -51,7 +61,7 @@ int nlua_spell_check(lua_State *lstate) while (*str != NUL) { attr = HLF_COUNT; - len = spell_check(curwin, (char_u *)str, &attr, &capcol, false); + len = spell_check(curwin, (char *)str, &attr, &capcol, false); assert(len <= INT_MAX); if (attr != HLF_COUNT) { diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index 5a82ae30b5..6ebca6d97e 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -1,62 +1,51 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> #include <lauxlib.h> #include <lua.h> -#include <lualib.h> - +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <sys/types.h> + +#include "auto/config.h" #include "cjson/lua_cjson.h" -#include "luv/luv.h" #include "mpack/lmpack.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/ascii.h" -#include "nvim/assert.h" #include "nvim/buffer_defs.h" -#include "nvim/change.h" -#include "nvim/cursor.h" #include "nvim/eval.h" -#include "nvim/eval/userfunc.h" -#include "nvim/event/loop.h" -#include "nvim/event/time.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_eval.h" -#include "nvim/ex_getln.h" -#include "nvim/extmark.h" -#include "nvim/func_attr.h" -#include "nvim/garray.h" -#include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/lua/converter.h" -#include "nvim/lua/executor.h" #include "nvim/lua/spell.h" #include "nvim/lua/stdlib.h" -#include "nvim/lua/treesitter.h" #include "nvim/lua/xdiff.h" -#include "nvim/macros.h" #include "nvim/map.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" -#include "nvim/message.h" -#include "nvim/msgpack_rpc/channel.h" -#include "nvim/os/os.h" +#include "nvim/memory.h" +#include "nvim/pos.h" #include "nvim/regexp.h" -#include "nvim/regexp_defs.h" -#include "nvim/screen.h" #include "nvim/types.h" -#include "nvim/undo.h" -#include "nvim/version.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/stdlib.c.generated.h" #endif -static int regex_match(lua_State *lstate, regprog_T **prog, char_u *str) +static int regex_match(lua_State *lstate, regprog_T **prog, char *str) { regmatch_T rm; rm.regprog = *prog; rm.rm_ic = false; - bool match = vim_regexec(&rm, (char *)str, 0); + bool match = vim_regexec(&rm, str, 0); *prog = rm.regprog; if (match) { @@ -71,7 +60,7 @@ static int regex_match_str(lua_State *lstate) { regprog_T **prog = regex_check(lstate); const char *str = luaL_checkstring(lstate, 2); - int nret = regex_match(lstate, prog, (char_u *)str); + int nret = regex_match(lstate, prog, (char *)str); if (!*prog) { return luaL_error(lstate, "regex: internal error"); @@ -111,14 +100,14 @@ static int regex_match_line(lua_State *lstate) return luaL_error(lstate, "invalid row"); } - char_u *line = ml_get_buf(buf, rownr + 1, false); - size_t len = STRLEN(line); + char *line = ml_get_buf(buf, rownr + 1, false); + size_t len = strlen(line); if (start < 0 || (size_t)start > len) { return luaL_error(lstate, "invalid start"); } - char_u save = NUL; + char save = NUL; if (end >= 0) { if ((size_t)end > len || end < start) { return luaL_error(lstate, "invalid end"); @@ -187,7 +176,7 @@ int nlua_str_utfindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL } size_t codepoints = 0, codeunits = 0; - mb_utflen((const char_u *)s1, (size_t)idx, &codepoints, &codeunits); + mb_utflen(s1, (size_t)idx, &codepoints, &codeunits); lua_pushinteger(lstate, (long)codepoints); lua_pushinteger(lstate, (long)codeunits); @@ -209,7 +198,7 @@ static int nlua_str_utf_pos(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL size_t idx = 1; size_t clen; for (size_t i = 0; i < s1_len && s1[i] != NUL; i += clen) { - clen = (size_t)utf_ptr2len_len((const char_u *)(s1) + i, (int)(s1_len - i)); + clen = (size_t)utf_ptr2len_len(s1 + i, (int)(s1_len - i)); lua_pushinteger(lstate, (long)i + 1); lua_rawseti(lstate, -2, (int)idx); idx++; @@ -277,8 +266,7 @@ int nlua_str_byteindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL use_utf16 = lua_toboolean(lstate, 3); } - ssize_t byteidx = mb_utf_index_to_bytes((const char_u *)s1, s1_len, - (size_t)idx, use_utf16); + ssize_t byteidx = mb_utf_index_to_bytes(s1, s1_len, (size_t)idx, use_utf16); if (byteidx == -1) { return luaL_error(lstate, "index out of range"); } @@ -304,8 +292,10 @@ int nlua_regex(lua_State *lstate) nlua_push_errstr(lstate, "couldn't parse regex: %s", err.msg); api_clear_error(&err); return lua_error(lstate); + } else if (prog == NULL) { + nlua_push_errstr(lstate, "couldn't parse regex"); + return lua_error(lstate); } - assert(prog); regprog_T **p = lua_newuserdata(lstate, sizeof(regprog_T *)); *p = prog; @@ -369,15 +359,21 @@ int nlua_setvar(lua_State *lstate) return 0; } + bool watched = tv_dict_is_watched(dict); + if (del) { // Delete the key if (di == NULL) { // Doesn't exist, nothing to do return 0; - } else { - // Delete the entry - tv_dict_item_remove(dict, di); } + // Notify watchers + if (watched) { + tv_dict_watcher_notify(dict, key.data, NULL, &di->di_tv); + } + + // Delete the entry + tv_dict_item_remove(dict, di); } else { // Update the key typval_T tv; @@ -388,17 +384,29 @@ int nlua_setvar(lua_State *lstate) return luaL_error(lstate, "Couldn't convert lua value"); } + typval_T oldtv = TV_INITIAL_VALUE; + if (di == NULL) { // Need to create an entry di = tv_dict_item_alloc_len(key.data, key.size); tv_dict_add(dict, di); } else { + if (watched) { + tv_copy(&di->di_tv, &oldtv); + } // Clear the old value tv_clear(&di->di_tv); } // Update the value tv_copy(&tv, &di->di_tv); + + // Notify watchers + if (watched) { + tv_dict_watcher_notify(dict, key.data, &tv, &oldtv); + tv_clear(&oldtv); + } + // Clear the temporary variable tv_clear(&tv); } @@ -474,6 +482,48 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return 1; } +/// Convert string from one encoding to another +static int nlua_iconv(lua_State *lstate) +{ + int narg = lua_gettop(lstate); + + if (narg < 3) { + return luaL_error(lstate, "Expected at least 3 arguments"); + } + + for (int i = 1; i <= 3; i++) { + if (lua_type(lstate, i) != LUA_TSTRING) { + return luaL_argerror(lstate, i, "expected string"); + } + } + + size_t str_len = 0; + const char *str = lua_tolstring(lstate, 1, &str_len); + + char *from = enc_canonize(enc_skip((char *)lua_tolstring(lstate, 2, NULL))); + char *to = enc_canonize(enc_skip((char *)lua_tolstring(lstate, 3, NULL))); + + vimconv_T vimconv; + vimconv.vc_type = CONV_NONE; + convert_setup_ext(&vimconv, from, false, to, false); + + char *ret = string_convert(&vimconv, (char *)str, &str_len); + + convert_setup(&vimconv, NULL, NULL); + + xfree(from); + xfree(to); + + if (ret == NULL) { + lua_pushnil(lstate); + } else { + lua_pushlstring(lstate, ret, str_len); + xfree(ret); + } + + return 1; +} + void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) { if (!is_thread) { @@ -519,6 +569,11 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) // vim.spell luaopen_spell(lstate); lua_setfield(lstate, -2, "spell"); + + // vim.iconv + // depends on p_ambw, p_emoji + lua_pushcfunction(lstate, &nlua_iconv); + lua_setfield(lstate, -2, "iconv"); } // vim.mpack diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index f0d847e352..56f4daed1a 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -6,20 +6,28 @@ // trees and nodes, and could be broken out as a reusable lua package #include <assert.h> -#include <inttypes.h> #include <lauxlib.h> +#include <limits.h> #include <lua.h> -#include <lualib.h> #include <stdbool.h> #include <stdint.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <uv.h> +#include "klib/kvec.h" #include "nvim/api/private/helpers.h" -#include "nvim/buffer.h" -#include "nvim/lib/kvec.h" +#include "nvim/buffer_defs.h" +#include "nvim/globals.h" #include "nvim/lua/treesitter.h" +#include "nvim/macros.h" +#include "nvim/map.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/pos.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "tree_sitter/api.h" #define TS_META_PARSER "treesitter_parser" @@ -85,6 +93,10 @@ static struct luaL_Reg node_meta[] = { { "prev_sibling", node_prev_sibling }, { "next_named_sibling", node_next_named_sibling }, { "prev_named_sibling", node_prev_named_sibling }, + { "named_children", node_named_children }, + { "root", node_root }, + { "byte_length", node_byte_length }, + { NULL, NULL } }; @@ -145,40 +157,50 @@ int tslua_has_language(lua_State *L) return 1; } +// Creates the language into the internal language map. +// +// Returns true if the language is correctly loaded in the language map int tslua_add_language(lua_State *L) { const char *path = luaL_checkstring(L, 1); const char *lang_name = luaL_checkstring(L, 2); + const char *symbol_name = lang_name; + + if (lua_gettop(L) >= 3 && !lua_isnil(L, 3)) { + symbol_name = luaL_checkstring(L, 3); + } if (pmap_has(cstr_t)(&langs, lang_name)) { - return 0; + lua_pushboolean(L, true); + return 1; } #define BUFSIZE 128 char symbol_buf[BUFSIZE]; - snprintf(symbol_buf, BUFSIZE, "tree_sitter_%s", lang_name); + snprintf(symbol_buf, BUFSIZE, "tree_sitter_%s", symbol_name); #undef BUFSIZE uv_lib_t lib; if (uv_dlopen(path, &lib)) { - snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlopen: %s", - uv_dlerror(&lib)); + snprintf(IObuff, IOSIZE, "Failed to load parser for language '%s': uv_dlopen: %s", + lang_name, uv_dlerror(&lib)); uv_dlclose(&lib); - lua_pushstring(L, (char *)IObuff); + lua_pushstring(L, IObuff); return lua_error(L); } TSLanguage *(*lang_parser)(void); if (uv_dlsym(&lib, symbol_buf, (void **)&lang_parser)) { - snprintf((char *)IObuff, IOSIZE, "Failed to load parser: uv_dlsym: %s", + snprintf(IObuff, IOSIZE, "Failed to load parser: uv_dlsym: %s", uv_dlerror(&lib)); uv_dlclose(&lib); - lua_pushstring(L, (char *)IObuff); + lua_pushstring(L, IObuff); return lua_error(L); } TSLanguage *lang = lang_parser(); if (lang == NULL) { + uv_dlclose(&lib); return luaL_error(L, "Failed to load parser %s: internal error", path); } @@ -198,6 +220,19 @@ int tslua_add_language(lua_State *L) return 1; } +int tslua_remove_lang(lua_State *L) +{ + const char *lang_name = luaL_checkstring(L, 1); + bool present = pmap_has(cstr_t)(&langs, lang_name); + if (present) { + char *key = (char *)pmap_key(cstr_t)(&langs, lang_name); + pmap_del(cstr_t)(&langs, lang_name); + xfree(key); + } + lua_pushboolean(L, present); + return 1; +} + int tslua_inspect_lang(lua_State *L) { const char *lang_name = luaL_checkstring(L, 1); @@ -302,8 +337,8 @@ static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position *bytes_read = 0; return ""; } - char *line = (char *)ml_get_buf(bp, (linenr_T)position.row + 1, false); - size_t len = STRLEN(line); + char *line = ml_get_buf(bp, (linenr_T)position.row + 1, false); + size_t len = strlen(line); if (position.column > len) { *bytes_read = 0; return ""; @@ -593,7 +628,7 @@ void push_tree(lua_State *L, TSTree *tree, bool do_copy) lua_setfenv(L, -2); // [udata] } -static TSTree **tree_check(lua_State *L, uint16_t index) +static TSTree **tree_check(lua_State *L, int index) { TSTree **ud = luaL_checkudata(L, index, TS_META_TREE); return ud; @@ -804,7 +839,7 @@ static int node_field(lua_State *L) do { const char *current_field = ts_tree_cursor_current_field_name(&cursor); - if (current_field != NULL && !STRCMP(field_name, current_field)) { + if (current_field != NULL && !strcmp(field_name, current_field)) { push_node(L, ts_tree_cursor_current_node(&cursor), 1); // [table, node] lua_rawseti(L, -2, (int)++curr_index); } @@ -1036,6 +1071,57 @@ static int node_prev_named_sibling(lua_State *L) return 1; } +static int node_named_children(lua_State *L) +{ + TSNode source; + if (!node_check(L, 1, &source)) { + return 0; + } + TSTreeCursor cursor = ts_tree_cursor_new(source); + + lua_newtable(L); + int curr_index = 0; + + if (ts_tree_cursor_goto_first_child(&cursor)) { + do { + TSNode node = ts_tree_cursor_current_node(&cursor); + if (ts_node_is_named(node)) { + push_node(L, node, 1); + lua_rawseti(L, -2, ++curr_index); + } + } while (ts_tree_cursor_goto_next_sibling(&cursor)); + } + + ts_tree_cursor_delete(&cursor); + return 1; +} + +static int node_root(lua_State *L) +{ + TSNode node; + if (!node_check(L, 1, &node)) { + return 0; + } + + TSNode root = ts_tree_root_node(node.tree); + push_node(L, root, 1); + return 1; +} + +static int node_byte_length(lua_State *L) +{ + TSNode node; + if (!node_check(L, 1, &node)) { + return 0; + } + + uint32_t start_byte = ts_node_start_byte(node); + uint32_t end_byte = ts_node_end_byte(node); + + lua_pushnumber(L, end_byte - start_byte); + return 1; +} + /// assumes the match table being on top of the stack static void set_match(lua_State *L, TSQueryMatch *match, int nodeidx) { @@ -1195,8 +1281,8 @@ int tslua_parse_query(lua_State *L) TSQuery *query = ts_query_new(lang, src, (uint32_t)len, &error_offset, &error_type); if (!query) { - return luaL_error(L, "query: %s at position %d", - query_err_string(error_type), (int)error_offset); + return luaL_error(L, "query: %s at position %d for language %s", + query_err_string(error_type), (int)error_offset, lang_name); } TSQuery **ud = lua_newuserdata(L, sizeof(TSQuery *)); // [udata] @@ -1288,7 +1374,7 @@ static int query_inspect(lua_State *L) lua_rawseti(L, -2, nextitem++); // [retval, patterns, pat, pred] } // last predicate should have ended with TypeDone - lua_pop(L, 1); // [retval, patters, pat] + lua_pop(L, 1); // [retval, patterns, pat] lua_rawseti(L, -2, (int)i + 1); // [retval, patterns] } lua_setfield(L, -2, "patterns"); // [retval] diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c index b2b5dfedee..857b159af5 100644 --- a/src/nvim/lua/xdiff.c +++ b/src/nvim/lua/xdiff.c @@ -1,21 +1,26 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <errno.h> #include <lauxlib.h> #include <lua.h> -#include <lualib.h> -#include <stdio.h> -#include <stdlib.h> +#include <stdbool.h> #include <string.h> +#include "luaconf.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/linematch.h" #include "nvim/lua/converter.h" #include "nvim/lua/executor.h" #include "nvim/lua/xdiff.h" +#include "nvim/macros.h" +#include "nvim/memory.h" #include "nvim/vim.h" #include "xdiff/xdiff.h" +#define COMPARED_BUFFER0 (1 << 0) +#define COMPARED_BUFFER1 (1 << 1) + typedef enum { kNluaXdiffModeUnified = 0, kNluaXdiffModeOnHunkCB, @@ -25,12 +30,81 @@ typedef enum { typedef struct { lua_State *lstate; Error *err; + mmfile_t *ma; + mmfile_t *mb; + bool linematch; + bool iwhite; } hunkpriv_t; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/xdiff.c.generated.h" #endif +static void lua_pushhunk(lua_State *lstate, long start_a, long count_a, long start_b, long count_b) +{ + // Mimic extra offsets done by xdiff, see: + // src/xdiff/xemit.c:284 + // src/xdiff/xutils.c:(356,368) + if (count_a > 0) { + start_a += 1; + } + if (count_b > 0) { + start_b += 1; + } + lua_createtable(lstate, 0, 0); + lua_pushinteger(lstate, start_a); + lua_rawseti(lstate, -2, 1); + lua_pushinteger(lstate, count_a); + lua_rawseti(lstate, -2, 2); + lua_pushinteger(lstate, start_b); + lua_rawseti(lstate, -2, 3); + lua_pushinteger(lstate, count_b); + lua_rawseti(lstate, -2, 4); + lua_rawseti(lstate, -2, (signed)lua_objlen(lstate, -2) + 1); +} + +static void get_linematch_results(lua_State *lstate, mmfile_t *ma, mmfile_t *mb, long start_a, + long count_a, long start_b, long count_b, bool iwhite) +{ + // get the pointer to char of the start of the diff to pass it to linematch algorithm + const char *diff_begin[2] = { ma->ptr, mb->ptr }; + int diff_length[2] = { (int)count_a, (int)count_b }; + + fastforward_buf_to_lnum(&diff_begin[0], start_a + 1); + fastforward_buf_to_lnum(&diff_begin[1], start_b + 1); + + int *decisions = NULL; + size_t decisions_length = linematch_nbuffers(diff_begin, diff_length, 2, &decisions, iwhite); + + long lnuma = start_a, lnumb = start_b; + + long hunkstarta = lnuma; + long hunkstartb = lnumb; + long hunkcounta = 0; + long hunkcountb = 0; + for (size_t i = 0; i < decisions_length; i++) { + if (i && (decisions[i - 1] != decisions[i])) { + lua_pushhunk(lstate, hunkstarta, hunkcounta, hunkstartb, hunkcountb); + + hunkstarta = lnuma; + hunkstartb = lnumb; + hunkcounta = 0; + hunkcountb = 0; + // create a new hunk + } + if (decisions[i] & COMPARED_BUFFER0) { + lnuma++; + hunkcounta++; + } + if (decisions[i] & COMPARED_BUFFER1) { + lnumb++; + hunkcountb++; + } + } + lua_pushhunk(lstate, hunkstarta, hunkcounta, hunkstartb, hunkcountb); + xfree(decisions); +} + static int write_string(void *priv, mmbuffer_t *mb, int nbuf) { luaL_Buffer *buf = (luaL_Buffer *)priv; @@ -52,30 +126,15 @@ static int write_string(void *priv, mmbuffer_t *mb, int nbuf) // hunk_func callback used when opts.hunk_lines = true static int hunk_locations_cb(long start_a, long count_a, long start_b, long count_b, void *cb_data) { - // Mimic extra offsets done by xdiff, see: - // src/xdiff/xemit.c:284 - // src/xdiff/xutils.c:(356,368) - if (count_a > 0) { - start_a += 1; - } - if (count_b > 0) { - start_b += 1; + hunkpriv_t *priv = (hunkpriv_t *)cb_data; + lua_State *lstate = priv->lstate; + if (priv->linematch) { + get_linematch_results(lstate, priv->ma, priv->mb, start_a, count_a, start_b, count_b, + priv->iwhite); + } else { + lua_pushhunk(lstate, start_a, count_a, start_b, count_b); } - lua_State *lstate = (lua_State *)cb_data; - lua_createtable(lstate, 0, 0); - - lua_pushinteger(lstate, start_a); - lua_rawseti(lstate, -2, 1); - lua_pushinteger(lstate, count_a); - lua_rawseti(lstate, -2, 2); - lua_pushinteger(lstate, start_b); - lua_rawseti(lstate, -2, 3); - lua_pushinteger(lstate, count_b); - lua_rawseti(lstate, -2, 4); - - lua_rawseti(lstate, -2, (signed)lua_objlen(lstate, -2) + 1); - return 0; } @@ -149,7 +208,7 @@ static bool check_xdiff_opt(ObjectType actType, ObjectType expType, const char * } static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, xpparam_t *params, - Error *err) + bool *linematch, Error *err) { const DictionaryOf(LuaRef) opts = nlua_pop_Dictionary(lstate, true, err); @@ -205,6 +264,11 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, goto exit_1; } cfg->interhunkctxlen = v->data.integer; + } else if (strequal("linematch", k.data)) { + *linematch = api_object_to_bool(*v, "linematch", false, err); + if (ERROR_SET(err)) { + goto exit_1; + } } else { struct { const char *name; @@ -244,10 +308,8 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, if (had_on_hunk) { mode = kNluaXdiffModeOnHunkCB; - cfg->hunk_func = call_on_hunk_cb; } else if (had_result_type_indices) { mode = kNluaXdiffModeLocations; - cfg->hunk_func = hunk_locations_cb; } exit_1: @@ -268,6 +330,7 @@ int nlua_xdl_diff(lua_State *lstate) xdemitconf_t cfg; xpparam_t params; xdemitcb_t ecb; + bool linematch = false; CLEAR_FIELD(cfg); CLEAR_FIELD(params); @@ -280,7 +343,7 @@ int nlua_xdl_diff(lua_State *lstate) return luaL_argerror(lstate, 3, "expected table"); } - mode = process_xdl_diff_opts(lstate, &cfg, ¶ms, &err); + mode = process_xdl_diff_opts(lstate, &cfg, ¶ms, &linematch, &err); if (ERROR_SET(&err)) { goto exit_0; @@ -288,7 +351,7 @@ int nlua_xdl_diff(lua_State *lstate) } luaL_Buffer buf; - hunkpriv_t *priv = NULL; + hunkpriv_t priv; switch (mode) { case kNluaXdiffModeUnified: luaL_buffinit(lstate, &buf); @@ -296,14 +359,24 @@ int nlua_xdl_diff(lua_State *lstate) ecb.out_line = write_string; break; case kNluaXdiffModeOnHunkCB: - priv = xmalloc(sizeof(*priv)); - priv->lstate = lstate; - priv->err = &err; - ecb.priv = priv; + cfg.hunk_func = call_on_hunk_cb; + priv = (hunkpriv_t) { + .lstate = lstate, + .err = &err, + }; + ecb.priv = &priv; break; case kNluaXdiffModeLocations: + cfg.hunk_func = hunk_locations_cb; + priv = (hunkpriv_t) { + .lstate = lstate, + .ma = &ma, + .mb = &mb, + .linematch = linematch, + .iwhite = (params.flags & XDF_IGNORE_WHITESPACE) > 0 + }; + ecb.priv = &priv; lua_createtable(lstate, 0, 0); - ecb.priv = lstate; break; } @@ -314,8 +387,6 @@ int nlua_xdl_diff(lua_State *lstate) } } - XFREE_CLEAR(priv); - exit_0: if (ERROR_SET(&err)) { luaL_where(lstate, 1); diff --git a/src/nvim/macros.h b/src/nvim/macros.h index a896a406d1..242e3c381a 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -11,7 +11,6 @@ #else # ifndef INIT # define INIT(...) __VA_ARGS__ -# define COMMA , # endif #endif @@ -31,7 +30,7 @@ /// @return `s, sizeof(s) - 1` #define S_LEN(s) (s), (sizeof(s) - 1) -/// LINEEMPTY() - return TRUE if the line is empty +/// LINEEMPTY() - return true if the line is empty #define LINEEMPTY(p) (*ml_get(p) == NUL) // toupper() and tolower() that use the current locale. @@ -54,7 +53,7 @@ #define ASCII_ISALNUM(c) (ASCII_ISALPHA(c) || ascii_isdigit(c)) // Returns empty string if it is NULL. -#define EMPTY_IF_NULL(x) (char *)((x) ? (x) : (char_u *)"") +#define EMPTY_IF_NULL(x) ((x) ? (x) : "") /// Adjust chars in a language according to 'langmap' option. /// NOTE that there is no noticeable overhead if 'langmap' is not set. @@ -86,7 +85,7 @@ // mch_open_rw(): invoke os_open() with third argument for user R/W. #if defined(UNIX) // open in rw------- mode # define MCH_OPEN_RW(n, f) os_open((n), (f), (mode_t)0600) -#elif defined(WIN32) +#elif defined(MSWIN) # define MCH_OPEN_RW(n, f) os_open((n), (f), S_IREAD | S_IWRITE) #else # define MCH_OPEN_RW(n, f) os_open((n), (f), 0) @@ -104,7 +103,7 @@ // MB_PTR_BACK(): backup a pointer to the previous character, taking care of // multi-byte characters if needed. Only use with "p" > "s" ! #define MB_PTR_BACK(s, p) \ - (p -= utf_head_off((char_u *)(s), (char_u *)(p) - 1) + 1) + (p -= utf_head_off((char *)(s), (char *)(p) - 1) + 1) // MB_CHAR2BYTES(): convert character to bytes and advance pointer to bytes #define MB_CHAR2BYTES(c, b) ((b) += utf_char2bytes((c), ((char *)b))) @@ -178,14 +177,14 @@ // Type of uv_buf_t.len is platform-dependent. // Related: https://github.com/libuv/libuv/pull/1236 -#if defined(WIN32) +#if defined(MSWIN) # define UV_BUF_LEN(x) (ULONG)(x) #else # define UV_BUF_LEN(x) (x) #endif // Type of read()/write() `count` param is platform-dependent. -#if defined(WIN32) +#if defined(MSWIN) # define IO_COUNT(x) (unsigned)(x) #else # define IO_COUNT(x) (x) diff --git a/src/nvim/main.c b/src/nvim/main.c index d06b475934..bbe877356d 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -3,47 +3,51 @@ #define EXTERN #include <assert.h> -#include <msgpack.h> +#include <limits.h> +#include <msgpack/pack.h> #include <stdbool.h> #include <stdint.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include "auto/config.h" #include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/channel.h" -#include "nvim/charset.h" #include "nvim/decoration.h" #include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/event/multiqueue.h" +#include "nvim/event/stream.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/garray.h" +#include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid.h" #include "nvim/hashtab.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" -#include "nvim/iconv.h" -#include "nvim/if_cscope.h" -#include "nvim/insexpand.h" +#include "nvim/keycodes.h" +#include "nvim/locale.h" +#include "nvim/log.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/mapping.h" -#include "nvim/ui_client.h" -#include "nvim/vim.h" -#ifdef HAVE_LOCALE_H -# include <locale.h> -#endif -#include "nvim/garray.h" -#include "nvim/grid.h" -#include "nvim/log.h" #include "nvim/mark.h" -#include "nvim/mbyte.h" +#include "nvim/memfile_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" @@ -52,32 +56,37 @@ #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/option_defs.h" #include "nvim/optionstr.h" #include "nvim/os/fileio.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/os_defs.h" +#include "nvim/os/stdpaths_defs.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/quickfix.h" #include "nvim/runtime.h" #include "nvim/shada.h" #include "nvim/sign.h" -#include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" +#include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" +#include "nvim/ui_client.h" #include "nvim/ui_compositor.h" #include "nvim/version.h" +#include "nvim/vim.h" #include "nvim/window.h" -#ifdef WIN32 +#ifdef MSWIN # include "nvim/os/os_win_console.h" #endif +#include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" -#include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/ui.h" #include "nvim/event/loop.h" @@ -86,22 +95,22 @@ #include "nvim/msgpack_rpc/helpers.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/os/signal.h" -#ifndef WIN32 -# include "nvim/os/pty_process_unix.h" -#endif -#include "nvim/api/extmark.h" // values for "window_layout" -#define WIN_HOR 1 // "-o" horizontally split windows -#define WIN_VER 2 // "-O" vertically split windows -#define WIN_TABS 3 // "-p" windows on tab pages +enum { + WIN_HOR = 1, // "-o" horizontally split windows + WIN_VER = 2, // "-O" vertically split windows + WIN_TABS = 3, // "-p" windows on tab pages +}; // Values for edit_type. -#define EDIT_NONE 0 // no edit type yet -#define EDIT_FILE 1 // file name argument[s] given, use argument list -#define EDIT_STDIN 2 // read file from stdin -#define EDIT_TAG 3 // tag name argument given, use tagname -#define EDIT_QF 4 // start in quickfix mode +enum { + EDIT_NONE = 0, // no edit type yet + EDIT_FILE = 1, // file name argument[s] given, use argument list + EDIT_STDIN = 2, // read file from stdin + EDIT_TAG = 3, // tag name argument given, use tagname + EDIT_QF = 4, // start in quickfix mode +}; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "main.c.generated.h" @@ -170,7 +179,7 @@ void early_init(mparm_T *paramp) runtime_init(); highlight_init(); -#ifdef WIN32 +#ifdef MSWIN OSVERSIONINFO ovi; ovi.dwOSVersionInfoSize = sizeof(ovi); GetVersionEx(&ovi); @@ -187,11 +196,11 @@ void early_init(mparm_T *paramp) init_locale(); #endif - // Allocate the first window and buffer. - // Can't do anything without it, exit when it fails. - if (!win_alloc_first()) { - os_exit(0); - } + // tabpage local options (p_ch) must be set before allocating first tabpage. + set_init_tablocal(); + + // Allocate the first tabpage, window and buffer. + win_alloc_first(); TIME_MSG("init first window"); alist_init(&global_alist); // Init the argument list to empty. @@ -212,13 +221,13 @@ void early_init(mparm_T *paramp) #ifdef MAKE_LIB int nvim_main(int argc, char **argv); // silence -Wmissing-prototypes int nvim_main(int argc, char **argv) -#elif defined(WIN32) +#elif defined(MSWIN) int wmain(int argc, wchar_t **argv_w) // multibyte args on Windows. #7060 #else int main(int argc, char **argv) #endif { -#if defined(WIN32) && !defined(MAKE_LIB) +#if defined(MSWIN) && !defined(MAKE_LIB) char **argv = xmalloc((size_t)argc * sizeof(char *)); for (int i = 0; i < argc; i++) { char *buf = NULL; @@ -230,10 +239,10 @@ int main(int argc, char **argv) argv0 = argv[0]; - char_u *fname = NULL; // file name from command line + char *fname = NULL; // file name from command line mparm_T params; // various parameters passed between // main() and other functions. - char_u *cwd = NULL; // current working dir on startup + char *cwd = NULL; // current working dir on startup time_init(); // Many variables are in `params` so that we can pass them around easily. @@ -267,8 +276,7 @@ int main(int argc, char **argv) // argument list "global_alist". command_line_scan(¶ms); - nlua_init(); - + nlua_init(argv, argc, params.lua_arg0); TIME_MSG("init lua interpreter"); if (embedded_mode) { @@ -278,9 +286,35 @@ int main(int argc, char **argv) } } - server_init(params.listen_addr); +#ifdef MSWIN + // on windows we use CONIN special file, thus we don't know this yet. + bool has_term = true; +#else + bool has_term = (stdin_isatty || stdout_isatty || stderr_isatty); +#endif + bool use_builtin_ui = (has_term && !headless_mode && !embedded_mode && !silent_mode); + + // don't bind the server yet, if we are using builtin ui. + // This will be done when nvim server has been forked from the ui process + if (!use_builtin_ui) { + server_init(params.listen_addr); + } + if (params.remote) { - remote_request(¶ms, params.remote, params.server_addr, argc, argv); + remote_request(¶ms, params.remote, params.server_addr, argc, argv, + use_builtin_ui); + } + + bool remote_ui = (ui_client_channel_id != 0); + + if (use_builtin_ui && !remote_ui) { + ui_client_forward_stdin = !stdin_isatty; + uint64_t rv = ui_client_start_server(params.argc, params.argv); + if (!rv) { + os_errmsg("Failed to start Nvim server!\n"); + getout(1); + } + ui_client_channel_id = rv; } if (GARGCOUNT > 0) { @@ -316,7 +350,7 @@ int main(int argc, char **argv) assert(p_ch >= 0 && Rows >= p_ch && Rows - p_ch <= INT_MAX); cmdline_row = (int)(Rows - p_ch); msg_row = cmdline_row; - screenalloc(); // allocate screen buffers + default_grid_alloc(); // allocate screen buffers set_init_2(headless_mode); TIME_MSG("inits 2"); @@ -331,57 +365,57 @@ int main(int argc, char **argv) debug_break_level = params.use_debug_break_level; // Read ex-commands if invoked with "-es". - if (!params.input_isatty && !params.input_neverscript - && silent_mode && exmode_active) { - input_start(STDIN_FILENO); + if (!stdin_isatty && !params.input_istext && silent_mode && exmode_active) { + input_start(); + } + + if (ui_client_channel_id) { + ui_client_run(remote_ui); // NORETURN } // Wait for UIs to set up Nvim or show early messages // and prompts (--cmd, swapfile dialog, …). bool use_remote_ui = (embedded_mode && !headless_mode); - bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); - if (use_remote_ui || use_builtin_ui) { + if (use_remote_ui) { TIME_MSG("waiting for UI"); - if (use_remote_ui) { - remote_ui_wait_for_attach(); - } else { - ui_builtin_start(); - } + remote_ui_wait_for_attach(); TIME_MSG("done waiting for UI"); - - // prepare screen now, so external UIs can display messages - starting = NO_BUFFERS; - screenclear(); - TIME_MSG("init screen for UI"); + firstwin->w_prev_height = firstwin->w_height; // may have changed } - if (ui_client_channel_id) { - ui_client_init(ui_client_channel_id); - ui_client_execute(ui_client_channel_id); - abort(); // unreachable + // prepare screen now + starting = NO_BUFFERS; + screenclear(); + win_new_screensize(); + TIME_MSG("clear screen"); + + // Handle "foo | nvim". EDIT_FILE may be overwritten now. #6299 + if (edit_stdin(¶ms)) { + params.edit_type = EDIT_STDIN; } + open_script_files(¶ms); + // Default mappings (incl. menus) Error err = ERROR_INIT; - Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim._init_default_mappings()"), - (Array)ARRAY_DICT_INIT, &err); + Object o = NLUA_EXEC_STATIC("return vim._init_default_mappings()", + (Array)ARRAY_DICT_INIT, &err); assert(!ERROR_SET(&err)); api_clear_error(&err); assert(o.type == kObjectTypeNil); api_free_object(o); + TIME_MSG("init default mappings"); init_default_autocmds(); TIME_MSG("init default autocommands"); - bool vimrc_none = params.use_vimrc != NULL && strequal(params.use_vimrc, "NONE"); + bool vimrc_none = strequal(params.use_vimrc, "NONE"); // Reset 'loadplugins' for "-u NONE" before "--cmd" arguments. // Allows for setting 'loadplugins' there. - if (vimrc_none) { - // When using --clean we still want to load plugins - p_lpl = params.clean; - } + // For --clean we still want to load plugins. + p_lpl = vimrc_none ? params.clean : p_lpl; // Execute --cmd arguments. exe_pre_commands(¶ms); @@ -434,10 +468,8 @@ int main(int argc, char **argv) p_ut = 1; } - // // Read in registers, history etc, from the ShaDa file. // This is where v:oldfiles gets filled. - // if (*p_shada != NUL) { shada_read_everything(NULL, false, true); TIME_MSG("reading ShaDa"); @@ -470,20 +502,13 @@ int main(int argc, char **argv) // writing end of the pipe doesn't like, e.g., in case stdin and stderr // are the same terminal: "cat | vim -". // Using autocommands here may cause trouble... - if ((params.edit_type == EDIT_STDIN || stdin_fd >= 0) && !recoverymode) { + if (params.edit_type == EDIT_STDIN && !recoverymode) { read_stdin(); } setmouse(); // may start using the mouse - if (exmode_active || use_remote_ui || use_builtin_ui) { - // Don't clear the screen when starting in Ex mode, or when a UI might have - // displayed messages. - redraw_later(curwin, VALID); - } else { - screenclear(); // clear screen - TIME_MSG("clearing screen"); - } + redraw_later(curwin, UPD_VALID); no_wait_return = true; @@ -508,7 +533,7 @@ int main(int argc, char **argv) // When started with "-q errorfile" jump to first error now. if (params.edit_type == EDIT_QF) { - qf_jump(NULL, 0, 0, FALSE); + qf_jump(NULL, 0, 0, false); TIME_MSG("jump to first error"); } @@ -520,7 +545,9 @@ int main(int argc, char **argv) if (params.diff_mode) { // set options in each window for "nvim -d". FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - diff_win_options(wp, TRUE); + if (!wp->w_arg_idx_invalid) { + diff_win_options(wp, true); + } } } @@ -529,7 +556,7 @@ int main(int argc, char **argv) // Need to jump to the tag before executing the '-c command'. // Makes "vim -c '/return' -t main" work. - handle_tag((char_u *)params.tagname); + handle_tag(params.tagname); // Execute any "+", "-c" and "-S" arguments. if (params.n_commands > 0) { @@ -539,7 +566,7 @@ int main(int argc, char **argv) starting = 0; RedrawingDisabled = 0; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); no_wait_return = false; // 'autochdir' has been postponed. @@ -553,6 +580,13 @@ int main(int argc, char **argv) TIME_MSG("UIEnter autocommands"); } +#ifdef MSWIN + if (use_builtin_ui) { + os_icon_init(); + } + os_title_save(); +#endif + // Adjust default register name for "unnamed" in 'clipboard'. Can only be // done after the clipboard is available and all initial commands that may // modify the 'clipboard' setting have run; i.e. just before entering the @@ -578,13 +612,19 @@ int main(int argc, char **argv) (void)eval_has_provider("clipboard"); } + if (params.luaf != NULL) { + bool lua_ok = nlua_exec_file(params.luaf); + TIME_MSG("executing Lua -l script"); + getout(lua_ok ? 0 : 1); + } + TIME_MSG("before starting main loop"); ILOG("starting main loop"); // Main loop: never returns. normal_enter(false, false); -#if defined(WIN32) && !defined(MAKE_LIB) +#if defined(MSWIN) && !defined(MAKE_LIB) xfree(argv); #endif return 0; @@ -595,15 +635,19 @@ void os_exit(int r) { exiting = true; - ui_flush(); - ui_call_stop(); - ml_close_all(true); // remove all memfiles + if (ui_client_channel_id) { + ui_client_stop(); + } else { + ui_flush(); + ui_call_stop(); + ml_close_all(true); // remove all memfiles + } if (!event_teardown() && r == 0) { r = 1; // Exit with error if main_loop did not teardown gracefully. } - if (input_global_fd() >= 0) { - stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) + if (used_stdin) { + stream_set_blocking(STDIN_FILENO, true); // normalize stream (#2598) } ILOG("Nvim exit: %d", r); @@ -621,9 +665,8 @@ void getout(int exitval) { exiting = true; - // When running in Ex mode an error causes us to exit with a non-zero exit - // code. POSIX requires this, although it's not 100% clear from the - // standard. + // On error during Ex mode, exit with a non-zero code. + // POSIX requires this, although it's not 100% clear from the standard. if (exmode_active) { exitval += ex_exitval; } @@ -713,8 +756,9 @@ void getout(int exitval) if (did_emsg) { // give the user a chance to read the (error) message - no_wait_return = FALSE; - wait_return(FALSE); + no_wait_return = false; + // TODO(justinmk): this may call getout(0), clobbering exitval... + wait_return(false); } // Position the cursor again, the autocommands may have moved it @@ -722,21 +766,25 @@ void getout(int exitval) // Apply 'titleold'. if (p_title && *p_titleold != NUL) { - ui_call_set_title(cstr_as_string((char *)p_titleold)); + ui_call_set_title(cstr_as_string(p_titleold)); } - cs_end(); if (garbage_collect_at_exit) { garbage_collect(false); } +#ifdef MSWIN + // Restore Windows console icon before exiting. + os_icon_set(NULL, NULL); + os_title_reset(); +#endif + os_exit(exitval); } -/// Preserve files and exit. -/// @note IObuff must contain a message. -/// @note This may be called from deadly_signal() in a signal handler, avoid -/// unsafe functions, such as allocating memory. +/// Preserve files, print contents of `IObuff`, and exit 1. +/// +/// May be called from deadly_signal(). void preserve_exit(void) FUNC_ATTR_NORETURN { @@ -745,9 +793,9 @@ void preserve_exit(void) // Prevent repeated calls into this method. if (really_exiting) { - if (input_global_fd() >= 0) { + if (used_stdin) { // normalize stream (#2598) - stream_set_blocking(input_global_fd(), true); + stream_set_blocking(STDIN_FILENO, true); } exit(2); } @@ -755,15 +803,15 @@ void preserve_exit(void) really_exiting = true; // Ignore SIGHUP while we are already exiting. #9274 signal_reject_deadly(); - mch_errmsg(IObuff); - mch_errmsg("\n"); + os_errmsg(IObuff); + os_errmsg("\n"); ui_flush(); ml_close_notmod(); // close all not-modified buffers FOR_ALL_BUFFERS(buf) { if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { - mch_errmsg("Vim: preserving files...\r\n"); + os_errmsg("Vim: preserving files...\r\n"); ui_flush(); ml_sync_all(false, false, true); // preserve all swap files break; @@ -772,7 +820,7 @@ void preserve_exit(void) ml_close_all(false); // close all memfiles, without deleting - mch_errmsg("Vim: Finished.\r\n"); + os_errmsg("Vim: Finished.\r\n"); getout(1); } @@ -801,30 +849,6 @@ static int get_number_arg(const char *p, int *idx, int def) return def; } -#if defined(HAVE_LOCALE_H) -/// Setup to use the current locale (for ctype() and many other things). -static void init_locale(void) -{ - setlocale(LC_ALL, ""); - -# ifdef LC_NUMERIC - // Make sure strtod() uses a decimal point, not a comma. - setlocale(LC_NUMERIC, "C"); -# endif - - char localepath[MAXPATHL] = { 0 }; - snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH)); - char *tail = path_tail_with_sep(localepath); - *tail = NUL; - tail = path_tail(localepath); - xstrlcpy(tail, "share/locale", - sizeof(localepath) - (size_t)(tail - localepath)); - bindtextdomain(PROJECT_NAME, localepath); - textdomain(PROJECT_NAME); - TIME_MSG("locale set"); -} -#endif - static uint64_t server_connect(char *server_addr, const char **errmsg) { if (server_addr == NULL) { @@ -845,16 +869,25 @@ static uint64_t server_connect(char *server_addr, const char **errmsg) /// Handle remote subcommands static void remote_request(mparm_T *params, int remote_args, char *server_addr, int argc, - char **argv) + char **argv, bool ui_only) { + bool is_ui = strequal(argv[remote_args], "--remote-ui"); + if (ui_only && !is_ui) { + // TODO(bfredl): this implies always starting the TUI. + // if we be smart we could delay this past should_exit + return; + } + const char *connect_error = NULL; uint64_t chan = server_connect(server_addr, &connect_error); Object rvobj = OBJECT_INIT; - if (strequal(argv[remote_args], "--remote-ui-test")) { + if (is_ui) { if (!chan) { - emsg(connect_error); - exit(1); + os_errmsg("Remote ui failed to start: "); + os_errmsg(connect_error); + os_errmsg("\n"); + os_exit(1); } ui_client_channel_id = chan; @@ -878,15 +911,15 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr, Object o = nlua_exec(s, a, &err); api_free_array(a); if (ERROR_SET(&err)) { - mch_errmsg(err.msg); - mch_errmsg("\n"); + os_errmsg(err.msg); + os_errmsg("\n"); os_exit(2); } if (o.type == kObjectTypeDictionary) { rvobj.data.dictionary = o.data.dictionary; } else { - mch_errmsg("vim._cs_remote returned unexpected value\n"); + os_errmsg("vim._cs_remote returned unexpected value\n"); os_exit(2); } @@ -896,28 +929,28 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr, for (size_t i = 0; i < rvobj.data.dictionary.size; i++) { if (strcmp(rvobj.data.dictionary.items[i].key.data, "errmsg") == 0) { if (rvobj.data.dictionary.items[i].value.type != kObjectTypeString) { - mch_errmsg("vim._cs_remote returned an unexpected type for 'errmsg'\n"); + os_errmsg("vim._cs_remote returned an unexpected type for 'errmsg'\n"); os_exit(2); } - mch_errmsg(rvobj.data.dictionary.items[i].value.data.string.data); - mch_errmsg("\n"); + os_errmsg(rvobj.data.dictionary.items[i].value.data.string.data); + os_errmsg("\n"); os_exit(2); } else if (strcmp(rvobj.data.dictionary.items[i].key.data, "tabbed") == 0) { if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) { - mch_errmsg("vim._cs_remote returned an unexpected type for 'tabbed'\n"); + os_errmsg("vim._cs_remote returned an unexpected type for 'tabbed'\n"); os_exit(2); } tabbed = rvobj.data.dictionary.items[i].value.data.boolean ? kTrue : kFalse; } else if (strcmp(rvobj.data.dictionary.items[i].key.data, "should_exit") == 0) { if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) { - mch_errmsg("vim._cs_remote returned an unexpected type for 'should_exit'\n"); + os_errmsg("vim._cs_remote returned an unexpected type for 'should_exit'\n"); os_exit(2); } should_exit = rvobj.data.dictionary.items[i].value.data.boolean ? kTrue : kFalse; } } if (should_exit == kNone || tabbed == kNone) { - mch_errmsg("vim._cs_remote didn't return a value for should_exit or tabbed, bailing\n"); + os_errmsg("vim._cs_remote didn't return a value for should_exit or tabbed, bailing\n"); os_exit(2); } api_free_object(o); @@ -933,14 +966,14 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr, /// Decides whether text (as opposed to commands) will be read from stdin. /// @see EDIT_STDIN -static bool edit_stdin(bool explicit, mparm_T *parmp) +static bool edit_stdin(mparm_T *parmp) { bool implicit = !headless_mode - && !embedded_mode - && (!exmode_active || parmp->input_neverscript) - && !parmp->input_isatty - && scriptin[0] == NULL; // `-s -` was not given. - return explicit || implicit; + && !(embedded_mode && stdin_fd <= 0) + && (!exmode_active || parmp->input_istext) + && !stdin_isatty + && parmp->scriptin == NULL; // `-s -` was not given. + return parmp->had_stdin_file || implicit; } /// Scan the command line arguments. @@ -949,7 +982,6 @@ static void command_line_scan(mparm_T *parmp) int argc = parmp->argc; char **argv = parmp->argv; int argv_idx; // index in argv[n][] - bool had_stdin_file = false; // found explicit "-" argument bool had_minmin = false; // found "--" argument int want_argument; // option argument with argument long n; @@ -969,9 +1001,9 @@ static void command_line_scan(mparm_T *parmp) } else { parmp->commands[parmp->n_commands++] = &(argv[0][1]); } - - // Optional argument. } else if (argv[0][0] == '-' && !had_minmin) { + // Optional argument. + want_argument = false; char c = argv[0][argv_idx++]; switch (c) { @@ -981,17 +1013,15 @@ static void command_line_scan(mparm_T *parmp) silent_mode = true; parmp->no_swap_file = true; } else { - if (parmp->edit_type != EDIT_NONE - && parmp->edit_type != EDIT_FILE - && parmp->edit_type != EDIT_STDIN) { + if (parmp->edit_type > EDIT_STDIN) { mainerr(err_too_many_args, argv[0]); } - had_stdin_file = true; + parmp->had_stdin_file = true; parmp->edit_type = EDIT_STDIN; } argv_idx = -1; // skip to next argument break; - case '-': // "--" don't take any more option arguments + case '-': // "--" No more option arguments. // "--help" give help message // "--version" give version message // "--noplugin[s]" skip plugins @@ -1052,7 +1082,7 @@ static void command_line_scan(mparm_T *parmp) } else if (STRNICMP(argv[0] + argv_idx, "clean", 5) == 0) { parmp->use_vimrc = "NONE"; parmp->clean = true; - set_option_value("shadafile", 0L, "NONE", 0); + set_option_value_give_err("shadafile", 0L, "NONE", 0); } else if (STRNICMP(argv[0] + argv_idx, "luamod-dev", 9) == 0) { nlua_disable_preload = true; } else { @@ -1066,7 +1096,7 @@ static void command_line_scan(mparm_T *parmp) } break; case 'A': // "-A" start in Arabic mode. - set_option_value("arabic", 1L, NULL, 0); + set_option_value_give_err("arabic", 1L, NULL, 0); break; case 'b': // "-b" binary mode. // Needs to be effective before expanding file names, because @@ -1087,7 +1117,7 @@ static void command_line_scan(mparm_T *parmp) break; case 'E': // "-E" Ex mode exmode_active = true; - parmp->input_neverscript = true; + parmp->input_istext = true; break; case 'f': // "-f" GUI: run in foreground. break; @@ -1097,11 +1127,7 @@ static void command_line_scan(mparm_T *parmp) os_exit(0); case 'H': // "-H" start in Hebrew mode: rl + hkmap set. p_hkmap = true; - set_option_value("rl", 1L, NULL, 0); - break; - case 'l': // "-l" lisp mode, 'lisp' and 'showmatch' on. - set_option_value("lisp", 1L, NULL, 0); - p_sm = true; + set_option_value_give_err("rl", 1L, NULL, 0); break; case 'M': // "-M" no changes or writing of files reset_modifiable(); @@ -1181,15 +1207,15 @@ static void command_line_scan(mparm_T *parmp) // default is 10: a little bit verbose p_verbose = get_number_arg(argv[0], &argv_idx, 10); if (argv[0][argv_idx] != NUL) { - set_option_value("verbosefile", 0L, argv[0] + argv_idx, 0); - argv_idx = (int)STRLEN(argv[0]); + set_option_value_give_err("verbosefile", 0L, argv[0] + argv_idx, 0); + argv_idx = (int)strlen(argv[0]); } break; case 'w': // "-w{number}" set window height // "-w {scriptout}" write to script - if (ascii_isdigit(((char_u *)argv[0])[argv_idx])) { + if (ascii_isdigit((argv[0])[argv_idx])) { n = get_number_arg(argv[0], &argv_idx, 10); - set_option_value("window", n, NULL, 0); + set_option_value_give_err("window", n, NULL, 0); break; } want_argument = true; @@ -1207,6 +1233,7 @@ static void command_line_scan(mparm_T *parmp) FALLTHROUGH; case 'S': // "-S {file}" execute Vim script case 'i': // "-i {shada}" use for ShaDa file + case 'l': // "-l {file}" Lua mode case 'u': // "-u {vimrc}" vim inits file case 'U': // "-U {gvimrc}" gvim inits file case 'W': // "-W {scriptout}" overwrite @@ -1252,7 +1279,7 @@ static void command_line_scan(mparm_T *parmp) a = argv[0]; } - size_t s_size = STRLEN(a) + 9; + size_t s_size = strlen(a) + 9; char *s = xmalloc(s_size); snprintf(s, s_size, "so %s", a); parmp->cmds_tofree[parmp->n_commands] = true; @@ -1284,41 +1311,37 @@ static void command_line_scan(mparm_T *parmp) break; case 'i': // "-i {shada}" use for shada - set_option_value("shadafile", 0L, argv[0], 0); + set_option_value_give_err("shadafile", 0L, argv[0], 0); break; - case 's': { // "-s {scriptin}" read from script file - if (scriptin[0] != NULL) { + case 'l': // "-l" Lua script: args after "-l". + headless_mode = true; + silent_mode = true; + p_verbose = 1; + parmp->no_swap_file = true; + parmp->use_vimrc = parmp->use_vimrc ? parmp->use_vimrc : "NONE"; + if (p_shadafile == NULL || *p_shadafile == NUL) { + set_option_value_give_err("shadafile", 0L, "NONE", 0); + } + parmp->luaf = argv[0]; + argc--; + if (argc > 0) { // Lua args after "-l <file>". + parmp->lua_arg0 = parmp->argc - argc; + argc = 0; + } + break; + + case 's': // "-s {scriptin}" read from script file + if (parmp->scriptin != NULL) { scripterror: - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Attempt to open script file again: \"%s %s\"\n"), argv[-1], argv[0]); - mch_errmsg((const char *)IObuff); - os_exit(2); - } - int error; - if (strequal(argv[0], "-")) { - const int stdin_dup_fd = os_dup(STDIN_FILENO); -#ifdef WIN32 - // Replace the original stdin with the console input handle. - os_replace_stdin_to_conin(); -#endif - FileDescriptor *const stdin_dup = file_open_fd_new(&error, stdin_dup_fd, - kFileReadOnly|kFileNonBlocking); - assert(stdin_dup != NULL); - scriptin[0] = stdin_dup; - } else if ((scriptin[0] = - file_open_new(&error, argv[0], kFileReadOnly|kFileNonBlocking, - 0)) == NULL) { - vim_snprintf((char *)IObuff, IOSIZE, - _("Cannot open for reading: \"%s\": %s\n"), - argv[0], os_strerror(error)); - mch_errmsg((const char *)IObuff); + os_errmsg(IObuff); os_exit(2); } - save_typebuf(); + parmp->scriptin = argv[0]; break; - } case 't': // "-t {tag}" parmp->tagname = argv[0]; @@ -1331,35 +1354,27 @@ scripterror: case 'w': // "-w {nr}" 'window' value // "-w {scriptout}" append to script file - if (ascii_isdigit(*((char_u *)argv[0]))) { + if (ascii_isdigit(*(argv[0]))) { argv_idx = 0; n = get_number_arg(argv[0], &argv_idx, 10); - set_option_value("window", n, NULL, 0); + set_option_value_give_err("window", n, NULL, 0); argv_idx = -1; break; } FALLTHROUGH; case 'W': // "-W {scriptout}" overwrite script file - if (scriptout != NULL) { + if (parmp->scriptout != NULL) { goto scripterror; } - if ((scriptout = os_fopen(argv[0], c == 'w' ? APPENDBIN : WRITEBIN)) - == NULL) { - mch_errmsg(_("Cannot open for script output: \"")); - mch_errmsg(argv[0]); - mch_errmsg("\"\n"); - os_exit(2); - } - break; + parmp->scriptout = argv[0]; + parmp->scriptout_append = (c == 'w'); } } } else { // File name argument. argv_idx = -1; // skip to next argument // Check for only one type of editing. - if (parmp->edit_type != EDIT_NONE - && parmp->edit_type != EDIT_FILE - && parmp->edit_type != EDIT_STDIN) { + if (parmp->edit_type > EDIT_STDIN) { mainerr(err_too_many_args, argv[0]); } parmp->edit_type = EDIT_FILE; @@ -1368,8 +1383,8 @@ scripterror: ga_grow(&global_alist.al_ga, 1); char *p = xstrdup(argv[0]); - if (parmp->diff_mode && os_isdir((char_u *)p) && GARGCOUNT > 0 - && !os_isdir((char_u *)alist_name(&GARGLIST[0]))) { + if (parmp->diff_mode && os_isdir(p) && GARGCOUNT > 0 + && !os_isdir(alist_name(&GARGLIST[0]))) { char *r = concat_fnames(p, path_tail(alist_name(&GARGLIST[0])), true); xfree(p); p = r; @@ -1380,7 +1395,7 @@ scripterror: path_fix_case(p); #endif - int alist_fnum_flag = edit_stdin(had_stdin_file, parmp) + int alist_fnum_flag = edit_stdin(parmp) ? 1 // add buffer nr after exp. : 2; // add buffer number now and use curbuf alist_add(&global_alist, p, alist_fnum_flag); @@ -1395,31 +1410,25 @@ scripterror: } } - if (embedded_mode && silent_mode) { - mainerr(_("--embed conflicts with -es/-Es"), NULL); + if (embedded_mode && (silent_mode || parmp->luaf)) { + mainerr(_("--embed conflicts with -es/-Es/-l"), NULL); } // If there is a "+123" or "-c" command, set v:swapcommand to the first one. if (parmp->n_commands > 0) { - const size_t swcmd_len = STRLEN(parmp->commands[0]) + 3; + const size_t swcmd_len = strlen(parmp->commands[0]) + 3; char *const swcmd = xmalloc(swcmd_len); snprintf(swcmd, swcmd_len, ":%s\r", parmp->commands[0]); set_vim_var_string(VV_SWAPCOMMAND, swcmd, -1); xfree(swcmd); } - // Handle "foo | nvim". EDIT_FILE may be overwritten now. #6299 - if (edit_stdin(had_stdin_file, parmp)) { - parmp->edit_type = EDIT_STDIN; - } - TIME_MSG("parsing arguments"); } -/* - * Many variables are in "params" so that we can pass them to invoked - * functions without a lot of arguments. "argc" and "argv" are also - * copied, so that they can be changed. */ +// Many variables are in "params" so that we can pass them to invoked +// functions without a lot of arguments. "argc" and "argv" are also +// copied, so that they can be changed. static void init_params(mparm_T *paramp, int argc, char **argv) { CLEAR_POINTER(paramp); @@ -1430,6 +1439,8 @@ static void init_params(mparm_T *paramp, int argc, char **argv) paramp->listen_addr = NULL; paramp->server_addr = NULL; paramp->remote = 0; + paramp->luaf = NULL; + paramp->lua_arg0 = -1; } /// Initialize global startuptime file if "--startuptime" passed as an argument. @@ -1442,25 +1453,13 @@ static void init_startuptime(mparm_T *paramp) break; } } - - starttime = time(NULL); } static void check_and_set_isatty(mparm_T *paramp) { - stdin_isatty - = paramp->input_isatty = os_isatty(STDIN_FILENO); - stdout_isatty - = paramp->output_isatty = os_isatty(STDOUT_FILENO); - paramp->err_isatty = os_isatty(STDERR_FILENO); -#ifndef WIN32 - int tty_fd = paramp->input_isatty - ? STDIN_FILENO - : (paramp->output_isatty - ? STDOUT_FILENO - : (paramp->err_isatty ? STDERR_FILENO : -1)); - pty_process_save_termios(tty_fd); -#endif + stdin_isatty = os_isatty(STDIN_FILENO); + stdout_isatty = os_isatty(STDOUT_FILENO); + stderr_isatty = os_isatty(STDERR_FILENO); TIME_MSG("window checked"); } @@ -1478,7 +1477,7 @@ static void init_path(const char *exename) set_vim_var_string(VV_PROGPATH, exepath, -1); set_vim_var_string(VV_PROGNAME, path_tail(exename), -1); -#ifdef WIN32 +#ifdef MSWIN // Append the process start directory to $PATH, so that ":!foo" finds tools // shipped with Windows package. This also mimics SearchPath(). os_setenv_append_path(exepath); @@ -1486,14 +1485,12 @@ static void init_path(const char *exename) } /// Get filename from command line, if any. -static char_u *get_fname(mparm_T *parmp, char_u *cwd) +static char *get_fname(mparm_T *parmp, char *cwd) { - return (char_u *)alist_name(&GARGLIST[0]); + return alist_name(&GARGLIST[0]); } -/* - * Decide about window layout for diff mode after reading vimrc. - */ +// Decide about window layout for diff mode after reading vimrc. static void set_window_layout(mparm_T *paramp) { if (paramp->diff_mode && paramp->window_layout == 0) { @@ -1505,18 +1502,16 @@ static void set_window_layout(mparm_T *paramp) } } -/* - * "-q errorfile": Load the error file now. - * If the error file can't be read, exit before doing anything else. - */ +// "-q errorfile": Load the error file now. +// If the error file can't be read, exit before doing anything else. static void handle_quickfix(mparm_T *paramp) { if (paramp->edit_type == EDIT_QF) { if (paramp->use_ef != NULL) { set_string_option_direct("ef", -1, paramp->use_ef, OPT_FREE, SID_CARG); } - vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); - if (qf_init(NULL, (char *)p_ef, p_efm, true, (char *)IObuff, (char *)p_menc) < 0) { + vim_snprintf(IObuff, IOSIZE, "cfile %s", p_ef); + if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { msg_putchar('\n'); os_exit(3); } @@ -1524,17 +1519,15 @@ static void handle_quickfix(mparm_T *paramp) } } -/* - * Need to jump to the tag before executing the '-c command'. - * Makes "vim -c '/return' -t main" work. - */ -static void handle_tag(char_u *tagname) +// Need to jump to the tag before executing the '-c command'. +// Makes "vim -c '/return' -t main" work. +static void handle_tag(char *tagname) { if (tagname != NULL) { swap_exists_did_quit = false; - vim_snprintf((char *)IObuff, IOSIZE, "ta %s", tagname); - do_cmdline_cmd((char *)IObuff); + vim_snprintf(IObuff, IOSIZE, "ta %s", tagname); + do_cmdline_cmd(IObuff); TIME_MSG("jumping to tag"); // If the user doesn't want to edit the file then we quit here. @@ -1566,18 +1559,46 @@ static void read_stdin(void) check_swap_exists_action(); } -/* - * Create the requested number of windows and edit buffers in them. - * Also does recovery if "recoverymode" set. - */ +static void open_script_files(mparm_T *parmp) +{ + if (parmp->scriptin) { + int error; + if (strequal(parmp->scriptin, "-")) { + FileDescriptor *stdin_dup = file_open_stdin(); + scriptin[0] = stdin_dup; + } else { + scriptin[0] = file_open_new(&error, parmp->scriptin, + kFileReadOnly|kFileNonBlocking, 0); + if (scriptin[0] == NULL) { + vim_snprintf((char *)IObuff, IOSIZE, + _("Cannot open for reading: \"%s\": %s\n"), + parmp->scriptin, os_strerror(error)); + os_errmsg(IObuff); + os_exit(2); + } + } + save_typebuf(); + } + + if (parmp->scriptout) { + scriptout = os_fopen(parmp->scriptout, parmp->scriptout_append ? APPENDBIN : WRITEBIN); + if (scriptout == NULL) { + os_errmsg(_("Cannot open for script output: \"")); + os_errmsg(parmp->scriptout); + os_errmsg("\"\n"); + os_exit(2); + } + } +} + +// Create the requested number of windows and edit buffers in them. +// Also does recovery if "recoverymode" set. static void create_windows(mparm_T *parmp) { int dorewind; int done = 0; - /* - * Create the number of windows that was requested. - */ + // Create the number of windows that was requested. if (parmp->window_count == -1) { // was not set parmp->window_count = 1; } @@ -1638,7 +1659,7 @@ static void create_windows(mparm_T *parmp) } curwin = curwin->w_next; } - dorewind = FALSE; + dorewind = false; curbuf = curwin->w_buffer; if (curbuf->b_ml.ml_mfp == NULL) { // Set 'foldlevel' to 'foldlevelstart' if it's not negative.. @@ -1647,15 +1668,15 @@ static void create_windows(mparm_T *parmp) } // When getting the ATTENTION prompt here, use a dialog. swap_exists_action = SEA_DIALOG; - set_buflisted(TRUE); + set_buflisted(true); // create memfile, read file - (void)open_buffer(FALSE, NULL, 0); + (void)open_buffer(false, NULL, 0); if (swap_exists_action == SEA_QUIT) { if (got_int || only_one_window()) { // abort selected or quit and only one window - did_emsg = FALSE; // avoid hit-enter prompt + did_emsg = false; // avoid hit-enter prompt getout(1); } // We can't close the window, it would disturb what @@ -1667,7 +1688,7 @@ static void create_windows(mparm_T *parmp) } else { handle_swap_exists(NULL); } - dorewind = TRUE; // start again + dorewind = true; // start again } os_breakcheck(); if (got_int) { @@ -1688,7 +1709,7 @@ static void create_windows(mparm_T *parmp) /// If opened more than one window, start editing files in the other /// windows. make_windows() has already opened the windows. -static void edit_buffers(mparm_T *parmp, char_u *cwd) +static void edit_buffers(mparm_T *parmp, char *cwd) { int arg_idx; // index in argument list int i; @@ -1696,9 +1717,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) win_T *win; char *p_shm_save = NULL; - /* - * Don't execute Win/Buf Enter/Leave autocommands here - */ + // Don't execute Win/Buf Enter/Leave autocommands here autocmd_no_enter++; autocmd_no_leave++; @@ -1709,9 +1728,9 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) } arg_idx = 1; - for (i = 1; i < parmp->window_count; ++i) { + for (i = 1; i < parmp->window_count; i++) { if (cwd != NULL) { - os_chdir((char *)cwd); + os_chdir(cwd); } // When w_arg_idx is -1 remove the window (see create_windows()). if (curwin->w_arg_idx == -1) { @@ -1733,9 +1752,9 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) if (i == 1) { char buf[100]; - p_shm_save = xstrdup((char *)p_shm); + p_shm_save = xstrdup(p_shm); snprintf(buf, sizeof(buf), "F%s", p_shm); - set_option_value("shm", 0L, buf, 0); + set_option_value_give_err("shm", 0L, buf, 0); } } else { if (curwin->w_next == NULL) { // just checking @@ -1760,7 +1779,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) // abort or quit selected if (got_int || only_one_window()) { // abort selected and only one window - did_emsg = FALSE; // avoid hit-enter prompt + did_emsg = false; // avoid hit-enter prompt getout(1); } win_close(curwin, true, false); @@ -1779,7 +1798,7 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) } if (p_shm_save != NULL) { - set_option_value("shm", 0L, p_shm_save, 0); + set_option_value_give_err("shm", 0L, p_shm_save, 0); xfree(p_shm_save); } @@ -1807,41 +1826,37 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd) } } -/* - * Execute the commands from --cmd arguments "cmds[cnt]". - */ +// Execute the commands from --cmd arguments "cmds[cnt]". static void exe_pre_commands(mparm_T *parmp) { char **cmds = parmp->pre_commands; int cnt = parmp->n_pre_commands; int i; - if (cnt > 0) { - curwin->w_cursor.lnum = 0; // just in case.. - estack_push(ETYPE_ARGS, _("pre-vimrc command line"), 0); - current_sctx.sc_sid = SID_CMDARG; - for (i = 0; i < cnt; i++) { - do_cmdline_cmd(cmds[i]); - } - estack_pop(); - current_sctx.sc_sid = 0; - TIME_MSG("--cmd commands"); + if (cnt <= 0) { + return; } + + curwin->w_cursor.lnum = 0; // just in case.. + estack_push(ETYPE_ARGS, _("pre-vimrc command line"), 0); + current_sctx.sc_sid = SID_CMDARG; + for (i = 0; i < cnt; i++) { + do_cmdline_cmd(cmds[i]); + } + estack_pop(); + current_sctx.sc_sid = 0; + TIME_MSG("--cmd commands"); } -/* - * Execute "+", "-c" and "-S" arguments. - */ +// Execute "+", "-c" and "-S" arguments. static void exe_commands(mparm_T *parmp) { int i; - /* - * We start commands on line 0, make "vim +/pat file" match a - * pattern on line 1. But don't move the cursor when an autocommand - * with g`" was used. - */ - msg_scroll = TRUE; + // We start commands on line 0, make "vim +/pat file" match a + // pattern on line 1. But don't move the cursor when an autocommand + // with g`" was used. + msg_scroll = true; if (parmp->tagname == NULL && curwin->w_cursor.lnum <= 1) { curwin->w_cursor.lnum = 0; } @@ -1861,12 +1876,12 @@ static void exe_commands(mparm_T *parmp) } if (!exmode_active) { - msg_scroll = FALSE; + msg_scroll = false; } // When started with "-q errorfile" jump to first error again. if (parmp->edit_type == EDIT_QF) { - qf_jump(NULL, 0, 0, FALSE); + qf_jump(NULL, 0, 0, false); } TIME_MSG("executing command arguments"); } @@ -1936,12 +1951,12 @@ static bool do_user_initialization(void) return do_exrc; } - char_u *init_lua_path = (char_u *)stdpaths_user_conf_subpath("init.lua"); - char_u *user_vimrc = (char_u *)stdpaths_user_conf_subpath("init.vim"); + char *init_lua_path = stdpaths_user_conf_subpath("init.lua"); + char *user_vimrc = stdpaths_user_conf_subpath("init.vim"); // init.lua if (os_path_exists(init_lua_path) - && do_source((char *)init_lua_path, true, DOSO_VIMRC)) { + && do_source(init_lua_path, true, DOSO_VIMRC)) { if (os_path_exists(user_vimrc)) { semsg(_("E5422: Conflicting configs: \"%s\" \"%s\""), init_lua_path, user_vimrc); @@ -1949,15 +1964,16 @@ static bool do_user_initialization(void) xfree(user_vimrc); xfree(init_lua_path); - return false; + do_exrc = p_exrc; + return do_exrc; } xfree(init_lua_path); // init.vim - if (do_source((char *)user_vimrc, true, DOSO_VIMRC) != FAIL) { + if (do_source(user_vimrc, true, DOSO_VIMRC) != FAIL) { do_exrc = p_exrc; if (do_exrc) { - do_exrc = (path_full_compare(VIMRC_FILE, (char *)user_vimrc, false, true) != kEqualFiles); + do_exrc = (path_full_compare(VIMRC_FILE, user_vimrc, false, true) != kEqualFiles); } xfree(user_vimrc); return do_exrc; @@ -2001,6 +2017,41 @@ static bool do_user_initialization(void) return do_exrc; } +// Read initialization commands from ".nvim.lua", ".nvimrc", or ".exrc" in +// current directory. This is only done if the 'exrc' option is set. +// Only do this if VIMRC_FILE is not the same as vimrc file sourced in +// do_user_initialization. +static void do_exrc_initialization(void) +{ + char *str; + + if (os_path_exists(VIMRC_LUA_FILE)) { + str = nlua_read_secure(VIMRC_LUA_FILE); + if (str != NULL) { + Error err = ERROR_INIT; + nlua_exec(cstr_as_string(str), (Array)ARRAY_DICT_INIT, &err); + xfree(str); + if (ERROR_SET(&err)) { + semsg("Error detected while processing %s:", VIMRC_LUA_FILE); + semsg_multiline(err.msg); + api_clear_error(&err); + } + } + } else if (os_path_exists(VIMRC_FILE)) { + str = nlua_read_secure(VIMRC_FILE); + if (str != NULL) { + do_source_str(str, VIMRC_FILE); + xfree(str); + } + } else if (os_path_exists(EXRC_FILE)) { + str = nlua_read_secure(EXRC_FILE); + if (str != NULL) { + do_source_str(str, EXRC_FILE); + xfree(str); + } + } +} + /// Source startup scripts static void source_startup_scripts(const mparm_T *const parmp) FUNC_ATTR_NONNULL_ALL @@ -2019,35 +2070,8 @@ static void source_startup_scripts(const mparm_T *const parmp) do_system_initialization(); if (do_user_initialization()) { - // Read initialization commands from ".vimrc" or ".exrc" in current - // directory. This is only done if the 'exrc' option is set. - // Because of security reasons we disallow shell and write commands - // now, except for unix if the file is owned by the user or 'secure' - // option has been reset in environment of global "exrc" or "vimrc". - // Only do this if VIMRC_FILE is not the same as vimrc file sourced in - // do_user_initialization. -#if defined(UNIX) - // If vimrc file is not owned by user, set 'secure' mode. - if (!os_file_owned(VIMRC_FILE)) // NOLINT(readability/braces) -#endif - secure = p_secure; - - if (do_source(VIMRC_FILE, true, DOSO_VIMRC) == FAIL) { -#if defined(UNIX) - // if ".exrc" is not owned by user set 'secure' mode - if (!os_file_owned(EXRC_FILE)) { - secure = p_secure; - } else { - secure = 0; - } -#endif - (void)do_source(EXRC_FILE, false, DOSO_NONE); - } + do_exrc_initialization(); } - if (secure == 2) { - need_wait_return = true; - } - secure = 0; } TIME_MSG("sourcing vimrc file(s)"); } @@ -2062,19 +2086,20 @@ static int execute_env(char *env) FUNC_ATTR_NONNULL_ALL { const char *initstr = os_getenv(env); - if (initstr != NULL) { - estack_push(ETYPE_ENV, env, 0); - const sctx_T save_current_sctx = current_sctx; - current_sctx.sc_sid = SID_ENV; - current_sctx.sc_seq = 0; - current_sctx.sc_lnum = 0; - do_cmdline_cmd((char *)initstr); - - estack_pop(); - current_sctx = save_current_sctx; - return OK; - } - return FAIL; + if (initstr == NULL) { + return FAIL; + } + + estack_push(ETYPE_ENV, env, 0); + const sctx_T save_current_sctx = current_sctx; + current_sctx.sc_sid = SID_ENV; + current_sctx.sc_seq = 0; + current_sctx.sc_lnum = 0; + do_cmdline_cmd((char *)initstr); + + estack_pop(); + current_sctx = save_current_sctx; + return OK; } /// Prints the following then exits: @@ -2090,17 +2115,17 @@ static void mainerr(const char *errstr, const char *str) signal_stop(); // kill us with CTRL-C here, if you like - mch_errmsg(prgname); - mch_errmsg(": "); - mch_errmsg(_(errstr)); + os_errmsg(prgname); + os_errmsg(": "); + os_errmsg(_(errstr)); if (str != NULL) { - mch_errmsg(": \""); - mch_errmsg(str); - mch_errmsg("\""); + os_errmsg(": \""); + os_errmsg((char *)str); + os_errmsg("\""); } - mch_errmsg(_("\nMore info with \"")); - mch_errmsg(prgname); - mch_errmsg(" -h\"\n"); + os_errmsg(_("\nMore info with \"")); + os_errmsg(prgname); + os_errmsg(" -h\"\n"); os_exit(1); } @@ -2109,8 +2134,8 @@ static void mainerr(const char *errstr, const char *str) static void version(void) { // TODO(bfred): not like this? - nlua_init(); - info_message = true; // use mch_msg(), not mch_errmsg() + nlua_init(NULL, 0, -1); + info_message = true; // use os_msg(), not os_errmsg() list_version(); msg_putchar('\n'); msg_didout = false; @@ -2121,53 +2146,53 @@ static void usage(void) { signal_stop(); // kill us with CTRL-C here, if you like - mch_msg(_("Usage:\n")); - mch_msg(_(" nvim [options] [file ...] Edit file(s)\n")); - mch_msg(_(" nvim [options] -t <tag> Edit file where tag is defined\n")); - mch_msg(_(" nvim [options] -q [errorfile] Edit file with first error\n")); - mch_msg(_("\nOptions:\n")); - mch_msg(_(" -- Only file names after this\n")); - mch_msg(_(" + Start at end of file\n")); - mch_msg(_(" --cmd <cmd> Execute <cmd> before any config\n")); - mch_msg(_(" +<cmd>, -c <cmd> Execute <cmd> after config and first file\n")); - mch_msg("\n"); - mch_msg(_(" -b Binary mode\n")); - mch_msg(_(" -d Diff mode\n")); - mch_msg(_(" -e, -E Ex mode\n")); - mch_msg(_(" -es, -Es Silent (batch) mode\n")); - mch_msg(_(" -h, --help Print this help message\n")); - mch_msg(_(" -i <shada> Use this shada file\n")); - mch_msg(_(" -m Modifications (writing files) not allowed\n")); - mch_msg(_(" -M Modifications in text not allowed\n")); - mch_msg(_(" -n No swap file, use memory only\n")); - mch_msg(_(" -o[N] Open N windows (default: one per file)\n")); - mch_msg(_(" -O[N] Open N vertical windows (default: one per file)\n")); - mch_msg(_(" -p[N] Open N tab pages (default: one per file)\n")); - mch_msg(_(" -r, -L List swap files\n")); - mch_msg(_(" -r <file> Recover edit state for this file\n")); - mch_msg(_(" -R Read-only mode\n")); - mch_msg(_(" -S <session> Source <session> after loading the first file\n")); - mch_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n")); - mch_msg(_(" -u <config> Use this config file\n")); - mch_msg(_(" -v, --version Print version information\n")); - mch_msg(_(" -V[N][file] Verbose [level][file]\n")); - mch_msg("\n"); - mch_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n")); - mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); - mch_msg(_(" --headless Don't start a user interface\n")); - mch_msg(_(" --listen <address> Serve RPC API from this address\n")); - mch_msg(_(" --noplugin Don't load plugins\n")); - mch_msg(_(" --remote[-subcommand] Execute commands remotely on a server\n")); - mch_msg(_(" --server <address> Specify RPC server to send commands to\n")); - mch_msg(_(" --startuptime <file> Write startup timing messages to <file>\n")); - mch_msg(_("\nSee \":help startup-options\" for all options.\n")); + os_msg(_("Usage:\n")); + os_msg(_(" nvim [options] [file ...] Edit file(s)\n")); + os_msg(_(" nvim [options] -t <tag> Edit file where tag is defined\n")); + os_msg(_(" nvim [options] -q [errorfile] Edit file with first error\n")); + os_msg(_("\nOptions:\n")); + os_msg(_(" -- Only file names after this\n")); + os_msg(_(" + Start at end of file\n")); + os_msg(_(" --cmd <cmd> Execute <cmd> before any config\n")); + os_msg(_(" +<cmd>, -c <cmd> Execute <cmd> after config and first file\n")); + os_msg(_(" -l <script> [args...] Execute Lua <script> (with optional args)\n")); + os_msg("\n"); + os_msg(_(" -b Binary mode\n")); + os_msg(_(" -d Diff mode\n")); + os_msg(_(" -e, -E Ex mode\n")); + os_msg(_(" -es, -Es Silent (batch) mode\n")); + os_msg(_(" -h, --help Print this help message\n")); + os_msg(_(" -i <shada> Use this shada file\n")); + os_msg(_(" -m Modifications (writing files) not allowed\n")); + os_msg(_(" -M Modifications in text not allowed\n")); + os_msg(_(" -n No swap file, use memory only\n")); + os_msg(_(" -o[N] Open N windows (default: one per file)\n")); + os_msg(_(" -O[N] Open N vertical windows (default: one per file)\n")); + os_msg(_(" -p[N] Open N tab pages (default: one per file)\n")); + os_msg(_(" -r, -L List swap files\n")); + os_msg(_(" -r <file> Recover edit state for this file\n")); + os_msg(_(" -R Read-only mode\n")); + os_msg(_(" -S <session> Source <session> after loading the first file\n")); + os_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n")); + os_msg(_(" -u <config> Use this config file\n")); + os_msg(_(" -v, --version Print version information\n")); + os_msg(_(" -V[N][file] Verbose [level][file]\n")); + os_msg("\n"); + os_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n")); + os_msg(_(" --clean \"Factory defaults\" (skip user config and plugins, shada)\n")); + os_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); + os_msg(_(" --headless Don't start a user interface\n")); + os_msg(_(" --listen <address> Serve RPC API from this address\n")); + os_msg(_(" --noplugin Don't load plugins\n")); + os_msg(_(" --remote[-subcommand] Execute commands remotely on a server\n")); + os_msg(_(" --server <address> Specify RPC server to send commands to\n")); + os_msg(_(" --startuptime <file> Write startup timing messages to <file>\n")); + os_msg(_("\nSee \":help startup-options\" for all options.\n")); } -/* - * Check the result of the ATTENTION dialog: - * When "Quit" selected, exit Vim. - * When "Recover" selected, recover the file. - */ +// Check the result of the ATTENTION dialog: +// When "Quit" selected, exit Vim. +// When "Recover" selected, recover the file. static void check_swap_exists_action(void) { if (swap_exists_action == SEA_QUIT) { diff --git a/src/nvim/main.h b/src/nvim/main.h index 0c497a7c0e..2d54837872 100644 --- a/src/nvim/main.h +++ b/src/nvim/main.h @@ -1,6 +1,8 @@ #ifndef NVIM_MAIN_H #define NVIM_MAIN_H +#include <stdbool.h> + #include "nvim/event/loop.h" // Maximum number of commands from + or -c arguments. @@ -21,15 +23,15 @@ typedef struct { char cmds_tofree[MAX_ARG_CMDS]; // commands that need free() int n_pre_commands; // no. of commands from --cmd char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument + char *luaf; // Lua script filename from "-l" + int lua_arg0; // Lua script args start index. int edit_type; // type of editing to do char *tagname; // tag from -t argument char *use_ef; // 'errorfile' from -q argument - bool input_isatty; // stdin is a terminal - bool output_isatty; // stdout is a terminal - bool err_isatty; // stderr is a terminal - bool input_neverscript; // never treat stdin as script (-E/-Es) + bool input_istext; // stdin is text, not executable (-E/-Es) + int no_swap_file; // "-n" argument used int use_debug_break_level; int window_count; // number of windows to use @@ -40,6 +42,10 @@ typedef struct { char *listen_addr; // --listen {address} int remote; // --remote-[subcmd] {file1} {file2} char *server_addr; // --server {address} + char *scriptin; // -s {filename} + char *scriptout; // -w/-W {filename} + bool scriptout_append; // append (-w) instead of overwrite (-W) + bool had_stdin_file; // explicit - as a file to edit } mparm_T; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/map.c b/src/nvim/map.c index 907c1d5996..24066659a8 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -8,17 +8,16 @@ // khash.h does not make its own copy of the key or value. // -#include <lauxlib.h> -#include <lua.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> -#include "nvim/lib/khash.h" +#include "auto/config.h" +#include "klib/khash.h" +#include "nvim/gettext.h" #include "nvim/map.h" #include "nvim/map_defs.h" #include "nvim/memory.h" -#include "nvim/vim.h" #define cstr_t_hash kh_str_hash_func #define cstr_t_eq kh_str_hash_equal @@ -169,6 +168,7 @@ MAP_IMPL(int, cstr_t, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, int, DEFAULT_INITIALIZER) MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER) +MAP_IMPL(uint32_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER) MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER) diff --git a/src/nvim/map.h b/src/nvim/map.h index 92c9ebcc34..6a548ad30b 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -2,12 +2,17 @@ #define NVIM_MAP_H #include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include "klib/khash.h" #include "nvim/api/private/defs.h" #include "nvim/extmark_defs.h" +#include "nvim/gettext.h" #include "nvim/highlight_defs.h" #include "nvim/map_defs.h" #include "nvim/tui/input_defs.h" +#include "nvim/types.h" #include "nvim/ui_client.h" #if defined(__NetBSD__) @@ -40,6 +45,7 @@ MAP_DECLS(int, cstr_t) MAP_DECLS(cstr_t, ptr_t) MAP_DECLS(cstr_t, int) MAP_DECLS(ptr_t, ptr_t) +MAP_DECLS(uint32_t, ptr_t) MAP_DECLS(uint64_t, ptr_t) MAP_DECLS(uint64_t, ssize_t) MAP_DECLS(uint64_t, uint64_t) diff --git a/src/nvim/map_defs.h b/src/nvim/map_defs.h index 7b4596ce2e..61afedbe50 100644 --- a/src/nvim/map_defs.h +++ b/src/nvim/map_defs.h @@ -1,7 +1,7 @@ #ifndef NVIM_MAP_DEFS_H #define NVIM_MAP_DEFS_H -#include "nvim/lib/khash.h" +#include "klib/khash.h" typedef const char *cstr_t; typedef void *ptr_t; diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index 64a798a27b..831d1299a8 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -5,32 +5,43 @@ #include <assert.h> #include <inttypes.h> +#include <lauxlib.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/api/private/converter.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" -#include "nvim/assert.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" -#include "nvim/ex_docmd.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_session.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/keycodes.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/option.h" +#include "nvim/option_defs.h" +#include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/runtime.h" -#include "nvim/ui.h" +#include "nvim/search.h" +#include "nvim/strings.h" #include "nvim/vim.h" /// List used for abbreviations. @@ -151,7 +162,7 @@ static void showmap(mapblock_T *mp, bool local) size_t len = 1; if (message_filtered(mp->m_keys) && message_filtered(mp->m_str) - && (mp->m_desc == NULL || message_filtered((char_u *)mp->m_desc))) { + && (mp->m_desc == NULL || message_filtered(mp->m_desc))) { return; } @@ -176,7 +187,7 @@ static void showmap(mapblock_T *mp, bool local) // Display the LHS. Get length of what we write. len = (size_t)msg_outtrans_special(mp->m_keys, true, 0); do { - msg_putchar(' '); // padd with blanks + msg_putchar(' '); // pad with blanks len++; } while (len < 12); @@ -214,7 +225,6 @@ static void showmap(mapblock_T *mp, bool local) last_set_msg(mp->m_script_ctx); } msg_clr_eos(); - ui_flush(); // show one line at a time } /// Replace termcodes in the given LHS and RHS and store the results into the @@ -263,52 +273,59 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs if (replaced == NULL) { return false; } - mapargs->lhs_len = STRLEN(replaced); - STRLCPY(mapargs->lhs, replaced, sizeof(mapargs->lhs)); + mapargs->lhs_len = strlen(replaced); + xstrlcpy(mapargs->lhs, replaced, sizeof(mapargs->lhs)); if (did_simplify) { replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, flags | REPTERM_NO_SIMPLIFY, NULL, cpo_flags); if (replaced == NULL) { return false; } - mapargs->alt_lhs_len = STRLEN(replaced); - STRLCPY(mapargs->alt_lhs, replaced, sizeof(mapargs->alt_lhs)); + mapargs->alt_lhs_len = strlen(replaced); + xstrlcpy(mapargs->alt_lhs, replaced, sizeof(mapargs->alt_lhs)); } else { mapargs->alt_lhs_len = 0; } + set_maparg_rhs(orig_rhs, orig_rhs_len, rhs_lua, cpo_flags, mapargs); + + return true; +} + +/// @see set_maparg_lhs_rhs +static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len, + const LuaRef rhs_lua, const int cpo_flags, MapArguments *const mapargs) +{ mapargs->rhs_lua = rhs_lua; if (rhs_lua == LUA_NOREF) { mapargs->orig_rhs_len = orig_rhs_len; - mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u)); - STRLCPY(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1); - + mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char)); + xstrlcpy(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1); if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing - mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char + mapargs->rhs = xcalloc(1, sizeof(char)); // single NUL-char mapargs->rhs_len = 0; mapargs->rhs_is_noop = true; } else { char *rhs_buf = NULL; - replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL, - cpo_flags); - mapargs->rhs_len = STRLEN(replaced); - // XXX: replace_termcodes may produce an empty string even if orig_rhs is non-empty + char *replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL, + cpo_flags); + mapargs->rhs_len = strlen(replaced); + // NB: replace_termcodes may produce an empty string even if orig_rhs is non-empty // (e.g. a single ^V, see :h map-empty-rhs) mapargs->rhs_is_noop = orig_rhs_len != 0 && mapargs->rhs_len == 0; - mapargs->rhs = (char_u *)replaced; + mapargs->rhs = replaced; } } else { char tmp_buf[64]; // orig_rhs is not used for Lua mappings, but still needs to be a string. - mapargs->orig_rhs = xcalloc(1, sizeof(char_u)); + mapargs->orig_rhs = xcalloc(1, sizeof(char)); mapargs->orig_rhs_len = 0; // stores <lua>ref_no<cr> in map_str mapargs->rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "%c%c%c%d\r", K_SPECIAL, (char_u)KS_EXTRA, KE_LUA, rhs_lua); - mapargs->rhs = vim_strsave((char_u *)tmp_buf); + mapargs->rhs = xstrdup(tmp_buf); } - return true; } /// Parse a string of |:map-arguments| into a @ref MapArguments struct. @@ -330,53 +347,53 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs /// @param[out] mapargs MapArguments struct holding all extracted argument /// values. /// @return 0 on success, 1 if invalid arguments are detected. -static int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs) +static int str_to_mapargs(const char *strargs, bool is_unmap, MapArguments *mapargs) { - const char_u *to_parse = strargs; - to_parse = (char_u *)skipwhite((char *)to_parse); + const char *to_parse = strargs; + to_parse = skipwhite(to_parse); CLEAR_POINTER(mapargs); // Accept <buffer>, <nowait>, <silent>, <expr>, <script>, and <unique> in // any order. while (true) { - if (STRNCMP(to_parse, "<buffer>", 8) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 8); + if (strncmp(to_parse, "<buffer>", 8) == 0) { + to_parse = skipwhite(to_parse + 8); mapargs->buffer = true; continue; } - if (STRNCMP(to_parse, "<nowait>", 8) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 8); + if (strncmp(to_parse, "<nowait>", 8) == 0) { + to_parse = skipwhite(to_parse + 8); mapargs->nowait = true; continue; } - if (STRNCMP(to_parse, "<silent>", 8) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 8); + if (strncmp(to_parse, "<silent>", 8) == 0) { + to_parse = skipwhite(to_parse + 8); mapargs->silent = true; continue; } // Ignore obsolete "<special>" modifier. - if (STRNCMP(to_parse, "<special>", 9) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 9); + if (strncmp(to_parse, "<special>", 9) == 0) { + to_parse = skipwhite(to_parse + 9); continue; } - if (STRNCMP(to_parse, "<script>", 8) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 8); + if (strncmp(to_parse, "<script>", 8) == 0) { + to_parse = skipwhite(to_parse + 8); mapargs->script = true; continue; } - if (STRNCMP(to_parse, "<expr>", 6) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 6); + if (strncmp(to_parse, "<expr>", 6) == 0) { + to_parse = skipwhite(to_parse + 6); mapargs->expr = true; continue; } - if (STRNCMP(to_parse, "<unique>", 8) == 0) { - to_parse = (char_u *)skipwhite((char *)to_parse + 8); + if (strncmp(to_parse, "<unique>", 8) == 0) { + to_parse = skipwhite(to_parse + 8); mapargs->unique = true; continue; } @@ -393,7 +410,7 @@ static int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *ma // // With :unmap, literal white space is included in the {lhs}; there is no // separate {rhs}. - const char *lhs_end = (char *)to_parse; + const char *lhs_end = to_parse; bool do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL); while (*lhs_end && (is_unmap || !ascii_iswhite(*lhs_end))) { if ((lhs_end[0] == Ctrl_V || (do_backslash && lhs_end[0] == '\\')) @@ -405,20 +422,20 @@ static int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *ma // {lhs_end} is a pointer to the "terminating whitespace" after {lhs}. // Use that to initialize {rhs_start}. - const char_u *rhs_start = (char_u *)skipwhite((char *)lhs_end); + const char *rhs_start = skipwhite((char *)lhs_end); // Given {lhs} might be larger than MAXMAPLEN before replace_termcodes // (e.g. "<Space>" is longer than ' '), so first copy into a buffer. - size_t orig_lhs_len = (size_t)((char_u *)lhs_end - to_parse); + size_t orig_lhs_len = (size_t)(lhs_end - to_parse); if (orig_lhs_len >= 256) { return 1; } - char_u lhs_to_replace[256]; - STRLCPY(lhs_to_replace, to_parse, orig_lhs_len + 1); + char lhs_to_replace[256]; + xstrlcpy(lhs_to_replace, to_parse, orig_lhs_len + 1); - size_t orig_rhs_len = STRLEN(rhs_start); - if (!set_maparg_lhs_rhs((char *)lhs_to_replace, orig_lhs_len, - (char *)rhs_start, orig_rhs_len, LUA_NOREF, + size_t orig_rhs_len = strlen(rhs_start); + if (!set_maparg_lhs_rhs(lhs_to_replace, orig_lhs_len, + rhs_start, orig_rhs_len, LUA_NOREF, CPO_TO_CPO_FLAGS, mapargs)) { return 1; } @@ -433,7 +450,7 @@ static int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *ma /// and "desc" fields are used. /// "rhs", "rhs_lua", "orig_rhs" fields are cleared if "simplified" is false. /// @param sid -1 to use current_sctx -static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, const char_u *keys, +static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, const char *keys, MapArguments *args, int noremap, int mode, bool is_abbr, scid_T sid, linenr_T lnum, bool simplified) { @@ -448,7 +465,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, } } - mp->m_keys = vim_strsave(keys); + mp->m_keys = xstrdup(keys); mp->m_str = args->rhs; mp->m_orig_str = args->orig_rhs; mp->m_luaref = args->rhs_lua; @@ -457,7 +474,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, args->orig_rhs = NULL; args->rhs_lua = LUA_NOREF; } - mp->m_keylen = (int)STRLEN(mp->m_keys); + mp->m_keylen = (int)strlen(mp->m_keys); mp->m_noremap = noremap; mp->m_nowait = args->nowait; mp->m_silent = args->silent; @@ -483,7 +500,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, mp->m_next = *abbr_table; *abbr_table = mp; } else { - const int n = MAP_HASH(mp->m_mode, mp->m_keys[0]); + const int n = MAP_HASH(mp->m_mode, (uint8_t)mp->m_keys[0]); mp->m_next = map_table[n]; map_table[n] = mp; } @@ -502,7 +519,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T *buf) { mapblock_T *mp, **mpp; - const char_u *p; + const char *p; int n; int retval = 0; mapblock_T **abbr_table; @@ -539,7 +556,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, goto theend; } - const char_u *lhs = (char_u *)&args->lhs; + const char *lhs = (char *)&args->lhs; const bool did_simplify = args->alt_lhs_len != 0; // The following is done twice if we have two versions of keys @@ -553,11 +570,11 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, if (!did_simplify) { break; } - lhs = (char_u *)&args->alt_lhs; + lhs = (char *)&args->alt_lhs; len = (int)args->alt_lhs_len; } else if (did_simplify && do_print) { // when printing always use the not-simplified map - lhs = (char_u *)&args->alt_lhs; + lhs = (char *)&args->alt_lhs; len = (int)args->alt_lhs_len; } @@ -577,9 +594,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, const int first = vim_iswordp(lhs); int last = first; - p = lhs + utfc_ptr2len((char *)lhs); + p = (char *)lhs + utfc_ptr2len((char *)lhs); n = 1; - while (p < lhs + len) { + while (p < (char *)lhs + len) { n++; // nr of (multi-byte) chars last = vim_iswordp(p); // type of last char if (same == -1 && last != first) { @@ -626,7 +643,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, // check entries with the same mode if ((mp->m_mode & mode) != 0 && mp->m_keylen == len - && STRNCMP(mp->m_keys, lhs, (size_t)len) == 0) { + && strncmp(mp->m_keys, lhs, (size_t)len) == 0) { if (is_abbrev) { semsg(_("E224: global abbreviation already exists for %s"), mp->m_keys); @@ -660,7 +677,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, did_local = true; } else { n = mp->m_keylen; - if (STRNCMP(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) { + if (strncmp(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) { showmap(mp, true); did_local = true; } @@ -679,9 +696,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, for (int round = 0; (round == 0 || maptype == MAPTYPE_UNMAP) && round <= 1 && !did_it && !got_int; round++) { int hash_start, hash_end; - if (has_lhs || is_abbrev) { + if ((round == 0 && has_lhs) || is_abbrev) { // just use one hash - hash_start = is_abbrev ? 0 : MAP_HASH(mode, lhs[0]); + hash_start = is_abbrev ? 0 : MAP_HASH(mode, (uint8_t)lhs[0]); hash_end = hash_start + 1; } else { // need to loop over all hash lists @@ -703,13 +720,13 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, } } else { // do we have a match? if (round) { // second round: Try unmap "rhs" string - n = (int)STRLEN(mp->m_str); + n = (int)strlen(mp->m_str); p = mp->m_str; } else { n = mp->m_keylen; p = mp->m_keys; } - if (STRNCMP(p, lhs, (size_t)(n < len ? n : len)) == 0) { + if (strncmp(p, lhs, (size_t)(n < len ? n : len)) == 0) { if (maptype == MAPTYPE_UNMAP) { // Delete entry. // Only accept a full match. For abbreviations @@ -791,7 +808,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, } // May need to put this entry into another hash list. - int new_hash = MAP_HASH(mp->m_mode, mp->m_keys[0]); + int new_hash = MAP_HASH(mp->m_mode, (uint8_t)mp->m_keys[0]); if (!is_abbrev && new_hash != hash) { *mpp = mp->m_next; mp->m_next = map_table[new_hash]; @@ -840,7 +857,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, } // Get here when adding a new entry to the maphash[] list or abbrlist. - map_add(buf, map_table, abbr_table, lhs, args, noremap, mode, is_abbrev, + map_add(buf, map_table, abbr_table, (char *)lhs, args, noremap, mode, is_abbrev, -1, // sid 0, // lnum keyround1_simplified); @@ -884,7 +901,7 @@ theend: /// /// @param maptype MAPTYPE_MAP for |:map| /// MAPTYPE_UNMAP for |:unmap| -/// MAPTYPE_NOREMAP for |noremap|. +/// MAPTYPE_NOREMAP for |:noremap|. /// @param arg C-string containing the arguments of the map/abbrev /// command, i.e. everything except the initial `:[X][nore]map`. /// - Cannot be a read-only string; it will be modified. @@ -898,7 +915,7 @@ theend: /// - 4 for out of mem (deprecated, WON'T HAPPEN) /// - 5 for entry not unique /// -int do_map(int maptype, char_u *arg, int mode, bool is_abbrev) +int do_map(int maptype, char *arg, int mode, bool is_abbrev) { MapArguments parsed_args; int result = str_to_mapargs(arg, maptype == MAPTYPE_UNMAP, &parsed_args); @@ -965,12 +982,12 @@ static int get_map_mode(char **cmdp, bool forceit) /// Clear all mappings (":mapclear") or abbreviations (":abclear"). /// "abbr" should be false for mappings, true for abbreviations. /// This function used to be called map_clear(). -static void do_mapclear(char *cmdp, char_u *arg, int forceit, int abbr) +static void do_mapclear(char *cmdp, char *arg, int forceit, int abbr) { int mode; int local; - local = (STRCMP(arg, "<buffer>") == 0); + local = (strcmp(arg, "<buffer>") == 0); if (!local && *arg != NUL) { emsg(_(e_invarg)); return; @@ -1018,7 +1035,7 @@ void map_clear_mode(buf_T *buf, int mode, bool local, bool abbr) continue; } // May need to put this entry into another hash list. - new_hash = MAP_HASH(mp->m_mode, mp->m_keys[0]); + new_hash = MAP_HASH(mp->m_mode, (uint8_t)mp->m_keys[0]); if (!abbr && new_hash != hash) { *mpp = mp->m_next; if (local) { @@ -1053,9 +1070,9 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo int retval; char *buf = NULL; - const char_u *const rhs = (char_u *)replace_termcodes(str, strlen(str), - &buf, REPTERM_DO_LT, - NULL, CPO_TO_CPO_FLAGS); + const char *const rhs = replace_termcodes(str, strlen(str), + &buf, REPTERM_DO_LT, + NULL, CPO_TO_CPO_FLAGS); #define MAPMODE(mode, modechars, chr, modeflags) \ do { \ @@ -1073,7 +1090,7 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo MAPMODE(mode, modechars, 'c', MODE_CMDLINE); #undef MAPMODE - retval = map_to_exists_mode((char *)rhs, mode, abbr); + retval = map_to_exists_mode(rhs, mode, abbr); xfree(buf); return retval; @@ -1113,7 +1130,7 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr) mp = maphash[hash]; } for (; mp; mp = mp->m_next) { - if ((mp->m_mode & mode) && strstr((char *)mp->m_str, rhs) != NULL) { + if ((mp->m_mode & mode) && strstr(mp->m_str, rhs) != NULL) { return true; } } @@ -1182,7 +1199,7 @@ static char_u *translate_mapping(char_u *str, int cpo_flags) } if (c) { - ga_append(&ga, (char)c); + ga_append(&ga, (uint8_t)c); } } ga_append(&ga, NUL); @@ -1195,7 +1212,7 @@ static char_u *translate_mapping(char_u *str, int cpo_flags) /// @param forceit true if '!' given /// @param isabbrev true if abbreviation /// @param isunmap true if unmap/unabbrev command -char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char_u *arg, bool forceit, bool isabbrev, +char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char *arg, bool forceit, bool isabbrev, bool isunmap, cmdidx_T cmdidx) { if (forceit && cmdidx != CMD_map && cmdidx != CMD_unmap) { @@ -1213,38 +1230,38 @@ char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char_u *arg, bool forcei xp->xp_context = EXPAND_MAPPINGS; expand_buffer = false; for (;;) { - if (STRNCMP(arg, "<buffer>", 8) == 0) { + if (strncmp(arg, "<buffer>", 8) == 0) { expand_buffer = true; - arg = (char_u *)skipwhite((char *)arg + 8); + arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<unique>", 8) == 0) { - arg = (char_u *)skipwhite((char *)arg + 8); + if (strncmp(arg, "<unique>", 8) == 0) { + arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<nowait>", 8) == 0) { - arg = (char_u *)skipwhite((char *)arg + 8); + if (strncmp(arg, "<nowait>", 8) == 0) { + arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<silent>", 8) == 0) { - arg = (char_u *)skipwhite((char *)arg + 8); + if (strncmp(arg, "<silent>", 8) == 0) { + arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<special>", 9) == 0) { - arg = (char_u *)skipwhite((char *)arg + 9); + if (strncmp(arg, "<special>", 9) == 0) { + arg = skipwhite(arg + 9); continue; } - if (STRNCMP(arg, "<script>", 8) == 0) { - arg = (char_u *)skipwhite((char *)arg + 8); + if (strncmp(arg, "<script>", 8) == 0) { + arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<expr>", 6) == 0) { - arg = (char_u *)skipwhite((char *)arg + 6); + if (strncmp(arg, "<expr>", 6) == 0) { + arg = skipwhite(arg + 6); continue; } break; } - xp->xp_pattern = (char *)arg; + xp->xp_pattern = arg; } return NULL; @@ -1253,98 +1270,140 @@ char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char_u *arg, bool forcei /// Find all mapping/abbreviation names that match regexp "regmatch". /// For command line expansion of ":[un]map" and ":[un]abbrev" in all modes. /// @return OK if matches found, FAIL otherwise. -int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file) +int ExpandMappings(char *pat, regmatch_T *regmatch, int *numMatches, char ***matches) { - mapblock_T *mp; - int hash; - int count; - int round; - char_u *p; - int i; - - *num_file = 0; // return values in case of FAIL - *file = NULL; - - // round == 1: Count the matches. - // round == 2: Build the array to keep the matches. - for (round = 1; round <= 2; round++) { - count = 0; - - for (i = 0; i < 7; i++) { - if (i == 0) { - p = (char_u *)"<silent>"; - } else if (i == 1) { - p = (char_u *)"<unique>"; - } else if (i == 2) { - p = (char_u *)"<script>"; - } else if (i == 3) { - p = (char_u *)"<expr>"; - } else if (i == 4 && !expand_buffer) { - p = (char_u *)"<buffer>"; - } else if (i == 5) { - p = (char_u *)"<nowait>"; - } else if (i == 6) { - p = (char_u *)"<special>"; - } else { + const bool fuzzy = cmdline_fuzzy_complete(pat); + + *numMatches = 0; // return values in case of FAIL + *matches = NULL; + + garray_T ga; + if (!fuzzy) { + ga_init(&ga, sizeof(char *), 3); + } else { + ga_init(&ga, sizeof(fuzmatch_str_T), 3); + } + + // First search in map modifier arguments + for (int i = 0; i < 7; i++) { + char *p; + if (i == 0) { + p = "<silent>"; + } else if (i == 1) { + p = "<unique>"; + } else if (i == 2) { + p = "<script>"; + } else if (i == 3) { + p = "<expr>"; + } else if (i == 4 && !expand_buffer) { + p = "<buffer>"; + } else if (i == 5) { + p = "<nowait>"; + } else if (i == 6) { + p = "<special>"; + } else { + continue; + } + + bool match; + int score = 0; + if (!fuzzy) { + match = vim_regexec(regmatch, p, (colnr_T)0); + } else { + score = fuzzy_match_str(p, pat); + match = (score != 0); + } + + if (!match) { + continue; + } + + if (fuzzy) { + GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){ + .idx = ga.ga_len, + .str = xstrdup(p), + .score = score, + })); + } else { + GA_APPEND(char *, &ga, xstrdup(p)); + } + } + + for (int hash = 0; hash < 256; hash++) { + mapblock_T *mp; + if (expand_isabbrev) { + if (hash > 0) { // only one abbrev list + break; // for (hash) + } + mp = first_abbr; + } else if (expand_buffer) { + mp = curbuf->b_maphash[hash]; + } else { + mp = maphash[hash]; + } + for (; mp; mp = mp->m_next) { + if (!(mp->m_mode & expand_mapmodes)) { continue; } - if (vim_regexec(regmatch, (char *)p, (colnr_T)0)) { - if (round == 1) { - count++; - } else { - (*file)[count++] = (char *)vim_strsave(p); - } + char *p = (char *)translate_mapping((char_u *)mp->m_keys, CPO_TO_CPO_FLAGS); + if (p == NULL) { + continue; } - } - for (hash = 0; hash < 256; hash++) { - if (expand_isabbrev) { - if (hash > 0) { // only one abbrev list - break; // for (hash) - } - mp = first_abbr; - } else if (expand_buffer) { - mp = curbuf->b_maphash[hash]; + bool match; + int score = 0; + if (!fuzzy) { + match = vim_regexec(regmatch, p, (colnr_T)0); } else { - mp = maphash[hash]; + score = fuzzy_match_str(p, pat); + match = (score != 0); } - for (; mp; mp = mp->m_next) { - if (mp->m_mode & expand_mapmodes) { - p = translate_mapping(mp->m_keys, CPO_TO_CPO_FLAGS); - if (p != NULL && vim_regexec(regmatch, (char *)p, (colnr_T)0)) { - if (round == 1) { - count++; - } else { - (*file)[count++] = (char *)p; - p = NULL; - } - } - xfree(p); - } - } // for (mp) - } // for (hash) - if (count == 0) { // no match found - break; // for (round) - } + if (!match) { + xfree(p); + continue; + } - if (round == 1) { - *file = xmalloc((size_t)count * sizeof(char_u *)); - } - } // for (round) + if (fuzzy) { + GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){ + .idx = ga.ga_len, + .str = p, + .score = score, + })); + } else { + GA_APPEND(char *, &ga, p); + } + } // for (mp) + } // for (hash) + if (ga.ga_len == 0) { + return FAIL; + } + + if (!fuzzy) { + *matches = ga.ga_data; + *numMatches = ga.ga_len; + } else { + fuzzymatches_to_strmatches(ga.ga_data, matches, ga.ga_len, false); + *numMatches = ga.ga_len; + } + + int count = *numMatches; if (count > 1) { // Sort the matches - sort_strings(*file, count); + // Fuzzy matching already sorts the matches + if (!fuzzy) { + sort_strings(*matches, count); + } // Remove multiple entries - char **ptr1 = *file; + char **ptr1 = *matches; char **ptr2 = ptr1 + 1; char **ptr3 = ptr1 + count; while (ptr2 < ptr3) { - if (STRCMP(*ptr1, *ptr2)) { + if (strcmp(*ptr1, *ptr2) != 0) { *++ptr1 = *ptr2++; } else { xfree(*ptr2++); @@ -1353,7 +1412,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file) } } - *num_file = count; + *numMatches = count; return count == 0 ? FAIL : OK; } @@ -1373,12 +1432,12 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char ***file) // Then there must be white space before the abbr. // // Return true if there is an abbreviation, false if not. -bool check_abbr(int c, char_u *ptr, int col, int mincol) +bool check_abbr(int c, char *ptr, int col, int mincol) { int len; int scol; // starting column of the abbr. int j; - char_u *s; + char *s; char_u tb[MB_MAXBYTES + 4]; mapblock_T *mp; mapblock_T *mp2; @@ -1404,7 +1463,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) { bool vim_abbr; - char_u *p = mb_prevptr(ptr, ptr + col); + char *p = mb_prevptr(ptr, ptr + col); if (!vim_iswordp(p)) { vim_abbr = true; // Vim added abbr. } else { @@ -1417,7 +1476,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) while (p > ptr + mincol) { p = mb_prevptr(ptr, p); if (ascii_isspace(*p) || (!vim_abbr && is_id != vim_iswordp(p))) { - p += utfc_ptr2len((char *)p); + p += utfc_ptr2len(p); break; } clen++; @@ -1441,19 +1500,19 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) mp->m_next == NULL ? (mp = mp2, mp2 = NULL) : (mp = mp->m_next)) { int qlen = mp->m_keylen; - char_u *q = mp->m_keys; + char *q = mp->m_keys; int match; if (strchr((const char *)mp->m_keys, K_SPECIAL) != NULL) { // Might have K_SPECIAL escaped mp->m_keys. - q = vim_strsave(mp->m_keys); - vim_unescape_ks(q); - qlen = (int)STRLEN(q); + q = xstrdup(mp->m_keys); + vim_unescape_ks((char_u *)q); + qlen = (int)strlen(q); } // find entries with right mode and keys match = (mp->m_mode & State) && qlen == len - && !STRNCMP(q, ptr, (size_t)len); + && !strncmp(q, ptr, (size_t)len); if (q != mp->m_keys) { xfree(q); } @@ -1491,9 +1550,9 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) int newlen = utf_char2bytes(c, (char *)tb + j); tb[j + newlen] = NUL; // Need to escape K_SPECIAL. - char_u *escaped = (char_u *)vim_strsave_escape_ks((char *)tb + j); + char *escaped = vim_strsave_escape_ks((char *)tb + j); if (escaped != NULL) { - newlen = (int)STRLEN(escaped); + newlen = (int)strlen(escaped); memmove(tb + j, escaped, (size_t)newlen); j += newlen; xfree(escaped); @@ -1503,17 +1562,23 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) // insert the last typed char (void)ins_typebuf((char *)tb, 1, 0, true, mp->m_silent); } - if (mp->m_expr) { + + // copy values here, calling eval_map_expr() may make "mp" invalid! + const int noremap = mp->m_noremap; + const bool silent = mp->m_silent; + const bool expr = mp->m_expr; + + if (expr) { s = eval_map_expr(mp, c); } else { s = mp->m_str; } if (s != NULL) { // insert the to string - (void)ins_typebuf((char *)s, mp->m_noremap, 0, true, mp->m_silent); + (void)ins_typebuf(s, noremap, 0, true, silent); // no abbrev. for these chars - typebuf.tb_no_abbr_cnt += (int)STRLEN(s) + j + 1; - if (mp->m_expr) { + typebuf.tb_no_abbr_cnt += (int)strlen(s) + j + 1; + if (expr) { xfree(s); } } @@ -1522,7 +1587,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) tb[1] = NUL; len = clen; // Delete characters instead of bytes while (len-- > 0) { // delete the from string - (void)ins_typebuf((char *)tb, 1, 0, true, mp->m_silent); + (void)ins_typebuf((char *)tb, 1, 0, true, silent); } return true; } @@ -1532,20 +1597,23 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) /// Evaluate the RHS of a mapping or abbreviations and take care of escaping /// special characters. +/// Careful: after this "mp" will be invalid if the mapping was deleted. /// /// @param c NUL or typed character for abbreviation -char_u *eval_map_expr(mapblock_T *mp, int c) +char *eval_map_expr(mapblock_T *mp, int c) { - char_u *p = NULL; - char_u *expr = NULL; + char *p = NULL; + char *expr = NULL; // Remove escaping of K_SPECIAL, because "str" is in a format to be used as // typeahead. if (mp->m_luaref == LUA_NOREF) { - expr = vim_strsave(mp->m_str); - vim_unescape_ks(expr); + expr = xstrdup(mp->m_str); + vim_unescape_ks((char_u *)expr); } + const bool replace_keycodes = mp->m_replace_keycodes; + // Forbid changing text or using ":normal" to avoid most of the bad side // effects. Also restore the cursor position. textlock++; @@ -1559,7 +1627,7 @@ char_u *eval_map_expr(mapblock_T *mp, int c) Array args = ARRAY_DICT_INIT; Object ret = nlua_call_ref(mp->m_luaref, NULL, args, true, &err); if (ret.type == kObjectTypeString) { - p = (char_u *)xstrndup(ret.data.string.data, ret.data.string.size); + p = xstrndup(ret.data.string.data, ret.data.string.size); } api_free_object(ret); if (err.type != kErrorTypeNone) { @@ -1567,7 +1635,7 @@ char_u *eval_map_expr(mapblock_T *mp, int c) api_clear_error(&err); } } else { - p = (char_u *)eval_to_string((char *)expr, NULL, false); + p = eval_to_string(expr, NULL, false); xfree(expr); } textlock--; @@ -1580,13 +1648,13 @@ char_u *eval_map_expr(mapblock_T *mp, int c) return NULL; } - char_u *res = NULL; + char *res = NULL; - if (mp->m_replace_keycodes) { - replace_termcodes((char *)p, STRLEN(p), (char **)&res, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); + if (replace_keycodes) { + replace_termcodes(p, strlen(p), &res, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); } else { // Escape K_SPECIAL in the result to be able to use the string as typeahead. - res = (char_u *)vim_strsave_escape_ks((char *)p); + res = vim_strsave_escape_ks(p); } xfree(p); @@ -1600,8 +1668,8 @@ char_u *eval_map_expr(mapblock_T *mp, int c) int makemap(FILE *fd, buf_T *buf) { mapblock_T *mp; - char_u c1, c2, c3; - char_u *p; + char c1, c2, c3; + char *p; char *cmd; int abbr; int hash; @@ -1640,7 +1708,7 @@ int makemap(FILE *fd, buf_T *buf) continue; } for (p = mp->m_str; *p != NUL; p++) { - if (p[0] == K_SPECIAL && p[1] == KS_EXTRA + if ((uint8_t)p[0] == K_SPECIAL && (uint8_t)p[1] == KS_EXTRA && p[2] == KE_SNR) { break; } @@ -1811,9 +1879,9 @@ int makemap(FILE *fd, buf_T *buf) // "what": 0 for :map lhs, 1 for :map rhs, 2 for :set // // return FAIL for failure, OK otherwise -int put_escstr(FILE *fd, char_u *strstart, int what) +int put_escstr(FILE *fd, char *strstart, int what) { - char_u *str = strstart; + char_u *str = (char_u *)strstart; int c; // :map xx <Nop> @@ -1890,7 +1958,7 @@ int put_escstr(FILE *fd, char_u *strstart, int what) } } else if (c < ' ' || c > '~' || c == '|' || (what == 0 && c == ' ') - || (what == 1 && str == strstart && c == ' ') + || (what == 1 && str == (char_u *)strstart && c == ' ') || (what != 2 && c == '<')) { if (putc(Ctrl_V, fd) < 0) { return FAIL; @@ -1912,14 +1980,14 @@ int put_escstr(FILE *fd, char_u *strstart, int what) /// @param abbr do abbreviations /// @param mp_ptr return: pointer to mapblock or NULL /// @param local_ptr return: buffer-local mapping or NULL -char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapblock_T **mp_ptr, - int *local_ptr, int *rhs_lua) +char *check_map(char *keys, int mode, int exact, int ign_mod, int abbr, mapblock_T **mp_ptr, + int *local_ptr, int *rhs_lua) { int len, minlen; mapblock_T *mp; *rhs_lua = LUA_NOREF; - len = (int)STRLEN(keys); + len = (int)strlen(keys); for (int local = 1; local >= 0; local--) { // loop over all hash lists for (int hash = 0; hash < 256; hash++) { @@ -1940,15 +2008,15 @@ char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapb for (; mp != NULL; mp = mp->m_next) { // skip entries with wrong mode, wrong length and not matching ones if ((mp->m_mode & mode) && (!exact || mp->m_keylen == len)) { - char_u *s = mp->m_keys; + char *s = mp->m_keys; int keylen = mp->m_keylen; if (ign_mod && keylen >= 3 - && s[0] == K_SPECIAL && s[1] == KS_MODIFIER) { + && (uint8_t)s[0] == K_SPECIAL && (uint8_t)s[1] == KS_MODIFIER) { s += 3; keylen -= 3; } minlen = keylen < len ? keylen : len; - if (STRNCMP(s, keys, minlen) == 0) { + if (strncmp(s, keys, (size_t)minlen) == 0) { if (mp_ptr != NULL) { *mp_ptr = mp; } @@ -1967,7 +2035,7 @@ char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapb } /// "hasmapto()" function -void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_hasmapto(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *mode; const char *const name = tv_get_string(&argvars[0]); @@ -1989,18 +2057,19 @@ void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/// Fill a dictionary with all applicable maparg() like dictionaries +/// Fill a Dictionary with all applicable maparg() like dictionaries /// -/// @param dict The dictionary to be filled /// @param mp The maphash that contains the mapping information /// @param buffer_value The "buffer" value /// @param compatible True for compatible with old maparg() dict -static void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, - const char *lhsrawalt, long buffer_value, bool compatible) - FUNC_ATTR_NONNULL_ARG(1, 2) +/// +/// @return A Dictionary. +static Dictionary mapblock_fill_dict(const mapblock_T *const mp, const char *lhsrawalt, + const long buffer_value, const bool compatible) + FUNC_ATTR_NONNULL_ARG(1) { - char *const lhs = str2special_save((const char *)mp->m_keys, - compatible, !compatible); + Dictionary dict = ARRAY_DICT_INIT; + char *const lhs = str2special_save(mp->m_keys, compatible, !compatible); char *const mapmode = map_mode_to_chars(mp->m_mode); varnumber_T noremap_value; @@ -2015,37 +2084,35 @@ static void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, } if (mp->m_luaref != LUA_NOREF) { - tv_dict_add_nr(dict, S_LEN("callback"), mp->m_luaref); + PUT(dict, "callback", LUAREF_OBJ(api_new_luaref(mp->m_luaref))); } else { - if (compatible) { - tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str); - } else { - tv_dict_add_allocated_str(dict, S_LEN("rhs"), - str2special_save((const char *)mp->m_str, false, - true)); - } + PUT(dict, "rhs", STRING_OBJ(compatible + ? cstr_to_string(mp->m_orig_str) + : cstr_as_string(str2special_save(mp->m_str, false, true)))); } if (mp->m_desc != NULL) { - tv_dict_add_allocated_str(dict, S_LEN("desc"), xstrdup(mp->m_desc)); + PUT(dict, "desc", STRING_OBJ(cstr_to_string(mp->m_desc))); } - tv_dict_add_allocated_str(dict, S_LEN("lhs"), lhs); - tv_dict_add_str(dict, S_LEN("lhsraw"), (const char *)mp->m_keys); + PUT(dict, "lhs", STRING_OBJ(cstr_as_string(lhs))); + PUT(dict, "lhsraw", STRING_OBJ(cstr_to_string((const char *)mp->m_keys))); if (lhsrawalt != NULL) { // Also add the value for the simplified entry. - tv_dict_add_str(dict, S_LEN("lhsrawalt"), lhsrawalt); - } - tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value); - tv_dict_add_nr(dict, S_LEN("script"), mp->m_noremap == REMAP_SCRIPT ? 1 : 0); - tv_dict_add_nr(dict, S_LEN("expr"), mp->m_expr ? 1 : 0); - tv_dict_add_nr(dict, S_LEN("silent"), mp->m_silent ? 1 : 0); - tv_dict_add_nr(dict, S_LEN("sid"), (varnumber_T)mp->m_script_ctx.sc_sid); - tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)mp->m_script_ctx.sc_lnum); - tv_dict_add_nr(dict, S_LEN("buffer"), (varnumber_T)buffer_value); - tv_dict_add_nr(dict, S_LEN("nowait"), mp->m_nowait ? 1 : 0); + PUT(dict, "lhsrawalt", STRING_OBJ(cstr_to_string(lhsrawalt))); + } + PUT(dict, "noremap", INTEGER_OBJ(noremap_value)); + PUT(dict, "script", INTEGER_OBJ(mp->m_noremap == REMAP_SCRIPT ? 1 : 0)); + PUT(dict, "expr", INTEGER_OBJ(mp->m_expr ? 1 : 0)); + PUT(dict, "silent", INTEGER_OBJ(mp->m_silent ? 1 : 0)); + PUT(dict, "sid", INTEGER_OBJ((varnumber_T)mp->m_script_ctx.sc_sid)); + PUT(dict, "lnum", INTEGER_OBJ((varnumber_T)mp->m_script_ctx.sc_lnum)); + PUT(dict, "buffer", INTEGER_OBJ((varnumber_T)buffer_value)); + PUT(dict, "nowait", INTEGER_OBJ(mp->m_nowait ? 1 : 0)); if (mp->m_replace_keycodes) { - tv_dict_add_nr(dict, S_LEN("replace_keycodes"), 1); + PUT(dict, "replace_keycodes", INTEGER_OBJ(1)); } - tv_dict_add_allocated_str(dict, S_LEN("mode"), mapmode); + PUT(dict, "mode", STRING_OBJ(cstr_as_string(mapmode))); + + return dict; } static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) @@ -2085,21 +2152,21 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) const int flags = REPTERM_FROM_PART | REPTERM_DO_LT; const int mode = get_map_mode((char **)&which, 0); - char_u *keys_simplified - = (char_u *)replace_termcodes(keys, STRLEN(keys), &keys_buf, flags, &did_simplify, - CPO_TO_CPO_FLAGS); + char *keys_simplified = replace_termcodes(keys, strlen(keys), &keys_buf, flags, &did_simplify, + CPO_TO_CPO_FLAGS); mapblock_T *mp = NULL; int buffer_local; LuaRef rhs_lua; - char_u *rhs = check_map(keys_simplified, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); + char *rhs = check_map(keys_simplified, mode, exact, false, abbr, &mp, &buffer_local, + &rhs_lua); if (did_simplify) { // When the lhs is being simplified the not-simplified keys are // preferred for printing, like in do_map(). (void)replace_termcodes(keys, - STRLEN(keys), + strlen(keys), &alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL, CPO_TO_CPO_FLAGS); - rhs = check_map((char_u *)alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); + rhs = check_map(alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); } if (!get_dict) { @@ -2108,17 +2175,22 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) if (*rhs == NUL) { rettv->vval.v_string = xstrdup("<Nop>"); } else { - rettv->vval.v_string = str2special_save((char *)rhs, false, false); + rettv->vval.v_string = str2special_save(rhs, false, false); } } else if (rhs_lua != LUA_NOREF) { rettv->vval.v_string = nlua_funcref_str(mp->m_luaref); } } else { - tv_dict_alloc_ret(rettv); + // Return a dictionary. if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) { - // Return a dictionary. - mapblock_fill_dict(rettv->vval.v_dict, mp, did_simplify ? (char *)keys_simplified : NULL, - buffer_local, true); + Dictionary dict = mapblock_fill_dict(mp, + did_simplify ? keys_simplified : NULL, + buffer_local, true); + (void)object_to_vim(DICTIONARY_OBJ(dict), rettv, NULL); + api_free_dictionary(dict); + } else { + // Return an empty dictionary. + tv_dict_alloc_ret(rettv); } } @@ -2127,7 +2199,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) } /// "mapset()" function -void f_mapset(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char buf[NUMBUFLEN]; const char *which = tv_get_string_buf_chk(&argvars[0], buf); @@ -2147,29 +2219,36 @@ void f_mapset(typval_T *argvars, typval_T *rettv, FunPtr fptr) char *lhs = tv_dict_get_string(d, "lhs", false); char *lhsraw = tv_dict_get_string(d, "lhsraw", false); char *lhsrawalt = tv_dict_get_string(d, "lhsrawalt", false); - char *rhs = tv_dict_get_string(d, "rhs", false); - if (lhs == NULL || lhsraw == NULL || rhs == NULL) { + char *orig_rhs = tv_dict_get_string(d, "rhs", false); + LuaRef rhs_lua = LUA_NOREF; + dictitem_T *callback_di = tv_dict_find(d, S_LEN("callback")); + if (callback_di != NULL) { + Object callback_obj = vim_to_object(&callback_di->di_tv); + if (callback_obj.type == kObjectTypeLuaRef && callback_obj.data.luaref != LUA_NOREF) { + rhs_lua = callback_obj.data.luaref; + orig_rhs = ""; + callback_obj.data.luaref = LUA_NOREF; + } + api_free_object(callback_obj); + } + if (lhs == NULL || lhsraw == NULL || orig_rhs == NULL) { emsg(_("E460: entries missing in mapset() dict argument")); + api_free_luaref(rhs_lua); return; } - char *orig_rhs = rhs; - char *arg_buf = NULL; - rhs = replace_termcodes(rhs, STRLEN(rhs), &arg_buf, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); - int noremap = tv_dict_get_number(d, "noremap") ? REMAP_NONE : 0; + int noremap = tv_dict_get_number(d, "noremap") != 0 ? REMAP_NONE : 0; if (tv_dict_get_number(d, "script") != 0) { noremap = REMAP_SCRIPT; } - MapArguments args = { // TODO(zeertzjq): support restoring "callback"? - .rhs = (char_u *)rhs, - .rhs_lua = LUA_NOREF, - .orig_rhs = vim_strsave((char_u *)orig_rhs), + MapArguments args = { .expr = tv_dict_get_number(d, "expr") != 0, .silent = tv_dict_get_number(d, "silent") != 0, .nowait = tv_dict_get_number(d, "nowait") != 0, .replace_keycodes = tv_dict_get_number(d, "replace_keycodes") != 0, .desc = tv_dict_get_string(d, "desc", false), }; + set_maparg_rhs(orig_rhs, strlen(orig_rhs), rhs_lua, CPO_TO_CPO_FLAGS, &args); scid_T sid = (scid_T)tv_dict_get_number(d, "sid"); linenr_T lnum = (linenr_T)tv_dict_get_number(d, "lnum"); bool buffer = tv_dict_get_number(d, "buffer") != 0; @@ -2180,28 +2259,28 @@ void f_mapset(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Delete any existing mapping for this lhs and mode. MapArguments unmap_args = MAP_ARGUMENTS_INIT; - set_maparg_lhs_rhs(lhs, strlen(lhs), rhs, strlen(rhs), LUA_NOREF, 0, &unmap_args); + set_maparg_lhs_rhs(lhs, strlen(lhs), "", 0, LUA_NOREF, 0, &unmap_args); unmap_args.buffer = buffer; - buf_do_map(MAPTYPE_UNMAP, &unmap_args, mode, false, curbuf); + buf_do_map(MAPTYPE_UNMAP, &unmap_args, mode, is_abbr, curbuf); xfree(unmap_args.rhs); xfree(unmap_args.orig_rhs); if (lhsrawalt != NULL) { - map_add(curbuf, map_table, abbr_table, (char_u *)lhsrawalt, &args, noremap, mode, is_abbr, + map_add(curbuf, map_table, abbr_table, lhsrawalt, &args, noremap, mode, is_abbr, sid, lnum, true); } - map_add(curbuf, map_table, abbr_table, (char_u *)lhsraw, &args, noremap, mode, is_abbr, + map_add(curbuf, map_table, abbr_table, lhsraw, &args, noremap, mode, is_abbr, sid, lnum, false); } /// "maparg()" function -void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_maparg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_maparg(argvars, rettv, true); } /// "mapcheck()" function -void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_mapcheck(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_maparg(argvars, rettv, false); } @@ -2315,8 +2394,8 @@ void langmap_init(void) /// changed at any time! void langmap_set(void) { - char_u *p; - char_u *p2; + char *p; + char *p2; int from, to; ga_clear(&langmap_mapga); // clear the previous map first @@ -2342,7 +2421,7 @@ void langmap_set(void) if (p[0] == '\\' && p[1] != NUL) { p++; } - from = utf_ptr2char((char *)p); + from = utf_ptr2char(p); to = NUL; if (p2 == NULL) { MB_PTR_ADV(p); @@ -2350,14 +2429,14 @@ void langmap_set(void) if (p[0] == '\\') { p++; } - to = utf_ptr2char((char *)p); + to = utf_ptr2char(p); } } else { if (p2[0] != ',') { if (p2[0] == '\\') { p2++; } - to = utf_ptr2char((char *)p2); + to = utf_ptr2char(p2); } } if (to == NUL) { @@ -2381,8 +2460,7 @@ void langmap_set(void) p = p2; if (p[0] != NUL) { if (p[0] != ',') { - semsg(_("E358: 'langmap': Extra characters after semicolon: %s"), - p); + semsg(_("E358: 'langmap': Extra characters after semicolon: %s"), p); return; } p++; @@ -2402,7 +2480,7 @@ static void do_exmap(exarg_T *eap, int isabbrev) switch (do_map((*cmdp == 'n') ? MAPTYPE_NOREMAP : (*cmdp == 'u') ? MAPTYPE_UNMAP : MAPTYPE_MAP, - (char_u *)eap->arg, mode, isabbrev)) { + eap->arg, mode, isabbrev)) { case 1: emsg(_(e_invarg)); break; @@ -2421,8 +2499,7 @@ void ex_abbreviate(exarg_T *eap) /// ":map" and friends. void ex_map(exarg_T *eap) { - // If we are sourcing .exrc or .vimrc in current directory we - // print the mappings for security reasons. + // If we are in a secure mode we print the mappings for security reasons. if (secure) { secure = 2; msg_outtrans(eap->cmd); @@ -2440,13 +2517,13 @@ void ex_unmap(exarg_T *eap) /// ":mapclear" and friends. void ex_mapclear(exarg_T *eap) { - do_mapclear(eap->cmd, (char_u *)eap->arg, eap->forceit, false); + do_mapclear(eap->cmd, eap->arg, eap->forceit, false); } /// ":abclear" and friends. void ex_abclear(exarg_T *eap) { - do_mapclear(eap->cmd, (char_u *)eap->arg, true, true); + do_mapclear(eap->cmd, eap->arg, true, true); } /// Set, tweak, or remove a mapping in a mode. Acts as the implementation for @@ -2523,7 +2600,7 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod } int mode_val; // integer value of the mapping mode, to be passed to do_map() char *p = (mode.size) ? mode.data : "m"; - if (STRNCMP(p, "!", 2) == 0) { + if (strncmp(p, "!", 2) == 0) { mode_val = get_map_mode(&p, true); // mapmode-ic } else { mode_val = get_map_mode(&p, false); @@ -2599,12 +2676,10 @@ fail_and_free: /// /// @param mode The abbreviation for the mode /// @param buf The buffer to get the mapping array. NULL for global -/// @param from_lua Whether it is called from internal Lua api. /// @returns Array of maparg()-like dictionaries describing mappings -ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua) +ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) { Array mappings = ARRAY_DICT_INIT; - dict_T *const dict = tv_dict_alloc(); // Convert the string mode to the integer mode // that is stored within each mapblock @@ -2623,25 +2698,11 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua) } // Check for correct mode if (int_mode & current_maphash->m_mode) { - mapblock_fill_dict(dict, current_maphash, NULL, buffer_value, false); - Object api_dict = vim_to_object((typval_T[]) { { .v_type = VAR_DICT, - .vval.v_dict = dict } }); - if (from_lua) { - Dictionary d = api_dict.data.dictionary; - for (size_t j = 0; j < d.size; j++) { - if (strequal("callback", d.items[j].key.data)) { - d.items[j].value.type = kObjectTypeLuaRef; - d.items[j].value.data.luaref = api_new_luaref((LuaRef)d.items[j].value.data.integer); - break; - } - } - } - ADD(mappings, api_dict); - tv_dict_clear(dict); + ADD(mappings, + DICTIONARY_OBJ(mapblock_fill_dict(current_maphash, NULL, buffer_value, false))); } } } - tv_dict_free(dict); return mappings; } diff --git a/src/nvim/mapping.h b/src/nvim/mapping.h index 7c48c3bce2..58e28810bc 100644 --- a/src/nvim/mapping.h +++ b/src/nvim/mapping.h @@ -1,6 +1,10 @@ #ifndef NVIM_MAPPING_H #define NVIM_MAPPING_H +#include <stdbool.h> +#include <stddef.h> + +#include "lauxlib.h" #include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/types.h" @@ -9,7 +13,7 @@ /// All possible |:map-arguments| usable in a |:map| command. /// /// The <special> argument has no effect on mappings and is excluded from this -/// struct declaration. |noremap| is included, since it behaves like a map +/// struct declaration. |:noremap| is included, since it behaves like a map /// argument when used in a mapping. /// /// @see mapblock_T @@ -28,19 +32,19 @@ struct map_arguments { /// vim limits this to MAXMAPLEN characters, allowing us to use a static /// buffer. Setting lhs_len to a value larger than MAXMAPLEN can signal /// that {lhs} was too long and truncated. - char_u lhs[MAXMAPLEN + 1]; + char lhs[MAXMAPLEN + 1]; size_t lhs_len; /// Unsimplifed {lhs} of the mapping. If no simplification has been done then alt_lhs_len is 0. - char_u alt_lhs[MAXMAPLEN + 1]; + char alt_lhs[MAXMAPLEN + 1]; size_t alt_lhs_len; - char_u *rhs; /// The {rhs} of the mapping. + char *rhs; /// The {rhs} of the mapping. size_t rhs_len; LuaRef rhs_lua; /// lua function as {rhs} bool rhs_is_noop; /// True when the {rhs} should be <Nop>. - char_u *orig_rhs; /// The original text of the {rhs}. + char *orig_rhs; /// The original text of the {rhs}. size_t orig_rhs_len; char *desc; /// map description }; diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 13ffbfbd21..0c7f5efd89 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -1,26 +1,30 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * mark.c: functions for setting marks and jumping to them - */ +// mark.c: functions for setting marks and jumping to them #include <assert.h> -#include <inttypes.h> #include <limits.h> +#include <stdint.h> +#include <stdio.h> #include <string.h> #include "nvim/ascii.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/edit.h" -#include "nvim/eval.h" #include "nvim/eval/typval.h" -#include "nvim/ex_cmds.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/extmark.h" +#include "nvim/extmark_defs.h" #include "nvim/fold.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/mark.h" #include "nvim/mark_defs.h" #include "nvim/mbyte.h" @@ -29,31 +33,26 @@ #include "nvim/message.h" #include "nvim/move.h" #include "nvim/normal.h" -#include "nvim/option.h" +#include "nvim/option_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/quickfix.h" -#include "nvim/search.h" #include "nvim/sign.h" #include "nvim/strings.h" -#include "nvim/ui.h" +#include "nvim/textobject.h" +#include "nvim/undo_defs.h" #include "nvim/vim.h" #include "nvim/map.h" #include "nvim/eval/userfunc.h" -/* - * This file contains routines to maintain and manipulate marks. - */ +// This file contains routines to maintain and manipulate marks. -/* - * If a named file mark's lnum is non-zero, it is valid. - * If a named file mark's fnum is non-zero, it is for an existing buffer, - * otherwise it is from .shada and namedfm[n].fname is the file name. - * There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing - * shada). - */ +// If a named file mark's lnum is non-zero, it is valid. +// If a named file mark's fnum is non-zero, it is for an existing buffer, +// otherwise it is from .shada and namedfm[n].fname is the file name. +// There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing +// shada). /// Global marks (marks with file number or name) static xfmark_T namedfm[NGLOBALMARKS]; @@ -87,10 +86,9 @@ static xfmark_T* lookup_user_mark(int mark) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mark.c.generated.h" #endif -/* - * Set named mark "c" at current cursor position. - * Returns OK on success, FAIL if bad name given. - */ + +// Set named mark "c" at current cursor position. +// Returns OK on success, FAIL if bad name given. int setmark(int c) { fmarkv_T view = mark_view_make(curwin->w_topline, curwin->w_cursor); @@ -118,11 +116,9 @@ void clear_fmark(fmark_T *fm) CLEAR_POINTER(fm); } -/* - * Set named mark "c" to position "pos". - * When "c" is upper case use file "fnum". - * Returns OK on success, FAIL if bad name given. - */ +// Set named mark "c" to position "pos". +// When "c" is upper case use file "fnum". +// Returns OK on success, FAIL if bad name given. int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt) { int i; @@ -197,10 +193,8 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt) return mark_set_user(buf, c); } -/* - * Set the previous context mark to the current position and add it to the - * jump list. - */ +// Set the previous context mark to the current position and add it to the +// jump list. void setpcmark(void) { xfmark_T *fm; @@ -241,12 +235,10 @@ void setpcmark(void) SET_XFMARK(fm, curwin->w_pcmark, curbuf->b_fnum, view, NULL); } -/* - * To change context, call setpcmark(), then move the current position to - * where ever, then call checkpcmark(). This ensures that the previous - * context will only be changed if the cursor moved to a different line. - * If pcmark was deleted (with "dG") the previous mark is restored. - */ +// To change context, call setpcmark(), then move the current position to +// where ever, then call checkpcmark(). This ensures that the previous +// context will only be changed if the cursor moved to a different line. +// If pcmark was deleted (with "dG") the previous mark is restored. void checkpcmark(void) { if (curwin->w_prev_pcmark.lnum != 0 @@ -504,7 +496,7 @@ static int call_umf( args[1].vval.v_string = markname_str; funcexe_T funcexe = FUNCEXE_INIT; - funcexe.evaluate = true; + funcexe.fe_evaluate = true; return call_func(umf, -1, out, 2, args, &funcexe); } @@ -715,6 +707,7 @@ MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags) // Need to change buffer fm_copy = *fm; // Copy, autocommand may change it fm = &fm_copy; + // Jump to the file with the mark res |= switch_to_mark_buf(fm, !(flags & kMarkJumpList)); // Failed switching buffer if (res & kMarkMoveFailed) { @@ -732,6 +725,7 @@ MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags) // Move the cursor while keeping track of what changed for the caller pos_T prev_pos = curwin->w_cursor; pos_T pos = fm->mark; + // Set lnum again, autocommands my have changed it curwin->w_cursor = fm->mark; if (flags & kMarkBeginLine) { beginline(BL_WHITE | BL_FIX); @@ -810,73 +804,66 @@ fmark_T *getnextmark(pos_T *startpos, int dir, int begin_line) return result; } -/* - * For an xtended filemark: set the fnum from the fname. - * This is used for marks obtained from the .shada file. It's postponed - * until the mark is used to avoid a long startup delay. - */ +// For an xtended filemark: set the fnum from the fname. +// This is used for marks obtained from the .shada file. It's postponed +// until the mark is used to avoid a long startup delay. static void fname2fnum(xfmark_T *fm) { - char_u *p; - - if (fm->fname != NULL) { - /* - * First expand "~/" in the file name to the home directory. - * Don't expand the whole name, it may contain other '~' chars. - */ - if (fm->fname[0] == '~' && (fm->fname[1] == '/' + if (fm->fname == NULL) { + return; + } + + // First expand "~/" in the file name to the home directory. + // Don't expand the whole name, it may contain other '~' chars. #ifdef BACKSLASH_IN_FILENAME - || fm->fname[1] == '\\' + if (fm->fname[0] == '~' && (fm->fname[1] == '/' || fm->fname[1] == '\\')) { +#else + if (fm->fname[0] == '~' && (fm->fname[1] == '/')) { #endif - )) { - int len; - expand_env((char_u *)"~/", NameBuff, MAXPATHL); - len = (int)STRLEN(NameBuff); - STRLCPY(NameBuff + len, fm->fname + 2, MAXPATHL - len); - } else { - STRLCPY(NameBuff, fm->fname, MAXPATHL); - } + expand_env("~/", NameBuff, MAXPATHL); + int len = (int)strlen(NameBuff); + xstrlcpy(NameBuff + len, fm->fname + 2, (size_t)(MAXPATHL - len)); + } else { + xstrlcpy(NameBuff, fm->fname, MAXPATHL); + } - // Try to shorten the file name. - os_dirname(IObuff, IOSIZE); - p = path_shorten_fname(NameBuff, IObuff); + // Try to shorten the file name. + os_dirname(IObuff, IOSIZE); + char *p = path_shorten_fname(NameBuff, IObuff); - // buflist_new() will call fmarks_check_names() - (void)buflist_new((char *)NameBuff, (char *)p, (linenr_T)1, 0); - } + // buflist_new() will call fmarks_check_names() + (void)buflist_new(NameBuff, p, (linenr_T)1, 0); } -/* - * Check all file marks for a name that matches the file name in buf. - * May replace the name with an fnum. - * Used for marks that come from the .shada file. - */ +// Check all file marks for a name that matches the file name in buf. +// May replace the name with an fnum. +// Used for marks that come from the .shada file. void fmarks_check_names(buf_T *buf) { - char_u *name = (char_u *)buf->b_ffname; + char *name = buf->b_ffname; int i; if (buf->b_ffname == NULL) { return; } - for (i = 0; i < NGLOBALMARKS; ++i) { + for (i = 0; i < NGLOBALMARKS; i++) { fmarks_check_one(&namedfm[i], name, buf); } FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - for (i = 0; i < wp->w_jumplistlen; ++i) { + for (i = 0; i < wp->w_jumplistlen; i++) { fmarks_check_one(&wp->w_jumplist[i], name, buf); } } } -static void fmarks_check_one(xfmark_T *fm, char_u *name, buf_T *buf) +static void fmarks_check_one(xfmark_T *fm, char *name, buf_T *buf) { if (fm->fmark.fnum == 0 && fm->fname != NULL - && FNAMECMP(name, fm->fname) == 0) { + && path_fnamecmp(name, fm->fname) == 0) { fm->fmark.fnum = buf->b_fnum; XFREE_CLEAR(fm->fname); } @@ -949,39 +936,35 @@ void clrallmarks(buf_T *const buf) buf->b_changelistlen = 0; } -/* - * Get name of file from a filemark. - * When it's in the current buffer, return the text at the mark. - * Returns an allocated string. - */ -char_u *fm_getname(fmark_T *fmark, int lead_len) +// Get name of file from a filemark. +// When it's in the current buffer, return the text at the mark. +// Returns an allocated string. +char *fm_getname(fmark_T *fmark, int lead_len) { if (fmark->fnum == curbuf->b_fnum) { // current buffer return mark_line(&(fmark->mark), lead_len); } - return (char_u *)buflist_nr2name(fmark->fnum, false, true); + return buflist_nr2name(fmark->fnum, false, true); } -/* - * Return the line at mark "mp". Truncate to fit in window. - * The returned string has been allocated. - */ -static char_u *mark_line(pos_T *mp, int lead_len) +/// Return the line at mark "mp". Truncate to fit in window. +/// The returned string has been allocated. +static char *mark_line(pos_T *mp, int lead_len) { - char_u *s, *p; + char *s, *p; int len; if (mp->lnum == 0 || mp->lnum > curbuf->b_ml.ml_line_count) { - return vim_strsave((char_u *)"-invalid-"); + return xstrdup("-invalid-"); } assert(Columns >= 0); // Allow for up to 5 bytes per character. - s = vim_strnsave((char_u *)skipwhite((char *)ml_get(mp->lnum)), (size_t)Columns * 5); + s = xstrnsave(skipwhite(ml_get(mp->lnum)), (size_t)Columns * 5); // Truncate the line to fit it in the window len = 0; for (p = s; *p != NUL; MB_PTR_ADV(p)) { - len += ptr2cells((char *)p); + len += ptr2cells(p); if (len >= Columns - lead_len) { break; } @@ -990,14 +973,12 @@ static char_u *mark_line(pos_T *mp, int lead_len) return s; } -/* - * print the marks - */ +// print the marks void ex_marks(exarg_T *eap) { - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; int i; - char_u *name; + char *name; pos_T *posp, *startp, *endp; if (arg != NULL && *arg == NUL) { @@ -1005,14 +986,14 @@ void ex_marks(exarg_T *eap) } show_one_mark('\'', arg, &curwin->w_pcmark, NULL, true); - for (i = 0; i < NMARKS; ++i) { + for (i = 0; i < NMARKS; i++) { show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, true); } - for (i = 0; i < NGLOBALMARKS; ++i) { + for (i = 0; i < NGLOBALMARKS; i++) { if (namedfm[i].fmark.fnum != 0) { name = fm_getname(&namedfm[i].fmark, 15); } else { - name = (char_u *)namedfm[i].fname; + name = namedfm[i].fname; } if (name != NULL) { show_one_mark(i >= NMARKS ? i - NMARKS + '0' : i + 'A', @@ -1044,11 +1025,11 @@ void ex_marks(exarg_T *eap) } /// @param current in current file -static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int current) +static void show_one_mark(int c, char *arg, pos_T *p, char *name_arg, int current) { static bool did_title = false; bool mustfree = false; - char_u *name = name_arg; + char *name = name_arg; if (c == -1) { // finish up if (did_title) { @@ -1061,7 +1042,7 @@ static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int cu } } } else if (!got_int - && (arg == NULL || vim_strchr((char *)arg, c) != NULL) + && (arg == NULL || vim_strchr(arg, c) != NULL) && p->lnum != 0) { // don't output anything if 'q' typed at --more-- prompt if (name == NULL && current) { @@ -1076,13 +1057,12 @@ static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int cu } msg_putchar('\n'); if (!got_int) { - snprintf((char *)IObuff, IOSIZE, " %c %6" PRIdLINENR " %4d ", c, p->lnum, p->col); - msg_outtrans((char *)IObuff); + snprintf(IObuff, IOSIZE, " %c %6" PRIdLINENR " %4d ", c, p->lnum, p->col); + msg_outtrans(IObuff); if (name != NULL) { msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0); } } - ui_flush(); // show one line at a time } if (mustfree) { xfree(name); @@ -1090,12 +1070,10 @@ static void show_one_mark(int c, char_u *arg, pos_T *p, char_u *name_arg, int cu } } -/* - * ":delmarks[!] [marks]" - */ +// ":delmarks[!] [marks]" void ex_delmarks(exarg_T *eap) { - char_u *p; + char *p; int from, to; int i; int lower; @@ -1111,17 +1089,17 @@ void ex_delmarks(exarg_T *eap) emsg(_(e_argreq)); } else { // clear specified marks only - for (p = (char_u *)eap->arg; *p != NUL; p++) { + for (p = eap->arg; *p != NUL; p++) { lower = ASCII_ISLOWER(*p); digit = ascii_isdigit(*p); if (lower || digit || ASCII_ISUPPER(*p)) { if (p[1] == '-') { // clear range of marks - from = *p; - to = p[2]; + from = (uint8_t)(*p); + to = (uint8_t)p[2]; if (!(lower ? ASCII_ISLOWER(p[2]) - : (digit ? ascii_isdigit(p[2]) - : ASCII_ISUPPER(p[2]))) + : (digit ? ascii_isdigit(p[2]) + : ASCII_ISUPPER(p[2]))) || to < from) { semsg(_(e_invarg2), p); return; @@ -1129,10 +1107,10 @@ void ex_delmarks(exarg_T *eap) p += 2; } else { // clear one lower case mark - from = to = *p; + from = to = (uint8_t)(*p); } - for (i = from; i <= to; ++i) { + for (i = from; i <= to; i++) { if (lower) { curbuf->b_namedm[i - 'a'].mark.lnum = 0; } else { @@ -1173,25 +1151,23 @@ void ex_delmarks(exarg_T *eap) } } -/* - * print the jumplist - */ +// print the jumplist void ex_jumps(exarg_T *eap) { int i; - char_u *name; + char *name; cleanup_jumplist(curwin, true); // Highlight title msg_puts_title(_("\n jump line col file/text")); - for (i = 0; i < curwin->w_jumplistlen && !got_int; ++i) { + for (i = 0; i < curwin->w_jumplistlen && !got_int; i++) { if (curwin->w_jumplist[i].fmark.mark.lnum != 0) { name = fm_getname(&curwin->w_jumplist[i].fmark, 16); // Make sure to output the current indicator, even when on an wiped // out buffer. ":filter" may still skip it. if (name == NULL && i == curwin->w_jumplistidx) { - name = vim_strsave((char_u *)"-invalid-"); + name = xstrdup("-invalid-"); } // apply :filter /pat/ or file name not available if (name == NULL || message_filtered(name)) { @@ -1204,18 +1180,17 @@ void ex_jumps(exarg_T *eap) xfree(name); break; } - snprintf((char *)IObuff, IOSIZE, "%c %2d %5" PRIdLINENR " %4d ", + snprintf(IObuff, IOSIZE, "%c %2d %5" PRIdLINENR " %4d ", i == curwin->w_jumplistidx ? '>' : ' ', i > curwin->w_jumplistidx ? i - curwin->w_jumplistidx : curwin->w_jumplistidx - i, curwin->w_jumplist[i].fmark.mark.lnum, curwin->w_jumplist[i].fmark.mark.col); - msg_outtrans((char *)IObuff); + msg_outtrans(IObuff); msg_outtrans_attr(name, curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0); xfree(name); os_breakcheck(); } - ui_flush(); } if (curwin->w_jumplistidx == curwin->w_jumplistlen) { msg_puts("\n>"); @@ -1229,36 +1204,32 @@ void ex_clearjumps(exarg_T *eap) curwin->w_jumplistidx = 0; } -/* - * print the changelist - */ +// print the changelist void ex_changes(exarg_T *eap) { int i; - char_u *name; + char *name; // Highlight title msg_puts_title(_("\nchange line col text")); - for (i = 0; i < curbuf->b_changelistlen && !got_int; ++i) { + for (i = 0; i < curbuf->b_changelistlen && !got_int; i++) { if (curbuf->b_changelist[i].mark.lnum != 0) { msg_putchar('\n'); if (got_int) { break; } - sprintf((char *)IObuff, "%c %3d %5ld %4d ", - i == curwin->w_changelistidx ? '>' : ' ', - i > curwin->w_changelistidx ? i - curwin->w_changelistidx - : curwin->w_changelistidx - i, - (long)curbuf->b_changelist[i].mark.lnum, - curbuf->b_changelist[i].mark.col); - msg_outtrans((char *)IObuff); + snprintf(IObuff, IOSIZE, "%c %3d %5ld %4d ", + i == curwin->w_changelistidx ? '>' : ' ', + i > curwin->w_changelistidx ? i - curwin->w_changelistidx : curwin->w_changelistidx - i, + (long)curbuf->b_changelist[i].mark.lnum, + curbuf->b_changelist[i].mark.col); + msg_outtrans(IObuff); name = mark_line(&curbuf->b_changelist[i].mark, 17); msg_outtrans_attr(name, HL_ATTR(HLF_D)); xfree(name); os_breakcheck(); } - ui_flush(); } if (curwin->w_changelistidx == curbuf->b_changelistlen) { msg_puts("\n>"); @@ -1268,43 +1239,41 @@ void ex_changes(exarg_T *eap) #define ONE_ADJUST(add) \ { \ lp = add; \ - if (*lp >= line1 && *lp <= line2) \ - { \ - if (amount == MAXLNUM) \ - *lp = 0; \ - else \ - *lp += amount; \ + if (*lp >= line1 && *lp <= line2) { \ + if (amount == MAXLNUM) { \ + *lp = 0; \ + } else { \ + *lp += amount; \ + } \ + } else if (amount_after && *lp > line2) { \ + *lp += amount_after; \ } \ - else if (amount_after && *lp > line2) \ - *lp += amount_after; \ } // don't delete the line, just put at first deleted line #define ONE_ADJUST_NODEL(add) \ { \ lp = add; \ - if (*lp >= line1 && *lp <= line2) \ - { \ - if (amount == MAXLNUM) \ - *lp = line1; \ - else \ - *lp += amount; \ + if (*lp >= line1 && *lp <= line2) { \ + if (amount == MAXLNUM) { \ + *lp = line1; \ + } else { \ + *lp += amount; \ + } \ + } else if (amount_after && *lp > line2) { \ + *lp += amount_after; \ } \ - else if (amount_after && *lp > line2) \ - *lp += amount_after; \ - } - -/* - * Adjust marks between line1 and line2 (inclusive) to move 'amount' lines. - * Must be called before changed_*(), appended_lines() or deleted_lines(). - * May be called before or after changing the text. - * When deleting lines line1 to line2, use an 'amount' of MAXLNUM: The marks - * within this range are made invalid. - * If 'amount_after' is non-zero adjust marks after line2. - * Example: Delete lines 34 and 35: mark_adjust(34, 35, MAXLNUM, -2); - * Example: Insert two lines below 55: mark_adjust(56, MAXLNUM, 2, 0); - * or: mark_adjust(56, 55, MAXLNUM, 2); - */ + } + +// Adjust marks between line1 and line2 (inclusive) to move 'amount' lines. +// Must be called before changed_*(), appended_lines() or deleted_lines(). +// May be called before or after changing the text. +// When deleting lines line1 to line2, use an 'amount' of MAXLNUM: The marks +// within this range are made invalid. +// If 'amount_after' is non-zero adjust marks after line2. +// Example: Delete lines 34 and 35: mark_adjust(34, 35, MAXLNUM, -2); +// Example: Insert two lines below 55: mark_adjust(56, MAXLNUM, 2, 0); +// or: mark_adjust(56, 55, MAXLNUM, 2); void mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after, ExtmarkOp op) { @@ -1399,9 +1368,7 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount ONE_ADJUST_NODEL(&(saved_cursor.lnum)); } - /* - * Adjust items in all windows related to the current buffer. - */ + // Adjust items in all windows related to the current buffer. FOR_ALL_TAB_WINDOWS(tab, win) { if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { // Marks in the jumplist. When deleting lines, this may create @@ -1477,8 +1444,7 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount #define COL_ADJUST(pp) \ { \ posp = pp; \ - if (posp->lnum == lnum && posp->col >= mincol) \ - { \ + if (posp->lnum == lnum && posp->col >= mincol) { \ posp->lnum += lnum_amount; \ assert(col_amount > INT_MIN && col_amount <= INT_MAX); \ if (col_amount < 0 && posp->col <= (colnr_T) - col_amount) { \ @@ -1543,12 +1509,10 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long c // saved cursor for formatting COL_ADJUST(&saved_cursor); - /* - * Adjust items in all windows related to the current buffer. - */ + // Adjust items in all windows related to the current buffer. FOR_ALL_WINDOWS_IN_TAB(win, curtab) { // marks in the jumplist - for (i = 0; i < win->w_jumplistlen; ++i) { + for (i = 0; i < win->w_jumplistlen; i++) { if (win->w_jumplist[i].fmark.fnum == fnum) { COL_ADJUST(&(win->w_jumplist[i].fmark.mark)); } @@ -1572,18 +1536,21 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long c // When deleting lines, this may create duplicate marks in the // jumplist. They will be removed here for the specified window. -// When "checktail" is true, removes tail jump if it matches current position. -void cleanup_jumplist(win_T *wp, bool checktail) +// When "loadfiles" is true first ensure entries have the "fnum" field set +// (this may be a bit slow). +void cleanup_jumplist(win_T *wp, bool loadfiles) { int i; - // Load all the files from the jump list. This is - // needed to properly clean up duplicate entries, but will take some - // time. - for (i = 0; i < wp->w_jumplistlen; i++) { - if ((wp->w_jumplist[i].fmark.fnum == 0) - && (wp->w_jumplist[i].fmark.mark.lnum != 0)) { - fname2fnum(&wp->w_jumplist[i]); + if (loadfiles) { + // If specified, load all the files from the jump list. This is + // needed to properly clean up duplicate entries, but will take some + // time. + for (i = 0; i < wp->w_jumplistlen; i++) { + if ((wp->w_jumplist[i].fmark.fnum == 0) + && (wp->w_jumplist[i].fmark.mark.lnum != 0)) { + fname2fnum(&wp->w_jumplist[i]); + } } } @@ -1631,8 +1598,8 @@ void cleanup_jumplist(win_T *wp, bool checktail) // When pointer is below last jump, remove the jump if it matches the current // line. This avoids useless/phantom jumps. #9805 - if (checktail && wp->w_jumplistlen - && wp->w_jumplistidx == wp->w_jumplistlen) { + if (loadfiles // otherwise (i.e.: Shada), last entry should be kept + && wp->w_jumplistlen && wp->w_jumplistidx == wp->w_jumplistlen) { const xfmark_T *fm_last = &wp->w_jumplist[wp->w_jumplistlen - 1]; if (fm_last->fmark.fnum == curbuf->b_fnum && fm_last->fmark.mark.lnum == wp->w_cursor.lnum) { @@ -1643,14 +1610,12 @@ void cleanup_jumplist(win_T *wp, bool checktail) } } -/* - * Copy the jumplist from window "from" to window "to". - */ +// Copy the jumplist from window "from" to window "to". void copy_jumplist(win_T *from, win_T *to) { int i; - for (i = 0; i < from->w_jumplistlen; ++i) { + for (i = 0; i < from->w_jumplistlen; i++) { to->w_jumplist[i] = from->w_jumplist[i]; if (from->w_jumplist[i].fname != NULL) { to->w_jumplist[i].fname = xstrdup(from->w_jumplist[i].fname); @@ -1678,16 +1643,13 @@ const void *mark_jumplist_iter(const void *const iter, const win_T *const win, x *fm = (xfmark_T)INIT_XFMARK; return NULL; } - const xfmark_T *const iter_mark = - (iter == NULL - ? &(win->w_jumplist[0]) - : (const xfmark_T *const)iter); + const xfmark_T *const iter_mark = iter == NULL ? &(win->w_jumplist[0]) + : (const xfmark_T *const)iter; *fm = *iter_mark; if (iter_mark == &(win->w_jumplist[win->w_jumplistlen - 1])) { return NULL; - } else { - return iter_mark + 1; } + return iter_mark + 1; } /// Iterate over global marks @@ -1717,9 +1679,9 @@ const void *mark_global_iter(const void *const iter, char *const name, xfmark_T return NULL; } size_t iter_off = (size_t)(iter_mark - &(namedfm[0])); - *name = (char)(iter_off < NMARKS - ? 'A' + (char)iter_off - : '0' + (char)(iter_off - NMARKS)); + *name = (char)(iter_off < NMARKS ? + 'A' + (char)iter_off : + '0' + (char)(iter_off - NMARKS)); *fm = *iter_mark; while ((size_t)(++iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) { if (iter_mark->fmark.mark.lnum) { @@ -1781,16 +1743,11 @@ const void *mark_buffer_iter(const void *const iter, const buf_T *const buf, cha FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT { *name = NUL; - char mark_name = (char)(iter == NULL - ? NUL - : (iter == &(buf->b_last_cursor) - ? '"' - : (iter == &(buf->b_last_insert) - ? '^' - : (iter == &(buf->b_last_change) - ? '.' - : 'a' + (char)((const fmark_T *)iter - - &(buf->b_namedm[0])))))); + char mark_name = (char)(iter == NULL ? NUL : + iter == &(buf->b_last_cursor) ? '"' : + iter == &(buf->b_last_insert) ? '^' : + iter == &(buf->b_last_change) ? '.' : + 'a' + (char)((const fmark_T *)iter - &(buf->b_namedm[0]))); const fmark_T *iter_mark = next_buffer_mark(buf, &mark_name); while (iter_mark != NULL && iter_mark->mark.lnum == 0) { iter_mark = next_buffer_mark(buf, &mark_name); @@ -1867,14 +1824,12 @@ bool mark_set_local(const char name, buf_T *const buf, const fmark_T fm, const b return true; } -/* - * Free items in the jumplist of window "wp". - */ +// Free items in the jumplist of window "wp". void free_jumplist(win_T *wp) { int i; - for (i = 0; i < wp->w_jumplistlen; ++i) { + for (i = 0; i < wp->w_jumplistlen; i++) { free_xfmark(wp->w_jumplist[i]); } wp->w_jumplistlen = 0; @@ -1911,8 +1866,8 @@ void mark_mb_adjustpos(buf_T *buf, pos_T *lp) FUNC_ATTR_NONNULL_ALL { if (lp->col > 0 || lp->coladd > 1) { - const char_u *const p = ml_get_buf(buf, lp->lnum, false); - if (*p == NUL || (int)STRLEN(p) < lp->col) { + const char *const p = ml_get_buf(buf, lp->lnum, false); + if (*p == NUL || (int)strlen(p) < lp->col) { lp->col = 0; } else { lp->col -= utf_head_off(p, p + lp->col); @@ -1921,8 +1876,8 @@ void mark_mb_adjustpos(buf_T *buf, pos_T *lp) // double-wide character. if (lp->coladd == 1 && p[lp->col] != TAB - && vim_isprintc(utf_ptr2char((char *)p + lp->col)) - && ptr2cells((char *)p + lp->col) > 1) { + && vim_isprintc(utf_ptr2char(p + lp->col)) + && ptr2cells(p + lp->col) > 1) { lp->coladd = 0; } } diff --git a/src/nvim/mark.h b/src/nvim/mark.h index 6da976e8d3..af0abba864 100644 --- a/src/nvim/mark.h +++ b/src/nvim/mark.h @@ -1,9 +1,12 @@ #ifndef NVIM_MARK_H #define NVIM_MARK_H +#include <stdbool.h> +#include <stddef.h> + #include "nvim/ascii.h" #include "nvim/buffer_defs.h" -#include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/ex_cmds_defs.h" #include "nvim/extmark_defs.h" #include "nvim/func_attr.h" #include "nvim/macros.h" @@ -78,12 +81,13 @@ static inline int mark_local_index(const char name) : -1)))); } -static inline bool lt(pos_T, pos_T) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool equalpos(pos_T, pos_T) +static inline bool lt(pos_T a, pos_T b) + REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; +static inline bool equalpos(pos_T a, pos_T b) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline bool ltoreq(pos_T, pos_T) +static inline bool ltoreq(pos_T a, pos_T b) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; -static inline void clearpos(pos_T *) +static inline void clearpos(pos_T *a) REAL_FATTR_ALWAYS_INLINE; /// Return true if position a is before (less than) position b. diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h index a78056c5f9..f9df0028db 100644 --- a/src/nvim/mark_defs.h +++ b/src/nvim/mark_defs.h @@ -5,10 +5,8 @@ #include "nvim/os/time.h" #include "nvim/pos.h" -/* - * marks: positions in a file - * (a normal mark is a lnum/col pair, the same as a file position) - */ +// marks: positions in a file +// (a normal mark is a lnum/col pair, the same as a file position) /// Flags for outcomes when moving to a mark. typedef enum { @@ -88,4 +86,4 @@ typedef struct xfilemark { #define INIT_XFMARK { INIT_FMARK, NULL } -#endif // NVIM_MARK_DEFS_H +#endif // NVIM_MARK_DEFS_H diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 03340a99d6..2036ddd21d 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -48,10 +48,15 @@ // at the repo root. #include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" #include "nvim/garray.h" -#include "nvim/lib/kvec.h" #include "nvim/marktree.h" +#include "nvim/memory.h" +#include "nvim/pos.h" #define T MT_BRANCH_FACTOR #define ILEN (sizeof(mtnode_t) + (2 * T) * sizeof(void *)) diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h index e2e05eebd5..5ce4b2cd24 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -2,14 +2,19 @@ #define NVIM_MARKTREE_H #include <assert.h> +#include <stdbool.h> +#include <stddef.h> #include <stdint.h> #include "nvim/assert.h" #include "nvim/garray.h" #include "nvim/map.h" +#include "nvim/map_defs.h" #include "nvim/pos.h" #include "nvim/types.h" +struct mtnode_s; + #define MT_MAX_DEPTH 20 #define MT_BRANCH_FACTOR 10 diff --git a/src/nvim/match.c b/src/nvim/match.c index 1c34c9f004..6663dfd7ec 100644 --- a/src/nvim/match.c +++ b/src/nvim/match.c @@ -3,22 +3,39 @@ // match.c: functions for highlighting matches +#include <assert.h> +#include <inttypes.h> #include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include "nvim/ascii.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" -#include "nvim/eval.h" #include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/window.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/fold.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" +#include "nvim/macros.h" #include "nvim/match.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/regexp.h" -#include "nvim/runtime.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -29,13 +46,13 @@ static char *e_invalwindow = N_("E957: Invalid window number"); #define SEARCH_HL_PRIORITY 0 -/// Add match to the match list of window 'wp'. The pattern 'pat' will be -/// highlighted with the group 'grp' with priority 'prio'. -/// Optionally, a desired ID 'id' can be specified (greater than or equal to 1). +/// Add match to the match list of window "wp". +/// If "pat" is not NULL the pattern will be highlighted with the group "grp" +/// with priority "prio". +/// If "pos_list" is not NULL the list of posisions defines the highlights. +/// Optionally, a desired ID "id" can be specified (greater than or equal to 1). +/// If no particular ID is desired, -1 must be specified for "id". /// -/// @param[in] id a desired ID 'id' can be specified -/// (greater than or equal to 1). -1 must be specified if no -/// particular ID is desired /// @param[in] conceal_char pointer to conceal replacement char /// @return ID of added match, -1 on failure. static int match_add(win_T *wp, const char *const grp, const char *const pat, int prio, int id, @@ -47,7 +64,7 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in matchitem_T *m; int hlg_id; regprog_T *regprog = NULL; - int rtype = SOME_VALID; + int rtype = UPD_SOME_VALID; if (*grp == NUL || (pat != NULL && *pat == NUL)) { return -1; @@ -58,16 +75,26 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in (int64_t)id); return -1; } - if (id != -1) { - cur = wp->w_match_head; - while (cur != NULL) { - if (cur->id == id) { + if (id == -1) { + // use the next available match ID + id = wp->w_next_match_id++; + } else { + // check the given ID is not already in use + for (cur = wp->w_match_head; cur != NULL; cur = cur->mit_next) { + if (cur->mit_id == id) { semsg(_("E801: ID already taken: %" PRId64), (int64_t)id); return -1; } - cur = cur->next; + } + + // Make sure the next match ID is always higher than the highest + // manually selected ID. Add some extra in case a few more IDs are + // added soon. + if (wp->w_next_match_id < id + 100) { + wp->w_next_match_id = id + 100; } } + if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) { return -1; } @@ -76,30 +103,22 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in return -1; } - // Find available match ID. - while (id == -1) { - cur = wp->w_match_head; - while (cur != NULL && cur->id != wp->w_next_match_id) { - cur = cur->next; - } - if (cur == NULL) { - id = wp->w_next_match_id; - } - wp->w_next_match_id++; - } - // Build new match. m = xcalloc(1, sizeof(matchitem_T)); - m->id = id; - m->priority = prio; - m->pattern = pat == NULL ? NULL: xstrdup(pat); - m->hlg_id = hlg_id; - m->match.regprog = regprog; - m->match.rmm_ic = false; - m->match.rmm_maxcol = 0; - m->conceal_char = 0; + if (pos_list != NULL) { + m->mit_pos_array = xcalloc((size_t)tv_list_len(pos_list), sizeof(llpos_T)); + m->mit_pos_count = tv_list_len(pos_list); + } + m->mit_id = id; + m->mit_priority = prio; + m->mit_pattern = pat == NULL ? NULL: xstrdup(pat); + m->mit_hlg_id = hlg_id; + m->mit_match.regprog = regprog; + m->mit_match.rmm_ic = false; + m->mit_match.rmm_maxcol = 0; + m->mit_conceal_char = 0; if (conceal_char != NULL) { - m->conceal_char = utf_ptr2char(conceal_char); + m->mit_conceal_char = utf_ptr2char(conceal_char); } // Set up position matches @@ -129,7 +148,7 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in if (lnum <= 0) { continue; } - m->pos.pos[i].lnum = lnum; + m->mit_pos_array[i].lnum = lnum; subli = TV_LIST_ITEM_NEXT(subl, subli); if (subli != NULL) { col = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); @@ -150,15 +169,15 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in } } } - m->pos.pos[i].col = col; - m->pos.pos[i].len = len; + m->mit_pos_array[i].col = col; + m->mit_pos_array[i].len = len; } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) { continue; } - m->pos.pos[i].lnum = (linenr_T)TV_LIST_ITEM_TV(li)->vval.v_number; - m->pos.pos[i].col = 0; - m->pos.pos[i].len = 0; + m->mit_pos_array[i].lnum = (linenr_T)TV_LIST_ITEM_TV(li)->vval.v_number; + m->mit_pos_array[i].col = 0; + m->mit_pos_array[i].len = 0; } else { semsg(_("E5031: List or number required at position %d"), (int)tv_list_idx_of_item(pos_list, li)); @@ -171,9 +190,6 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in botlnum = lnum + 1; } i++; - if (i >= MAXPOSMATCH) { - break; - } }); // Calculate top and bottom lines for redrawing area @@ -191,9 +207,9 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in wp->w_buffer->b_mod_bot = botlnum; wp->w_buffer->b_mod_xlines = 0; } - m->pos.toplnum = toplnum; - m->pos.botlnum = botlnum; - rtype = VALID; + m->mit_toplnum = toplnum; + m->mit_botlnum = botlnum; + rtype = UPD_VALID; } } @@ -201,21 +217,23 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in // the match priorities. cur = wp->w_match_head; prev = cur; - while (cur != NULL && prio >= cur->priority) { + while (cur != NULL && prio >= cur->mit_priority) { prev = cur; - cur = cur->next; + cur = cur->mit_next; } if (cur == prev) { wp->w_match_head = m; } else { - prev->next = m; + prev->mit_next = m; } - m->next = cur; + m->mit_next = cur; redraw_later(wp, rtype); return id; fail: + xfree(m->mit_pattern); + xfree(m->mit_pos_array); xfree(m); return -1; } @@ -227,19 +245,18 @@ static int match_delete(win_T *wp, int id, bool perr) { matchitem_T *cur = wp->w_match_head; matchitem_T *prev = cur; - int rtype = SOME_VALID; + int rtype = UPD_SOME_VALID; if (id < 1) { if (perr) { - semsg(_("E802: Invalid ID: %" PRId64 - " (must be greater than or equal to 1)"), + semsg(_("E802: Invalid ID: %" PRId64 " (must be greater than or equal to 1)"), (int64_t)id); } return -1; } - while (cur != NULL && cur->id != id) { + while (cur != NULL && cur->mit_id != id) { prev = cur; - cur = cur->next; + cur = cur->mit_next; } if (cur == NULL) { if (perr) { @@ -248,28 +265,29 @@ static int match_delete(win_T *wp, int id, bool perr) return -1; } if (cur == prev) { - wp->w_match_head = cur->next; + wp->w_match_head = cur->mit_next; } else { - prev->next = cur->next; + prev->mit_next = cur->mit_next; } - vim_regfree(cur->match.regprog); - xfree(cur->pattern); - if (cur->pos.toplnum != 0) { + vim_regfree(cur->mit_match.regprog); + xfree(cur->mit_pattern); + if (cur->mit_toplnum != 0) { if (wp->w_buffer->b_mod_set) { - if (wp->w_buffer->b_mod_top > cur->pos.toplnum) { - wp->w_buffer->b_mod_top = cur->pos.toplnum; + if (wp->w_buffer->b_mod_top > cur->mit_toplnum) { + wp->w_buffer->b_mod_top = cur->mit_toplnum; } - if (wp->w_buffer->b_mod_bot < cur->pos.botlnum) { - wp->w_buffer->b_mod_bot = cur->pos.botlnum; + if (wp->w_buffer->b_mod_bot < cur->mit_botlnum) { + wp->w_buffer->b_mod_bot = cur->mit_botlnum; } } else { wp->w_buffer->b_mod_set = true; - wp->w_buffer->b_mod_top = cur->pos.toplnum; - wp->w_buffer->b_mod_bot = cur->pos.botlnum; + wp->w_buffer->b_mod_top = cur->mit_toplnum; + wp->w_buffer->b_mod_bot = cur->mit_botlnum; wp->w_buffer->b_mod_xlines = 0; } - rtype = VALID; + rtype = UPD_VALID; } + xfree(cur->mit_pos_array); xfree(cur); redraw_later(wp, rtype); return 0; @@ -281,23 +299,24 @@ void clear_matches(win_T *wp) matchitem_T *m; while (wp->w_match_head != NULL) { - m = wp->w_match_head->next; - vim_regfree(wp->w_match_head->match.regprog); - xfree(wp->w_match_head->pattern); + m = wp->w_match_head->mit_next; + vim_regfree(wp->w_match_head->mit_match.regprog); + xfree(wp->w_match_head->mit_pattern); + xfree(wp->w_match_head->mit_pos_array); xfree(wp->w_match_head); wp->w_match_head = m; } - redraw_later(wp, SOME_VALID); + redraw_later(wp, UPD_SOME_VALID); } /// Get match from ID 'id' in window 'wp'. /// Return NULL if match not found. -matchitem_T *get_match(win_T *wp, int id) +static matchitem_T *get_match(win_T *wp, int id) { matchitem_T *cur = wp->w_match_head; - while (cur != NULL && cur->id != id) { - cur = cur->next; + while (cur != NULL && cur->mit_id != id) { + cur = cur->mit_next; } return cur; } @@ -310,18 +329,18 @@ void init_search_hl(win_T *wp, match_T *search_hl) // match matchitem_T *cur = wp->w_match_head; while (cur != NULL) { - cur->hl.rm = cur->match; - if (cur->hlg_id == 0) { - cur->hl.attr = 0; + cur->mit_hl.rm = cur->mit_match; + if (cur->mit_hlg_id == 0) { + cur->mit_hl.attr = 0; } else { - cur->hl.attr = syn_id2attr(cur->hlg_id); + cur->mit_hl.attr = syn_id2attr(cur->mit_hlg_id); } - cur->hl.buf = wp->w_buffer; - cur->hl.lnum = 0; - cur->hl.first_lnum = 0; + cur->mit_hl.buf = wp->w_buffer; + cur->mit_hl.lnum = 0; + cur->mit_hl.first_lnum = 0; // Set the time limit to 'redrawtime'. - cur->hl.tm = profile_setlimit(p_rdt); - cur = cur->next; + cur->mit_hl.tm = profile_setlimit(p_rdt); + cur = cur->mit_next; } search_hl->buf = wp->w_buffer; search_hl->lnum = 0; @@ -332,19 +351,19 @@ void init_search_hl(win_T *wp, match_T *search_hl) } /// @param shl points to a match. Fill on match. -/// @param posmatch match positions +/// @param posmatch match item with positions /// @param mincol minimal column for a match /// /// @return one on match, otherwise return zero. -static int next_search_hl_pos(match_T *shl, linenr_T lnum, posmatch_T *posmatch, colnr_T mincol) +static int next_search_hl_pos(match_T *shl, linenr_T lnum, matchitem_T *match, colnr_T mincol) FUNC_ATTR_NONNULL_ALL { int i; int found = -1; shl->lnum = 0; - for (i = posmatch->cur; i < MAXPOSMATCH; i++) { - llpos_T *pos = &posmatch->pos[i]; + for (i = match->mit_pos_cur; i < match->mit_pos_count; i++) { + llpos_T *pos = &match->mit_pos_array[i]; if (pos->lnum == 0) { break; @@ -354,25 +373,24 @@ static int next_search_hl_pos(match_T *shl, linenr_T lnum, posmatch_T *posmatch, } if (pos->lnum == lnum) { if (found >= 0) { - // if this match comes before the one at "found" then swap - // them - if (pos->col < posmatch->pos[found].col) { + // if this match comes before the one at "found" then swap them + if (pos->col < match->mit_pos_array[found].col) { llpos_T tmp = *pos; - *pos = posmatch->pos[found]; - posmatch->pos[found] = tmp; + *pos = match->mit_pos_array[found]; + match->mit_pos_array[found] = tmp; } } else { found = i; } } } - posmatch->cur = 0; + match->mit_pos_cur = 0; if (found >= 0) { - colnr_T start = posmatch->pos[found].col == 0 - ? 0: posmatch->pos[found].col - 1; - colnr_T end = posmatch->pos[found].col == 0 - ? MAXCOL : start + posmatch->pos[found].len; + colnr_T start = match->mit_pos_array[found].col == 0 + ? 0: match->mit_pos_array[found].col - 1; + colnr_T end = match->mit_pos_array[found].col == 0 + ? MAXCOL : start + match->mit_pos_array[found].len; shl->lnum = lnum; shl->rm.startpos[0].lnum = 0; @@ -381,7 +399,7 @@ static int next_search_hl_pos(match_T *shl, linenr_T lnum, posmatch_T *posmatch, shl->rm.endpos[0].col = end; shl->is_addpos = true; shl->has_cursor = false; - posmatch->cur = found + 1; + match->mit_pos_cur = found + 1; return 1; } return 0; @@ -407,7 +425,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ const int called_emsg_before = called_emsg; // for :{range}s/pat only highlight inside the range - if (lnum < search_first_line || lnum > search_last_line) { + if ((lnum < search_first_line || lnum > search_last_line) && cur == NULL) { shl->lnum = 0; return; } @@ -443,7 +461,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ } else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL || (shl->rm.endpos[0].lnum == 0 && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) { - char_u *ml; + char *ml; matchcol = shl->rm.startpos[0].col; ml = ml_get_buf(shl->buf, lnum, false) + matchcol; @@ -452,7 +470,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ shl->lnum = 0; break; } - matchcol += utfc_ptr2len((char *)ml); + matchcol += utfc_ptr2len(ml); } else { matchcol = shl->rm.endpos[0].col; } @@ -460,18 +478,17 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ shl->lnum = lnum; if (shl->rm.regprog != NULL) { // Remember whether shl->rm is using a copy of the regprog in - // cur->match. - bool regprog_is_copy = (shl != search_hl - && cur != NULL - && shl == &cur->hl - && cur->match.regprog == cur->hl.rm.regprog); + // cur->mit_match. + bool regprog_is_copy = (shl != search_hl && cur != NULL + && shl == &cur->mit_hl + && cur->mit_match.regprog == cur->mit_hl.rm.regprog); int timed_out = false; nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol, &(shl->tm), &timed_out); // Copy the regprog, in case it got freed and recompiled. if (regprog_is_copy) { - cur->match.regprog = cur->hl.rm.regprog; + cur->mit_match.regprog = cur->mit_hl.rm.regprog; } if (called_emsg > called_emsg_before || got_int || timed_out) { // Error while handling regexp: stop using this regexp. @@ -486,7 +503,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ break; } } else if (cur != NULL) { - nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol); + nmatched = next_search_hl_pos(shl, lnum, cur, matchcol); } if (nmatched == 0) { shl->lnum = 0; // no match found @@ -521,7 +538,7 @@ void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum) shl = search_hl; shl_flag = true; } else { - shl = &cur->hl; // -V595 + shl = &cur->mit_hl; // -V595 } if (shl->rm.regprog != NULL && shl->lnum == 0 @@ -536,7 +553,7 @@ void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum) } } if (cur != NULL) { - cur->pos.cur = 0; + cur->mit_pos_cur = 0; } bool pos_inprogress = true; // mark that a position match search is // in progress @@ -545,7 +562,7 @@ void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum) || (cur != NULL && pos_inprogress))) { next_search_hl(wp, search_hl, shl, shl->first_lnum, (colnr_T)n, shl == search_hl ? NULL : cur); - pos_inprogress = !(cur == NULL || cur->pos.cur == 0); + pos_inprogress = !(cur == NULL || cur->mit_pos_cur == 0); if (shl->lnum != 0) { shl->first_lnum = shl->lnum + shl->rm.endpos[0].lnum @@ -558,7 +575,7 @@ void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum) } } if (shl != search_hl && cur != NULL) { - cur = cur->next; + cur = cur->mit_next; } } } @@ -580,9 +597,10 @@ static void check_cur_search_hl(win_T *wp, match_T *shl) } /// Prepare for 'hlsearch' and match highlighting in one window line. -/// Return true if there is such highlighting and set "search_attr" to the -/// current highlight attribute. -bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **line, +/// +/// @return true if there is such highlighting and set "search_attr" to the +/// current highlight attribute. +bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char **line, match_T *search_hl, int *search_attr, bool *search_attr_from_match) { matchitem_T *cur = wp->w_match_head; // points to the match list @@ -598,7 +616,7 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **l shl = search_hl; shl_flag = true; } else { - shl = &cur->hl; // -V595 + shl = &cur->mit_hl; // -V595 } shl->startcol = MAXCOL; shl->endcol = MAXCOL; @@ -606,7 +624,7 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **l shl->is_addpos = false; shl->has_cursor = false; if (cur != NULL) { - cur->pos.cur = 0; + cur->mit_pos_cur = 0; } next_search_hl(wp, search_hl, shl, lnum, mincol, shl == search_hl ? NULL : cur); @@ -636,7 +654,7 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **l // Highlight one character for an empty match. if (shl->startcol == shl->endcol) { if ((*line)[shl->endcol] != NUL) { - shl->endcol += utfc_ptr2len((char *)(*line) + shl->endcol); + shl->endcol += utfc_ptr2len(*line + shl->endcol); } else { shl->endcol++; } @@ -649,7 +667,7 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **l area_highlighting = true; } if (shl != search_hl && cur != NULL) { - cur = cur->next; + cur = cur->mit_next; } } return area_highlighting; @@ -660,9 +678,11 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **l /// After end, check for start/end of next match. /// When another match, have to check for start again. /// Watch out for matching an empty string! +/// "on_last_col" is set to true with non-zero search_attr and the next column +/// is endcol. /// Return the updated search_attr. -int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match_T *search_hl, - int *has_match_conc, int *match_conc, int lcs_eol_one, +int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char **line, match_T *search_hl, + int *has_match_conc, int *match_conc, int lcs_eol_one, bool *on_last_col, bool *search_attr_from_match) { matchitem_T *cur = wp->w_match_head; // points to the match list @@ -674,14 +694,14 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match // Do this for 'search_hl' and the match list (ordered by priority). while (cur != NULL || !shl_flag) { if (!shl_flag - && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { + && (cur == NULL || cur->mit_priority > SEARCH_HL_PRIORITY)) { shl = search_hl; shl_flag = true; } else { - shl = &cur->hl; + shl = &cur->mit_hl; } if (cur != NULL) { - cur->pos.cur = 0; + cur->mit_pos_cur = 0; } bool pos_inprogress = true; // mark that a position match search is // in progress @@ -690,7 +710,7 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match if (shl->startcol != MAXCOL && col >= shl->startcol && col < shl->endcol) { - int next_col = col + utfc_ptr2len((char *)(*line) + col); + int next_col = col + utfc_ptr2len(*line + col); if (shl->endcol < next_col) { shl->endcol = next_col; @@ -706,9 +726,9 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match // the match. if (cur != NULL && shl != search_hl - && syn_name2id("Conceal") == cur->hlg_id) { + && syn_name2id("Conceal") == cur->mit_hlg_id) { *has_match_conc = col == shl->startcol ? 2 : 1; - *match_conc = cur->conceal_char; + *match_conc = cur->mit_conceal_char; } else { *has_match_conc = 0; } @@ -717,7 +737,7 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match next_search_hl(wp, search_hl, shl, lnum, col, shl == search_hl ? NULL : cur); - pos_inprogress = !(cur == NULL || cur->pos.cur == 0); + pos_inprogress = !(cur == NULL || cur->mit_pos_cur == 0); // Need to get the line again, a multi-line regexp // may have made it invalid. @@ -738,7 +758,13 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match if (shl->startcol == shl->endcol) { // highlight empty match, try again after it - shl->endcol += utfc_ptr2len((char *)(*line) + shl->endcol); + char *p = *line + shl->endcol; + + if (*p == NUL) { + shl->endcol++; + } else { + shl->endcol += utfc_ptr2len(p); + } } // Loop to check if the match starts at the @@ -749,30 +775,31 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match break; } if (shl != search_hl && cur != NULL) { - cur = cur->next; + cur = cur->mit_next; } } - // Use attributes from match with highest priority among - // 'search_hl' and the match list. + // Use attributes from match with highest priority among 'search_hl' and + // the match list. *search_attr_from_match = false; search_attr = search_hl->attr_cur; cur = wp->w_match_head; shl_flag = false; while (cur != NULL || !shl_flag) { if (!shl_flag - && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { + && (cur == NULL || cur->mit_priority > SEARCH_HL_PRIORITY)) { shl = search_hl; shl_flag = true; } else { - shl = &cur->hl; + shl = &cur->mit_hl; } if (shl->attr_cur != 0) { search_attr = shl->attr_cur; + *on_last_col = col + 1 >= shl->endcol; *search_attr_from_match = shl != search_hl; } if (shl != search_hl && cur != NULL) { - cur = cur->next; + cur = cur->mit_next; } } // Only highlight one character after the last column. @@ -792,17 +819,24 @@ bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol) prevcol++; } - if (!search_hl->is_addpos && prevcol == search_hl->startcol) { + // Highlight a character after the end of the line if the match started + // at the end of the line or when the match continues in the next line + // (match includes the line break). + if (!search_hl->is_addpos && (prevcol == (long)search_hl->startcol + || (prevcol > (long)search_hl->startcol + && search_hl->endcol == MAXCOL))) { return true; - } else { - cur = wp->w_match_head; - while (cur != NULL) { - if (!cur->hl.is_addpos && prevcol == cur->hl.startcol) { - return true; - } - cur = cur->next; + } + cur = wp->w_match_head; + while (cur != NULL) { + if (!cur->mit_hl.is_addpos && (prevcol == (long)cur->mit_hl.startcol + || (prevcol > (long)cur->mit_hl.startcol + && cur->mit_hl.endcol == MAXCOL))) { + return true; } + cur = cur->mit_next; } + return false; } @@ -815,21 +849,20 @@ void get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr bool shl_flag = false; // flag to indicate whether search_hl // has been processed or not - *char_attr = search_hl->attr; while (cur != NULL || !shl_flag) { if (!shl_flag - && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { + && (cur == NULL || cur->mit_priority > SEARCH_HL_PRIORITY)) { shl = search_hl; shl_flag = true; } else { - shl = &cur->hl; + shl = &cur->mit_hl; } if (col - 1 == (long)shl->startcol && (shl == search_hl || !shl->is_addpos)) { *char_attr = shl->attr; } if (shl != search_hl && cur != NULL) { - cur = cur->next; + cur = cur->mit_next; } } } @@ -847,19 +880,21 @@ static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **wi *conceal_char = tv_get_string(&di->di_tv); } - if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) != NULL) { - *win = find_win_by_nr_or_id(&di->di_tv); - if (*win == NULL) { - emsg(_(e_invalwindow)); - return FAIL; - } + if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) == NULL) { + return OK; + } + + *win = find_win_by_nr_or_id(&di->di_tv); + if (*win == NULL) { + emsg(_(e_invalwindow)); + return FAIL; } return OK; } /// "clearmatches()" function -void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_clearmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *win = get_optional_window(argvars, 0); @@ -869,7 +904,7 @@ void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "getmatches()" function -void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { matchitem_T *cur; int i; @@ -883,13 +918,13 @@ void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) cur = win->w_match_head; while (cur != NULL) { dict_T *dict = tv_dict_alloc(); - if (cur->match.regprog == NULL) { + if (cur->mit_match.regprog == NULL) { // match added with matchaddpos() - for (i = 0; i < MAXPOSMATCH; i++) { + for (i = 0; i < cur->mit_pos_count; i++) { llpos_T *llpos; char buf[30]; // use 30 to avoid compiler warning - llpos = &cur->pos.pos[i]; + llpos = &cur->mit_pos_array[i]; if (llpos->lnum == 0) { break; } @@ -904,27 +939,27 @@ void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_list(dict, buf, (size_t)len, l); } } else { - tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern); + tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->mit_pattern); } tv_dict_add_str(dict, S_LEN("group"), - (const char *)syn_id2name(cur->hlg_id)); - tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority); - tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id); + (const char *)syn_id2name(cur->mit_hlg_id)); + tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->mit_priority); + tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->mit_id); - if (cur->conceal_char) { + if (cur->mit_conceal_char) { char buf[MB_MAXBYTES + 1]; - buf[utf_char2bytes(cur->conceal_char, buf)] = NUL; + buf[utf_char2bytes(cur->mit_conceal_char, buf)] = NUL; tv_dict_add_str(dict, S_LEN("conceal"), buf); } tv_list_append_dict(rettv->vval.v_list, dict); - cur = cur->next; + cur = cur->mit_next; } } /// "setmatches()" function -void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_setmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { dict_T *d; list_T *s = NULL; @@ -1027,7 +1062,7 @@ void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "matchadd()" function -void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { char grpbuf[NUMBUFLEN]; char patbuf[NUMBUFLEN]; @@ -1069,7 +1104,7 @@ void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "matchaddpo()" function -void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchaddpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -1120,7 +1155,7 @@ void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "matcharg()" function -void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matcharg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const int id = (int)tv_get_number(&argvars[0]); @@ -1133,8 +1168,8 @@ void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (m != NULL) { tv_list_append_string(rettv->vval.v_list, - (const char *)syn_id2name(m->hlg_id), -1); - tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); + (const char *)syn_id2name(m->mit_hlg_id), -1); + tv_list_append_string(rettv->vval.v_list, (const char *)m->mit_pattern, -1); } else { tv_list_append_string(rettv->vval.v_list, NULL, 0); tv_list_append_string(rettv->vval.v_list, NULL, 0); @@ -1143,7 +1178,7 @@ void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "matchdelete()" function -void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchdelete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *win = get_optional_window(argvars, 1); if (win == NULL) { @@ -1159,9 +1194,9 @@ void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// skipping commands to find the next command. void ex_match(exarg_T *eap) { - char_u *p; - char_u *g = NULL; - char_u *end; + char *p; + char *g = NULL; + char *end; int c; int id; @@ -1178,25 +1213,25 @@ void ex_match(exarg_T *eap) } if (ends_excmd(*eap->arg)) { - end = (char_u *)eap->arg; + end = eap->arg; } else if ((STRNICMP(eap->arg, "none", 4) == 0 && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) { - end = (char_u *)eap->arg + 4; + end = eap->arg + 4; } else { - p = skiptowhite((char_u *)eap->arg); + p = skiptowhite(eap->arg); if (!eap->skip) { - g = vim_strnsave((char_u *)eap->arg, (size_t)(p - (char_u *)eap->arg)); + g = xstrnsave(eap->arg, (size_t)(p - eap->arg)); } - p = (char_u *)skipwhite((char *)p); + p = skipwhite(p); if (*p == NUL) { // There must be two arguments. xfree(g); semsg(_(e_invarg2), eap->arg); return; } - end = skip_regexp(p + 1, *p, true, NULL); + end = skip_regexp(p + 1, *p, true); if (!eap->skip) { - if (*end != NUL && !ends_excmd(*skipwhite((char *)end + 1))) { + if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) { xfree(g); eap->errmsg = ex_errmsg(e_trailing_arg, (const char *)end); return; @@ -1207,13 +1242,13 @@ void ex_match(exarg_T *eap) return; } - c = *end; + c = (uint8_t)(*end); *end = NUL; match_add(curwin, (const char *)g, (const char *)p + 1, 10, id, NULL, NULL); xfree(g); - *end = (char_u)c; + *end = (char)c; } } - eap->nextcmd = (char *)find_nextcmd(end); + eap->nextcmd = find_nextcmd(end); } diff --git a/src/nvim/math.c b/src/nvim/math.c index b427688083..31c6b5af69 100644 --- a/src/nvim/math.c +++ b/src/nvim/math.c @@ -10,7 +10,7 @@ #include "nvim/math.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "math.c.generated.h" +# include "math.c.generated.h" // IWYU pragma: export #endif int xfpclassify(double d) diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index af9e214d92..8b50ba719a 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -25,36 +25,51 @@ /// Vim scripts may contain an ":scriptencoding" command. This has an effect /// for some commands, like ":menutrans". -#include <inttypes.h> +#include <assert.h> +#include <ctype.h> +#include <errno.h> +#include <iconv.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include <wchar.h> #include <wctype.h> -#include "nvim/ascii.h" -#include "nvim/vim.h" -#ifdef HAVE_LOCALE_H -# include <locale.h> -#endif +#include "auto/config.h" #include "nvim/arabic.h" +#include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" -#include "nvim/eval.h" -#include "nvim/fileio.h" -#include "nvim/func_attr.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/iconv.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" +#include "nvim/mbyte_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/os/os.h" -#include "nvim/path.h" +#include "nvim/os/os_defs.h" +#include "nvim/pos.h" #include "nvim/screen.h" -#include "nvim/spell.h" #include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/vim.h" + +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif typedef struct { int rangeStart; @@ -68,11 +83,12 @@ struct interval { long last; }; +// uncrustify:off #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mbyte.c.generated.h" - # include "unicode_tables.generated.h" #endif +// uncrustify:on static char e_list_item_nr_is_not_list[] = N_("E1109: List item %d is not a List"); @@ -84,8 +100,8 @@ static char e_list_item_nr_cell_width_invalid[] = N_("E1112: List item %d cell width invalid"); static char e_overlapping_ranges_for_nr[] = N_("E1113: Overlapping ranges for 0x%lx"); -static char e_only_values_of_0x100_and_higher_supported[] - = N_("E1114: Only values of 0x100 and higher supported"); +static char e_only_values_of_0x80_and_higher_supported[] + = N_("E1114: Only values of 0x80 and higher supported"); // To speed up BYTELEN(); keep a lookup table to quickly get the length in // bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes @@ -132,14 +148,11 @@ const uint8_t utf8len_tab_zero[] = { 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0, // F? }; -/* - * Canonical encoding names and their properties. - * "iso-8859-n" is handled by enc_canonize() directly. - */ +// Canonical encoding names and their properties. +// "iso-8859-n" is handled by enc_canonize() directly. static struct { const char *name; int prop; int codepage; } -enc_canon_table[] = -{ +enc_canon_table[] = { #define IDX_LATIN_1 0 { "latin1", ENC_8BIT + ENC_LATIN1, 1252 }, #define IDX_ISO_2 1 @@ -269,13 +282,10 @@ enc_canon_table[] = #define IDX_COUNT 59 }; -/* - * Aliases for encoding names. - */ +// Aliases for encoding names. static struct { const char *name; int canon; } -enc_alias_table[] = -{ +enc_alias_table[] = { { "ansi", IDX_LATIN_1 }, { "iso-8859-1", IDX_LATIN_1 }, { "latin2", IDX_ISO_2 }, @@ -342,46 +352,40 @@ enc_alias_table[] = { NULL, 0 } }; -/* - * Find encoding "name" in the list of canonical encoding names. - * Returns -1 if not found. - */ -static int enc_canon_search(const char_u *name) +/// Find encoding "name" in the list of canonical encoding names. +/// Returns -1 if not found. +static int enc_canon_search(const char *name) FUNC_ATTR_PURE { for (int i = 0; i < IDX_COUNT; i++) { - if (STRCMP(name, enc_canon_table[i].name) == 0) { + if (strcmp(name, enc_canon_table[i].name) == 0) { return i; } } return -1; } -/* - * Find canonical encoding "name" in the list and return its properties. - * Returns 0 if not found. - */ -int enc_canon_props(const char_u *name) +// Find canonical encoding "name" in the list and return its properties. +// Returns 0 if not found. +int enc_canon_props(const char *name) FUNC_ATTR_PURE { - int i = enc_canon_search(name); + int i = enc_canon_search((char *)name); if (i >= 0) { return enc_canon_table[i].prop; - } else if (STRNCMP(name, "2byte-", 6) == 0) { + } else if (strncmp(name, "2byte-", 6) == 0) { return ENC_DBCS; - } else if (STRNCMP(name, "8bit-", 5) == 0 || STRNCMP(name, "iso-8859-", 9) == 0) { + } else if (strncmp(name, "8bit-", 5) == 0 || strncmp(name, "iso-8859-", 9) == 0) { return ENC_8BIT; } return 0; } -/* - * Return the size of the BOM for the current buffer: - * 0 - no BOM - * 2 - UCS-2 or UTF-16 BOM - * 4 - UCS-4 BOM - * 3 - UTF-8 BOM - */ +// Return the size of the BOM for the current buffer: +// 0 - no BOM +// 2 - UCS-2 or UTF-16 BOM +// 4 - UCS-4 BOM +// 3 - UTF-8 BOM int bomb_size(void) FUNC_ATTR_PURE { @@ -389,24 +393,22 @@ int bomb_size(void) if (curbuf->b_p_bomb && !curbuf->b_p_bin) { if (*curbuf->b_p_fenc == NUL - || STRCMP(curbuf->b_p_fenc, "utf-8") == 0) { + || strcmp(curbuf->b_p_fenc, "utf-8") == 0) { n = 3; - } else if (STRNCMP(curbuf->b_p_fenc, "ucs-2", 5) == 0 - || STRNCMP(curbuf->b_p_fenc, "utf-16", 6) == 0) { + } else if (strncmp(curbuf->b_p_fenc, "ucs-2", 5) == 0 + || strncmp(curbuf->b_p_fenc, "utf-16", 6) == 0) { n = 2; - } else if (STRNCMP(curbuf->b_p_fenc, "ucs-4", 5) == 0) { + } else if (strncmp(curbuf->b_p_fenc, "ucs-4", 5) == 0) { n = 4; } } return n; } -/* - * Remove all BOM from "s" by moving remaining text. - */ -void remove_bom(char_u *s) +// Remove all BOM from "s" by moving remaining text. +void remove_bom(char *s) { - char *p = (char *)s; + char *p = s; while ((p = strchr(p, 0xef)) != NULL) { if ((uint8_t)p[1] == 0xbb && (uint8_t)p[2] == 0xbf) { @@ -417,37 +419,33 @@ void remove_bom(char_u *s) } } -/* - * Get class of pointer: - * 0 for blank or NUL - * 1 for punctuation - * 2 for an (ASCII) word character - * >2 for other word characters - */ -int mb_get_class(const char_u *p) +// Get class of pointer: +// 0 for blank or NUL +// 1 for punctuation +// 2 for an (ASCII) word character +// >2 for other word characters +int mb_get_class(const char *p) FUNC_ATTR_PURE { return mb_get_class_tab(p, curbuf->b_chartab); } -int mb_get_class_tab(const char_u *p, const uint64_t *const chartab) +int mb_get_class_tab(const char *p, const uint64_t *const chartab) FUNC_ATTR_PURE { - if (MB_BYTE2LEN(p[0]) == 1) { + if (MB_BYTE2LEN((uint8_t)p[0]) == 1) { if (p[0] == NUL || ascii_iswhite(p[0])) { return 0; } - if (vim_iswordc_tab(p[0], chartab)) { + if (vim_iswordc_tab((uint8_t)p[0], chartab)) { return 2; } return 1; } - return utf_class_tab(utf_ptr2char((char *)p), chartab); + return utf_class_tab(utf_ptr2char(p), chartab); } -/* - * Return true if "c" is in "table". - */ +// Return true if "c" is in "table". static bool intable(const struct interval *table, size_t n_items, int c) FUNC_ATTR_PURE { @@ -484,12 +482,16 @@ static bool intable(const struct interval *table, size_t n_items, int c) /// gen_unicode_tables.lua, which must be manually invoked as needed. int utf_char2cells(int c) { - if (c >= 0x100) { + // Use the value from setcellwidths() at 0x80 and higher, unless the + // character is not printable. + if (c >= 0x80 && vim_isprintc(c)) { int n = cw_value(c); if (n != 0) { return n; } + } + if (c >= 0x100) { if (!utf_printable(c)) { return 6; // unprintable, displays <xxxx> } @@ -536,13 +538,13 @@ int utf_ptr2cells(const char *p) /// Like utf_ptr2cells(), but limit string length to "size". /// For an empty string or truncated character returns 1. -int utf_ptr2cells_len(const char_u *p, int size) +int utf_ptr2cells_len(const char *p, int size) { int c; // Need to convert to a wide character. - if (size > 0 && *p >= 0x80) { - if (utf_ptr2len_len(p, size) < utf8len_tab[*p]) { + if (size > 0 && (uint8_t)(*p) >= 0x80) { + if (utf_ptr2len_len(p, size) < utf8len_tab[(uint8_t)(*p)]) { return 1; // truncated } c = utf_ptr2char((char *)p); @@ -568,8 +570,8 @@ size_t mb_string2cells(const char *str) { size_t clen = 0; - for (const char_u *p = (char_u *)str; *p != NUL; p += utfc_ptr2len((char *)p)) { - clen += (size_t)utf_ptr2cells((char *)p); + for (const char *p = str; *p != NUL; p += utfc_ptr2len(p)) { + clen += (size_t)utf_ptr2cells(p); } return clen; @@ -586,9 +588,9 @@ size_t mb_string2cells_len(const char *str, size_t size) { size_t clen = 0; - for (const char_u *p = (char_u *)str; *p != NUL && p < (char_u *)str + size; - p += utfc_ptr2len_len(p, (int)size + (int)(p - (char_u *)str))) { - clen += (size_t)utf_ptr2cells((char *)p); + for (const char *p = str; *p != NUL && p < str + size; + p += utfc_ptr2len_len(p, (int)size + (int)(p - str))) { + clen += (size_t)utf_ptr2cells(p); } return clen; @@ -601,7 +603,7 @@ size_t mb_string2cells_len(const char *str, size_t size) /// For an overlong sequence this may return zero. /// Does not include composing characters for obvious reasons. /// -/// @param[in] p String to convert. +/// @param[in] p_in String to convert. /// /// @return Unicode codepoint or byte value. int utf_ptr2char(const char *const p_in) @@ -646,22 +648,20 @@ int utf_ptr2char(const char *const p_in) return p[0]; } -/* - * Convert a UTF-8 byte sequence to a wide character. - * String is assumed to be terminated by NUL or after "n" bytes, whichever - * comes first. - * The function is safe in the sense that it never accesses memory beyond the - * first "n" bytes of "s". - * - * On success, returns decoded codepoint, advances "s" to the beginning of - * next character and decreases "n" accordingly. - * - * If end of string was reached, returns 0 and, if "n" > 0, advances "s" past - * NUL byte. - * - * If byte sequence is illegal or incomplete, returns -1 and does not advance - * "s". - */ +// Convert a UTF-8 byte sequence to a wide character. +// String is assumed to be terminated by NUL or after "n" bytes, whichever +// comes first. +// The function is safe in the sense that it never accesses memory beyond the +// first "n" bytes of "s". +// +// On success, returns decoded codepoint, advances "s" to the beginning of +// next character and decreases "n" accordingly. +// +// If end of string was reached, returns 0 and, if "n" > 0, advances "s" past +// NUL byte. +// +// If byte sequence is illegal or incomplete, returns -1 and does not advance +// "s". static int utf_safe_read_char_adv(const char_u **s, size_t *n) { int c; @@ -701,38 +701,32 @@ static int utf_safe_read_char_adv(const char_u **s, size_t *n) return -1; } -/* - * Get character at **pp and advance *pp to the next character. - * Note: composing characters are skipped! - */ -int mb_ptr2char_adv(const char_u **const pp) +// Get character at **pp and advance *pp to the next character. +// Note: composing characters are skipped! +int mb_ptr2char_adv(const char **const pp) { int c; - c = utf_ptr2char((char *)(*pp)); - *pp += utfc_ptr2len((char *)(*pp)); + c = utf_ptr2char(*pp); + *pp += utfc_ptr2len(*pp); return c; } -/* - * Get character at **pp and advance *pp to the next character. - * Note: composing characters are returned as separate characters. - */ -int mb_cptr2char_adv(const char_u **pp) +// Get character at **pp and advance *pp to the next character. +// Note: composing characters are returned as separate characters. +int mb_cptr2char_adv(const char **pp) { int c; - c = utf_ptr2char((char *)(*pp)); - *pp += utf_ptr2len((char *)(*pp)); + c = utf_ptr2char(*pp); + *pp += utf_ptr2len(*pp); return c; } -/* - * Check if the character pointed to by "p2" is a composing character when it - * comes after "p1". For Arabic sometimes "ab" is replaced with "c", which - * behaves like a composing character. - */ -bool utf_composinglike(const char_u *p1, const char_u *p2) +/// Check if the character pointed to by "p2" is a composing character when it +/// comes after "p1". For Arabic sometimes "ab" is replaced with "c", which +/// behaves like a composing character. +bool utf_composinglike(const char *p1, const char *p2) { int c2; @@ -754,28 +748,25 @@ bool utf_composinglike(const char_u *p1, const char_u *p2) /// space at least for #MAX_MCO + 1 elements. /// /// @return leading character. -int utfc_ptr2char(const char_u *p, int *pcc) +int utfc_ptr2char(const char *p, int *pcc) { - int len; - int c; - int cc; int i = 0; - c = utf_ptr2char((char *)p); - len = utf_ptr2len((char *)p); + int c = utf_ptr2char(p); + int len = utf_ptr2len(p); // Only accept a composing char when the first char isn't illegal. - if ((len > 1 || *p < 0x80) - && p[len] >= 0x80 + if ((len > 1 || (uint8_t)(*p) < 0x80) + && (uint8_t)p[len] >= 0x80 && utf_composinglike(p, p + len)) { - cc = utf_ptr2char((char *)p + len); + int cc = utf_ptr2char(p + len); for (;;) { pcc[i++] = cc; if (i == MAX_MCO) { break; } - len += utf_ptr2len((char *)p + len); - if (p[len] < 0x80 || !utf_iscomposing(cc = utf_ptr2char((char *)p + len))) { + len += utf_ptr2len(p + len); + if ((uint8_t)p[len] < 0x80 || !utf_iscomposing(cc = utf_ptr2char(p + len))) { break; } } @@ -788,13 +779,11 @@ int utfc_ptr2char(const char_u *p, int *pcc) return c; } -/* - * Convert a UTF-8 byte string to a wide character. Also get up to MAX_MCO - * composing characters. Use no more than p[maxlen]. - * - * @param [out] pcc: composing chars, last one is 0 - */ -int utfc_ptr2char_len(const char_u *p, int *pcc, int maxlen) +// Convert a UTF-8 byte string to a wide character. Also get up to MAX_MCO +// composing characters. Use no more than p[maxlen]. +// +// @param [out] pcc: composing chars, last one is 0 +int utfc_ptr2char_len(const char *p, int *pcc, int maxlen) { assert(maxlen > 0); @@ -803,14 +792,14 @@ int utfc_ptr2char_len(const char_u *p, int *pcc, int maxlen) int len = utf_ptr2len_len(p, maxlen); // Is it safe to use utf_ptr2char()? bool safe = len > 1 && len <= maxlen; - int c = safe ? utf_ptr2char((char *)p) : *p; + int c = safe ? utf_ptr2char(p) : (uint8_t)(*p); // Only accept a composing char when the first char isn't illegal. - if ((safe || c < 0x80) && len < maxlen && p[len] >= 0x80) { + if ((safe || c < 0x80) && len < maxlen && (uint8_t)p[len] >= 0x80) { for (; i < MAX_MCO; i++) { int len_cc = utf_ptr2len_len(p + len, maxlen - len); safe = len_cc > 1 && len_cc <= maxlen - len; - if (!safe || (pcc[i] = utf_ptr2char((char *)p + len)) < 0x80 + if (!safe || (pcc[i] = utf_ptr2char(p + len)) < 0x80 || !(i == 0 ? utf_composinglike(p, p + len) : utf_iscomposing(pcc[i]))) { break; } @@ -849,31 +838,27 @@ int utf_ptr2len(const char *const p_in) return len; } -/* - * Return length of UTF-8 character, obtained from the first byte. - * "b" must be between 0 and 255! - * Returns 1 for an invalid first byte value. - */ +// Return length of UTF-8 character, obtained from the first byte. +// "b" must be between 0 and 255! +// Returns 1 for an invalid first byte value. int utf_byte2len(int b) { return utf8len_tab[b]; } -/* - * Get the length of UTF-8 byte sequence "p[size]". Does not include any - * following composing characters. - * Returns 1 for "". - * Returns 1 for an illegal byte sequence (also in incomplete byte seq.). - * Returns number > "size" for an incomplete byte sequence. - * Never returns zero. - */ -int utf_ptr2len_len(const char_u *p, int size) +// Get the length of UTF-8 byte sequence "p[size]". Does not include any +// following composing characters. +// Returns 1 for "". +// Returns 1 for an illegal byte sequence (also in incomplete byte seq.). +// Returns number > "size" for an incomplete byte sequence. +// Never returns zero. +int utf_ptr2len_len(const char *p, int size) { int len; int i; int m; - len = utf8len_tab[*p]; + len = utf8len_tab[(uint8_t)(*p)]; if (len == 1) { return 1; // NUL, ascii or illegal lead byte } @@ -882,7 +867,7 @@ int utf_ptr2len_len(const char_u *p, int size) } else { m = len; } - for (i = 1; i < m; ++i) { + for (i = 1; i < m; i++) { if ((p[i] & 0xc0) != 0x80) { return 1; } @@ -893,21 +878,20 @@ int utf_ptr2len_len(const char_u *p, int size) /// Return the number of bytes occupied by a UTF-8 character in a string. /// This includes following composing characters. /// Returns zero for NUL. -int utfc_ptr2len(const char *const p_in) +int utfc_ptr2len(const char *const p) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - uint8_t *p = (uint8_t *)p_in; - uint8_t b0 = *p; + uint8_t b0 = (uint8_t)(*p); if (b0 == NUL) { return 0; } - if (b0 < 0x80 && p[1] < 0x80) { // be quick for ASCII + if (b0 < 0x80 && (uint8_t)p[1] < 0x80) { // be quick for ASCII return 1; } // Skip over first UTF-8 char, stopping at a NUL byte. - int len = utf_ptr2len((char *)p); + int len = utf_ptr2len(p); // Check for illegal byte. if (len == 1 && b0 >= 0x80) { @@ -918,23 +902,21 @@ int utfc_ptr2len(const char *const p_in) // skip all of them (otherwise the cursor would get stuck). int prevlen = 0; for (;;) { - if (p[len] < 0x80 || !utf_composinglike(p + prevlen, p + len)) { + if ((uint8_t)p[len] < 0x80 || !utf_composinglike(p + prevlen, p + len)) { return len; } // Skip over composing char. prevlen = len; - len += utf_ptr2len((char *)p + len); + len += utf_ptr2len(p + len); } } -/* - * Return the number of bytes the UTF-8 encoding of the character at "p[size]" - * takes. This includes following composing characters. - * Returns 0 for an empty string. - * Returns 1 for an illegal char or an incomplete byte sequence. - */ -int utfc_ptr2len_len(const char_u *p, int size) +/// Return the number of bytes the UTF-8 encoding of the character at "p[size]" +/// takes. This includes following composing characters. +/// Returns 0 for an empty string. +/// Returns 1 for an illegal char or an incomplete byte sequence. +int utfc_ptr2len_len(const char *p, int size) { int len; int prevlen; @@ -942,7 +924,7 @@ int utfc_ptr2len_len(const char_u *p, int size) if (size < 1 || *p == NUL) { return 0; } - if (p[0] < 0x80 && (size == 1 || p[1] < 0x80)) { // be quick for ASCII + if ((uint8_t)p[0] < 0x80 && (size == 1 || (uint8_t)p[1] < 0x80)) { // be quick for ASCII return 1; } @@ -950,26 +932,22 @@ int utfc_ptr2len_len(const char_u *p, int size) len = utf_ptr2len_len(p, size); // Check for illegal byte and incomplete byte sequence. - if ((len == 1 && p[0] >= 0x80) || len > size) { + if ((len == 1 && (uint8_t)p[0] >= 0x80) || len > size) { return 1; } - /* - * Check for composing characters. We can handle only the first six, but - * skip all of them (otherwise the cursor would get stuck). - */ + // Check for composing characters. We can handle only the first six, but + // skip all of them (otherwise the cursor would get stuck). prevlen = 0; while (len < size) { int len_next_char; - if (p[len] < 0x80) { + if ((uint8_t)p[len] < 0x80) { break; } - /* - * Next character length should not go beyond size to ensure that - * utf_composinglike(...) does not read beyond size. - */ + // Next character length should not go beyond size to ensure that + // utf_composinglike(...) does not read beyond size. len_next_char = utf_ptr2len_len(p + len, size - len); if (len_next_char > size - len) { break; @@ -1048,26 +1026,21 @@ 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. bool utf_iscomposing(int c) { return intable(combining, ARRAY_SIZE(combining), c); } -/* - * Return true for characters that can be displayed in a normal way. - * Only for characters of 0x100 and above! - */ +// Return true for characters that can be displayed in a normal way. +// Only for characters of 0x100 and above! bool utf_printable(int c) { // Sorted list of non-overlapping intervals. // 0xd800-0xdfff is reserved for UTF-16, actually illegal. - static struct interval nonprint[] = - { + static struct interval nonprint[] = { { 0x070f, 0x070f }, { 0x180b, 0x180e }, { 0x200b, 0x200f }, { 0x202a, 0x202e }, { 0x2060, 0x206f }, { 0xd800, 0xdfff }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb }, { 0xfffe, 0xffff } @@ -1076,12 +1049,10 @@ bool utf_printable(int c) return !intable(nonprint, ARRAY_SIZE(nonprint), c); } -/* - * Get class of a Unicode character. - * 0: white space - * 1: punctuation - * 2 or bigger: some class of word character. - */ +// Get class of a Unicode character. +// 0: white space +// 1: punctuation +// 2 or bigger: some class of word character. int utf_class(const int c) { return utf_class_tab(c, curbuf->b_chartab); @@ -1210,11 +1181,9 @@ bool utf_ambiguous_width(int c) || intable(emoji_all, ARRAY_SIZE(emoji_all), c)); } -/* - * Generic conversion function for case operations. - * Return the converted equivalent of "a", which is a UCS-4 character. Use - * the given conversion "table". Uses binary search on "table". - */ +// Generic conversion function for case operations. +// Return the converted equivalent of "a", which is a UCS-4 character. Use +// the given conversion "table". Uses binary search on "table". static int utf_convert(int a, const convertStruct *const table, size_t n_items) { size_t start, mid, end; // indices into table @@ -1235,15 +1204,12 @@ static int utf_convert(int a, const convertStruct *const table, size_t n_items) && a <= table[start].rangeEnd && (a - table[start].rangeStart) % table[start].step == 0) { return a + table[start].offset; - } else { - return a; } + return a; } -/* - * Return the folded-case equivalent of "a", which is a UCS-4 character. Uses - * simple case folding. - */ +// Return the folded-case equivalent of "a", which is a UCS-4 character. Uses +// simple case folding. int utf_fold(int a) { if (a < 0x80) { @@ -1267,12 +1233,9 @@ int mb_toupper(int a) return TOUPPER_ASC(a); } -#if defined(__STDC_ISO_10646__) - // If towupper() is available and handles Unicode, use it. if (!(cmp_flags & CMP_INTERNAL)) { return (int)towupper((wint_t)a); } -#endif // For characters below 128 use locale sensitive toupper(). if (a < 128) { @@ -1298,12 +1261,9 @@ int mb_tolower(int a) return TOLOWER_ASC(a); } -#if defined(__STDC_ISO_10646__) - // If towlower() is available and handles Unicode, use it. if (!(cmp_flags & CMP_INTERNAL)) { return (int)towlower((wint_t)a); } -#endif // For characters below 128 use locale sensitive tolower(). if (a < 128) { @@ -1398,7 +1358,7 @@ static int utf_strnicmp(const char_u *s1, const char_u *s2, size_t n1, size_t n2 return n1 == 0 ? -1 : 1; } -#ifdef WIN32 +#ifdef MSWIN # ifndef CP_UTF8 # define CP_UTF8 65001 // magic number from winnls.h # endif @@ -1500,16 +1460,16 @@ int utf16_to_utf8(const wchar_t *utf16, int utf16len, char **utf8) /// @param len maximum length (an earlier NUL terminates) /// @param[out] codepoints incremented with UTF-32 code point size /// @param[out] codeunits incremented with UTF-16 code unit size -void mb_utflen(const char_u *s, size_t len, size_t *codepoints, size_t *codeunits) +void mb_utflen(const char *s, size_t len, size_t *codepoints, size_t *codeunits) FUNC_ATTR_NONNULL_ALL { size_t count = 0, extra = 0; size_t clen; - for (size_t i = 0; i < len && s[i] != NUL; i += clen) { + for (size_t i = 0; i < len; i += clen) { clen = (size_t)utf_ptr2len_len(s + i, (int)(len - i)); // NB: gets the byte value of invalid sequence bytes. // we only care whether the char fits in the BMP or not - int c = (clen > 1) ? utf_ptr2char((char *)s + i) : s[i]; + int c = (clen > 1) ? utf_ptr2char(s + i) : (uint8_t)s[i]; count++; if (c > 0xFFFF) { extra++; @@ -1519,7 +1479,7 @@ void mb_utflen(const char_u *s, size_t len, size_t *codepoints, size_t *codeunit *codeunits += count + extra; } -ssize_t mb_utf_index_to_bytes(const char_u *s, size_t len, size_t index, bool use_utf16_units) +ssize_t mb_utf_index_to_bytes(const char *s, size_t len, size_t index, bool use_utf16_units) FUNC_ATTR_NONNULL_ALL { size_t count = 0; @@ -1527,11 +1487,11 @@ ssize_t mb_utf_index_to_bytes(const char_u *s, size_t len, size_t index, bool us if (index == 0) { return 0; } - for (i = 0; i < len && s[i] != NUL; i += clen) { + for (i = 0; i < len; i += clen) { clen = (size_t)utf_ptr2len_len(s + i, (int)(len - i)); // NB: gets the byte value of invalid sequence bytes. // we only care whether the char fits in the BMP or not - int c = (clen > 1) ? utf_ptr2char((char *)s + i) : s[i]; + int c = (clen > 1) ? utf_ptr2char(s + i) : (uint8_t)s[i]; count++; if (use_utf16_units && c > 0xFFFF) { count++; @@ -1543,17 +1503,16 @@ ssize_t mb_utf_index_to_bytes(const char_u *s, size_t len, size_t index, bool us return -1; } -/* - * Version of strnicmp() that handles multi-byte characters. - * Needed for Big5, Shift-JIS and UTF-8 encoding. Other DBCS encodings can - * probably use strnicmp(), because there are no ASCII characters in the - * second byte. - * Returns zero if s1 and s2 are equal (ignoring case), the difference between - * two characters otherwise. - */ -int mb_strnicmp(const char_u *s1, const char_u *s2, const size_t nn) +/// Version of strnicmp() that handles multi-byte characters. +/// Needed for Big5, Shift-JIS and UTF-8 encoding. Other DBCS encodings can +/// probably use strnicmp(), because there are no ASCII characters in the +/// second byte. +/// +/// @return zero if s1 and s2 are equal (ignoring case), the difference between +/// two characters otherwise. +int mb_strnicmp(const char *s1, const char *s2, const size_t nn) { - return utf_strnicmp(s1, s2, nn, nn); + return utf_strnicmp((char_u *)s1, (char_u *)s2, nn, nn); } /// Compare strings case-insensitively @@ -1570,72 +1529,73 @@ int mb_strnicmp(const char_u *s1, const char_u *s2, const size_t nn) /// @return 0 if strings are equal, <0 if s1 < s2, >0 if s1 > s2. int mb_stricmp(const char *s1, const char *s2) { - return mb_strnicmp((const char_u *)s1, (const char_u *)s2, MAXCOL); + return mb_strnicmp(s1, s2, MAXCOL); } -/* - * "g8": show bytes of the UTF-8 char under the cursor. Doesn't matter what - * 'encoding' has been set to. - */ +// "g8": show bytes of the UTF-8 char under the cursor. Doesn't matter what +// 'encoding' has been set to. void show_utf8(void) { int len; int rlen = 0; - char_u *line; + char *line; int clen; int i; // Get the byte length of the char under the cursor, including composing // characters. line = get_cursor_pos_ptr(); - len = utfc_ptr2len((char *)line); + len = utfc_ptr2len(line); if (len == 0) { msg("NUL"); return; } clen = 0; - for (i = 0; i < len; ++i) { + for (i = 0; i < len; i++) { if (clen == 0) { // start of (composing) character, get its length if (i > 0) { STRCPY(IObuff + rlen, "+ "); rlen += 2; } - clen = utf_ptr2len((char *)line + i); + clen = utf_ptr2len(line + i); } - sprintf((char *)IObuff + rlen, "%02x ", - (line[i] == NL) ? NUL : line[i]); // NUL is stored as NL + sprintf(IObuff + rlen, "%02x ", // NOLINT(runtime/printf) + (line[i] == NL) ? NUL : (uint8_t)line[i]); // NUL is stored as NL clen--; - rlen += (int)STRLEN(IObuff + rlen); + rlen += (int)strlen(IObuff + rlen); if (rlen > IOSIZE - 20) { break; } } - msg((char *)IObuff); + msg(IObuff); } /// Return offset from "p" to the start of a character, including composing characters. /// "base" must be the start of the string, which must be NUL terminated. /// If "p" points to the NUL at the end of the string return 0. /// Returns 0 when already at the first byte of a character. -int utf_head_off(const char_u *base, const char_u *p) +int utf_head_off(const char *base_in, const char *p_in) { int c; int len; - if (*p < 0x80) { // be quick for ASCII + if ((uint8_t)(*p_in) < 0x80) { // be quick for ASCII return 0; } + const uint8_t *base = (uint8_t *)base_in; + const uint8_t *p = (uint8_t *)p_in; + // Skip backwards over trailing bytes: 10xx.xxxx // Skip backwards again if on a composing char. - const char_u *q; - for (q = p;; --q) { + const uint8_t *q; + for (q = p;; q--) { // Move s to the last byte of this char. - const char_u *s; - for (s = q; (s[1] & 0xc0) == 0x80; ++s) {} + const uint8_t *s; + for (s = q; (s[1] & 0xc0) == 0x80; s++) {} // Move q to the first byte of this char. while (q > base && (*q & 0xc0) == 0x80) { @@ -1659,7 +1619,7 @@ int utf_head_off(const char_u *base, const char_u *p) if (arabic_maycombine(c)) { // Advance to get a sneak-peak at the next char - const char_u *j = q; + const uint8_t *j = q; j--; // Move j to the first byte of this char. while (j > base && (*j & 0xc0) == 0x80) { @@ -1828,11 +1788,12 @@ void mb_copy_char(const char **const fp, char **const tp) *fp += l; } -/// Return the offset from "p" to the first byte of a character. When "p" is +/// Return the offset from "p_in" to the first byte of a character. When "p_in" is /// at the start of a character 0 is returned, otherwise the offset to the next /// character. Can start anywhere in a stream of bytes. -int mb_off_next(const char_u *base, const char_u *p) +int mb_off_next(const char *base, const char *p_in) { + const uint8_t *p = (uint8_t *)p_in; int i; int j; @@ -1844,7 +1805,7 @@ int mb_off_next(const char_u *base, const char_u *p) for (i = 0; (p[i] & 0xc0) == 0x80; i++) {} if (i > 0) { // Check for illegal sequence. - for (j = 0; p - j > base; j++) { + for (j = 0; p - j > (uint8_t *)base; j++) { if ((p[-j] & 0xc0) != 0x80) { break; } @@ -1920,16 +1881,14 @@ int utf_cp_head_off(const char_u *base, const char_u *p) return i; } -/* - * Find the next illegal byte sequence. - */ +// Find the next illegal byte sequence. void utf_find_illegal(void) { pos_T pos = curwin->w_cursor; - char_u *p; + char *p; int len; vimconv_T vimconv; - char_u *tofree = NULL; + char *tofree = NULL; vimconv.vc_type = CONV_NONE; if (enc_canon_props(curbuf->b_p_fenc) & ENC_8BIT) { @@ -1954,9 +1913,8 @@ void utf_find_illegal(void) while (*p != NUL) { // Illegal means that there are not enough trail bytes (checked by // utf_ptr2len()) or too many of them (overlong sequence). - len = utf_ptr2len((char *)p); - if (*p >= 0x80 && (len == 1 - || utf_char2len(utf_ptr2char((char *)p)) != len)) { + len = utf_ptr2len(p); + if ((uint8_t)(*p) >= 0x80 && (len == 1 || utf_char2len(utf_ptr2char(p)) != len)) { if (vimconv.vc_type == CONV_NONE) { curwin->w_cursor.col += (colnr_T)(p - get_cursor_pos_ptr()); } else { @@ -1964,7 +1922,7 @@ void utf_find_illegal(void) len = (int)(p - tofree); for (p = get_cursor_pos_ptr(); *p != NUL && len-- > 0; p += l) { - l = utf_ptr2len((char *)p); + l = utf_ptr2len(p); curwin->w_cursor.col += l; } } @@ -1975,7 +1933,7 @@ void utf_find_illegal(void) if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { break; } - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; } @@ -1989,8 +1947,7 @@ theend: } /// @return true if string "s" is a valid utf-8 string. -/// When "end" is NULL stop at the first NUL. -/// When "end" is positive stop there. +/// When "end" is NULL stop at the first NUL. Otherwise stop at "end". bool utf_valid_string(const char_u *s, const char_u *end) { const char_u *p = s; @@ -2013,10 +1970,8 @@ bool utf_valid_string(const char_u *s, const char_u *end) return true; } -/* - * If the cursor moves on an trail byte, set the cursor on the lead byte. - * Thus it moves left if necessary. - */ +// If the cursor moves on an trail byte, set the cursor on the lead byte. +// Thus it moves left if necessary. void mb_adjust_cursor(void) { mark_mb_adjustpos(curbuf, &curwin->w_cursor); @@ -2033,8 +1988,8 @@ void mb_check_adjust_col(void *win_) // Column 0 is always valid. if (oldcol != 0) { - char *p = (char *)ml_get_buf(win->w_buffer, win->w_cursor.lnum, false); - colnr_T len = (colnr_T)STRLEN(p); + char *p = ml_get_buf(win->w_buffer, win->w_cursor.lnum, false); + colnr_T len = (colnr_T)strlen(p); // Empty line or invalid column? if (len == 0 || oldcol < 0) { @@ -2045,7 +2000,7 @@ void mb_check_adjust_col(void *win_) win->w_cursor.col = len - 1; } // Move the cursor to the head byte. - win->w_cursor.col -= utf_head_off((char_u *)p, (char_u *)p + win->w_cursor.col); + win->w_cursor.col -= utf_head_off(p, p + win->w_cursor.col); } // Reset `coladd` when the cursor would be on the right half of a @@ -2061,7 +2016,7 @@ void mb_check_adjust_col(void *win_) /// @param line start of the string /// /// @return a pointer to the character before "*p", if there is one. -char_u *mb_prevptr(char_u *line, char_u *p) +char *mb_prevptr(char *line, char *p) { if (p > line) { MB_PTR_BACK(line, p); @@ -2071,9 +2026,9 @@ char_u *mb_prevptr(char_u *line, char_u *p) /// Return the character length of "str". Each multi-byte character (with /// following composing characters) counts as one. -int mb_charlen(const char_u *str) +int mb_charlen(const char *str) { - const char_u *p = str; + const char *p = str; int count; if (p == NULL) { @@ -2081,20 +2036,20 @@ int mb_charlen(const char_u *str) } for (count = 0; *p != NUL; count++) { - p += utfc_ptr2len((char *)p); + p += utfc_ptr2len(p); } return count; } /// Like mb_charlen() but for a string with specified length. -int mb_charlen_len(const char_u *str, int len) +int mb_charlen_len(const char *str, int len) { - const char_u *p = str; + const char *p = str; int count; for (count = 0; *p != NUL && p < str + len; count++) { - p += utfc_ptr2len((char *)p); + p += utfc_ptr2len(p); } return count; @@ -2147,45 +2102,41 @@ const char *mb_unescape(const char **const pp) return NULL; } -/* - * Skip the Vim specific head of a 'encoding' name. - */ -char_u *enc_skip(char_u *p) +/// Skip the Vim specific head of a 'encoding' name. +char *enc_skip(char *p) { - if (STRNCMP(p, "2byte-", 6) == 0) { + if (strncmp(p, "2byte-", 6) == 0) { return p + 6; } - if (STRNCMP(p, "8bit-", 5) == 0) { + if (strncmp(p, "8bit-", 5) == 0) { return p + 5; } return p; } -/* - * Find the canonical name for encoding "enc". - * When the name isn't recognized, returns "enc" itself, but with all lower - * case characters and '_' replaced with '-'. - * Returns an allocated string. - */ -char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET +/// Find the canonical name for encoding "enc". +/// When the name isn't recognized, returns "enc" itself, but with all lower +/// case characters and '_' replaced with '-'. +/// +/// @return an allocated string. +char *enc_canonize(char *enc) + FUNC_ATTR_NONNULL_RET { - char_u *p, *s; - int i; - - if (STRCMP(enc, "default") == 0) { + char *p, *s; + if (strcmp(enc, "default") == 0) { // Use the default encoding as found by set_init_1(). - return vim_strsave(fenc_default); + return xstrdup(fenc_default); } // copy "enc" to allocated memory, with room for two '-' - char_u *r = xmalloc(STRLEN(enc) + 3); + char *r = xmalloc(strlen(enc) + 3); // Make it all lower case and replace '_' with '-'. p = r; - for (s = enc; *s != NUL; ++s) { + for (s = enc; *s != NUL; s++) { if (*s == '_') { *p++ = '-'; } else { - *p++ = (char_u)TOLOWER_ASC(*s); + *p++ = (char)TOLOWER_ASC(*s); } } *p = NUL; @@ -2194,27 +2145,28 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET p = enc_skip(r); // Change "microsoft-cp" to "cp". Used in some spell files. - if (STRNCMP(p, "microsoft-cp", 12) == 0) { + if (strncmp(p, "microsoft-cp", 12) == 0) { STRMOVE(p, p + 10); } // "iso8859" -> "iso-8859" - if (STRNCMP(p, "iso8859", 7) == 0) { + if (strncmp(p, "iso8859", 7) == 0) { STRMOVE(p + 4, p + 3); p[3] = '-'; } // "iso-8859n" -> "iso-8859-n" - if (STRNCMP(p, "iso-8859", 8) == 0 && p[8] != '-') { + if (strncmp(p, "iso-8859", 8) == 0 && p[8] != '-') { STRMOVE(p + 9, p + 8); p[8] = '-'; } // "latin-N" -> "latinN" - if (STRNCMP(p, "latin-", 6) == 0) { + if (strncmp(p, "latin-", 6) == 0) { STRMOVE(p + 5, p + 6); } + int i; if (enc_canon_search(p) >= 0) { // canonical name can be used unmodified if (p != r) { @@ -2223,19 +2175,19 @@ char_u *enc_canonize(char_u *enc) FUNC_ATTR_NONNULL_RET } else if ((i = enc_alias_search(p)) >= 0) { // alias recognized, get canonical name xfree(r); - r = vim_strsave((char_u *)enc_canon_table[i].name); + r = xstrdup(enc_canon_table[i].name); } return r; } /// Search for an encoding alias of "name". /// Returns -1 when not found. -static int enc_alias_search(const char_u *name) +static int enc_alias_search(const char *name) { int i; - for (i = 0; enc_alias_table[i].name != NULL; ++i) { - if (STRCMP(name, enc_alias_table[i].name) == 0) { + for (i = 0; enc_alias_table[i].name != NULL; i++) { + if (strcmp(name, enc_alias_table[i].name) == 0) { return enc_alias_table[i].canon; } } @@ -2246,11 +2198,9 @@ static int enc_alias_search(const char_u *name) # include <langinfo.h> #endif -/* - * Get the canonicalized encoding of the current locale. - * Returns an allocated string when successful, NULL when not. - */ -char_u *enc_locale(void) +// Get the canonicalized encoding of the current locale. +// Returns an allocated string when successful, NULL when not. +char *enc_locale(void) { int i; char buf[50]; @@ -2286,7 +2236,7 @@ char_u *enc_locale(void) const char *p = vim_strchr(s, '.'); if (p != NULL) { if (p > s + 2 && !STRNICMP(p + 1, "EUC", 3) - && !isalnum((int)p[4]) && p[4] != '-' && p[-3] == '_') { + && !isalnum((uint8_t)p[4]) && p[4] != '-' && p[-3] == '_') { // Copy "XY.EUC" to "euc-XY" to buf[10]. memmove(buf, "euc-", 4); buf[4] = (char)(ASCII_ISALNUM(p[-2]) ? TOLOWER_ASC(p[-2]) : 0); @@ -2310,22 +2260,18 @@ enc_locale_copy_enc: buf[i] = NUL; } - return enc_canonize((char_u *)buf); + return enc_canonize(buf); } -#if defined(HAVE_ICONV) - -/* - * Call iconv_open() with a check if iconv() works properly (there are broken - * versions). - * Returns (void *)-1 if failed. - * (should return iconv_t, but that causes problems with prototypes). - */ -void *my_iconv_open(char_u *to, char_u *from) +// Call iconv_open() with a check if iconv() works properly (there are broken +// versions). +// Returns (void *)-1 if failed. +// (should return iconv_t, but that causes problems with prototypes). +void *my_iconv_open(char *to, char *from) { iconv_t fd; -# define ICONV_TESTLEN 400 - char_u tobuf[ICONV_TESTLEN]; +#define ICONV_TESTLEN 400 + char tobuf[ICONV_TESTLEN]; char *p; size_t tolen; static WorkingStatus iconv_working = kUnknown; @@ -2333,17 +2279,15 @@ void *my_iconv_open(char_u *to, char_u *from) if (iconv_working == kBroken) { return (void *)-1; // detected a broken iconv() previously } - fd = iconv_open((char *)enc_skip(to), (char *)enc_skip(from)); + fd = iconv_open(enc_skip(to), enc_skip(from)); if (fd != (iconv_t)-1 && iconv_working == kUnknown) { - /* - * Do a dummy iconv() call to check if it actually works. There is a - * version of iconv() on Linux that is broken. We can't ignore it, - * because it's wide-spread. The symptoms are that after outputting - * the initial shift state the "to" pointer is NULL and conversion - * stops for no apparent reason after about 8160 characters. - */ - p = (char *)tobuf; + // Do a dummy iconv() call to check if it actually works. There is a + // version of iconv() on Linux that is broken. We can't ignore it, + // because it's wide-spread. The symptoms are that after outputting + // the initial shift state the "to" pointer is NULL and conversion + // stops for no apparent reason after about 8160 characters. + p = tobuf; tolen = ICONV_TESTLEN; (void)iconv(fd, NULL, NULL, &p, &tolen); if (p == NULL) { @@ -2358,15 +2302,13 @@ void *my_iconv_open(char_u *to, char_u *from) return (void *)fd; } -/* - * Convert the string "str[slen]" with iconv(). - * If "unconvlenp" is not NULL handle the string ending in an incomplete - * sequence and set "*unconvlenp" to the length of it. - * Returns the converted string in allocated memory. NULL for an error. - * If resultlenp is not NULL, sets it to the result length in bytes. - */ -static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, size_t slen, - size_t *unconvlenp, size_t *resultlenp) +// Convert the string "str[slen]" with iconv(). +// If "unconvlenp" is not NULL handle the string ending in an incomplete +// sequence and set "*unconvlenp" to the length of it. +// Returns the converted string in allocated memory. NULL for an error. +// If resultlenp is not NULL, sets it to the result length in bytes. +static char *iconv_string(const vimconv_T *const vcp, const char *str, size_t slen, + size_t *unconvlenp, size_t *resultlenp) { const char *from; size_t fromlen; @@ -2374,11 +2316,11 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, size_t slen size_t tolen; size_t len = 0; size_t done = 0; - char_u *result = NULL; - char_u *p; + char *result = NULL; + char *p; int l; - from = (char *)str; + from = str; fromlen = slen; for (;;) { if (len == 0 || ICONV_ERRNO == ICONV_E2BIG) { @@ -2393,7 +2335,7 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, size_t slen result = p; } - to = (char *)result + done; + to = result + done; tolen = len - done - 2; // Avoid a warning for systems with a wrong iconv() prototype by // casting the second argument to void *. @@ -2424,7 +2366,7 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, size_t slen if (utf_ptr2cells(from) > 1) { *to++ = '?'; } - l = utfc_ptr2len_len((const char_u *)from, (int)fromlen); + l = utfc_ptr2len_len(from, (int)fromlen); from += l; fromlen -= (size_t)l; } else if (ICONV_ERRNO != ICONV_E2BIG) { @@ -2433,34 +2375,31 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, size_t slen break; } // Not enough room or skipping illegal sequence. - done = (size_t)(to - (char *)result); + done = (size_t)(to - result); } if (resultlenp != NULL && result != NULL) { - *resultlenp = (size_t)(to - (char *)result); + *resultlenp = (size_t)(to - result); } return result; } -#endif // HAVE_ICONV - -/* - * Setup "vcp" for conversion from "from" to "to". - * The names must have been made canonical with enc_canonize(). - * vcp->vc_type must have been initialized to CONV_NONE. - * Note: cannot be used for conversion from/to ucs-2 and ucs-4 (will use utf-8 - * instead). - * Afterwards invoke with "from" and "to" equal to NULL to cleanup. - * Return FAIL when conversion is not supported, OK otherwise. - */ -int convert_setup(vimconv_T *vcp, char_u *from, char_u *to) +/// Setup "vcp" for conversion from "from" to "to". +/// The names must have been made canonical with enc_canonize(). +/// vcp->vc_type must have been initialized to CONV_NONE. +/// Note: cannot be used for conversion from/to ucs-2 and ucs-4 (will use utf-8 +/// instead). +/// Afterwards invoke with "from" and "to" equal to NULL to cleanup. +/// +/// @return FAIL when conversion is not supported, OK otherwise. +int convert_setup(vimconv_T *vcp, char *from, char *to) { return convert_setup_ext(vcp, from, true, to, true); } /// As convert_setup(), but only when from_unicode_is_utf8 is true will all /// "from" unicode charsets be considered utf-8. Same for "to". -int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8, char_u *to, +int convert_setup_ext(vimconv_T *vcp, char *from, bool from_unicode_is_utf8, char *to, bool to_unicode_is_utf8) { int from_prop; @@ -2469,16 +2408,14 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8, c int to_is_utf8; // Reset to no conversion. -#ifdef HAVE_ICONV if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1) { iconv_close(vcp->vc_fd); } -#endif *vcp = (vimconv_T)MBYTE_NONE_CONV; // No conversion when one of the names is empty or they are equal. if (from == NULL || *from == NUL || to == NULL || *to == NUL - || STRCMP(from, to) == 0) { + || strcmp(from, to) == 0) { return OK; } @@ -2509,18 +2446,15 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8, c } else if (from_is_utf8 && (to_prop & ENC_LATIN9)) { // Internal utf-8 -> latin9 conversion. vcp->vc_type = CONV_TO_LATIN9; - } -#ifdef HAVE_ICONV - else { // NOLINT(readability/braces) + } else { // Use iconv() for conversion. - vcp->vc_fd = (iconv_t)my_iconv_open(to_is_utf8 ? (char_u *)"utf-8" : to, - from_is_utf8 ? (char_u *)"utf-8" : from); + vcp->vc_fd = (iconv_t)my_iconv_open(to_is_utf8 ? "utf-8" : to, + from_is_utf8 ? "utf-8" : from); if (vcp->vc_fd != (iconv_t)-1) { vcp->vc_type = CONV_ICONV; vcp->vc_factor = 4; // could be longer too... } } -#endif if (vcp->vc_type == CONV_NONE) { return FAIL; } @@ -2528,25 +2462,20 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8, c return OK; } -/* - * Convert text "ptr[*lenp]" according to "vcp". - * Returns the result in allocated memory and sets "*lenp". - * When "lenp" is NULL, use NUL terminated strings. - * Illegal chars are often changed to "?", unless vcp->vc_fail is set. - * When something goes wrong, NULL is returned and "*lenp" is unchanged. - */ -char_u *string_convert(const vimconv_T *const vcp, char_u *ptr, size_t *lenp) +/// Convert text "ptr[*lenp]" according to "vcp". +/// Returns the result in allocated memory and sets "*lenp". +/// When "lenp" is NULL, use NUL terminated strings. +/// Illegal chars are often changed to "?", unless vcp->vc_fail is set. +/// When something goes wrong, NULL is returned and "*lenp" is unchanged. +char *string_convert(const vimconv_T *const vcp, char *ptr, size_t *lenp) { return string_convert_ext(vcp, ptr, lenp, NULL); } -/* - * Like string_convert(), but when "unconvlenp" is not NULL and there are is - * an incomplete sequence at the end it is not converted and "*unconvlenp" is - * set to the number of remaining bytes. - */ -char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp, - size_t *unconvlenp) +// Like string_convert(), but when "unconvlenp" is not NULL and there are is +// an incomplete sequence at the end it is not converted and "*unconvlenp" is +// set to the number of remaining bytes. +char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, size_t *unconvlenp) { char_u *retval = NULL; char_u *d; @@ -2555,20 +2484,20 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp size_t len; if (lenp == NULL) { - len = STRLEN(ptr); + len = strlen(ptr); } else { len = *lenp; } if (len == 0) { - return vim_strsave((char_u *)""); + return xstrdup(""); } switch (vcp->vc_type) { case CONV_TO_UTF8: // latin1 to utf-8 conversion retval = xmalloc(len * 2 + 1); d = retval; - for (size_t i = 0; i < len; ++i) { - c = ptr[i]; + for (size_t i = 0; i < len; i++) { + c = (uint8_t)ptr[i]; if (c < 0x80) { *d++ = (char_u)c; } else { @@ -2585,8 +2514,8 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp case CONV_9_TO_UTF8: // latin9 to utf-8 conversion retval = xmalloc(len * 3 + 1); d = retval; - for (size_t i = 0; i < len; ++i) { - c = ptr[i]; + for (size_t i = 0; i < len; i++) { + c = (uint8_t)ptr[i]; switch (c) { case 0xa4: c = 0x20ac; break; // euro @@ -2622,7 +2551,7 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp if (l == 0) { *d++ = NUL; } else if (l == 1) { - uint8_t l_w = utf8len_tab_zero[ptr[i]]; + uint8_t l_w = utf8len_tab_zero[(uint8_t)ptr[i]]; if (l_w == 0) { // Illegal utf-8 byte cannot be converted @@ -2634,9 +2563,9 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp *unconvlenp = len - i; break; } - *d++ = ptr[i]; + *d++ = (uint8_t)ptr[i]; } else { - c = utf_ptr2char((char *)ptr + i); + c = utf_ptr2char(ptr + i); if (vcp->vc_type == CONV_TO_LATIN9) { switch (c) { case 0x20ac: @@ -2688,14 +2617,12 @@ char_u *string_convert_ext(const vimconv_T *const vcp, char_u *ptr, size_t *lenp } break; -#ifdef HAVE_ICONV case CONV_ICONV: // conversion with vcp->vc_fd - retval = iconv_string(vcp, ptr, len, unconvlenp, lenp); + retval = (char_u *)iconv_string(vcp, ptr, len, unconvlenp, lenp); break; -#endif } - return retval; + return (char *)retval; } /// Table set by setcellwidths(). @@ -2748,7 +2675,7 @@ static int tv_nr_compare(const void *a1, const void *a2) } /// "setcellwidths()" function -void f_setcellwidths(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) { emsg(_(e_listreq)); @@ -2774,7 +2701,7 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (li_tv->v_type != VAR_LIST || li_tv->vval.v_list == NULL) { semsg(_(e_list_item_nr_is_not_list), item); - xfree(ptrs); + xfree((void *)ptrs); return; } @@ -2790,25 +2717,25 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, FunPtr fptr) } if (i == 0) { n1 = lili_tv->vval.v_number; - if (n1 < 0x100) { - emsg(_(e_only_values_of_0x100_and_higher_supported)); - xfree(ptrs); + if (n1 < 0x80) { + emsg(_(e_only_values_of_0x80_and_higher_supported)); + xfree((void *)ptrs); return; } } else if (i == 1 && lili_tv->vval.v_number < n1) { semsg(_(e_list_item_nr_range_invalid), item); - xfree(ptrs); + xfree((void *)ptrs); return; } else if (i == 2 && (lili_tv->vval.v_number < 1 || lili_tv->vval.v_number > 2)) { semsg(_(e_list_item_nr_cell_width_invalid), item); - xfree(ptrs); + xfree((void *)ptrs); return; } } if (i != 3) { semsg(_(e_list_item_nr_does_not_contain_3_numbers), item); - xfree(ptrs); + xfree((void *)ptrs); return; } @@ -2827,7 +2754,7 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, FunPtr fptr) const varnumber_T n1 = TV_LIST_ITEM_TV(lili)->vval.v_number; if (item > 0 && n1 <= table[item - 1].last) { semsg(_(e_overlapping_ranges_for_nr), (long)n1); - xfree(ptrs); + xfree((void *)ptrs); xfree(table); return; } @@ -2838,7 +2765,7 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, FunPtr fptr) table[item].width = (char)TV_LIST_ITEM_TV(lili)->vval.v_number; } - xfree(ptrs); + xfree((void *)ptrs); cw_interval_T *const cw_table_save = cw_table; const size_t cw_table_size_save = cw_table_size; @@ -2857,14 +2784,29 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, FunPtr fptr) } xfree(cw_table_save); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); +} + +/// "getcellwidths()" function +void f_getcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_list_alloc_ret(rettv, (ptrdiff_t)cw_table_size); + + for (size_t i = 0; i < cw_table_size; i++) { + list_T *entry = tv_list_alloc(3); + tv_list_append_number(entry, (varnumber_T)cw_table[i].first); + tv_list_append_number(entry, (varnumber_T)cw_table[i].last); + tv_list_append_number(entry, (varnumber_T)cw_table[i].width); + + tv_list_append_list(rettv->vval.v_list, entry); + } } -void f_charclass(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_charclass(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { - if (tv_check_for_string(&argvars[0]) == FAIL + if (tv_check_for_string_arg(argvars, 0) == FAIL || argvars[0].vval.v_string == NULL) { return; } - rettv->vval.v_number = mb_get_class((const char_u *)argvars[0].vval.v_string); + rettv->vval.v_number = mb_get_class(argvars[0].vval.v_string); } diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index 2a9afcbd03..780f33e05b 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -8,15 +8,13 @@ #include "nvim/eval/typval.h" #include "nvim/func_attr.h" #include "nvim/mbyte_defs.h" -#include "nvim/os/os_defs.h" // For indirect -#include "nvim/types.h" // for char_u - -/* - * 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. - * Don't call MB_BYTE2LEN(b) with b < 0 or b > 255! - */ +#include "nvim/os/os_defs.h" +#include "nvim/types.h" + +// 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. +// Don't call MB_BYTE2LEN(b) with b < 0 or b > 255! #define MB_BYTE2LEN(b) utf8len_tab[b] #define MB_BYTE2LEN_CHECK(b) (((b) < 0 || (b) > 255) ? 1 : utf8len_tab[b]) diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h index 53b01a211f..e913e20f9f 100644 --- a/src/nvim/mbyte_defs.h +++ b/src/nvim/mbyte_defs.h @@ -46,9 +46,7 @@ typedef enum { typedef struct { int vc_type; ///< Zero or more ConvFlags. int vc_factor; ///< Maximal expansion factor. -#ifdef HAVE_ICONV iconv_t vc_fd; ///< Value for CONV_ICONV. -#endif bool vc_fail; ///< What to do with invalid characters: if true, fail, ///< otherwise use '?'. } vimconv_T; diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 216ee26620..46be9ccea5 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -43,19 +43,25 @@ #include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stdio.h> #include <string.h> -#include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/buffer_defs.h" #include "nvim/fileio.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/macros.h" #include "nvim/memfile.h" +#include "nvim/memfile_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/vim.h" #define MEMFILE_PAGE_SIZE 4096 /// default page size @@ -77,7 +83,7 @@ /// /// @return - The open memory file, on success. /// - NULL, on failure (e.g. file does not exist). -memfile_T *mf_open(char_u *fname, int flags) +memfile_T *mf_open(char *fname, int flags) { memfile_T *mfp = xmalloc(sizeof(memfile_T)); @@ -148,7 +154,7 @@ memfile_T *mf_open(char_u *fname, int flags) /// /// @return OK On success. /// FAIL If file could not be opened. -int mf_open_file(memfile_T *mfp, char_u *fname) +int mf_open_file(memfile_T *mfp, char *fname) { if (mf_do_open(mfp, fname, O_RDWR | O_CREAT | O_EXCL)) { mfp->mf_dirty = true; @@ -170,7 +176,7 @@ void mf_close(memfile_T *mfp, bool del_file) emsg(_(e_swapclose)); } if (del_file && mfp->mf_fname != NULL) { - os_remove((char *)mfp->mf_fname); + os_remove(mfp->mf_fname); } // free entries in used list @@ -210,7 +216,7 @@ void mf_close_file(buf_T *buf, bool getlines) mfp->mf_fd = -1; if (mfp->mf_fname != NULL) { - os_remove((char *)mfp->mf_fname); // delete the swap file + os_remove(mfp->mf_fname); // delete the swap file mf_free_fnames(mfp); } } @@ -749,10 +755,10 @@ void mf_free_fnames(memfile_T *mfp) /// /// Only called when creating or renaming the swapfile. Either way it's a new /// name so we must work out the full path name. -void mf_set_fnames(memfile_T *mfp, char_u *fname) +void mf_set_fnames(memfile_T *mfp, char *fname) { mfp->mf_fname = fname; - mfp->mf_ffname = (char_u *)FullName_save((char *)mfp->mf_fname, false); + mfp->mf_ffname = FullName_save(mfp->mf_fname, false); } /// Make name of memfile's swapfile a full path. @@ -760,11 +766,13 @@ void mf_set_fnames(memfile_T *mfp, char_u *fname) /// Used before doing a :cd void mf_fullname(memfile_T *mfp) { - if (mfp != NULL && mfp->mf_fname != NULL && mfp->mf_ffname != NULL) { - xfree(mfp->mf_fname); - mfp->mf_fname = mfp->mf_ffname; - mfp->mf_ffname = NULL; + if (mfp == NULL || mfp->mf_fname == NULL || mfp->mf_ffname == NULL) { + return; } + + xfree(mfp->mf_fname); + mfp->mf_fname = mfp->mf_ffname; + mfp->mf_ffname = NULL; } /// Return true if there are any translations pending for memfile. @@ -779,7 +787,7 @@ bool mf_need_trans(memfile_T *mfp) /// /// @param flags Flags for open(). /// @return A bool indicating success of the `open` call. -static bool mf_do_open(memfile_T *mfp, char_u *fname, int flags) +static bool mf_do_open(memfile_T *mfp, char *fname, int flags) { // fname cannot be NameBuff, because it must have been allocated. mf_set_fnames(mfp, fname); @@ -789,7 +797,7 @@ static bool mf_do_open(memfile_T *mfp, char_u *fname, int flags) /// exist yet. If there is a symbolic link, this is most likely an attack. FileInfo file_info; if ((flags & O_CREAT) - && os_fileinfo_link((char *)mfp->mf_fname, &file_info)) { + && os_fileinfo_link(mfp->mf_fname, &file_info)) { mfp->mf_fd = -1; emsg(_("E300: Swap file already exists (symlink attack?)")); } else { @@ -815,8 +823,10 @@ static bool mf_do_open(memfile_T *mfp, char_u *fname, int flags) /// The number of buckets in the hashtable is increased by a factor of /// MHT_GROWTH_FACTOR when the average number of items per bucket /// exceeds 2 ^ MHT_LOG_LOAD_FACTOR. -#define MHT_LOG_LOAD_FACTOR 6 -#define MHT_GROWTH_FACTOR 2 // must be a power of two +enum { + MHT_LOG_LOAD_FACTOR = 6, + MHT_GROWTH_FACTOR = 2, // must be a power of two +}; /// Initialize an empty hash table. static void mf_hash_init(mf_hashtab_T *mht) diff --git a/src/nvim/memfile_defs.h b/src/nvim/memfile_defs.h index 537d3e5f65..53152c28f8 100644 --- a/src/nvim/memfile_defs.h +++ b/src/nvim/memfile_defs.h @@ -88,8 +88,8 @@ typedef struct mf_blocknr_trans_item { /// A memory file. typedef struct memfile { - char_u *mf_fname; /// name of the file - char_u *mf_ffname; /// idem, full path + char *mf_fname; /// name of the file + char *mf_ffname; /// idem, full path int mf_fd; /// file descriptor bhdr_T *mf_free_first; /// first block header in free list bhdr_T *mf_used_first; /// mru block header in used list diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 23bc5d59c8..10a8195e7a 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -5,55 +5,65 @@ // #define CHECK(c, s) do { if (c) emsg(s); } while (0) #define CHECK(c, s) do {} while (0) -/* - * memline.c: Contains the functions for appending, deleting and changing the - * text lines. The memfile functions are used to store the information in - * blocks of memory, backed up by a file. The structure of the information is - * a tree. The root of the tree is a pointer block. The leaves of the tree - * are data blocks. In between may be several layers of pointer blocks, - * forming branches. - * - * Three types of blocks are used: - * - Block nr 0 contains information for recovery - * - Pointer blocks contain list of pointers to other blocks. - * - Data blocks contain the actual text. - * - * Block nr 0 contains the block0 structure (see below). - * - * Block nr 1 is the first pointer block. It is the root of the tree. - * Other pointer blocks are branches. - * - * If a line is too big to fit in a single page, the block containing that - * line is made big enough to hold the line. It may span several pages. - * Otherwise all blocks are one page. - * - * A data block that was filled when starting to edit a file and was not - * changed since then, can have a negative block number. This means that it - * has not yet been assigned a place in the file. When recovering, the lines - * in this data block can be read from the original file. When the block is - * changed (lines appended/deleted/changed) or when it is flushed it gets a - * positive number. Use mf_trans_del() to get the new number, before calling - * mf_get(). - */ +// memline.c: Contains the functions for appending, deleting and changing the +// text lines. The memfile functions are used to store the information in +// blocks of memory, backed up by a file. The structure of the information is +// a tree. The root of the tree is a pointer block. The leaves of the tree +// are data blocks. In between may be several layers of pointer blocks, +// forming branches. +// +// Three types of blocks are used: +// - Block nr 0 contains information for recovery +// - Pointer blocks contain list of pointers to other blocks. +// - Data blocks contain the actual text. +// +// Block nr 0 contains the block0 structure (see below). +// +// Block nr 1 is the first pointer block. It is the root of the tree. +// Other pointer blocks are branches. +// +// If a line is too big to fit in a single page, the block containing that +// line is made big enough to hold the line. It may span several pages. +// Otherwise all blocks are one page. +// +// A data block that was filled when starting to edit a file and was not +// changed since then, can have a negative block number. This means that it +// has not yet been assigned a place in the file. When recovering, the lines +// in this data block can be read from the original file. When the block is +// changed (lines appended/deleted/changed) or when it is flushed it gets a +// positive number. Use mf_trans_del() to get the new number, before calling +// mf_get(). #include <assert.h> #include <errno.h> #include <fcntl.h> #include <inttypes.h> #include <stdbool.h> +#include <stdio.h> #include <string.h> +#include <time.h> +#include <uv.h> +#include "auto/config.h" +#include "klib/kvec.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/fileio.h" -#include "nvim/func_attr.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/input.h" +#include "nvim/macros.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -62,19 +72,21 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" +#include "nvim/os/fs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/process.h" -#include "nvim/os_unix.h" +#include "nvim/os/time.h" #include "nvim/path.h" -#include "nvim/sha256.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/spell.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/version.h" #include "nvim/vim.h" -#include "nvim/window.h" #ifndef UNIX // it's in os/unix_defs.h for Unix # include <time.h> @@ -85,14 +97,14 @@ typedef struct pointer_block PTR_BL; // contents of a pointer block typedef struct data_block DATA_BL; // contents of a data block typedef struct pointer_entry PTR_EN; // block/line-count pair -#define DATA_ID (('d' << 8) + 'a') // data block id -#define PTR_ID (('p' << 8) + 't') // pointer block id -#define BLOCK0_ID0 'b' // block 0 id 0 -#define BLOCK0_ID1 '0' // block 0 id 1 +enum { + DATA_ID = (('d' << 8) + 'a'), // data block id + PTR_ID = (('p' << 8) + 't'), // pointer block id + BLOCK0_ID0 = 'b', // block 0 id 0 + BLOCK0_ID1 = '0', // block 0 id 1 +}; -/* - * pointer to a block, used in a pointer block - */ +// pointer to a block, used in a pointer block struct pointer_entry { blocknr_T pe_bnum; // block number linenr_T pe_line_count; // number of lines in this branch @@ -100,9 +112,7 @@ struct pointer_entry { int pe_page_count; // number of pages in block pe_bnum }; -/* - * A pointer block contains a list of branches in the tree. - */ +// A pointer block contains a list of branches in the tree. struct pointer_block { uint16_t pb_id; // ID for pointer block: PTR_ID uint16_t pb_count; // number of pointers in this block @@ -111,94 +121,87 @@ struct pointer_block { // followed by empty space until end of page }; -/* - * A data block is a leaf in the tree. - * - * The text of the lines is at the end of the block. The text of the first line - * in the block is put at the end, the text of the second line in front of it, - * etc. Thus the order of the lines is the opposite of the line number. - */ +// A data block is a leaf in the tree. +// +// The text of the lines is at the end of the block. The text of the first line +// in the block is put at the end, the text of the second line in front of it, +// etc. Thus the order of the lines is the opposite of the line number. struct data_block { uint16_t db_id; // ID for data block: DATA_ID unsigned db_free; // free space available unsigned db_txt_start; // byte where text starts unsigned db_txt_end; // byte just after data block - linenr_T db_line_count; // number of lines in this block + // linenr_T db_line_count; + long db_line_count; // number of lines in this block unsigned db_index[1]; // index for start of line (actually bigger) // followed by empty space up to db_txt_start // followed by the text in the lines until // end of page }; -/* - * The low bits of db_index hold the actual index. The topmost bit is - * used for the global command to be able to mark a line. - * This method is not clean, but otherwise there would be at least one extra - * byte used for each line. - * The mark has to be in this place to keep it with the correct line when other - * lines are inserted or deleted. - */ +// The low bits of db_index hold the actual index. The topmost bit is +// used for the global command to be able to mark a line. +// This method is not clean, but otherwise there would be at least one extra +// byte used for each line. +// The mark has to be in this place to keep it with the correct line when other +// lines are inserted or deleted. #define DB_MARKED ((unsigned)1 << ((sizeof(unsigned) * 8) - 1)) #define DB_INDEX_MASK (~DB_MARKED) #define INDEX_SIZE (sizeof(unsigned)) // size of one db_index entry #define HEADER_SIZE (sizeof(DATA_BL) - INDEX_SIZE) // size of data block header -#define B0_FNAME_SIZE_ORG 900 // what it was in older versions -#define B0_FNAME_SIZE_NOCRYPT 898 // 2 bytes used for other things -#define B0_FNAME_SIZE_CRYPT 890 // 10 bytes used for other things -#define B0_UNAME_SIZE 40 -#define B0_HNAME_SIZE 40 -/* - * Restrict the numbers to 32 bits, otherwise most compilers will complain. - * This won't detect a 64 bit machine that only swaps a byte in the top 32 - * bits, but that is crazy anyway. - */ -#define B0_MAGIC_LONG 0x30313233L -#define B0_MAGIC_INT 0x20212223L -#define B0_MAGIC_SHORT 0x10111213L -#define B0_MAGIC_CHAR 0x55 - -/* - * Block zero holds all info about the swap file. - * - * NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE! It would make all existing - * swap files unusable! - * - * If size of block0 changes anyway, adjust MIN_SWAP_PAGE_SIZE in vim.h!! - * - * This block is built up of single bytes, to make it portable across - * different machines. b0_magic_* is used to check the byte order and size of - * variables, because the rest of the swap file is not portable. - */ +enum { + B0_FNAME_SIZE_ORG = 900, // what it was in older versions + B0_FNAME_SIZE_NOCRYPT = 898, // 2 bytes used for other things + B0_FNAME_SIZE_CRYPT = 890, // 10 bytes used for other things + B0_UNAME_SIZE = 40, + B0_HNAME_SIZE = 40, +}; +// Restrict the numbers to 32 bits, otherwise most compilers will complain. +// This won't detect a 64 bit machine that only swaps a byte in the top 32 +// bits, but that is crazy anyway. +enum { + B0_MAGIC_LONG = 0x30313233L, + B0_MAGIC_INT = 0x20212223L, + B0_MAGIC_SHORT = 0x10111213L, + B0_MAGIC_CHAR = 0x55, +}; + +// Block zero holds all info about the swap file. +// +// NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE! It would make all existing +// swap files unusable! +// +// If size of block0 changes anyway, adjust MIN_SWAP_PAGE_SIZE in vim.h!! +// +// This block is built up of single bytes, to make it portable across +// different machines. b0_magic_* is used to check the byte order and size of +// variables, because the rest of the swap file is not portable. struct block0 { - char_u b0_id[2]; ///< ID for block 0: BLOCK0_ID0 and BLOCK0_ID1. - char_u b0_version[10]; // Vim version string - char_u b0_page_size[4]; // number of bytes per page - char_u b0_mtime[4]; // last modification time of file - char_u b0_ino[4]; // inode of b0_fname - char_u b0_pid[4]; // process id of creator (or 0) - char_u b0_uname[B0_UNAME_SIZE]; // name of user (uid if no name) - char_u b0_hname[B0_HNAME_SIZE]; // host name (if it has a name) - char_u b0_fname[B0_FNAME_SIZE_ORG]; // name of file being edited - long b0_magic_long; // check for byte order of long - int b0_magic_int; // check for byte order of int - short b0_magic_short; // check for byte order of short - char_u b0_magic_char; // check for last char + char_u b0_id[2]; ///< ID for block 0: BLOCK0_ID0 and BLOCK0_ID1. + char b0_version[10]; // Vim version string + char_u b0_page_size[4]; // number of bytes per page + char_u b0_mtime[4]; // last modification time of file + char_u b0_ino[4]; // inode of b0_fname + char_u b0_pid[4]; // process id of creator (or 0) + char_u b0_uname[B0_UNAME_SIZE]; // name of user (uid if no name) + char_u b0_hname[B0_HNAME_SIZE]; // host name (if it has a name) + char b0_fname[B0_FNAME_SIZE_ORG]; // name of file being edited + long b0_magic_long; // check for byte order of long + int b0_magic_int; // check for byte order of int + int16_t b0_magic_short; // check for byte order of short + char_u b0_magic_char; // check for last char }; -/* - * Note: b0_dirty and b0_flags are put at the end of the file name. For very - * long file names in older versions of Vim they are invalid. - * The 'fileencoding' comes before b0_flags, with a NUL in front. But only - * when there is room, for very long file names it's omitted. - */ +// Note: b0_dirty and b0_flags are put at the end of the file name. For very +// long file names in older versions of Vim they are invalid. +// The 'fileencoding' comes before b0_flags, with a NUL in front. But only +// when there is room, for very long file names it's omitted. #define B0_DIRTY 0x55 #define b0_dirty b0_fname[B0_FNAME_SIZE_ORG - 1] -/* - * The b0_flags field is new in Vim 7.0. - */ +// The b0_flags field is new in Vim 7.0. #define b0_flags b0_fname[B0_FNAME_SIZE_ORG - 2] // The lowest two bits contain the fileformat. Zero means it's not set @@ -216,21 +219,19 @@ struct block0 { #define STACK_INCR 5 // nr of entries added to ml_stack at a time -/* - * The line number where the first mark may be is remembered. - * If it is 0 there are no marks at all. - * (always used for the current buffer only, no buffer change possible while - * executing a global command). - */ +// The line number where the first mark may be is remembered. +// If it is 0 there are no marks at all. +// (always used for the current buffer only, no buffer change possible while +// executing a global command). static linenr_T lowest_marked = 0; -/* - * arguments for ml_find_line() - */ -#define ML_DELETE 0x11 // delete line -#define ML_INSERT 0x12 // insert line -#define ML_FIND 0x13 // just find the line -#define ML_FLUSH 0x02 // flush locked block +// arguments for ml_find_line() +enum { + ML_DELETE = 0x11, // delete line + ML_INSERT = 0x12, // insert line + ML_FIND = 0x13, // just find the line + ML_FLUSH = 0x02, // flush locked block +}; #define ML_SIMPLE(x) ((x) & 0x10) // DEL, INS or FIND // argument for ml_upd_block0() @@ -249,13 +250,8 @@ typedef enum { int ml_open(buf_T *buf) { bhdr_T *hp = NULL; - ZERO_BL *b0p; - PTR_BL *pp; - DATA_BL *dp; - /* - * init fields in memline struct - */ + // init fields in memline struct buf->b_ml.ml_stack_size = 0; // no stack yet buf->b_ml.ml_stack = NULL; // no stack yet buf->b_ml.ml_stack_top = 0; // nothing in the stack @@ -269,9 +265,7 @@ int ml_open(buf_T *buf) buf->b_p_swf = false; } - /* - * When 'updatecount' is non-zero swap file may be opened later. - */ + // When 'updatecount' is non-zero swap file may be opened later. if (!buf->terminal && p_uc && buf->b_p_swf) { buf->b_may_swap = true; } else { @@ -287,30 +281,27 @@ int ml_open(buf_T *buf) buf->b_ml.ml_mfp = mfp; buf->b_ml.ml_flags = ML_EMPTY; buf->b_ml.ml_line_count = 1; - curwin->w_nrwidth_line_count = 0; - /* - * fill block0 struct and write page 0 - */ + // fill block0 struct and write page 0 hp = mf_new(mfp, false, 1); if (hp->bh_bnum != 0) { iemsg(_("E298: Didn't get block nr 0?")); goto error; } - b0p = hp->bh_data; + ZERO_BL *b0p = hp->bh_data; b0p->b0_id[0] = BLOCK0_ID0; b0p->b0_id[1] = BLOCK0_ID1; b0p->b0_magic_long = B0_MAGIC_LONG; - b0p->b0_magic_int = (int)B0_MAGIC_INT; - b0p->b0_magic_short = (short)B0_MAGIC_SHORT; + b0p->b0_magic_int = B0_MAGIC_INT; + b0p->b0_magic_short = (int16_t)B0_MAGIC_SHORT; b0p->b0_magic_char = B0_MAGIC_CHAR; - xstrlcpy(xstpcpy((char *)b0p->b0_version, "VIM "), Version, 6); + xstrlcpy(xstpcpy(b0p->b0_version, "VIM "), Version, 6); long_to_char((long)mfp->mf_page_size, b0p->b0_page_size); if (!buf->b_spell) { b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0; - b0p->b0_flags = (uint8_t)(get_fileformat(buf) + 1); + b0p->b0_flags = (char)(get_fileformat(buf) + 1); set_b0_fname(b0p, buf); (void)os_get_username((char *)b0p->b0_uname, B0_UNAME_SIZE); b0p->b0_uname[B0_UNAME_SIZE - 1] = NUL; @@ -319,21 +310,17 @@ int ml_open(buf_T *buf) long_to_char(os_get_pid(), b0p->b0_pid); } - /* - * Always sync block number 0 to disk, so we can check the file name in - * the swap file in findswapname(). Don't do this for a help files or - * a spell buffer though. - * Only works when there's a swapfile, otherwise it's done when the file - * is created. - */ + // Always sync block number 0 to disk, so we can check the file name in + // the swap file in findswapname(). Don't do this for a help files or + // a spell buffer though. + // Only works when there's a swapfile, otherwise it's done when the file + // is created. mf_put(mfp, hp, true, false); if (!buf->b_help && !B_SPELL(buf)) { (void)mf_sync(mfp, 0); } - /* - * Fill in root pointer block and write page 1. - */ + // Fill in root pointer block and write page 1. if ((hp = ml_new_ptr(mfp)) == NULL) { goto error; } @@ -341,7 +328,7 @@ int ml_open(buf_T *buf) iemsg(_("E298: Didn't get block nr 1?")); goto error; } - pp = hp->bh_data; + PTR_BL *pp = hp->bh_data; pp->pb_count = 1; pp->pb_pointer[0].pe_bnum = 2; pp->pb_pointer[0].pe_page_count = 1; @@ -349,16 +336,14 @@ int ml_open(buf_T *buf) pp->pb_pointer[0].pe_line_count = 1; // line count after insertion mf_put(mfp, hp, true, false); - /* - * Allocate first data block and create an empty line 1. - */ + // Allocate first data block and create an empty line 1. hp = ml_new_data(mfp, false, 1); if (hp->bh_bnum != 2) { iemsg(_("E298: Didn't get block nr 2?")); goto error; } - dp = hp->bh_data; + DATA_BL *dp = hp->bh_data; dp->db_index[0] = --dp->db_txt_start; // at end of block dp->db_free -= 1 + (unsigned)INDEX_SIZE; dp->db_line_count = 1; @@ -382,16 +367,11 @@ error: void ml_setname(buf_T *buf) { bool success = false; - memfile_T *mfp; - char_u *fname; - char *dirp; - mfp = buf->b_ml.ml_mfp; + memfile_T *mfp = buf->b_ml.ml_mfp; if (mfp->mf_fd < 0) { // there is no swap file yet - /* - * When 'updatecount' is 0 and 'noswapfile' there is no swap file. - * For help files we will make a swap file now. - */ + // When 'updatecount' is 0 and 'noswapfile' there is no swap file. + // For help files we will make a swap file now. if (p_uc != 0 && (cmdmod.cmod_flags & CMOD_NOSWAPFILE) == 0) { ml_open_file(buf); // create a swap file } @@ -399,13 +379,13 @@ void ml_setname(buf_T *buf) } // Try all directories in the 'directory' option. - dirp = (char *)p_dir; + char *dirp = p_dir; bool found_existing_dir = false; for (;;) { if (*dirp == NUL) { // tried all directories, fail break; } - fname = (char_u *)findswapname(buf, &dirp, (char *)mfp->mf_fname, &found_existing_dir); + char *fname = findswapname(buf, &dirp, mfp->mf_fname, &found_existing_dir); // alloc's fname if (dirp == NULL) { // out of memory break; @@ -415,7 +395,7 @@ void ml_setname(buf_T *buf) } // if the file name is the same we don't have to do anything - if (FNAMECMP(fname, mfp->mf_fname) == 0) { + if (path_fnamecmp(fname, mfp->mf_fname) == 0) { xfree(fname); success = true; break; @@ -438,7 +418,7 @@ void ml_setname(buf_T *buf) } if (mfp->mf_fd == -1) { // need to (re)open the swap file - mfp->mf_fd = os_open((char *)mfp->mf_fname, O_RDWR, 0); + mfp->mf_fd = os_open(mfp->mf_fname, O_RDWR, 0); if (mfp->mf_fd < 0) { // could not (re)open the swap file, what can we do???? emsg(_("E301: Oops, lost the swap file!!!")); @@ -468,11 +448,7 @@ void ml_open_files(void) /// and the memfile will be in memory only (no recovery possible). void ml_open_file(buf_T *buf) { - memfile_T *mfp; - char_u *fname; - char *dirp; - - mfp = buf->b_ml.ml_mfp; + memfile_T *mfp = buf->b_ml.ml_mfp; if (mfp == NULL || mfp->mf_fd >= 0 || !buf->b_p_swf || (cmdmod.cmod_flags & CMOD_NOSWAPFILE) || buf->terminal) { @@ -481,7 +457,7 @@ void ml_open_file(buf_T *buf) // For a spell buffer use a temp file name. if (buf->b_spell) { - fname = vim_tempname(); + char *fname = vim_tempname(); if (fname != NULL) { (void)mf_open_file(mfp, fname); // consumes fname! } @@ -490,7 +466,7 @@ void ml_open_file(buf_T *buf) } // Try all directories in 'directory' option. - dirp = (char *)p_dir; + char *dirp = p_dir; bool found_existing_dir = false; for (;;) { if (*dirp == NUL) { @@ -499,7 +475,7 @@ void ml_open_file(buf_T *buf) // There is a small chance that between choosing the swap file name // and creating it, another Vim creates the file. In that case the // creation will fail and we will use another directory. - fname = (char_u *)findswapname(buf, &dirp, NULL, &found_existing_dir); + char *fname = findswapname(buf, &dirp, NULL, &found_existing_dir); if (dirp == NULL) { break; // out of memory } @@ -523,7 +499,7 @@ void ml_open_file(buf_T *buf) } if (*p_dir != NUL && mfp->mf_fname == NULL) { - need_wait_return = true; // call wait_return later + need_wait_return = true; // call wait_return() later no_wait_return++; (void)semsg(_("E303: Unable to open swap file for \"%s\", recovery impossible"), buf_spname(buf) != NULL ? buf_spname(buf) : buf->b_fname); @@ -552,7 +528,7 @@ void check_need_swap(bool newfile) /// Close memline for buffer 'buf'. /// -/// @param del_file if TRUE, delete the swap file +/// @param del_file if true, delete the swap file void ml_close(buf_T *buf, int del_file) { if (buf->b_ml.ml_mfp == NULL) { // not open @@ -590,7 +566,7 @@ void ml_close_notmod(void) { FOR_ALL_BUFFERS(buf) { if (!bufIsChanged(buf)) { - ml_close(buf, TRUE); // close all not-modified buffers + ml_close(buf, true); // close all not-modified buffers } } } @@ -616,21 +592,19 @@ static bool ml_check_b0_strings(ZERO_BL *b0p) return (memchr(b0p->b0_version, NUL, 10) && memchr(b0p->b0_uname, NUL, B0_UNAME_SIZE) && memchr(b0p->b0_hname, NUL, B0_HNAME_SIZE) - && memchr(b0p->b0_fname, NUL, B0_FNAME_SIZE_CRYPT)); // -V512 + && memchr(b0p->b0_fname, NUL, B0_FNAME_SIZE_CRYPT)); // -V1086 } /// Update the timestamp or the B0_SAME_DIR flag of the .swp file. static void ml_upd_block0(buf_T *buf, upd_block0_T what) { - memfile_T *mfp; bhdr_T *hp; - ZERO_BL *b0p; - mfp = buf->b_ml.ml_mfp; + memfile_T *mfp = buf->b_ml.ml_mfp; if (mfp == NULL || (hp = mf_get(mfp, 0, 1)) == NULL) { return; } - b0p = hp->bh_data; + ZERO_BL *b0p = hp->bh_data; if (ml_check_b0_id(b0p) == FAIL) { iemsg(_("E304: ml_upd_block0(): Didn't get block 0??")); } else { @@ -653,22 +627,20 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) } else { char uname[B0_UNAME_SIZE]; - /* - * For a file under the home directory of the current user, we try to - * replace the home directory path with "~user". This helps when - * editing the same file on different machines over a network. - * First replace home dir path with "~/" with home_replace(). - * Then insert the user name to get "~user/". - */ + // For a file under the home directory of the current user, we try to + // replace the home directory path with "~user". This helps when + // editing the same file on different machines over a network. + // First replace home dir path with "~/" with home_replace(). + // Then insert the user name to get "~user/". home_replace(NULL, buf->b_ffname, (char *)b0p->b0_fname, B0_FNAME_SIZE_CRYPT, true); if (b0p->b0_fname[0] == '~') { // If there is no user name or it is too long, don't use "~/" int retval = os_get_username(uname, B0_UNAME_SIZE); - size_t ulen = STRLEN(uname); - size_t flen = STRLEN(b0p->b0_fname); + size_t ulen = strlen(uname); + size_t flen = strlen(b0p->b0_fname); if (retval == FAIL || ulen + flen > B0_FNAME_SIZE_CRYPT - 1) { - STRLCPY(b0p->b0_fname, buf->b_ffname, B0_FNAME_SIZE_CRYPT); + xstrlcpy(b0p->b0_fname, buf->b_ffname, B0_FNAME_SIZE_CRYPT); } else { memmove(b0p->b0_fname + ulen + 1, b0p->b0_fname + 1, flen); memmove(b0p->b0_fname + 1, uname, ulen); @@ -703,30 +675,46 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) /// not set. static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf) { - if (same_directory(buf->b_ml.ml_mfp->mf_fname, (char_u *)buf->b_ffname)) { + if (same_directory(buf->b_ml.ml_mfp->mf_fname, buf->b_ffname)) { b0p->b0_flags |= B0_SAME_DIR; } else { - b0p->b0_flags &= (uint8_t) ~B0_SAME_DIR; + b0p->b0_flags = (char)(b0p->b0_flags & ~B0_SAME_DIR); } } /// When there is room, add the 'fileencoding' to block zero. static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) { - int n; - int size = B0_FNAME_SIZE_NOCRYPT; + const int size = B0_FNAME_SIZE_NOCRYPT; - n = (int)STRLEN(buf->b_p_fenc); - if ((int)STRLEN(b0p->b0_fname) + n + 1 > size) { - b0p->b0_flags &= (uint8_t) ~B0_HAS_FENC; + int n = (int)strlen(buf->b_p_fenc); + if ((int)strlen(b0p->b0_fname) + n + 1 > size) { + b0p->b0_flags = (char)(b0p->b0_flags & ~B0_HAS_FENC); } else { memmove((char *)b0p->b0_fname + size - n, - (char *)buf->b_p_fenc, (size_t)n); + buf->b_p_fenc, (size_t)n); *(b0p->b0_fname + size - n - 1) = NUL; b0p->b0_flags |= B0_HAS_FENC; } } +/// Return true if the process with number "b0p->b0_pid" is still running. +/// "swap_fname" is the name of the swap file, if it's from before a reboot then +/// the result is false; +static bool swapfile_process_running(const ZERO_BL *b0p, const char *swap_fname) +{ + FileInfo st; + double uptime; + // If the system rebooted after when the swap file was written then the + // process can't be running now. + if (os_fileinfo(swap_fname, &st) + && uv_uptime(&uptime) == 0 + && (Timestamp)st.stat.st_mtim.tv_sec < os_time() - (Timestamp)uptime) { + return false; + } + return os_proc_running((int)char_to_long(b0p->b0_pid)); +} + /// Try to recover curbuf from the .swp file. /// /// @param checkext if true, check the extension and detect whether it is a @@ -735,75 +723,60 @@ void ml_recover(bool checkext) { buf_T *buf = NULL; memfile_T *mfp = NULL; - char_u *fname; - char_u *fname_used = NULL; + char *fname_used = NULL; bhdr_T *hp = NULL; ZERO_BL *b0p; int b0_ff; - char_u *b0_fenc = NULL; + char *b0_fenc = NULL; PTR_BL *pp; DATA_BL *dp; infoptr_T *ip; - blocknr_T bnum; - int len; bool directly; - linenr_T lnum; - char_u *p; - int i; - long error; - bool cannot_open; - linenr_T line_count; - bool has_error; - int idx; - int top; - int txt_start; - off_T size; - int called_from_main; + char *p; bool serious_error = true; - long mtime; - int attr; int orig_file_status = NOTDONE; - recoverymode = TRUE; - called_from_main = (curbuf->b_ml.ml_mfp == NULL); - attr = HL_ATTR(HLF_E); + recoverymode = true; + int called_from_main = (curbuf->b_ml.ml_mfp == NULL); + int attr = HL_ATTR(HLF_E); // If the file name ends in ".s[a-w][a-z]" we assume this is the swap file. // Otherwise a search is done to find the swap file(s). - fname = (char_u *)curbuf->b_fname; + char *fname = curbuf->b_fname; if (fname == NULL) { // When there is no file name - fname = (char_u *)""; + fname = ""; } - len = (int)STRLEN(fname); + int len = (int)strlen(fname); if (checkext && len >= 4 && STRNICMP(fname + len - 4, ".s", 2) == 0 - && vim_strchr("abcdefghijklmnopqrstuvw", TOLOWER_ASC(fname[len - 2])) != NULL + && vim_strchr("abcdefghijklmnopqrstuvw", TOLOWER_ASC((uint8_t)fname[len - 2])) != NULL && ASCII_ISALPHA(fname[len - 1])) { directly = true; - fname_used = vim_strsave(fname); // make a copy for mf_open() + fname_used = xstrdup(fname); // make a copy for mf_open() } else { directly = false; // count the number of matching swap files - len = recover_names(fname, FALSE, 0, NULL); + len = recover_names(fname, false, 0, NULL); if (len == 0) { // no swap files found semsg(_("E305: No swap file found for %s"), fname); goto theend; } + int i; if (len == 1) { // one swap file found, use it i = 1; } else { // several swap files found, choose // list the names of the swap files - (void)recover_names(fname, TRUE, 0, NULL); + (void)recover_names(fname, true, 0, NULL); msg_putchar('\n'); msg_puts(_("Enter number of swap file to use (0 to quit): ")); - i = get_number(FALSE, NULL); + i = get_number(false, NULL); if (i < 1 || i > len) { goto theend; } } // get the swap file name that will be used - (void)recover_names(fname, FALSE, i, &fname_used); + (void)recover_names(fname, false, i, &fname_used); } if (fname_used == NULL) { goto theend; // user chose invalid number. @@ -813,15 +786,11 @@ void ml_recover(bool checkext) getout(1); } - /* - * Allocate a buffer structure for the swap file that is used for recovery. - * Only the memline in it is really used. - */ + // Allocate a buffer structure for the swap file that is used for recovery. + // Only the memline in it is really used. buf = xmalloc(sizeof(buf_T)); - /* - * init fields in memline struct - */ + // init fields in memline struct buf->b_ml.ml_stack_size = 0; // no stack yet buf->b_ml.ml_stack = NULL; // no stack yet buf->b_ml.ml_stack_top = 0; // nothing in the stack @@ -830,11 +799,9 @@ void ml_recover(bool checkext) buf->b_ml.ml_locked = NULL; // no locked block buf->b_ml.ml_flags = 0; - /* - * open the memfile from the old swap file - */ - p = vim_strsave(fname_used); // save "fname_used" for the message: - // mf_open() will consume "fname_used"! + // open the memfile from the old swap file + p = xstrdup(fname_used); // save "fname_used" for the message: + // mf_open() will consume "fname_used"! mfp = mf_open(fname_used, O_RDONLY); fname_used = p; if (mfp == NULL || mfp->mf_fd < 0) { @@ -843,17 +810,13 @@ void ml_recover(bool checkext) } buf->b_ml.ml_mfp = mfp; - /* - * The page size set in mf_open() might be different from the page size - * used in the swap file, we must get it from block 0. But to read block - * 0 we need a page size. Use the minimal size for block 0 here, it will - * be set to the real value below. - */ + // The page size set in mf_open() might be different from the page size + // used in the swap file, we must get it from block 0. But to read block + // 0 we need a page size. Use the minimal size for block 0 here, it will + // be set to the real value below. mfp->mf_page_size = MIN_SWAP_PAGE_SIZE; - /* - * try to read block 0 - */ + // try to read block 0 if ((hp = mf_get(mfp, 0, 1)) == NULL) { msg_start(); msg_puts_attr(_("Unable to read block 0 from "), attr | MSG_HIST); @@ -864,7 +827,7 @@ void ml_recover(bool checkext) goto theend; } b0p = hp->bh_data; - if (STRNCMP(b0p->b0_version, "VIM 3.0", 7) == 0) { + if (strncmp(b0p->b0_version, "VIM 3.0", 7) == 0) { msg_start(); msg_outtrans_attr(mfp->mf_fname, MSG_HIST); msg_puts_attr(_(" cannot be used with this version of Vim.\n"), @@ -891,10 +854,8 @@ void ml_recover(bool checkext) goto theend; } - /* - * If we guessed the wrong page size, we have to recalculate the - * highest block number in the file. - */ + // If we guessed the wrong page size, we have to recalculate the + // highest block number in the file. if (mfp->mf_page_size != (unsigned)char_to_long(b0p->b0_page_size)) { unsigned previous_page_size = mfp->mf_page_size; @@ -907,6 +868,7 @@ void ml_recover(bool checkext) msg_end(); goto theend; } + off_T size; if ((size = vim_lseek(mfp->mf_fd, (off_T)0L, SEEK_END)) <= 0) { mfp->mf_blocknr_max = 0; // no file or empty file } else { @@ -922,36 +884,32 @@ void ml_recover(bool checkext) b0p = hp->bh_data; } - /* - * If .swp file name given directly, use name from swap file for buffer. - */ + // If .swp file name given directly, use name from swap file for buffer. if (directly) { - expand_env(b0p->b0_fname, NameBuff, MAXPATHL); - if (setfname(curbuf, (char *)NameBuff, NULL, true) == FAIL) { + expand_env((char *)b0p->b0_fname, NameBuff, MAXPATHL); + if (setfname(curbuf, NameBuff, NULL, true) == FAIL) { goto theend; } } - home_replace(NULL, (char *)mfp->mf_fname, (char *)NameBuff, MAXPATHL, true); + home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL, true); smsg(_("Using swap file \"%s\""), NameBuff); if (buf_spname(curbuf) != NULL) { - STRLCPY(NameBuff, buf_spname(curbuf), MAXPATHL); + xstrlcpy(NameBuff, buf_spname(curbuf), MAXPATHL); } else { - home_replace(NULL, curbuf->b_ffname, (char *)NameBuff, MAXPATHL, true); + home_replace(NULL, curbuf->b_ffname, NameBuff, MAXPATHL, true); } smsg(_("Original file \"%s\""), NameBuff); msg_putchar('\n'); - /* - * check date of swap file and original file - */ + // check date of swap file and original file FileInfo org_file_info; FileInfo swp_file_info; - mtime = char_to_long(b0p->b0_mtime); + long mtime = char_to_long(b0p->b0_mtime); if (curbuf->b_ffname != NULL && os_fileinfo(curbuf->b_ffname, &org_file_info) - && ((os_fileinfo((char *)mfp->mf_fname, &swp_file_info) + && ((os_fileinfo(mfp->mf_fname, &swp_file_info) && org_file_info.stat.st_mtim.tv_sec > swp_file_info.stat.st_mtim.tv_sec) || org_file_info.stat.st_mtim.tv_sec != mtime)) { @@ -964,25 +922,21 @@ void ml_recover(bool checkext) if (b0p->b0_flags & B0_HAS_FENC) { int fnsize = B0_FNAME_SIZE_NOCRYPT; - for (p = b0p->b0_fname + fnsize; p > b0p->b0_fname && p[-1] != NUL; p--) {} - b0_fenc = vim_strnsave(p, (size_t)(b0p->b0_fname + fnsize - p)); + for (p = (char *)b0p->b0_fname + fnsize; p > b0p->b0_fname && p[-1] != NUL; p--) {} + b0_fenc = xstrnsave(p, (size_t)(b0p->b0_fname + fnsize - p)); } mf_put(mfp, hp, false, false); // release block 0 hp = NULL; - /* - * Now that we are sure that the file is going to be recovered, clear the - * contents of the current buffer. - */ + // Now that we are sure that the file is going to be recovered, clear the + // contents of the current buffer. while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) { ml_delete((linenr_T)1, false); } - /* - * Try reading the original file to obtain the values of 'fileformat', - * 'fileencoding', etc. Ignore errors. The text itself is not used. - */ + // Try reading the original file to obtain the values of 'fileformat', + // 'fileencoding', etc. Ignore errors. The text itself is not used. if (curbuf->b_ffname != NULL) { orig_file_status = readfile(curbuf->b_ffname, NULL, (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, NULL, READ_NEW, false); @@ -993,35 +947,29 @@ void ml_recover(bool checkext) set_fileformat(b0_ff - 1, OPT_LOCAL); } if (b0_fenc != NULL) { - set_option_value("fenc", 0L, (char *)b0_fenc, OPT_LOCAL); + set_option_value_give_err("fenc", 0L, b0_fenc, OPT_LOCAL); xfree(b0_fenc); } unchanged(curbuf, true, true); - bnum = 1; // start with block 1 + blocknr_T bnum = 1; // start with block 1 unsigned page_count = 1; // which is 1 page - lnum = 0; // append after line 0 in curbuf - line_count = 0; - idx = 0; // start with first index in block 1 - error = 0; + linenr_T lnum = 0; // append after line 0 in curbuf + linenr_T line_count = 0; + int idx = 0; // start with first index in block 1 + long error = 0; buf->b_ml.ml_stack_top = 0; // -V1048 buf->b_ml.ml_stack = NULL; buf->b_ml.ml_stack_size = 0; // -V1048 - if (curbuf->b_ffname == NULL) { - cannot_open = true; - } else { - cannot_open = false; - } + bool cannot_open = (curbuf->b_ffname == NULL); serious_error = false; for (; !got_int; line_breakcheck()) { if (hp != NULL) { mf_put(mfp, hp, false, false); // release previous block } - /* - * get block - */ + // get block if ((hp = mf_get(mfp, bnum, page_count)) == NULL) { if (bnum == 1) { semsg(_("E309: Unable to read block 1 from %s"), mfp->mf_fname); @@ -1035,7 +983,7 @@ void ml_recover(bool checkext) if (pp->pb_id == PTR_ID) { // it is a pointer block // check line count when using pointer block first time if (idx == 0 && line_count != 0) { - for (i = 0; i < (int)pp->pb_count; ++i) { + for (int i = 0; i < (int)pp->pb_count; i++) { line_count -= pp->pb_pointer[i].pe_line_count; } if (line_count != 0) { @@ -1051,11 +999,9 @@ void ml_recover(bool checkext) error++; } else if (idx < (int)pp->pb_count) { // go a block deeper if (pp->pb_pointer[idx].pe_bnum < 0) { - /* - * Data block with negative block number. - * Try to read lines from the original file. - * This is slow, but it works. - */ + // Data block with negative block number. + // Try to read lines from the original file. + // This is slow, but it works. if (!cannot_open) { line_count = pp->pb_pointer[idx].pe_line_count; if (readfile(curbuf->b_ffname, NULL, lnum, @@ -1071,14 +1017,12 @@ void ml_recover(bool checkext) ml_append(lnum++, _("???LINES MISSING"), (colnr_T)0, true); } - ++idx; // get same block again for next index + idx++; // get same block again for next index continue; } - /* - * going one block deeper in the tree - */ - top = ml_add_stack(buf); // new entry in stack + // going one block deeper in the tree + int top = ml_add_stack(buf); // new entry in stack ip = &(buf->b_ml.ml_stack[top]); ip->ip_bnum = bnum; ip->ip_index = idx; @@ -1103,7 +1047,7 @@ void ml_recover(bool checkext) } else { // it is a data block // Append all the lines in this block - has_error = false; + bool has_error = false; // check length of block // if wrong, use length in pointer block if (page_count * mfp->mf_page_size != dp->db_txt_end) { @@ -1118,10 +1062,8 @@ void ml_recover(bool checkext) // make sure there is a NUL at the end of the block *((char_u *)dp + dp->db_txt_end - 1) = NUL; - /* - * check number of lines in block - * if wrong, use count in data block - */ + // check number of lines in block + // if wrong, use count in data block if (line_count != dp->db_line_count) { ml_append(lnum++, _("??? from here until ???END lines" @@ -1131,16 +1073,16 @@ void ml_recover(bool checkext) has_error = true; } - for (i = 0; i < dp->db_line_count; ++i) { - txt_start = (dp->db_index[i] & DB_INDEX_MASK); + for (int i = 0; i < dp->db_line_count; i++) { + int txt_start = (dp->db_index[i] & DB_INDEX_MASK); if (txt_start <= (int)HEADER_SIZE || txt_start >= (int)dp->db_txt_end) { - p = (char_u *)"???"; + p = "???"; error++; } else { - p = (char_u *)dp + txt_start; + p = (char *)dp + txt_start; } - ml_append(lnum++, (char *)p, (colnr_T)0, true); + ml_append(lnum++, p, (colnr_T)0, true); } if (has_error) { ml_append(lnum++, _("???END"), (colnr_T)0, true); @@ -1153,22 +1095,18 @@ void ml_recover(bool checkext) break; } - /* - * go one block up in the tree - */ + // go one block up in the tree ip = &(buf->b_ml.ml_stack[--(buf->b_ml.ml_stack_top)]); bnum = ip->ip_bnum; idx = ip->ip_index + 1; // go to next index page_count = 1; } - /* - * Compare the buffer contents with the original file. When they differ - * set the 'modified' flag. - * Lines 1 - lnum are the new contents. - * Lines lnum + 1 to ml_line_count are the original contents. - * Line ml_line_count + 1 in the dummy empty line. - */ + // Compare the buffer contents with the original file. When they differ + // set the 'modified' flag. + // Lines 1 - lnum are the new contents. + // Lines lnum + 1 to ml_line_count are the original contents. + // Line ml_line_count + 1 in the dummy empty line. if (orig_file_status != OK || curbuf->b_ml.ml_line_count != lnum * 2 + 1) { // Recovering an empty file results in two lines and the first line is // empty. Don't set the modified flag then. @@ -1177,10 +1115,10 @@ void ml_recover(bool checkext) buf_inc_changedtick(curbuf); } } else { - for (idx = 1; idx <= lnum; ++idx) { + for (idx = 1; idx <= lnum; idx++) { // Need to copy one line, fetching the other one may flush it. - p = vim_strsave(ml_get(idx)); - i = STRCMP(p, ml_get(idx + lnum)); + p = xstrdup(ml_get(idx)); + int i = strcmp(p, ml_get(idx + lnum)); xfree(p); if (i != 0) { changed_internal(); @@ -1190,10 +1128,8 @@ void ml_recover(bool checkext) } } - /* - * Delete the lines from the original file and the dummy line from the - * empty buffer. These will now be after the last line in the buffer. - */ + // Delete the lines from the original file and the dummy line from the + // empty buffer. These will now be after the last line in the buffer. while (curbuf->b_ml.ml_line_count > lnum && !(curbuf->b_ml.ml_flags & ML_EMPTY)) { ml_delete(curbuf->b_ml.ml_line_count, false); @@ -1201,7 +1137,7 @@ void ml_recover(bool checkext) curbuf->b_flags |= BF_RECOVERED; check_cursor(); - recoverymode = FALSE; + recoverymode = false; if (got_int) { emsg(_("E311: Recovery Interrupted")); } else if (error) { @@ -1219,26 +1155,33 @@ void ml_recover(bool checkext) } else { msg(_("Recovery completed. Buffer contents equals file contents.")); } - msg_puts(_("\nYou may want to delete the .swp file now.\n\n")); + msg_puts(_("\nYou may want to delete the .swp file now.")); + if (swapfile_process_running(b0p, fname_used)) { + // Warn there could be an active Vim on the same file, the user may + // want to kill it. + msg_puts(_("\nNote: process STILL RUNNING: ")); + msg_outnum(char_to_long(b0p->b0_pid)); + } + msg_puts("\n\n"); cmdline_row = msg_row; } - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); theend: xfree(fname_used); - recoverymode = FALSE; + recoverymode = false; if (mfp != NULL) { if (hp != NULL) { mf_put(mfp, hp, false, false); } mf_close(mfp, false); // will also xfree(mfp->mf_fname) } - if (buf != NULL) { //may be NULL if swap file not found. + if (buf != NULL) { // may be NULL if swap file not found. xfree(buf->b_ml.ml_stack); xfree(buf); } if (serious_error && called_from_main) { - ml_close(curbuf, TRUE); + ml_close(curbuf, true); } else { apply_autocmds(EVENT_BUFREADPOST, NULL, curbuf->b_fname, false, curbuf); apply_autocmds(EVENT_BUFWINENTER, NULL, curbuf->b_fname, false, curbuf); @@ -1255,23 +1198,20 @@ theend: /// - find the name of the n'th swap file when recovering /// /// @param fname base for swap file name -/// @param list when TRUE, list the swap file names +/// @param list when true, list the swap file names /// @param nr when non-zero, return nr'th swap file name /// @param fname_out result when "nr" > 0 -int recover_names(char_u *fname, int list, int nr, char_u **fname_out) +int recover_names(char *fname, int list, int nr, char **fname_out) { int num_names; char *(names[6]); - char_u *tail; - char_u *p; - int num_files; + char *tail; + char *p; int file_count = 0; char **files; - char *dirp; - char_u *dir_name; - char_u *fname_res = NULL; + char *fname_res = NULL; #ifdef HAVE_READLINK - char_u fname_buf[MAXPATHL]; + char fname_buf[MAXPATHL]; #endif if (fname != NULL) { @@ -1293,13 +1233,13 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) // Do the loop for every directory in 'directory'. // First allocate some memory to put the directory name in. - dir_name = xmalloc(STRLEN(p_dir) + 1); - dirp = (char *)p_dir; + char *dir_name = xmalloc(strlen(p_dir) + 1); + char *dirp = p_dir; while (*dirp) { // Isolate a directory name from *dirp and put it in dir_name (we know // it is large enough, so use 31000 for length). // Advance dirp to next directory name. - (void)copy_option_part(&dirp, (char *)dir_name, 31000, ","); + (void)copy_option_part(&dirp, dir_name, 31000, ","); if (dir_name[0] == '.' && dir_name[1] == NUL) { // check current dir if (fname == NULL) { @@ -1310,34 +1250,34 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) names[2] = xstrdup(".sw?"); num_names = 3; } else { - num_names = recov_file_names(names, fname_res, TRUE); + num_names = recov_file_names(names, fname_res, true); } } else { // check directory dir_name if (fname == NULL) { - names[0] = concat_fnames((char *)dir_name, "*.sw?", true); + names[0] = concat_fnames(dir_name, "*.sw?", true); // For Unix names starting with a dot are special. MS-Windows // supports this too, on some file systems. - names[1] = concat_fnames((char *)dir_name, ".*.sw?", true); - names[2] = concat_fnames((char *)dir_name, ".sw?", true); + names[1] = concat_fnames(dir_name, ".*.sw?", true); + names[2] = concat_fnames(dir_name, ".sw?", true); num_names = 3; } else { - int len = (int)STRLEN(dir_name); + int len = (int)strlen(dir_name); p = dir_name + len; - if (after_pathsep((char *)dir_name, (char *)p) + if (after_pathsep(dir_name, p) && len > 1 && p[-1] == p[-2]) { // Ends with '//', Use Full path for swap name - tail = (char_u *)make_percent_swname((char *)dir_name, - (char *)fname_res); + tail = make_percent_swname(dir_name, fname_res); } else { - tail = (char_u *)path_tail((char *)fname_res); - tail = (char_u *)concat_fnames((char *)dir_name, (char *)tail, true); + tail = path_tail(fname_res); + tail = concat_fnames(dir_name, tail, true); } - num_names = recov_file_names(names, tail, FALSE); + num_names = recov_file_names(names, tail, false); xfree(tail); } } + int num_files; if (num_names == 0) { num_files = 0; } else if (expand_wildcards(num_names, names, &num_files, &files, @@ -1345,17 +1285,15 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) num_files = 0; } - /* - * When no swap file found, wildcard expansion might have failed (e.g. - * not able to execute the shell). - * Try finding a swap file by simply adding ".swp" to the file name. - */ + // When no swap file found, wildcard expansion might have failed (e.g. + // not able to execute the shell). + // Try finding a swap file by simply adding ".swp" to the file name. if (*dirp == NUL && file_count + num_files == 0 && fname != NULL) { - char_u *swapname = (char_u *)modname((char *)fname_res, ".swp", true); + char *swapname = modname(fname_res, ".swp", true); if (swapname != NULL) { if (os_path_exists(swapname)) { - files = xmalloc(sizeof(char_u *)); - files[0] = (char *)swapname; + files = xmalloc(sizeof(char *)); + files[0] = swapname; swapname = NULL; num_files = 1; } @@ -1363,15 +1301,13 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) } } - /* - * remove swapfile name of the current buffer, it must be ignored - */ + // remove swapfile name of the current buffer, it must be ignored if (curbuf->b_ml.ml_mfp != NULL && (p = curbuf->b_ml.ml_mfp->mf_fname) != NULL) { for (int i = 0; i < num_files; i++) { // Do not expand wildcards, on Windows would try to expand // "%tmp%" in "%tmp%file" - if (path_full_compare((char *)p, files[i], true, false) & kEqualFiles) { + if (path_full_compare(p, files[i], true, false) & kEqualFiles) { // Remove the name from files[i]. Move further entries // down. When the array becomes empty free it here, since // FreeWild() won't be called below. @@ -1379,7 +1315,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) if (--num_files == 0) { xfree(files); } else { - for (; i < num_files; ++i) { + for (; i < num_files; i++) { files[i] = files[i + 1]; } } @@ -1389,7 +1325,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) if (nr > 0) { file_count += num_files; if (nr <= file_count) { - *fname_out = vim_strsave((char_u *)files[nr - 1 + num_files - file_count]); + *fname_out = xstrdup(files[nr - 1 + num_files - file_count]); dirp = ""; // stop searching } } else if (list) { @@ -1406,13 +1342,13 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) } if (num_files) { - for (int i = 0; i < num_files; ++i) { + for (int i = 0; i < num_files; i++) { // print the swap file name msg_outnum((long)++file_count); msg_puts(". "); msg_puts((const char *)path_tail(files[i])); msg_putchar('\n'); - (void)swapfile_info((char_u *)files[i]); + (void)swapfile_info(files[i]); } } else { msg_puts(_(" -- none --\n")); @@ -1422,7 +1358,7 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) file_count += num_files; } - for (int i = 0; i < num_names; ++i) { + for (int i = 0; i < num_names; i++) { xfree(names[i]); } if (num_files > 0) { @@ -1440,17 +1376,19 @@ char *make_percent_swname(const char *dir, const char *name) { char *d = NULL; char *f = fix_fname(name != NULL ? name : ""); - if (f != NULL) { - char *s = xstrdup(f); - for (d = s; *d != NUL; MB_PTR_ADV(d)) { - if (vim_ispathsep(*d)) { - *d = '%'; - } + if (f == NULL) { + return NULL; + } + + char *s = xstrdup(f); + for (d = s; *d != NUL; MB_PTR_ADV(d)) { + if (vim_ispathsep(*d)) { + *d = '%'; } - d = concat_fnames(dir, s, TRUE); - xfree(s); - xfree(f); } + d = concat_fnames(dir, s, true); + xfree(s); + xfree(f); return d; } @@ -1472,7 +1410,7 @@ void get_b0_dict(const char *fname, dict_T *d) tv_dict_add_str(d, S_LEN("error"), "Magic number mismatch"); } else { // We have swap information. - tv_dict_add_str_len(d, S_LEN("version"), (char *)b0.b0_version, 10); + tv_dict_add_str_len(d, S_LEN("version"), b0.b0_version, 10); tv_dict_add_str_len(d, S_LEN("user"), (char *)b0.b0_uname, B0_UNAME_SIZE); tv_dict_add_str_len(d, S_LEN("host"), (char *)b0.b0_hname, @@ -1497,7 +1435,7 @@ void get_b0_dict(const char *fname, dict_T *d) /// Give information about an existing swap file. /// /// @return timestamp (0 when unknown). -static time_t swapfile_info(char_u *fname) +static time_t swapfile_info(char *fname) { assert(fname != NULL); int fd; @@ -1509,28 +1447,29 @@ static time_t swapfile_info(char_u *fname) // print the swap file date FileInfo file_info; - if (os_fileinfo((char *)fname, &file_info)) { + if (os_fileinfo(fname, &file_info)) { #ifdef UNIX // print name of owner of the file if (os_get_uname((uv_uid_t)file_info.stat.st_uid, uname, B0_UNAME_SIZE) == OK) { msg_puts(_(" owned by: ")); msg_outtrans(uname); msg_puts(_(" dated: ")); - } else -#endif + } else { + msg_puts(_(" dated: ")); + } +#else msg_puts(_(" dated: ")); +#endif x = file_info.stat.st_mtim.tv_sec; - char ctime_buf[50]; - msg_puts(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf))); + char ctime_buf[100]; // hopefully enough for every language + msg_puts(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf), true)); } - /* - * print the original file name - */ - fd = os_open((char *)fname, O_RDONLY, 0); + // print the original file name + fd = os_open(fname, O_RDONLY, 0); if (fd >= 0) { if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) { - if (STRNCMP(b0.b0_version, "VIM 3.0", 7) == 0) { + if (strncmp(b0.b0_version, "VIM 3.0", 7) == 0) { msg_puts(_(" [from Vim version 3.0]")); } else if (ml_check_b0_id(&b0) == FAIL) { msg_puts(_(" [does not look like a Vim swap file]")); @@ -1564,7 +1503,7 @@ static time_t swapfile_info(char_u *fname) if (char_to_long(b0.b0_pid) != 0L) { msg_puts(_("\n process ID: ")); msg_outnum(char_to_long(b0.b0_pid)); - if (os_proc_running((int)char_to_long(b0.b0_pid))) { + if (swapfile_process_running(&b0, (const char *)fname)) { msg_puts(_(" (STILL RUNNING)")); process_still_running = true; } @@ -1591,10 +1530,9 @@ static time_t swapfile_info(char_u *fname) static bool swapfile_unchanged(char *fname) { struct block0 b0; - int ret = true; // Swap file must exist. - if (!os_path_exists((char_u *)fname)) { + if (!os_path_exists(fname)) { return false; } @@ -1608,6 +1546,8 @@ static bool swapfile_unchanged(char *fname) return false; } + bool ret = true; + // the ID and magic number must be correct if (ml_check_b0_id(&b0) == FAIL || b0_magic_wrong(&b0)) { ret = false; @@ -1618,20 +1558,33 @@ static bool swapfile_unchanged(char *fname) ret = false; } + // Host name must be known and must equal the current host name, otherwise + // comparing pid is meaningless. + if (*(b0.b0_hname) == NUL) { + ret = false; + } else { + char hostname[B0_HNAME_SIZE]; + os_get_hostname(hostname, B0_HNAME_SIZE); + hostname[B0_HNAME_SIZE - 1] = NUL; + b0.b0_hname[B0_HNAME_SIZE - 1] = NUL; // in case of corruption + if (STRICMP(b0.b0_hname, hostname) != 0) { + ret = false; + } + } + // process must be known and not running. - long pid = char_to_long(b0.b0_pid); - if (pid == 0L || os_proc_running((int)pid)) { + if (char_to_long(b0.b0_pid) == 0L || swapfile_process_running(&b0, fname)) { ret = false; } - // TODO(bram): Should we check if the swap file was created on the current - // system? And the current user? + // We do not check the user, it should be irrelevant for whether the swap + // file is still useful. close(fd); return ret; } -static int recov_file_names(char **names, char_u *path, int prepend_dot) +static int recov_file_names(char **names, char *path, int prepend_dot) FUNC_ATTR_NONNULL_ALL { int num_names = 0; @@ -1639,7 +1592,7 @@ static int recov_file_names(char **names, char_u *path, int prepend_dot) // May also add the file name with a dot prepended, for swap file in same // dir as original file. if (prepend_dot) { - names[num_names] = modname((char *)path, ".sw?", true); + names[num_names] = modname(path, ".sw?", true); if (names[num_names] == NULL) { return num_names; } @@ -1647,14 +1600,14 @@ static int recov_file_names(char **names, char_u *path, int prepend_dot) } // Form the normal swap file name pattern by appending ".sw?". - names[num_names] = concat_fnames((char *)path, ".sw?", false); + names[num_names] = concat_fnames(path, ".sw?", false); if (num_names >= 1) { // check if we have the same name twice - char_u *p = (char_u *)names[num_names - 1]; - int i = (int)STRLEN(names[num_names - 1]) - (int)STRLEN(names[num_names]); + char *p = names[num_names - 1]; + int i = (int)strlen(names[num_names - 1]) - (int)strlen(names[num_names]); if (i > 0) { p += i; // file name has been expanded to full path } - if (STRCMP(p, names[num_names]) != 0) { + if (strcmp(p, names[num_names]) != 0) { num_names++; } else { xfree(names[num_names]); @@ -1668,8 +1621,8 @@ static int recov_file_names(char **names, char_u *path, int prepend_dot) /// sync all memlines /// -/// @param check_file if TRUE, check if original file exists and was not changed. -/// @param check_char if TRUE, stop syncing when character becomes available, but +/// @param check_file if true, check if original file exists and was not changed. +/// @param check_char if true, stop syncing when character becomes available, but /// /// always sync at least one block. void ml_sync_all(int check_file, int check_char, bool do_fsync) @@ -1683,10 +1636,8 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync) (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); if (bufIsChanged(buf) && check_file && mf_need_trans(buf->b_ml.ml_mfp) && buf->b_ffname != NULL) { - /* - * If the original file does not exist anymore or has been changed - * call ml_preserve() to get rid of all negative numbered blocks. - */ + // If the original file does not exist anymore or has been changed + // call ml_preserve() to get rid of all negative numbered blocks. FileInfo file_info; if (!os_fileinfo(buf->b_ffname, &file_info) || file_info.stat.st_mtim.tv_sec != buf->b_mtime_read @@ -1714,13 +1665,10 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync) /// Used for the :preserve command and when the original file has been /// changed or deleted. /// -/// @param message if TRUE, the success of preserving is reported. +/// @param message if true, the success of preserving is reported. void ml_preserve(buf_T *buf, int message, bool do_fsync) { - bhdr_T *hp; - linenr_T lnum; memfile_T *mfp = buf->b_ml.ml_mfp; - int status; int got_int_save = got_int; if (mfp == NULL || mfp->mf_fname == NULL) { @@ -1736,27 +1684,25 @@ void ml_preserve(buf_T *buf, int message, bool do_fsync) ml_flush_line(buf); // flush buffered line (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block - status = mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0)); + int status = mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0)); // stack is invalid after mf_sync(.., MFS_ALL) buf->b_ml.ml_stack_top = 0; - /* - * Some of the data blocks may have been changed from negative to - * positive block number. In that case the pointer blocks need to be - * updated. - * - * We don't know in which pointer block the references are, so we visit - * all data blocks until there are no more translations to be done (or - * we hit the end of the file, which can only happen in case a write fails, - * e.g. when file system if full). - * ml_find_line() does the work by translating the negative block numbers - * when getting the first line of each data block. - */ + // Some of the data blocks may have been changed from negative to + // positive block number. In that case the pointer blocks need to be + // updated. + // + // We don't know in which pointer block the references are, so we visit + // all data blocks until there are no more translations to be done (or + // we hit the end of the file, which can only happen in case a write fails, + // e.g. when file system if full). + // ml_find_line() does the work by translating the negative block numbers + // when getting the first line of each data block. if (mf_need_trans(mfp) && !got_int) { - lnum = 1; + linenr_T lnum = 1; while (mf_need_trans(mfp) && lnum <= buf->b_ml.ml_line_count) { - hp = ml_find_line(buf, lnum, ML_FIND); + bhdr_T *hp = ml_find_line(buf, lnum, ML_FIND); if (hp == NULL) { status = FAIL; goto theend; @@ -1783,25 +1729,23 @@ theend: } } -/* - * NOTE: The pointer returned by the ml_get_*() functions only remains valid - * until the next call! - * line1 = ml_get(1); - * line2 = ml_get(2); // line1 is now invalid! - * Make a copy of the line if necessary. - */ +// NOTE: The pointer returned by the ml_get_*() functions only remains valid +// until the next call! +// line1 = ml_get(1); +// line2 = ml_get(2); // line1 is now invalid! +// Make a copy of the line if necessary. /// @return a pointer to a (read-only copy of a) line. /// /// On failure an error message is given and IObuff is returned (to avoid /// having to check for error everywhere). -char_u *ml_get(linenr_T lnum) +char *ml_get(linenr_T lnum) { return ml_get_buf(curbuf, lnum, false); } /// @return pointer to position "pos". -char_u *ml_get_pos(const pos_T *pos) +char *ml_get_pos(const pos_T *pos) FUNC_ATTR_NONNULL_ALL { return ml_get_buf(curbuf, pos->lnum, false) + pos->col; @@ -1815,20 +1759,17 @@ int gchar_pos(pos_T *pos) if (pos->col == MAXCOL) { return NUL; } - return utf_ptr2char((char *)ml_get_pos(pos)); + return utf_ptr2char(ml_get_pos(pos)); } /// @param will_change true mark the buffer dirty (chars in the line will be changed) /// /// @return a pointer to a line in a specific buffer -char_u *ml_get_buf(buf_T *buf, linenr_T lnum, bool will_change) +char *ml_get_buf(buf_T *buf, linenr_T lnum, bool will_change) FUNC_ATTR_NONNULL_ALL { - bhdr_T *hp; - DATA_BL *dp; - char_u *ptr; static int recursive = 0; - static char_u questions[4]; + static char questions[4]; if (lnum > buf->b_ml.ml_line_count) { // invalid line number if (recursive == 0) { @@ -1850,23 +1791,20 @@ errorret: } if (buf->b_ml.ml_mfp == NULL) { // there are no lines - return (char_u *)""; + return ""; } - /* - * See if it is the same line as requested last time. - * Otherwise may need to flush last used line. - * Don't use the last used line when 'swapfile' is reset, need to load all - * blocks. - */ + // See if it is the same line as requested last time. + // Otherwise may need to flush last used line. + // Don't use the last used line when 'swapfile' is reset, need to load all + // blocks. if (buf->b_ml.ml_line_lnum != lnum) { ml_flush_line(buf); - /* - * Find the data block containing the line. - * This also fills the stack with the blocks from the root to the data - * block and releases any locked block. - */ + // Find the data block containing the line. + // This also fills the stack with the blocks from the root to the data + // block and releases any locked block. + bhdr_T *hp; if ((hp = ml_find_line(buf, lnum, ML_FIND)) == NULL) { if (recursive == 0) { // Avoid giving this message for a recursive call, may happen @@ -1881,10 +1819,9 @@ errorret: goto errorret; } - dp = hp->bh_data; + DATA_BL *dp = hp->bh_data; - ptr = (char_u *)dp + - ((dp->db_index[lnum - buf->b_ml.ml_locked_low]) & DB_INDEX_MASK); + char *ptr = (char *)dp + (dp->db_index[lnum - buf->b_ml.ml_locked_low] & DB_INDEX_MASK); buf->b_ml.ml_line_ptr = ptr; buf->b_ml.ml_line_lnum = lnum; buf->b_ml.ml_flags &= ~ML_LINE_DIRTY; @@ -1908,7 +1845,7 @@ int ml_line_alloced(void) /// "line" does not need to be allocated, but can't be another line in a /// buffer, unlocking may make it invalid. /// -/// newfile: TRUE when starting to edit a new file, meaning that pe_old_lnum +/// newfile: true when starting to edit a new file, meaning that pe_old_lnum /// will be set for recovery /// Check: The caller of this function should probably also call /// appended_lines(). @@ -1922,14 +1859,14 @@ int ml_line_alloced(void) int ml_append(linenr_T lnum, char *line, colnr_T len, bool newfile) { // When starting up, we might still need to create the memfile - if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) { + if (curbuf->b_ml.ml_mfp == NULL && open_buffer(false, NULL, 0) == FAIL) { return FAIL; } if (curbuf->b_ml.ml_line_lnum != 0) { ml_flush_line(curbuf); } - return ml_append_int(curbuf, lnum, (char_u *)line, len, newfile, false); + return ml_append_int(curbuf, lnum, line, len, newfile, false); } /// Like ml_append() but for an arbitrary buffer. The buffer must already have @@ -1939,7 +1876,7 @@ int ml_append(linenr_T lnum, char *line, colnr_T len, bool newfile) /// @param line text of the new line /// @param len length of new line, including NUL, or 0 /// @param newfile flag, see above -int ml_append_buf(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, bool newfile) +int ml_append_buf(buf_T *buf, linenr_T lnum, char *line, colnr_T len, bool newfile) FUNC_ATTR_NONNULL_ARG(1) { if (buf->b_ml.ml_mfp == NULL) { @@ -1949,7 +1886,7 @@ int ml_append_buf(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, bool new if (buf->b_ml.ml_line_lnum != 0) { ml_flush_line(buf); } - return ml_append_int(buf, lnum, line, len, newfile, FALSE); + return ml_append_int(buf, lnum, line, len, newfile, false); } /// @param lnum append after this line (can be 0) @@ -1957,20 +1894,8 @@ int ml_append_buf(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, bool new /// @param len length of line, including NUL, or 0 /// @param newfile flag, see above /// @param mark mark the new line -static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, bool newfile, - int mark) +static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, bool newfile, int mark) { - int i; - int line_count; // number of indexes in current block - int offset; - int from, to; - int page_count; - int db_idx; // index for lnum in data block - bhdr_T *hp; - DATA_BL *dp; - PTR_BL *pp; - infoptr_T *ip; - // lnum out of range if (lnum > buf->b_ml.ml_line_count || buf->b_ml.ml_mfp == NULL) { return FAIL; @@ -1981,18 +1906,17 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b } if (len == 0) { - len = (colnr_T)STRLEN(line) + 1; // space needed for the text + len = (colnr_T)strlen(line) + 1; // space needed for the text } int space_needed = len + (int)INDEX_SIZE; // space needed for text + index memfile_T *mfp = buf->b_ml.ml_mfp; int page_size = (int)mfp->mf_page_size; - /* - * find the data block containing the previous line - * This also fills the stack with the blocks from the root to the data block - * This also releases any locked block. - */ + // find the data block containing the previous line + // This also fills the stack with the blocks from the root to the data block + // This also releases any locked block. + bhdr_T *hp; if ((hp = ml_find_line(buf, lnum == 0 ? (linenr_T)1 : lnum, ML_INSERT)) == NULL) { return FAIL; @@ -2000,32 +1924,29 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b buf->b_ml.ml_flags &= ~ML_EMPTY; + int db_idx; // index for lnum in data block if (lnum == 0) { // got line one instead, correct db_idx db_idx = -1; // careful, it is negative! } else { db_idx = lnum - buf->b_ml.ml_locked_low; } - // get line count before the insertion - line_count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low; + // get line count (number of indexes in current block) before the insertion + int line_count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low; - dp = hp->bh_data; + DATA_BL *dp = hp->bh_data; - /* - * If - * - there is not enough room in the current block - * - appending to the last line in the block - * - not appending to the last line in the file - * insert in front of the next block. - */ + // If + // - there is not enough room in the current block + // - appending to the last line in the block + // - not appending to the last line in the file + // insert in front of the next block. if ((int)dp->db_free < space_needed && db_idx == line_count - 1 && lnum < buf->b_ml.ml_line_count) { - /* - * Now that the line is not going to be inserted in the block that we - * expected, the line count has to be adjusted in the pointer blocks - * by using ml_locked_lineadd. - */ - --(buf->b_ml.ml_locked_lineadd); - --(buf->b_ml.ml_locked_high); + // Now that the line is not going to be inserted in the block that we + // expected, the line count has to be adjusted in the pointer blocks + // by using ml_locked_lineadd. + (buf->b_ml.ml_locked_lineadd)--; + (buf->b_ml.ml_locked_high)--; if ((hp = ml_find_line(buf, lnum + 1, ML_INSERT)) == NULL) { return FAIL; } @@ -2038,25 +1959,20 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b dp = hp->bh_data; } - ++buf->b_ml.ml_line_count; + buf->b_ml.ml_line_count++; if ((int)dp->db_free >= space_needed) { // enough room in data block - /* - * Insert new line in existing data block, or in data block allocated above. - */ + // Insert new line in existing data block, or in data block allocated above. dp->db_txt_start -= (unsigned)len; dp->db_free -= (unsigned)space_needed; dp->db_line_count++; - /* - * move the text of the lines that follow to the front - * adjust the indexes of the lines that follow - */ + // move the text of the lines that follow to the front + // adjust the indexes of the lines that follow if (line_count > db_idx + 1) { // if there are following lines - /* - * Offset is the start of the previous line. - * This will become the character just after the new line. - */ + // Offset is the start of the previous line. + // This will become the character just after the new line. + int offset; if (db_idx < 0) { offset = (int)dp->db_txt_end; } else { @@ -2065,7 +1981,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b memmove((char *)dp + dp->db_txt_start, (char *)dp + dp->db_txt_start + len, (size_t)offset - (dp->db_txt_start + (size_t)len)); - for (i = line_count - 1; i > db_idx; i--) { + for (int i = line_count - 1; i > db_idx; i--) { dp->db_index[i + 1] = dp->db_index[i] - (unsigned)len; } dp->db_index[db_idx + 1] = (unsigned)(offset - len); @@ -2073,31 +1989,25 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b dp->db_index[db_idx + 1] = dp->db_txt_start; } - /* - * copy the text into the block - */ + // copy the text into the block memmove((char *)dp + dp->db_index[db_idx + 1], line, (size_t)len); if (mark) { dp->db_index[db_idx + 1] |= DB_MARKED; } - /* - * Mark the block dirty. - */ + // Mark the block dirty. buf->b_ml.ml_flags |= ML_LOCKED_DIRTY; if (!newfile) { buf->b_ml.ml_flags |= ML_LOCKED_POS; } } else { // not enough space in data block - /* - * If there is not enough room we have to create a new data block and copy some - * lines into it. - * Then we have to insert an entry in the pointer block. - * If this pointer block also is full, we go up another block, and so on, up - * to the root if necessary. - * The line counts in the pointer blocks have already been adjusted by - * ml_find_line(). - */ + // If there is not enough room we have to create a new data block and copy some + // lines into it. + // Then we have to insert an entry in the pointer block. + // If this pointer block also is full, we go up another block, and so on, up + // to the root if necessary. + // The line counts in the pointer blocks have already been adjusted by + // ml_find_line(). int line_count_left, line_count_right; int page_count_left, page_count_right; bhdr_T *hp_left; @@ -2115,14 +2025,12 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b int pb_idx; PTR_BL *pp_new; - /* - * We are going to allocate a new data block. Depending on the - * situation it will be put to the left or right of the existing - * block. If possible we put the new line in the left block and move - * the lines after it to the right block. Otherwise the new line is - * also put in the right block. This method is more efficient when - * inserting a lot of lines at one place. - */ + // We are going to allocate a new data block. Depending on the + // situation it will be put to the left or right of the existing + // block. If possible we put the new line in the left block and move + // the lines after it to the right block. Otherwise the new line is + // also put in the right block. This method is more efficient when + // inserting a lot of lines at one place. if (db_idx < 0) { // left block is new, right block is existing lines_moved = 0; in_left = true; @@ -2146,7 +2054,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b } } - page_count = ((space_needed + (int)HEADER_SIZE) + page_size - 1) / page_size; + int page_count = ((space_needed + (int)HEADER_SIZE) + page_size - 1) / page_size; hp_new = ml_new_data(mfp, newfile, page_count); if (db_idx < 0) { // left block is new hp_left = hp_new; @@ -2166,9 +2074,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b page_count_left = (int)hp_left->bh_page_count; page_count_right = (int)hp_right->bh_page_count; - /* - * May move the new line into the right/new block. - */ + // May move the new line into the right/new block. if (!in_left) { dp_right->db_txt_start -= (unsigned)len; dp_right->db_free -= (unsigned)len + (unsigned)INDEX_SIZE; @@ -2181,25 +2087,19 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b line, (size_t)len); line_count_right++; } - /* - * may move lines from the left/old block to the right/new one. - */ + // may move lines from the left/old block to the right/new one. if (lines_moved) { - /* - */ dp_right->db_txt_start -= (unsigned)data_moved; dp_right->db_free -= (unsigned)total_moved; memmove((char *)dp_right + dp_right->db_txt_start, (char *)dp_left + dp_left->db_txt_start, (size_t)data_moved); - offset = (int)(dp_right->db_txt_start - dp_left->db_txt_start); + int offset = (int)(dp_right->db_txt_start - dp_left->db_txt_start); dp_left->db_txt_start += (unsigned)data_moved; dp_left->db_free += (unsigned)total_moved; - /* - * update indexes in the new block - */ - for (to = line_count_right, from = db_idx + 1; + // update indexes in the new block + for (int to = line_count_right, from = db_idx + 1; from < line_count_left; from++, to++) { dp_right->db_index[to] = dp->db_index[from] + (unsigned)offset; } @@ -2207,9 +2107,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b line_count_left -= lines_moved; } - /* - * May move the new line into the left (old or new) block. - */ + // May move the new line into the left (old or new) block. if (in_left) { dp_left->db_txt_start -= (unsigned)len; dp_left->db_free -= (unsigned)len + (unsigned)INDEX_SIZE; @@ -2236,12 +2134,10 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b dp_left->db_line_count = line_count_left; dp_right->db_line_count = line_count_right; - /* - * release the two data blocks - * The new one (hp_new) already has a correct blocknumber. - * The old one (hp, in ml_locked) gets a positive blocknumber if - * we changed it and we are not editing a new file. - */ + // release the two data blocks + // The new one (hp_new) already has a correct blocknumber. + // The old one (hp, in ml_locked) gets a positive blocknumber if + // we changed it and we are not editing a new file. if (lines_moved || in_left) { buf->b_ml.ml_flags |= ML_LOCKED_DIRTY; } @@ -2250,35 +2146,28 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b } mf_put(mfp, hp_new, true, false); - /* - * flush the old data block - * set ml_locked_lineadd to 0, because the updating of the - * pointer blocks is done below - */ + // flush the old data block + // set ml_locked_lineadd to 0, because the updating of the + // pointer blocks is done below lineadd = buf->b_ml.ml_locked_lineadd; buf->b_ml.ml_locked_lineadd = 0; (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush data block - /* - * update pointer blocks for the new data block - */ - for (stack_idx = buf->b_ml.ml_stack_top - 1; stack_idx >= 0; - --stack_idx) { - ip = &(buf->b_ml.ml_stack[stack_idx]); + // update pointer blocks for the new data block + for (stack_idx = buf->b_ml.ml_stack_top - 1; stack_idx >= 0; stack_idx--) { + infoptr_T *ip = &(buf->b_ml.ml_stack[stack_idx]); pb_idx = ip->ip_index; if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) { return FAIL; } - pp = hp->bh_data; // must be pointer block + PTR_BL *pp = hp->bh_data; // must be pointer block if (pp->pb_id != PTR_ID) { iemsg(_("E317: pointer block id wrong 3")); mf_put(mfp, hp, false, false); return FAIL; } - /* - * TODO: If the pointer block is full and we are adding at the end - * try to insert in front of the next block - */ + // TODO(vim): If the pointer block is full and we are adding at the end + // try to insert in front of the next block // block not full, add one entry if (pp->pb_count < pp->pb_count_max) { if (pb_idx + 1 < (int)pp->pb_count) { @@ -2286,7 +2175,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b &pp->pb_pointer[pb_idx + 1], (size_t)(pp->pb_count - pb_idx - 1) * sizeof(PTR_EN)); } - ++pp->pb_count; + pp->pb_count++; pp->pb_pointer[pb_idx].pe_line_count = line_count_left; pp->pb_pointer[pb_idx].pe_bnum = bnum_left; pp->pb_pointer[pb_idx].pe_page_count = page_count_left; @@ -2305,18 +2194,16 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b buf->b_ml.ml_stack_top = stack_idx + 1; // truncate stack if (lineadd) { - --(buf->b_ml.ml_stack_top); + (buf->b_ml.ml_stack_top)--; // fix line count for rest of blocks in the stack ml_lineadd(buf, lineadd); // fix stack itself buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high += lineadd; - ++(buf->b_ml.ml_stack_top); + (buf->b_ml.ml_stack_top)++; } - /* - * We are finished, break the loop here. - */ + // We are finished, break the loop here. break; } // pointer block full @@ -2386,11 +2273,11 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b // recompute line counts line_count_right = 0; - for (i = 0; i < (int)pp_new->pb_count; i++) { + for (int i = 0; i < (int)pp_new->pb_count; i++) { line_count_right += pp_new->pb_pointer[i].pe_line_count; } line_count_left = 0; - for (i = 0; i < (int)pp->pb_count; i++) { + for (int i = 0; i < (int)pp->pb_count; i++) { line_count_left += pp->pb_pointer[i].pe_line_count; } @@ -2402,9 +2289,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b mf_put(mfp, hp_new, true, false); } - /* - * Safety check: fallen out of for loop? - */ + // Safety check: fallen out of for loop? if (stack_idx < 0) { iemsg(_("E318: Updated too many blocks?")); buf->b_ml.ml_stack_top = 0; // invalidate stack @@ -2416,18 +2301,19 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b return OK; } -void ml_add_deleted_len(char_u *ptr, ssize_t len) +void ml_add_deleted_len(char *ptr, ssize_t len) { ml_add_deleted_len_buf(curbuf, ptr, len); } -void ml_add_deleted_len_buf(buf_T *buf, char_u *ptr, ssize_t len) +void ml_add_deleted_len_buf(buf_T *buf, char *ptr, ssize_t len) { if (inhibit_delete_count) { return; } - if (len == -1) { - len = (ssize_t)STRLEN(ptr); + ssize_t maxlen = (ssize_t)strlen(ptr); + if (len == -1 || len > maxlen) { + len = maxlen; } curbuf->deleted_bytes += (size_t)len + 1; curbuf->deleted_bytes2 += (size_t)len + 1; @@ -2441,7 +2327,7 @@ void ml_add_deleted_len_buf(buf_T *buf, char_u *ptr, ssize_t len) int ml_replace(linenr_T lnum, char *line, bool copy) { - return ml_replace_buf(curbuf, lnum, (char_u *)line, copy); + return ml_replace_buf(curbuf, lnum, line, copy); } /// Replace line "lnum", with buffering, in current buffer. @@ -2453,10 +2339,10 @@ int ml_replace(linenr_T lnum, char *line, bool copy) /// Do not use it after calling ml_replace(). /// /// Check: The caller of this function should probably also call -/// changed_lines(), unless update_screen(NOT_VALID) is used. +/// changed_lines(), unless update_screen(UPD_NOT_VALID) is used. /// /// @return FAIL for failure, OK otherwise -int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy) +int ml_replace_buf(buf_T *buf, linenr_T lnum, char *line, bool copy) { if (line == NULL) { // just checking... return FAIL; @@ -2470,7 +2356,7 @@ int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy) bool readlen = true; if (copy) { - line = vim_strsave(line); + line = xstrdup(line); } if (buf->b_ml.ml_line_lnum != lnum) { // other line buffered ml_flush_line(buf); // flush it @@ -2508,18 +2394,6 @@ int ml_delete(linenr_T lnum, bool message) static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) { - bhdr_T *hp; - memfile_T *mfp; - DATA_BL *dp; - PTR_BL *pp; - infoptr_T *ip; - int count; // number of entries in block - int idx; - int stack_idx; - int line_start; - long line_size; - int i; - if (lnum < 1 || lnum > buf->b_ml.ml_line_count) { return FAIL; } @@ -2528,42 +2402,40 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) lowest_marked--; } - /* - * If the file becomes empty the last line is replaced by an empty line. - */ + // If the file becomes empty the last line is replaced by an empty line. if (buf->b_ml.ml_line_count == 1) { // file becomes empty if (message) { set_keep_msg(_(no_lines_msg), 0); } - i = ml_replace((linenr_T)1, "", true); + int i = ml_replace((linenr_T)1, "", true); buf->b_ml.ml_flags |= ML_EMPTY; return i; } - /* - * find the data block containing the line - * This also fills the stack with the blocks from the root to the data block - * This also releases any locked block. - */ - mfp = buf->b_ml.ml_mfp; + // find the data block containing the line + // This also fills the stack with the blocks from the root to the data block + // This also releases any locked block. + memfile_T *mfp = buf->b_ml.ml_mfp; if (mfp == NULL) { return FAIL; } + bhdr_T *hp; if ((hp = ml_find_line(buf, lnum, ML_DELETE)) == NULL) { return FAIL; } - dp = hp->bh_data; - // compute line count before the delete - count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 2; - idx = lnum - buf->b_ml.ml_locked_low; + DATA_BL *dp = hp->bh_data; + // compute line count (number of entries in block) before the delete + int count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 2; + int idx = lnum - buf->b_ml.ml_locked_low; - --buf->b_ml.ml_line_count; + buf->b_ml.ml_line_count--; - line_start = ((dp->db_index[idx]) & DB_INDEX_MASK); + int line_start = ((dp->db_index[idx]) & DB_INDEX_MASK); + long line_size; if (idx == 0) { // first line in block, text at the end line_size = dp->db_txt_end - (unsigned)line_start; } else { @@ -2573,29 +2445,26 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) // Line should always have an NL char internally (represented as NUL), // even if 'noeol' is set. assert(line_size >= 1); - ml_add_deleted_len_buf(buf, (char_u *)dp + line_start, line_size - 1); - - /* - * special case: If there is only one line in the data block it becomes empty. - * Then we have to remove the entry, pointing to this data block, from the - * pointer block. If this pointer block also becomes empty, we go up another - * block, and so on, up to the root if necessary. - * The line counts in the pointer blocks have already been adjusted by - * ml_find_line(). - */ + ml_add_deleted_len_buf(buf, (char *)dp + line_start, line_size - 1); + + // special case: If there is only one line in the data block it becomes empty. + // Then we have to remove the entry, pointing to this data block, from the + // pointer block. If this pointer block also becomes empty, we go up another + // block, and so on, up to the root if necessary. + // The line counts in the pointer blocks have already been adjusted by + // ml_find_line(). if (count == 1) { mf_free(mfp, hp); // free the data block buf->b_ml.ml_locked = NULL; - for (stack_idx = buf->b_ml.ml_stack_top - 1; stack_idx >= 0; - --stack_idx) { + for (int stack_idx = buf->b_ml.ml_stack_top - 1; stack_idx >= 0; stack_idx--) { buf->b_ml.ml_stack_top = 0; // stack is invalid when failing - ip = &(buf->b_ml.ml_stack[stack_idx]); + infoptr_T *ip = &(buf->b_ml.ml_stack[stack_idx]); idx = ip->ip_index; if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) { return FAIL; } - pp = hp->bh_data; // must be pointer block + PTR_BL *pp = hp->bh_data; // must be pointer block if (pp->pb_id != PTR_ID) { iemsg(_("E317: pointer block id wrong 4")); mf_put(mfp, hp, false, false); @@ -2618,7 +2487,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) buf->b_ml.ml_stack[buf->b_ml.ml_stack_top].ip_high += buf->b_ml.ml_locked_lineadd; } - ++(buf->b_ml.ml_stack_top); + (buf->b_ml.ml_stack_top)++; break; } @@ -2632,7 +2501,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) // delete the index by moving the next indexes backwards // Adjust the indexes for the text movement. - for (i = idx; i < count - 1; i++) { + for (int i = idx; i < count - 1; i++) { dp->db_index[i] = dp->db_index[i + 1] + (unsigned)line_size; } @@ -2640,9 +2509,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) dp->db_txt_start += (unsigned)line_size; dp->db_line_count--; - /* - * mark the block dirty and make sure it is in the file (for recovery) - */ + // mark the block dirty and make sure it is in the file (for recovery) buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS); } @@ -2653,8 +2520,6 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) /// set the B_MARKED flag for line 'lnum' void ml_setmarked(linenr_T lnum) { - bhdr_T *hp; - DATA_BL *dp; // invalid line number if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count || curbuf->b_ml.ml_mfp == NULL) { @@ -2664,15 +2529,14 @@ void ml_setmarked(linenr_T lnum) lowest_marked = lnum; } - /* - * find the data block containing the line - * This also fills the stack with the blocks from the root to the data block - * This also releases any locked block. - */ + // find the data block containing the line + // This also fills the stack with the blocks from the root to the data block + // This also releases any locked block. + bhdr_T *hp; if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) { return; // give error message? } - dp = hp->bh_data; + DATA_BL *dp = hp->bh_data; dp->db_index[lnum - curbuf->b_ml.ml_locked_low] |= DB_MARKED; curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; } @@ -2680,32 +2544,24 @@ void ml_setmarked(linenr_T lnum) /// find the first line with its B_MARKED flag set linenr_T ml_firstmarked(void) { - bhdr_T *hp; - DATA_BL *dp; - linenr_T lnum; - int i; - if (curbuf->b_ml.ml_mfp == NULL) { return (linenr_T)0; } - /* - * The search starts with lowest_marked line. This is the last line where - * a mark was found, adjusted by inserting/deleting lines. - */ - for (lnum = lowest_marked; lnum <= curbuf->b_ml.ml_line_count;) { - /* - * Find the data block containing the line. - * This also fills the stack with the blocks from the root to the data - * block This also releases any locked block. - */ + // The search starts with lowest_marked line. This is the last line where + // a mark was found, adjusted by inserting/deleting lines. + for (linenr_T lnum = lowest_marked; lnum <= curbuf->b_ml.ml_line_count;) { + // Find the data block containing the line. + // This also fills the stack with the blocks from the root to the data + // block This also releases any locked block. + bhdr_T *hp; if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) { return (linenr_T)0; // give error message? } - dp = hp->bh_data; + DATA_BL *dp = hp->bh_data; - for (i = lnum - curbuf->b_ml.ml_locked_low; - lnum <= curbuf->b_ml.ml_locked_high; ++i, ++lnum) { + for (int i = lnum - curbuf->b_ml.ml_locked_low; + lnum <= curbuf->b_ml.ml_locked_high; i++, lnum++) { if ((dp->db_index[i]) & DB_MARKED) { (dp->db_index[i]) &= DB_INDEX_MASK; curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; @@ -2721,31 +2577,23 @@ linenr_T ml_firstmarked(void) /// clear all DB_MARKED flags void ml_clearmarked(void) { - bhdr_T *hp; - DATA_BL *dp; - linenr_T lnum; - int i; - if (curbuf->b_ml.ml_mfp == NULL) { // nothing to do return; } - /* - * The search starts with line lowest_marked. - */ - for (lnum = lowest_marked; lnum <= curbuf->b_ml.ml_line_count;) { - /* - * Find the data block containing the line. - * This also fills the stack with the blocks from the root to the data - * block and releases any locked block. - */ + // The search starts with line lowest_marked. + for (linenr_T lnum = lowest_marked; lnum <= curbuf->b_ml.ml_line_count;) { + // Find the data block containing the line. + // This also fills the stack with the blocks from the root to the data + // block and releases any locked block. + bhdr_T *hp; if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) { return; // give error message? } - dp = hp->bh_data; + DATA_BL *dp = hp->bh_data; - for (i = lnum - curbuf->b_ml.ml_locked_low; - lnum <= curbuf->b_ml.ml_locked_high; ++i, ++lnum) { + for (int i = lnum - curbuf->b_ml.ml_locked_low; + lnum <= curbuf->b_ml.ml_locked_high; i++, lnum++) { if ((dp->db_index[i]) & DB_MARKED) { (dp->db_index[i]) &= DB_INDEX_MASK; curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; @@ -2770,18 +2618,6 @@ size_t ml_flush_deleted_bytes(buf_T *buf, size_t *codepoints, size_t *codeunits) /// flush ml_line if necessary static void ml_flush_line(buf_T *buf) { - bhdr_T *hp; - DATA_BL *dp; - linenr_T lnum; - char_u *new_line; - char_u *old_line; - colnr_T new_len; - int old_len; - int extra; - int idx; - int start; - int count; - int i; static bool entered = false; if (buf->b_ml.ml_line_lnum == 0 || buf->b_ml.ml_mfp == NULL) { @@ -2796,31 +2632,30 @@ static void ml_flush_line(buf_T *buf) buf->flush_count++; - lnum = buf->b_ml.ml_line_lnum; - new_line = buf->b_ml.ml_line_ptr; + linenr_T lnum = buf->b_ml.ml_line_lnum; + char *new_line = buf->b_ml.ml_line_ptr; - hp = ml_find_line(buf, lnum, ML_FIND); + bhdr_T *hp = ml_find_line(buf, lnum, ML_FIND); if (hp == NULL) { siemsg(_("E320: Cannot find line %" PRId64), (int64_t)lnum); } else { - dp = hp->bh_data; - idx = lnum - buf->b_ml.ml_locked_low; - start = ((dp->db_index[idx]) & DB_INDEX_MASK); - old_line = (char_u *)dp + start; + DATA_BL *dp = hp->bh_data; + int idx = lnum - buf->b_ml.ml_locked_low; + int start = ((dp->db_index[idx]) & DB_INDEX_MASK); + char *old_line = (char *)dp + start; + int old_len; if (idx == 0) { // line is last in block old_len = (int)dp->db_txt_end - start; } else { // text of previous line follows old_len = (int)(dp->db_index[idx - 1] & DB_INDEX_MASK) - start; } - new_len = (colnr_T)STRLEN(new_line) + 1; - extra = new_len - old_len; // negative if lines gets smaller + colnr_T new_len = (colnr_T)strlen(new_line) + 1; + int extra = new_len - old_len; // negative if lines gets smaller - /* - * if new line fits in data block, replace directly - */ + // if new line fits in data block, replace directly if ((int)dp->db_free >= extra) { // if the length changes and there are following lines - count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1; + int count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1; if (extra != 0 && idx < count - 1) { // move text of following lines memmove((char *)dp + dp->db_txt_start - extra, @@ -2828,7 +2663,7 @@ static void ml_flush_line(buf_T *buf) (size_t)(start - (int)dp->db_txt_start)); // adjust pointers of this and following lines - for (i = idx + 1; i < count; i++) { + for (int i = idx + 1; i < count; i++) { dp->db_index[i] -= (unsigned)extra; } } @@ -2905,39 +2740,28 @@ static bhdr_T *ml_new_ptr(memfile_T *mfp) /// @return NULL for failure, pointer to block header otherwise static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) { - DATA_BL *dp; PTR_BL *pp; - infoptr_T *ip; bhdr_T *hp; - memfile_T *mfp; - linenr_T t; - blocknr_T bnum, bnum2; - int dirty; - linenr_T low, high; int top; - int page_count; - int idx; - mfp = buf->b_ml.ml_mfp; + memfile_T *mfp = buf->b_ml.ml_mfp; - /* - * If there is a locked block check if the wanted line is in it. - * If not, flush and release the locked block. - * Don't do this for ML_INSERT_SAME, because the stack need to be updated. - * Don't do this for ML_FLUSH, because we want to flush the locked block. - * Don't do this when 'swapfile' is reset, we want to load all the blocks. - */ + // If there is a locked block check if the wanted line is in it. + // If not, flush and release the locked block. + // Don't do this for ML_INSERT_SAME, because the stack need to be updated. + // Don't do this for ML_FLUSH, because we want to flush the locked block. + // Don't do this when 'swapfile' is reset, we want to load all the blocks. if (buf->b_ml.ml_locked) { if (ML_SIMPLE(action) && buf->b_ml.ml_locked_low <= lnum && buf->b_ml.ml_locked_high >= lnum) { // remember to update pointer blocks and stack later if (action == ML_INSERT) { - ++(buf->b_ml.ml_locked_lineadd); - ++(buf->b_ml.ml_locked_high); + (buf->b_ml.ml_locked_lineadd)++; + (buf->b_ml.ml_locked_high)++; } else if (action == ML_DELETE) { - --(buf->b_ml.ml_locked_lineadd); - --(buf->b_ml.ml_locked_high); + (buf->b_ml.ml_locked_lineadd)--; + (buf->b_ml.ml_locked_high)--; } return buf->b_ml.ml_locked; } @@ -2946,10 +2770,8 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) buf->b_ml.ml_flags & ML_LOCKED_POS); buf->b_ml.ml_locked = NULL; - /* - * If lines have been added or deleted in the locked block, need to - * update the line count in pointer blocks. - */ + // If lines have been added or deleted in the locked block, need to + // update the line count in pointer blocks. if (buf->b_ml.ml_locked_lineadd != 0) { ml_lineadd(buf, buf->b_ml.ml_locked_lineadd); } @@ -2959,14 +2781,15 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) return NULL; } - bnum = 1; // start at the root of the tree - page_count = 1; - low = 1; - high = buf->b_ml.ml_line_count; + blocknr_T bnum = 1; // start at the root of the tree + blocknr_T bnum2; + int page_count = 1; + linenr_T low = 1; + linenr_T high = buf->b_ml.ml_line_count; if (action == ML_FIND) { // first try stack entries - for (top = buf->b_ml.ml_stack_top - 1; top >= 0; --top) { - ip = &(buf->b_ml.ml_stack[top]); + for (top = buf->b_ml.ml_stack_top - 1; top >= 0; top--) { + infoptr_T *ip = &(buf->b_ml.ml_stack[top]); if (ip->ip_low <= lnum && ip->ip_high >= lnum) { bnum = ip->ip_bnum; low = ip->ip_low; @@ -2981,24 +2804,20 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) } else { // ML_DELETE or ML_INSERT buf->b_ml.ml_stack_top = 0; // start at the root } - /* - * search downwards in the tree until a data block is found - */ + // search downwards in the tree until a data block is found for (;;) { if ((hp = mf_get(mfp, bnum, (unsigned)page_count)) == NULL) { goto error_noblock; } - /* - * update high for insert/delete - */ + // update high for insert/delete if (action == ML_INSERT) { high++; } else if (action == ML_DELETE) { high--; } - dp = hp->bh_data; + DATA_BL *dp = hp->bh_data; if (dp->db_id == DATA_ID) { // data block buf->b_ml.ml_locked = hp; buf->b_ml.ml_locked_low = low; @@ -3015,15 +2834,16 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) } top = ml_add_stack(buf); // add new entry to stack - ip = &(buf->b_ml.ml_stack[top]); + infoptr_T *ip = &(buf->b_ml.ml_stack[top]); ip->ip_bnum = bnum; ip->ip_low = low; ip->ip_high = high; ip->ip_index = -1; // index not known yet - dirty = FALSE; - for (idx = 0; idx < (int)pp->pb_count; ++idx) { - t = pp->pb_pointer[idx].pe_line_count; + bool dirty = false; + int idx; + for (idx = 0; idx < (int)pp->pb_count; idx++) { + linenr_T t = pp->pb_pointer[idx].pe_line_count; CHECK(t == 0, _("pe_line_count is zero")); if ((low += t) > lnum) { ip->ip_index = idx; @@ -3032,15 +2852,13 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) high = low - 1; low -= t; - /* - * a negative block number may have been changed - */ + // a negative block number may have been changed if (bnum < 0) { bnum2 = mf_trans_del(mfp, bnum); if (bnum != bnum2) { bnum = bnum2; pp->pb_pointer[idx].pe_bnum = bnum; - dirty = TRUE; + dirty = true; } } @@ -3058,10 +2876,10 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) } if (action == ML_DELETE) { pp->pb_pointer[idx].pe_line_count--; - dirty = TRUE; + dirty = true; } else if (action == ML_INSERT) { pp->pb_pointer[idx].pe_line_count++; - dirty = TRUE; + dirty = true; } mf_put(mfp, hp, dirty, false); } @@ -3069,11 +2887,9 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) error_block: mf_put(mfp, hp, false, false); error_noblock: - /* - * If action is ML_DELETE or ML_INSERT we have to correct the tree for - * the incremented/decremented line counts, because there won't be a line - * inserted/deleted after all. - */ + // If action is ML_DELETE or ML_INSERT we have to correct the tree for + // the incremented/decremented line counts, because there won't be a line + // inserted/deleted after all. if (action == ML_DELETE) { ml_lineadd(buf, 1); } else if (action == ML_INSERT) { @@ -3113,18 +2929,15 @@ static int ml_add_stack(buf_T *buf) /// Count is the number of lines added, negative if lines have been deleted. static void ml_lineadd(buf_T *buf, int count) { - int idx; - infoptr_T *ip; - PTR_BL *pp; memfile_T *mfp = buf->b_ml.ml_mfp; - bhdr_T *hp; - for (idx = buf->b_ml.ml_stack_top - 1; idx >= 0; --idx) { - ip = &(buf->b_ml.ml_stack[idx]); + for (int idx = buf->b_ml.ml_stack_top - 1; idx >= 0; idx--) { + infoptr_T *ip = &(buf->b_ml.ml_stack[idx]); + bhdr_T *hp; if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) { break; } - pp = hp->bh_data; // must be pointer block + PTR_BL *pp = hp->bh_data; // must be pointer block if (pp->pb_id != PTR_ID) { mf_put(mfp, hp, false, false); iemsg(_("E317: pointer block id wrong 2")); @@ -3144,10 +2957,9 @@ static void ml_lineadd(buf_T *buf, int count) /// /// @return OK if it worked and the resolved link in "buf[MAXPATHL]", /// FAIL otherwise -int resolve_symlink(const char_u *fname, char_u *buf) +int resolve_symlink(const char *fname, char *buf) { - char_u tmp[MAXPATHL]; - int ret; + char tmp[MAXPATHL]; int depth = 0; if (fname == NULL) { @@ -3155,7 +2967,7 @@ int resolve_symlink(const char_u *fname, char_u *buf) } // Put the result so far in tmp[], starting with the original name. - STRLCPY(tmp, fname, MAXPATHL); + xstrlcpy(tmp, fname, MAXPATHL); for (;;) { // Limit symlink depth to 100, catch recursive loops. @@ -3164,7 +2976,7 @@ int resolve_symlink(const char_u *fname, char_u *buf) return FAIL; } - ret = (int)readlink((char *)tmp, (char *)buf, MAXPATHL - 1); + int ret = (int)readlink(tmp, buf, MAXPATHL - 1); if (ret <= 0) { if (errno == EINVAL || errno == ENOENT) { // Found non-symlink or not existing file, stop here. @@ -3190,57 +3002,54 @@ int resolve_symlink(const char_u *fname, char_u *buf) if (path_is_absolute(buf)) { STRCPY(tmp, buf); } else { - char_u *tail = (char_u *)path_tail((char *)tmp); - if (STRLEN(tail) + STRLEN(buf) >= MAXPATHL) { + char *tail = path_tail(tmp); + if (strlen(tail) + strlen(buf) >= MAXPATHL) { return FAIL; } STRCPY(tail, buf); } } - /* - * Try to resolve the full name of the file so that the swapfile name will - * be consistent even when opening a relative symlink from different - * working directories. - */ - return vim_FullName((char *)tmp, (char *)buf, MAXPATHL, TRUE); + // Try to resolve the full name of the file so that the swapfile name will + // be consistent even when opening a relative symlink from different + // working directories. + return vim_FullName(tmp, buf, MAXPATHL, true); } #endif /// Make swap file name out of the file name and a directory name. /// /// @return pointer to allocated memory or NULL. -char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name) +char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name) { - char_u *r, *s; - char_u *fname_res = fname; + char *fname_res = fname; #ifdef HAVE_READLINK - char_u fname_buf[MAXPATHL]; + char fname_buf[MAXPATHL]; // Expand symlink in the file name, so that we put the swap file with the // actual file instead of with the symlink. - if (resolve_symlink(fname, fname_buf) == OK) { + if (resolve_symlink(fname, (char *)fname_buf) == OK) { fname_res = fname_buf; } #endif - int len = (int)STRLEN(dir_name); + int len = (int)strlen(dir_name); - s = dir_name + len; - if (after_pathsep((char *)dir_name, (char *)s) + char *s = dir_name + len; + if (after_pathsep(dir_name, s) && len > 1 && s[-1] == s[-2]) { // Ends with '//', Use Full path - r = NULL; - s = (char_u *)make_percent_swname((char *)dir_name, (char *)fname_res); + char *r = NULL; + s = make_percent_swname(dir_name, fname_res); if (s != NULL) { - r = (char_u *)modname((char *)s, ".swp", false); + r = modname(s, ".swp", false); xfree(s); } return r; } // Prepend a '.' to the swap file name for the current directory. - r = (char_u *)modname((char *)fname_res, ".swp", - dir_name[0] == '.' && dir_name[1] == NUL); + char *r = modname(fname_res, ".swp", + dir_name[0] == '.' && dir_name[1] == NUL); if (r == NULL) { // out of memory return NULL; } @@ -3262,30 +3071,27 @@ char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name /// The return value is an allocated string and can be NULL. /// /// @param dname don't use "dirname", it is a global for Alpha -char_u *get_file_in_dir(char_u *fname, char_u *dname) +char *get_file_in_dir(char *fname, char *dname) { - char_u *t; - char_u *tail; - char_u *retval; - int save_char; + char *retval; - tail = (char_u *)path_tail((char *)fname); + char *tail = path_tail(fname); if (dname[0] == '.' && dname[1] == NUL) { - retval = vim_strsave(fname); + retval = xstrdup(fname); } else if (dname[0] == '.' && vim_ispathsep(dname[1])) { if (tail == fname) { // no path before file name - retval = (char_u *)concat_fnames((char *)dname + 2, (char *)tail, TRUE); + retval = concat_fnames(dname + 2, tail, true); } else { - save_char = *tail; + char save_char = *tail; *tail = NUL; - t = (char_u *)concat_fnames((char *)fname, (char *)dname + 2, true); - *tail = (uint8_t)save_char; - retval = (char_u *)concat_fnames((char *)t, (char *)tail, true); + char *t = concat_fnames(fname, dname + 2, true); + *tail = save_char; + retval = concat_fnames(t, tail, true); xfree(t); } } else { - retval = (char_u *)concat_fnames((char *)dname, (char *)tail, TRUE); + retval = concat_fnames(dname, tail, true); } return retval; @@ -3295,7 +3101,7 @@ char_u *get_file_in_dir(char_u *fname, char_u *dname) /// /// @param buf buffer being edited /// @param fname swap file name -static void attention_message(buf_T *buf, char_u *fname) +static void attention_message(buf_T *buf, char *fname) { assert(buf->b_fname != NULL); @@ -3315,7 +3121,7 @@ static void attention_message(buf_T *buf, char_u *fname) msg_puts(_(" dated: ")); time_t x = file_info.stat.st_mtim.tv_sec; char ctime_buf[50]; - msg_puts(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf))); + msg_puts(os_ctime_r(&x, ctime_buf, sizeof(ctime_buf), true)); if (swap_mtime != 0 && x > swap_mtime) { msg_puts(_(" NEWER than swap file!\n")); } @@ -3331,7 +3137,7 @@ static void attention_message(buf_T *buf, char_u *fname) msg_outtrans(buf->b_fname); msg_puts(_("\"\n to recover the changes (see \":help recovery\").\n")); msg_puts(_(" If you did this already, delete the swap file \"")); - msg_outtrans((char *)fname); + msg_outtrans(fname); msg_puts(_("\"\n to avoid this message.\n")); cmdline_row = msg_row; no_wait_return--; @@ -3347,9 +3153,9 @@ static void attention_message(buf_T *buf, char_u *fname) /// 4: delete it /// 5: quit /// 6: abort -static int do_swapexists(buf_T *buf, char_u *fname) +static int do_swapexists(buf_T *buf, char *fname) { - set_vim_var_string(VV_SWAPNAME, (char *)fname, -1); + set_vim_var_string(VV_SWAPNAME, fname, -1); set_vim_var_string(VV_SWAPCHOICE, NULL, -1); // Trigger SwapExists autocommands with <afile> set to the file being @@ -3404,24 +3210,17 @@ static int do_swapexists(buf_T *buf, char_u *fname) static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_existing_dir) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 2, 4) { - char *fname; size_t n; - char *dir_name; char *buf_fname = buf->b_fname; - /* - * Isolate a directory name from *dirp and put it in dir_name. - * First allocate some memory to put the directory name in. - */ + // Isolate a directory name from *dirp and put it in dir_name. + // First allocate some memory to put the directory name in. const size_t dir_len = strlen(*dirp) + 1; - dir_name = xmalloc(dir_len); + char *dir_name = xmalloc(dir_len); (void)copy_option_part(dirp, dir_name, dir_len, ","); - /* - * we try different names until we find one that does not exist yet - */ - fname = (char *)makeswapname((char_u *)buf_fname, (char_u *)buf->b_ffname, buf, - (char_u *)dir_name); + // we try different names until we find one that does not exist yet + char *fname = makeswapname(buf_fname, buf->b_ffname, buf, dir_name); for (;;) { if (fname == NULL) { // must be out of memory @@ -3441,7 +3240,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ } // A file name equal to old_fname is OK to use. - if (old_fname != NULL && FNAMECMP(fname, old_fname) == 0) { + if (old_fname != NULL && path_fnamecmp(fname, old_fname) == 0) { break; } @@ -3455,7 +3254,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ && !buf->b_help && !(buf->b_flags & BF_DUMMY)) { int fd; struct block0 b0; - int differ = FALSE; + int differ = false; // Try to read block 0 from the swap file to get the original // file name (and inode number). @@ -3466,25 +3265,25 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ // buffer don't compare the directory names, they can // have a different mountpoint. if (b0.b0_flags & B0_SAME_DIR) { - if (FNAMECMP(path_tail((char *)buf->b_ffname), - path_tail((char *)b0.b0_fname)) != 0 - || !same_directory((char_u *)fname, (char_u *)buf->b_ffname)) { + if (path_fnamecmp(path_tail(buf->b_ffname), + path_tail((char *)b0.b0_fname)) != 0 + || !same_directory(fname, buf->b_ffname)) { // Symlinks may point to the same file even // when the name differs, need to check the // inode too. - expand_env(b0.b0_fname, NameBuff, MAXPATHL); - if (fnamecmp_ino((char_u *)buf->b_ffname, NameBuff, + expand_env((char *)b0.b0_fname, NameBuff, MAXPATHL); + if (fnamecmp_ino(buf->b_ffname, NameBuff, char_to_long(b0.b0_ino))) { - differ = TRUE; + differ = true; } } } else { // The name in the swap file may be // "~user/path/file". Expand it first. - expand_env(b0.b0_fname, NameBuff, MAXPATHL); - if (fnamecmp_ino((char_u *)buf->b_ffname, NameBuff, + expand_env((char *)b0.b0_fname, NameBuff, MAXPATHL); + if (fnamecmp_ino(buf->b_ffname, NameBuff, char_to_long(b0.b0_ino))) { - differ = TRUE; + differ = true; } } } @@ -3494,14 +3293,14 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ // give the ATTENTION message when there is an old swap file // for the current file, and the buffer was not recovered. if (differ == false && !(curbuf->b_flags & BF_RECOVERED) - && vim_strchr((char *)p_shm, SHM_ATTENTION) == NULL) { + && vim_strchr(p_shm, SHM_ATTENTION) == NULL) { int choice = 0; process_still_running = false; // It's safe to delete the swap file if all these are true: // - the edited file exists // - the swap file has no changes and looks OK - if (os_path_exists((char_u *)buf->b_fname) && swapfile_unchanged(fname)) { + if (os_path_exists(buf->b_fname) && swapfile_unchanged(fname)) { choice = 4; if (p_verbose > 0) { verb_msg(_("Found a swap file that is not useful, deleting it")); @@ -3513,12 +3312,12 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ if (choice == 0 && swap_exists_action != SEA_NONE && has_autocmd(EVENT_SWAPEXISTS, buf_fname, buf)) { - choice = do_swapexists(buf, (char_u *)fname); + choice = do_swapexists(buf, fname); } if (choice == 0) { // Show info about the existing swap file. - attention_message(buf, (char_u *)fname); + attention_message(buf, fname); // We don't want a 'q' typed at the more-prompt // interrupt loading a file. @@ -3543,13 +3342,13 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ memcpy(name, sw_msg_1, sw_msg_1_len + 1); home_replace(NULL, fname, &name[sw_msg_1_len], fname_len, true); xstrlcat(name, sw_msg_2, name_len); - choice = do_dialog(VIM_WARNING, (char_u *)_("VIM - ATTENTION"), - (char_u *)name, + choice = do_dialog(VIM_WARNING, _("VIM - ATTENTION"), + name, process_still_running - ? (char_u *)_("&Open Read-Only\n&Edit anyway\n&Recover" - "\n&Quit\n&Abort") : - (char_u *)_("&Open Read-Only\n&Edit anyway\n&Recover" - "\n&Delete it\n&Quit\n&Abort"), + ? _("&Open Read-Only\n&Edit anyway\n&Recover" + "\n&Quit\n&Abort") : + _("&Open Read-Only\n&Edit anyway\n&Recover" + "\n&Delete it\n&Quit\n&Abort"), 1, NULL, false); if (process_still_running && choice >= 4) { @@ -3564,7 +3363,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ if (choice > 0) { switch (choice) { case 1: - buf->b_p_ro = TRUE; + buf->b_p_ro = true; break; case 2: break; @@ -3579,12 +3378,12 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ break; case 6: swap_exists_action = SEA_QUIT; - got_int = TRUE; + got_int = true; break; } // If the file was deleted this fname can be used. - if (!os_path_exists((char_u *)fname)) { + if (!os_path_exists(fname)) { break; } } else { @@ -3598,25 +3397,23 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ } } - /* - * Change the ".swp" extension to find another file that can be used. - * First decrement the last char: ".swo", ".swn", etc. - * If that still isn't enough decrement the last but one char: ".svz" - * Can happen when editing many "No Name" buffers. - */ + // Change the ".swp" extension to find another file that can be used. + // First decrement the last char: ".swo", ".swn", etc. + // If that still isn't enough decrement the last but one char: ".svz" + // Can happen when editing many "No Name" buffers. if (fname[n - 1] == 'a') { // ".s?a" if (fname[n - 2] == 'a') { // ".saa": tried enough, give up emsg(_("E326: Too many swap files found")); XFREE_CLEAR(fname); break; } - --fname[n - 2]; // ".svz", ".suz", etc. + fname[n - 2]--; // ".svz", ".suz", etc. fname[n - 1] = 'z' + 1; } - --fname[n - 1]; // ".swo", ".swn", etc. + fname[n - 1]--; // ".swo", ".swn", etc. } - if (os_isdir((char_u *)dir_name)) { + if (os_isdir(dir_name)) { *found_existing_dir = true; } else if (!*found_existing_dir && **dirp == NUL) { int ret; @@ -3636,8 +3433,8 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ static int b0_magic_wrong(ZERO_BL *b0p) { return b0p->b0_magic_long != B0_MAGIC_LONG - || b0p->b0_magic_int != (int)B0_MAGIC_INT - || b0p->b0_magic_short != (short)B0_MAGIC_SHORT + || b0p->b0_magic_int != B0_MAGIC_INT + || b0p->b0_magic_short != (int16_t)B0_MAGIC_SHORT || b0p->b0_magic_char != B0_MAGIC_CHAR; } @@ -3667,18 +3464,18 @@ static int b0_magic_wrong(ZERO_BL *b0p) /// /// current file doesn't exist, file for swap file exist, file name(s) not /// available -> probably different -/// == 0 != 0 FAIL X TRUE -/// == 0 != 0 X FAIL TRUE +/// == 0 != 0 FAIL X true +/// == 0 != 0 X FAIL true /// /// current file exists, inode for swap unknown, file name(s) not /// available -> probably different -/// != 0 == 0 FAIL X TRUE -/// != 0 == 0 X FAIL TRUE +/// != 0 == 0 FAIL X true +/// != 0 == 0 X FAIL true /// /// current file doesn't exist, inode for swap unknown, one file name not /// available -> probably different -/// == 0 == 0 FAIL OK TRUE -/// == 0 == 0 OK FAIL TRUE +/// == 0 == 0 FAIL OK true +/// == 0 == 0 OK FAIL true /// /// current file doesn't exist, inode for swap unknown, both file names not /// available -> compare file names @@ -3689,26 +3486,24 @@ static int b0_magic_wrong(ZERO_BL *b0p) /// /// @param fname_c current file name /// @param fname_s file name from swap file -static bool fnamecmp_ino(char_u *fname_c, char_u *fname_s, long ino_block0) +static bool fnamecmp_ino(char *fname_c, char *fname_s, long ino_block0) { uint64_t ino_c = 0; // ino of current file uint64_t ino_s; // ino of file from swap file - char_u buf_c[MAXPATHL]; // full path of fname_c - char_u buf_s[MAXPATHL]; // full path of fname_s + char buf_c[MAXPATHL]; // full path of fname_c + char buf_s[MAXPATHL]; // full path of fname_s int retval_c; // flag: buf_c valid int retval_s; // flag: buf_s valid FileInfo file_info; - if (os_fileinfo((char *)fname_c, &file_info)) { + if (os_fileinfo(fname_c, &file_info)) { ino_c = os_fileinfo_inode(&file_info); } - /* - * First we try to get the inode from the file name, because the inode in - * the swap file may be outdated. If that fails (e.g. this path is not - * valid on this machine), use the inode from block 0. - */ - if (os_fileinfo((char *)fname_s, &file_info)) { + // First we try to get the inode from the file name, because the inode in + // the swap file may be outdated. If that fails (e.g. this path is not + // valid on this machine), use the inode from block 0. + if (os_fileinfo(fname_s, &file_info)) { ino_s = os_fileinfo_inode(&file_info); } else { ino_s = (uint64_t)ino_block0; @@ -3718,23 +3513,19 @@ static bool fnamecmp_ino(char_u *fname_c, char_u *fname_s, long ino_block0) return ino_c != ino_s; } - /* - * One of the inode numbers is unknown, try a forced vim_FullName() and - * compare the file names. - */ - retval_c = vim_FullName((char *)fname_c, (char *)buf_c, MAXPATHL, TRUE); - retval_s = vim_FullName((char *)fname_s, (char *)buf_s, MAXPATHL, TRUE); + // One of the inode numbers is unknown, try a forced vim_FullName() and + // compare the file names. + retval_c = vim_FullName(fname_c, (char *)buf_c, MAXPATHL, true); + retval_s = vim_FullName(fname_s, (char *)buf_s, MAXPATHL, true); if (retval_c == OK && retval_s == OK) { - return STRCMP(buf_c, buf_s) != 0; + return strcmp(buf_c, buf_s) != 0; } - /* - * Can't compare inodes or file names, guess that the files are different, - * unless both appear not to exist at all, then compare with the file name - * in the swap file. - */ + // Can't compare inodes or file names, guess that the files are different, + // unless both appear not to exist at all, then compare with the file name + // in the swap file. if (ino_s == 0 && ino_c == 0 && retval_c == FAIL && retval_s == FAIL) { - return STRCMP(fname_c, fname_s) != 0; + return strcmp(fname_c, fname_s) != 0; } return true; } @@ -3752,7 +3543,7 @@ static void long_to_char(long n, char_u *s) s[3] = (char_u)(n & 0xff); } -static long char_to_long(char_u *s) +static long char_to_long(const char_u *s) { long retval; @@ -3783,7 +3574,7 @@ void ml_setflags(buf_T *buf) if (hp->bh_bnum == 0) { b0p = hp->bh_data; b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0; - b0p->b0_flags = (uint8_t)((b0p->b0_flags & ~B0_FF_MASK) | (uint8_t)(get_fileformat(buf) + 1)); + b0p->b0_flags = (char)((b0p->b0_flags & ~B0_FF_MASK) | (uint8_t)(get_fileformat(buf) + 1)); add_b0_fenc(b0p, buf); hp->bh_flags |= BH_DIRTY; mf_sync(buf->b_ml.ml_mfp, MFS_ZERO); @@ -3792,8 +3583,10 @@ void ml_setflags(buf_T *buf) } } -#define MLCS_MAXL 800 // max no of lines in chunk -#define MLCS_MINL 400 // should be half of MLCS_MAXL +enum { + MLCS_MAXL = 800, // max no of lines in chunk + MLCS_MINL = 400, // should be half of MLCS_MAXL +}; /// Keep information for finding byte offset of a line /// @@ -3829,20 +3622,16 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype) } if (updtype == ML_CHNK_UPDLINE && buf->b_ml.ml_line_count == 1) { - /* - * First line in empty buffer from ml_flush_line() -- reset - */ + // First line in empty buffer from ml_flush_line() -- reset buf->b_ml.ml_usedchunks = 1; buf->b_ml.ml_chunksize[0].mlcs_numlines = 1; buf->b_ml.ml_chunksize[0].mlcs_totalsize = - (long)STRLEN(buf->b_ml.ml_line_ptr) + 1; + (long)strlen(buf->b_ml.ml_line_ptr) + 1; return; } - /* - * Find chunk that our line belongs to, curline will be at start of the - * chunk. - */ + // Find chunk that our line belongs to, curline will be at start of the + // chunk. if (buf != ml_upd_lastbuf || line != ml_upd_lastline + 1 || updtype != ML_CHNK_ADDLINE) { for (curline = 1, curix = 0; @@ -3930,10 +3719,8 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype) curchnk->mlcs_numlines = 0; curchnk->mlcs_totalsize = 0; } else { - /* - * Line is just prior to last, move count for last - * This is the common case when loading a new file - */ + // Line is just prior to last, move count for last + // This is the common case when loading a new file hp = ml_find_line(buf, buf->b_ml.ml_line_count, ML_FIND); if (hp == NULL) { buf->b_ml.ml_usedchunks = -1; @@ -4046,10 +3833,8 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff) if (lnum == 0 && offset <= 0) { return 1; // Not a "find offset" and offset 0 _must_ be in line 1 } - /* - * Find the last chunk before the one containing our line. Last chunk is - * special because it will never qualify - */ + // Find the last chunk before the one containing our line. Last chunk is + // special because it will never qualify curline = 1; curix = 0; size = 0; @@ -4163,7 +3948,7 @@ void goto_byte(long cnt) curwin->w_cursor.lnum = lnum; curwin->w_cursor.col = (colnr_T)boff; curwin->w_cursor.coladd = 0; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } check_cursor(); @@ -4181,7 +3966,7 @@ int inc(pos_T *lp) { // when searching position may be set to end of a line if (lp->col != MAXCOL) { - const char_u *const p = ml_get_pos(lp); + const char *const p = ml_get_pos(lp); if (*p != NUL) { // still within line, move to next char (may be NUL) const int l = utfc_ptr2len((char *)p); @@ -4214,8 +3999,8 @@ int dec(pos_T *lp) lp->coladd = 0; if (lp->col == MAXCOL) { // past end of line - char_u *p = ml_get(lp->lnum); - lp->col = (colnr_T)STRLEN(p); + char *p = ml_get(lp->lnum); + lp->col = (colnr_T)strlen(p); lp->col -= utf_head_off(p, p + lp->col); return 0; } @@ -4223,15 +4008,15 @@ int dec(pos_T *lp) if (lp->col > 0) { // still within line lp->col--; - char_u *p = ml_get(lp->lnum); + char *p = ml_get(lp->lnum); lp->col -= utf_head_off(p, p + lp->col); return 0; } if (lp->lnum > 1) { // there is a prior line lp->lnum--; - char_u *p = ml_get(lp->lnum); - lp->col = (colnr_T)STRLEN(p); + char *p = ml_get(lp->lnum); + lp->col = (colnr_T)strlen(p); lp->col -= utf_head_off(p, p + lp->col); return 1; } diff --git a/src/nvim/memline.h b/src/nvim/memline.h index 441adf3e87..f4190f0210 100644 --- a/src/nvim/memline.h +++ b/src/nvim/memline.h @@ -1,8 +1,8 @@ #ifndef NVIM_MEMLINE_H #define NVIM_MEMLINE_H -#include "nvim/buffer_defs.h" // for buf_T -#include "nvim/pos.h" // for pos_T, linenr_T, colnr_T +#include "nvim/buffer_defs.h" +#include "nvim/pos.h" #include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h index 922a2c98d1..6bb9255909 100644 --- a/src/nvim/memline_defs.h +++ b/src/nvim/memline_defs.h @@ -56,7 +56,7 @@ typedef struct memline { int ml_flags; linenr_T ml_line_lnum; // line number of cached line, 0 if not valid - char_u *ml_line_ptr; // pointer to cached line + char *ml_line_ptr; // pointer to cached line size_t ml_line_offset; // cached byte offset of ml_line_lnum int ml_line_offset_ff; // fileformat of cached line @@ -69,4 +69,4 @@ typedef struct memline { int ml_usedchunks; } memline_T; -#endif // NVIM_MEMLINE_DEFS_H +#endif // NVIM_MEMLINE_DEFS_H diff --git a/src/nvim/memory.c b/src/nvim/memory.c index fb36d4ccf4..5356300382 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -6,23 +6,32 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> #include <string.h> +#include <time.h> #include "nvim/api/extmark.h" #include "nvim/arglist.h" +#include "nvim/ascii.h" +#include "nvim/buffer_updates.h" #include "nvim/context.h" #include "nvim/decoration_provider.h" #include "nvim/eval.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/insexpand.h" #include "nvim/lua/executor.h" +#include "nvim/main.h" #include "nvim/mapping.h" #include "nvim/memfile.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/sign.h" #include "nvim/ui.h" +#include "nvim/usercmd.h" #include "nvim/vim.h" #ifdef UNIT_TESTING @@ -60,6 +69,8 @@ void try_to_free_memory(void) // Try to save all buffers and release as many blocks as possible mf_release_all(); + arena_free_reuse_blks(); + trying_to_free = false; } @@ -110,8 +121,8 @@ void *xmalloc(size_t size) { void *ret = try_malloc(size); if (!ret) { - mch_errmsg(e_outofmem); - mch_errmsg("\n"); + os_errmsg(e_outofmem); + os_errmsg("\n"); preserve_exit(); } return ret; @@ -141,8 +152,8 @@ void *xcalloc(size_t count, size_t size) try_to_free_memory(); ret = calloc(allocated_count, allocated_size); if (!ret) { - mch_errmsg(e_outofmem); - mch_errmsg("\n"); + os_errmsg(e_outofmem); + os_errmsg("\n"); preserve_exit(); } } @@ -163,8 +174,8 @@ void *xrealloc(void *ptr, size_t size) try_to_free_memory(); ret = realloc(ptr, allocated_size); if (!ret) { - mch_errmsg(e_outofmem); - mch_errmsg("\n"); + os_errmsg(e_outofmem); + os_errmsg("\n"); preserve_exit(); } } @@ -183,7 +194,7 @@ void *xmallocz(size_t size) { size_t total_size = size + 1; if (total_size < size) { - mch_errmsg(_("Vim: Data too large to fit into virtual memory space\n")); + os_errmsg(_("Vim: Data too large to fit into virtual memory space\n")); preserve_exit(); } @@ -435,9 +446,8 @@ char *xstrdupnul(const char *const str) { if (str == NULL) { return xmallocz(0); - } else { - return xstrdup(str); } + return xstrdup(str); } /// A version of memchr that starts the search at `src + len`. @@ -499,22 +509,22 @@ bool striequal(const char *a, const char *b) return (a == NULL && b == NULL) || (a && b && STRICMP(a, b) == 0); } -/* - * Avoid repeating the error message many times (they take 1 second each). - * Did_outofmem_msg is reset when a character is read. - */ +// Avoid repeating the error message many times (they take 1 second each). +// Did_outofmem_msg is reset when a character is read. void do_outofmem_msg(size_t size) { - if (!did_outofmem_msg) { - // Don't hide this message - emsg_silent = 0; + if (did_outofmem_msg) { + return; + } - /* Must come first to avoid coming back here when printing the error - * message fails, e.g. when setting v:errmsg. */ - did_outofmem_msg = true; + // Don't hide this message + emsg_silent = 0; - semsg(_("E342: Out of memory! (allocating %" PRIu64 " bytes)"), (uint64_t)size); - } + // Must come first to avoid coming back here when printing the error + // message fails, e.g. when setting v:errmsg. + did_outofmem_msg = true; + + semsg(_("E342: Out of memory! (allocating %" PRIu64 " bytes)"), (uint64_t)size); } /// Writes time_t to "buf[8]". @@ -528,24 +538,22 @@ void time_to_bytes(time_t time_, uint8_t buf[8]) } #define ARENA_BLOCK_SIZE 4096 +#define REUSE_MAX 4 -void arena_start(Arena *arena, ArenaMem *reuse_blk) +static struct consumed_blk *arena_reuse_blk; +static size_t arena_reuse_blk_count = 0; + +static void arena_free_reuse_blks(void) { - if (reuse_blk && *reuse_blk) { - arena->cur_blk = (char *)(*reuse_blk); - *reuse_blk = NULL; - } else { - arena->cur_blk = xmalloc(ARENA_BLOCK_SIZE); + while (arena_reuse_blk_count > 0) { + struct consumed_blk *blk = arena_reuse_blk; + arena_reuse_blk = arena_reuse_blk->prev; + xfree(blk); + arena_reuse_blk_count--; } - arena->pos = 0; - arena->size = ARENA_BLOCK_SIZE; - // address is the same as as (struct consumed_blk *)arena->cur_blk - struct consumed_blk *blk = arena_alloc(arena, sizeof(struct consumed_blk), true); - assert((char *)blk == (char *)arena->cur_blk); - blk->prev = NULL; } -/// Finnish the allocations in an arena. +/// Finish the allocations in an arena. /// /// This does not immediately free the memory, but leaves existing allocated /// objects valid, and returns an opaque ArenaMem handle, which can be used to @@ -558,47 +566,79 @@ ArenaMem arena_finish(Arena *arena) return res; } +void alloc_block(Arena *arena) +{ + struct consumed_blk *prev_blk = (struct consumed_blk *)arena->cur_blk; + if (arena_reuse_blk_count > 0) { + arena->cur_blk = (char *)arena_reuse_blk; + arena_reuse_blk = arena_reuse_blk->prev; + arena_reuse_blk_count--; + } else { + arena_alloc_count++; + arena->cur_blk = xmalloc(ARENA_BLOCK_SIZE); + } + arena->pos = 0; + arena->size = ARENA_BLOCK_SIZE; + struct consumed_blk *blk = arena_alloc(arena, sizeof(struct consumed_blk), true); + blk->prev = prev_blk; +} + +static size_t arena_align_offset(uint64_t off) +{ + return ((off + (ARENA_ALIGN - 1)) & ~(ARENA_ALIGN - 1)); +} + +/// @param arena if NULL, do a global allocation. caller must then free the value! +/// @param size if zero, will still return a non-null pointer, but not a usable or unique one void *arena_alloc(Arena *arena, size_t size, bool align) { - if (align) { - arena->pos = (arena->pos + (ARENA_ALIGN - 1)) & ~(ARENA_ALIGN - 1); + if (!arena) { + return xmalloc(size); } - if (arena->pos + size > arena->size) { - if (size > (arena->size - sizeof(struct consumed_blk)) >> 1) { + if (!arena->cur_blk) { + alloc_block(arena); + } + size_t alloc_pos = align ? arena_align_offset(arena->pos) : arena->pos; + if (alloc_pos + size > arena->size) { + if (size > (ARENA_BLOCK_SIZE - sizeof(struct consumed_blk)) >> 1) { // if allocation is too big, allocate a large block with the requested // size, but still with block pointer head. We do this even for // arena->size / 2, as there likely is space left for the next // small allocation in the current block. - char *alloc = xmalloc(size + sizeof(struct consumed_blk)); + arena_alloc_count++; + size_t hdr_size = sizeof(struct consumed_blk); + size_t aligned_hdr_size = (align ? arena_align_offset(hdr_size) : hdr_size); + char *alloc = xmalloc(size + aligned_hdr_size); + + // to simplify free-list management, arena->cur_blk must + // always be a normal, ARENA_BLOCK_SIZE sized, block struct consumed_blk *cur_blk = (struct consumed_blk *)arena->cur_blk; struct consumed_blk *fix_blk = (struct consumed_blk *)alloc; fix_blk->prev = cur_blk->prev; cur_blk->prev = fix_blk; - return (alloc + sizeof(struct consumed_blk)); + return alloc + aligned_hdr_size; } else { - struct consumed_blk *prev_blk = (struct consumed_blk *)arena->cur_blk; - arena->cur_blk = xmalloc(ARENA_BLOCK_SIZE); - arena->pos = 0; - arena->size = ARENA_BLOCK_SIZE; - struct consumed_blk *blk = arena_alloc(arena, sizeof(struct consumed_blk), true); - blk->prev = prev_blk; + alloc_block(arena); // resets arena->pos + alloc_pos = align ? arena_align_offset(arena->pos) : arena->pos; } } - char *mem = arena->cur_blk + arena->pos; - arena->pos += size; + char *mem = arena->cur_blk + alloc_pos; + arena->pos = alloc_pos + size; return mem; } -void arena_mem_free(ArenaMem mem, ArenaMem *reuse_blk) +void arena_mem_free(ArenaMem mem) { struct consumed_blk *b = mem; // peel of the first block, as it is guaranteed to be ARENA_BLOCK_SIZE, // not a custom fix_blk - if (reuse_blk && *reuse_blk == NULL && b != NULL) { - *reuse_blk = b; + if (arena_reuse_blk_count < REUSE_MAX && b != NULL) { + struct consumed_blk *reuse_blk = b; b = b->prev; - (*reuse_blk)->prev = NULL; + reuse_blk->prev = arena_reuse_blk; + arena_reuse_blk = reuse_blk; + arena_reuse_blk_count++; } while (b) { @@ -620,7 +660,6 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size) # include "nvim/autocmd.h" # include "nvim/buffer.h" -# include "nvim/charset.h" # include "nvim/cmdhist.h" # include "nvim/diff.h" # include "nvim/edit.h" @@ -629,33 +668,24 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size) # include "nvim/ex_docmd.h" # include "nvim/ex_getln.h" # include "nvim/file_search.h" -# include "nvim/fold.h" # include "nvim/getchar.h" # include "nvim/grid.h" # include "nvim/mark.h" -# include "nvim/mbyte.h" -# include "nvim/memline.h" -# include "nvim/move.h" # include "nvim/ops.h" # include "nvim/option.h" # include "nvim/os/os.h" -# include "nvim/os_unix.h" -# include "nvim/path.h" # include "nvim/quickfix.h" # include "nvim/regexp.h" # include "nvim/search.h" # include "nvim/spell.h" -# include "nvim/syntax.h" # include "nvim/tag.h" # include "nvim/window.h" -/* - * Free everything that we allocated. - * Can be used to detect memory leaks, e.g., with ccmalloc. - * NOTE: This is tricky! Things are freed that functions depend on. Don't be - * surprised if Vim crashes... - * Some things can't be freed, esp. things local to a library function. - */ +// Free everything that we allocated. +// Can be used to detect memory leaks, e.g., with ccmalloc. +// NOTE: This is tricky! Things are freed that functions depend on. Don't be +// surprised if Vim crashes... +// Some things can't be freed, esp. things local to a library function. void free_all_mem(void) { buf_T *buf, *nextbuf; @@ -782,6 +812,11 @@ void free_all_mem(void) bufref_T bufref; set_bufref(&bufref, buf); nextbuf = buf->b_next; + + // Since options (in addition to other stuff) have been freed above we need to ensure no + // callbacks are called, so free them before closing the buffer. + buf_free_callbacks(buf); + close_buffer(NULL, buf, DOBUF_WIPE, false, false); // Didn't work, try next one. buf = bufref_valid(&bufref) ? nextbuf : firstbuf; @@ -797,8 +832,11 @@ void free_all_mem(void) decor_free_all_mem(); - nlua_free_all_mem(); ui_free_all_mem(); + nlua_free_all_mem(); + + // should be last, in case earlier free functions deallocates arenas + arena_free_reuse_blks(); } #endif diff --git a/src/nvim/memory.h b/src/nvim/memory.h index 63d607c2ce..5b9798dc0d 100644 --- a/src/nvim/memory.h +++ b/src/nvim/memory.h @@ -1,10 +1,12 @@ #ifndef NVIM_MEMORY_H #define NVIM_MEMORY_H -#include <stdbool.h> // for bool -#include <stddef.h> // for size_t -#include <stdint.h> // for uint8_t -#include <time.h> // for time_t +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <time.h> + +#include "nvim/macros.h" /// `malloc()` function signature typedef void *(*MemMalloc)(size_t); @@ -37,18 +39,20 @@ extern MemRealloc mem_realloc; extern bool entered_free_all_mem; #endif +EXTERN size_t arena_alloc_count INIT(= 0); + typedef struct consumed_blk { struct consumed_blk *prev; } *ArenaMem; -#define ARENA_ALIGN sizeof(void *) +#define ARENA_ALIGN MAX(sizeof(void *), sizeof(double)) typedef struct { char *cur_blk; size_t pos, size; } Arena; -// inits an empty arena. use arena_start() to actually allocate space! +// inits an empty arena. #define ARENA_EMPTY { .cur_blk = NULL, .pos = 0, .size = 0 } #define kv_fixsize_arena(a, v, s) \ diff --git a/src/nvim/menu.c b/src/nvim/menu.c index c3cf4457fc..2a18b08d8d 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -1,35 +1,45 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * Code for menus. Used for the GUI and 'wildmenu'. - * GUI/Motif support by Robert Webb - */ +// Code for menus. Used for the GUI and 'wildmenu'. +// GUI/Motif support by Robert Webb #include <assert.h> -#include <inttypes.h> +#include <stdbool.h> +#include <stdint.h> #include <string.h> #include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/keycodes.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/menu.h" +#include "nvim/menu_defs.h" #include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/popupmenu.h" -#include "nvim/screen.h" +#include "nvim/pos.h" #include "nvim/state.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" +#include "nvim/undo_defs.h" #include "nvim/vim.h" -#include "nvim/window.h" #define MENUDEPTH 10 // maximum depth of menus @@ -47,7 +57,7 @@ static char e_nomenu[] = N_("E329: No menu \"%s\""); static bool menu_is_winbar(const char *const name) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - return (STRNCMP(name, "WinBar", 6) == 0); + return (strncmp(name, "WinBar", 6) == 0); } static vimmenu_T **get_root_menu(const char *const name) @@ -79,17 +89,17 @@ void ex_menu(exarg_T *eap) arg = eap->arg; for (;;) { - if (STRNCMP(arg, "<script>", 8) == 0) { + if (strncmp(arg, "<script>", 8) == 0) { noremap = REMAP_SCRIPT; arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<silent>", 8) == 0) { + if (strncmp(arg, "<silent>", 8) == 0) { silent = true; arg = skipwhite(arg + 8); continue; } - if (STRNCMP(arg, "<special>", 9) == 0) { + if (strncmp(arg, "<special>", 9) == 0) { // Ignore obsolete "<special>" modifier. arg = skipwhite(arg + 9); continue; @@ -99,7 +109,7 @@ void ex_menu(exarg_T *eap) // Locate an optional "icon=filename" argument // TODO(nvim): Currently this is only parsed. Should expose it to UIs. - if (STRNCMP(arg, "icon=", 5) == 0) { + if (strncmp(arg, "icon=", 5) == 0) { arg += 5; while (*arg != NUL && *arg != ' ') { if (*arg == '\\') { @@ -141,20 +151,16 @@ void ex_menu(exarg_T *eap) } pri_tab[MENUDEPTH] = -1; // mark end of the table - /* - * Check for "disable" or "enable" argument. - */ - if (STRNCMP(arg, "enable", 6) == 0 && ascii_iswhite(arg[6])) { + // Check for "disable" or "enable" argument. + if (strncmp(arg, "enable", 6) == 0 && ascii_iswhite(arg[6])) { enable = kTrue; arg = skipwhite(arg + 6); - } else if (STRNCMP(arg, "disable", 7) == 0 && ascii_iswhite(arg[7])) { + } else if (strncmp(arg, "disable", 7) == 0 && ascii_iswhite(arg[7])) { enable = kFalse; arg = skipwhite(arg + 7); } - /* - * If there is no argument, display all menus. - */ + // If there is no argument, display all menus. if (*arg == NUL) { show_menus(arg, modes); return; @@ -168,9 +174,7 @@ void ex_menu(exarg_T *eap) map_to = menu_translate_tab_and_shift(arg); - /* - * If there is only a menu name, display menus with that name. - */ + // If there is only a menu name, display menus with that name. if (*map_to == NUL && !unmenu && enable == kNone) { show_menus(menu_path, modes); goto theend; @@ -185,7 +189,7 @@ void ex_menu(exarg_T *eap) // Change sensitivity of the menu. // For the PopUp menu, remove a menu for each mode separately. // Careful: menu_enable_recurse() changes menu_path. - if (STRCMP(menu_path, "*") == 0) { // meaning: do all menus + if (strcmp(menu_path, "*") == 0) { // meaning: do all menus menu_path = ""; } @@ -200,16 +204,12 @@ void ex_menu(exarg_T *eap) } menu_enable_recurse(*root_menu_ptr, menu_path, modes, enable); } else if (unmenu) { - /* - * Delete menu(s). - */ - if (STRCMP(menu_path, "*") == 0) { // meaning: remove all menus + // Delete menu(s). + if (strcmp(menu_path, "*") == 0) { // meaning: remove all menus menu_path = ""; } - /* - * For the PopUp menu, remove a menu for each mode separately. - */ + // For the PopUp menu, remove a menu for each mode separately. if (menu_is_popup(menu_path)) { for (i = 0; i < MENU_INDEX_TIP; i++) { if (modes & (1 << i)) { @@ -223,10 +223,8 @@ void ex_menu(exarg_T *eap) // Careful: remove_menu() changes menu_path remove_menu(root_menu_ptr, menu_path, modes, false); } else { - /* - * Add menu(s). - * Replace special key codes. - */ + // Add menu(s). + // Replace special key codes. if (STRICMP(map_to, "<nop>") == 0) { // "<Nop>" means nothing map_to = ""; map_buf = NULL; @@ -234,7 +232,7 @@ void ex_menu(exarg_T *eap) map_buf = NULL; // Menu tips are plain text. } else { map_buf = NULL; - map_to = replace_termcodes(map_to, STRLEN(map_to), &map_buf, + map_to = replace_termcodes(map_to, strlen(map_to), &map_buf, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS); } menuarg.modes = modes; @@ -242,9 +240,7 @@ void ex_menu(exarg_T *eap) menuarg.silent[0] = silent; add_menu_path(menu_path, &menuarg, pri_tab, map_to); - /* - * For the PopUp menu, add a menu for each mode separately. - */ + // For the PopUp menu, add a menu for each mode separately. if (menu_is_popup(menu_path)) { for (i = 0; i < MENU_INDEX_TIP; i++) { if (modes & (1 << i)) { @@ -302,7 +298,7 @@ static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const // Get name of this element in the menu hierarchy, and the simplified // name (without mnemonic and accelerator text). next_name = menu_name_skip(name); - map_to = menutrans_lookup(name, (int)STRLEN(name)); + map_to = menutrans_lookup(name, (int)strlen(name)); if (map_to != NULL) { en_name = name; name = map_to; @@ -384,11 +380,9 @@ static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const } else { old_modes = menu->modes; - /* - * If this menu option was previously only available in other - * modes, then make sure it's available for this one now - * Also enable a menu when it's created or changed. - */ + // If this menu option was previously only available in other + // modes, then make sure it's available for this one now + // Also enable a menu when it's created or changed. { menu->modes |= modes; menu->enabled |= modes; @@ -405,10 +399,8 @@ static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const } xfree(path_name); - /* - * Only add system menu items which have not been defined yet. - * First check if this was an ":amenu". - */ + // Only add system menu items which have not been defined yet. + // First check if this was an ":amenu". amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) == (MENU_NORMAL_MODE | MENU_INSERT_MODE)); if (sys_menu) { @@ -419,7 +411,7 @@ static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const p = (call_data == NULL) ? NULL : xstrdup(call_data); // loop over all modes, may add more than one - for (i = 0; i < MENU_MODES; ++i) { + for (i = 0; i < MENU_MODES; i++) { if (modes & (1 << i)) { // free any old menu free_menu_string(menu, i); @@ -444,7 +436,7 @@ static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const } if (c != 0) { - menu->strings[i] = xmalloc(STRLEN(call_data) + 5); + menu->strings[i] = xmalloc(strlen(call_data) + 5); menu->strings[i][0] = c; if (d == 0) { STRCPY(menu->strings[i] + 1, call_data); @@ -453,7 +445,7 @@ static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const STRCPY(menu->strings[i] + 2, call_data); } if (c == Ctrl_C) { - int len = (int)STRLEN(menu->strings[i]); + int len = (int)strlen(menu->strings[i]); menu->strings[i][len] = Ctrl_BSL; menu->strings[i][len + 1] = Ctrl_G; @@ -491,10 +483,8 @@ erret: return FAIL; } -/* - * Set the (sub)menu with the given name to enabled or disabled. - * Called recursively. - */ +// Set the (sub)menu with the given name to enabled or disabled. +// Called recursively. static int menu_enable_recurse(vimmenu_T *menu, char *name, int modes, int enable) { char *p; @@ -522,11 +512,9 @@ static int menu_enable_recurse(vimmenu_T *menu, char *name, int modes, int enabl menu->enabled &= ~modes; } - /* - * When name is empty, we are doing all menu items for the given - * modes, so keep looping, otherwise we are just doing the named - * menu item (which has been found) so break here. - */ + // When name is empty, we are doing all menu items for the given + // modes, so keep looping, otherwise we are just doing the named + // menu item (which has been found) so break here. if (*name != NUL && *name != '*') { break; } @@ -577,11 +565,9 @@ static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent) return FAIL; } - /* - * When name is empty, we are removing all menu items for the given - * modes, so keep looping, otherwise we are just removing the named - * menu item (which has been found) so break here. - */ + // When name is empty, we are removing all menu items for the given + // modes, so keep looping, otherwise we are just removing the named + // menu item (which has been found) so break here. if (*name != NUL) { break; } @@ -628,9 +614,7 @@ static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent) return OK; } -/* - * Free the given menu structure and remove it from the linked list. - */ +// Free the given menu structure and remove it from the linked list. static void free_menu(vimmenu_T **menup) { int i; @@ -652,9 +636,7 @@ static void free_menu(vimmenu_T **menup) xfree(menu); } -/* - * Free the menu->string with the given index. - */ +// Free the menu->string with the given index. static void free_menu_string(vimmenu_T *menu, int idx) { int count = 0; @@ -715,8 +697,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) if ((menu->modes & modes & (1 << bit)) != 0) { dict_T *impl = tv_dict_alloc(); tv_dict_add_allocated_str(impl, S_LEN("rhs"), - str2special_save(menu->strings[bit], - false, false)); + str2special_save(menu->strings[bit], false, false)); tv_dict_add_nr(impl, S_LEN("silent"), menu->silent[bit]); tv_dict_add_nr(impl, S_LEN("enabled"), (menu->enabled & (1 << bit)) ? 1 : 0); @@ -853,7 +834,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) msg_puts(" "); } // Same highlighting as for directories!? - msg_outtrans_attr((char_u *)menu->name, HL_ATTR(HLF_D)); + msg_outtrans_attr(menu->name, HL_ATTR(HLF_D)); } if (menu != NULL && menu->children == NULL) { @@ -888,7 +869,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) if (*menu->strings[bit] == NUL) { msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); } else { - msg_outtrans_special((char_u *)menu->strings[bit], false, 0); + msg_outtrans_special(menu->strings[bit], false, 0); } } } @@ -909,16 +890,12 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth) } } -/* - * Used when expanding menu names. - */ +// Used when expanding menu names. static vimmenu_T *expand_menu = NULL; static int expand_modes = 0x0; -static int expand_emenu; // TRUE for ":emenu" command +static int expand_emenu; // true for ":emenu" command -/* - * Work out what to complete when doing command line completion of menu names. - */ +// Work out what to complete when doing command line completion of menu names. char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool forceit) FUNC_ATTR_NONNULL_ALL { @@ -933,17 +910,17 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for xp->xp_context = EXPAND_UNSUCCESSFUL; // Check for priority numbers, enable and disable - for (p = arg; *p; ++p) { + for (p = arg; *p; p++) { if (!ascii_isdigit(*p) && *p != '.') { break; } } if (!ascii_iswhite(*p)) { - if (STRNCMP(arg, "enable", 6) == 0 + if (strncmp(arg, "enable", 6) == 0 && (arg[6] == NUL || ascii_iswhite(arg[6]))) { p = arg + 6; - } else if (STRNCMP(arg, "disable", 7) == 0 + } else if (strncmp(arg, "disable", 7) == 0 && (arg[7] == NUL || ascii_iswhite(arg[7]))) { p = arg + 7; } else { @@ -957,7 +934,7 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for arg = after_dot = p; - for (; *p && !ascii_iswhite(*p); ++p) { + for (; *p && !ascii_iswhite(*p); p++) { if ((*p == '\\' || *p == Ctrl_V) && p[1] != NUL) { p++; } else if (*p == '.') { @@ -984,7 +961,7 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for if (after_dot > arg) { size_t path_len = (size_t)(after_dot - arg); path_name = xmalloc(path_len); - STRLCPY(path_name, arg, path_len); + xstrlcpy(path_name, arg, path_len); } name = path_name; while (name != NULL && *name) { @@ -994,10 +971,8 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for // Found menu if ((*p != NUL && menu->children == NULL) || ((menu->modes & expand_modes) == 0x0)) { - /* - * Menu path continues, but we have reached a leaf. - * Or menu exists only in another mode. - */ + // Menu path continues, but we have reached a leaf. + // Or menu exists only in another mode. xfree(path_name); return NULL; } @@ -1024,10 +999,8 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for return NULL; } -/* - * Function given to ExpandGeneric() to obtain the list of (sub)menus (not - * entries). - */ +// Function given to ExpandGeneric() to obtain the list of (sub)menus (not +// entries). char *get_menu_name(expand_T *xp, int idx) { static vimmenu_T *menu = NULL; @@ -1056,7 +1029,7 @@ char *get_menu_name(expand_T *xp, int idx) } else { str = menu->dname; if (menu->en_dname == NULL) { - should_advance = TRUE; + should_advance = true; } } } else { @@ -1073,10 +1046,8 @@ char *get_menu_name(expand_T *xp, int idx) return str; } -/* - * Function given to ExpandGeneric() to obtain the list of menus and menu - * entries. - */ +// Function given to ExpandGeneric() to obtain the list of menus and menu +// entries. char *get_menu_names(expand_T *xp, int idx) { static vimmenu_T *menu = NULL; @@ -1094,7 +1065,7 @@ char *get_menu_names(expand_T *xp, int idx) while (menu != NULL && (menu_is_hidden(menu->dname) || (expand_emenu && menu_is_separator(menu->dname)) - || menu->dname[STRLEN(menu->dname) - 1] == '.')) { + || menu->dname[strlen(menu->dname) - 1] == '.')) { menu = menu->next; } @@ -1105,9 +1076,9 @@ char *get_menu_names(expand_T *xp, int idx) if (menu->modes & expand_modes) { if (menu->children != NULL) { if (should_advance) { - STRLCPY(tbuffer, menu->en_dname, TBUFFER_LEN); + xstrlcpy(tbuffer, menu->en_dname, TBUFFER_LEN); } else { - STRLCPY(tbuffer, menu->dname, TBUFFER_LEN); + xstrlcpy(tbuffer, menu->dname, TBUFFER_LEN); if (menu->en_dname == NULL) { should_advance = true; } @@ -1163,10 +1134,8 @@ char *menu_name_skip(char *const name) return p; } -/* - * Return TRUE when "name" matches with menu "menu". The name is compared in - * two ways: raw menu name and menu name without '&'. ignore part after a TAB. - */ +/// Return true when "name" matches with menu "menu". The name is compared in +/// two ways: raw menu name and menu name without '&'. ignore part after a TAB. static bool menu_name_equal(const char *const name, const vimmenu_T *const menu) { if (menu->en_name != NULL @@ -1181,7 +1150,7 @@ static bool menu_namecmp(const char *const name, const char *const mname) { int i; - for (i = 0; name[i] != NUL && name[i] != TAB; ++i) { + for (i = 0; name[i] != NUL && name[i] != TAB; i++) { if (name[i] != mname[i]) { break; } @@ -1314,13 +1283,11 @@ static char *get_menu_mode_str(int modes) return ""; } -/* - * Modify a menu name starting with "PopUp" to include the mode character. - * Returns the name in allocated memory. - */ +// Modify a menu name starting with "PopUp" to include the mode character. +// Returns the name in allocated memory. static char *popup_mode_name(char *name, int idx) { - size_t len = STRLEN(name); + size_t len = strlen(name); assert(len >= 4); char *mode_chars = menu_mode_chars[idx]; @@ -1372,7 +1339,7 @@ static char *menu_text(const char *str, int *mnemonic, char **actext) break; } if (mnemonic != NULL && p[1] != '&') { - *mnemonic = (char_u)p[1]; + *mnemonic = (uint8_t)p[1]; } STRMOVE(p, p + 1); p = p + 1; @@ -1395,23 +1362,21 @@ bool menu_is_menubar(const char *const name) bool menu_is_popup(const char *const name) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - return STRNCMP(name, "PopUp", 5) == 0; + return strncmp(name, "PopUp", 5) == 0; } // Return true if "name" is a toolbar menu name. bool menu_is_toolbar(const char *const name) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - return STRNCMP(name, "ToolBar", 7) == 0; + return strncmp(name, "ToolBar", 7) == 0; } -/* - * Return TRUE if the name is a menu separator identifier: Starts and ends - * with '-' - */ +/// Return true if the name is a menu separator identifier: Starts and ends +/// with '-' int menu_is_separator(char *name) { - return name[0] == '-' && name[STRLEN(name) - 1] == '-'; + return name[0] == '-' && name[strlen(name) - 1] == '-'; } /// True if a popup menu or starts with \ref MNU_HIDDEN_CHAR @@ -1479,15 +1444,17 @@ void show_popupmenu(void) vimmenu_T *menu; for (menu = root_menu; menu != NULL; menu = menu->next) { - if (STRNCMP("PopUp", menu->name, 5) == 0 && STRNCMP(menu->name + 5, mode, mode_len) == 0) { + if (strncmp("PopUp", menu->name, 5) == 0 && strncmp(menu->name + 5, mode, mode_len) == 0) { break; } } // Only show a popup when it is defined and has entries - if (menu != NULL && menu->children != NULL) { - pum_show_popupmenu(menu); + if (menu == NULL || menu->children == NULL) { + return; } + + pum_show_popupmenu(menu); } /// Execute "menu". Use by ":emenu" and the window toolbar. @@ -1567,7 +1534,7 @@ void execute_menu(const exarg_T *eap, vimmenu_T *menu, int mode_idx) ex_normal_busy++; if (save_current_state(&save_state)) { - exec_normal_cmd((char_u *)menu->strings[idx], menu->noremap[idx], + exec_normal_cmd(menu->strings[idx], menu->noremap[idx], menu->silent[idx]); } restore_current_state(&save_state); @@ -1734,9 +1701,7 @@ theend: return menu; } -/* - * Translation of menu names. Just a simple lookup table. - */ +// Translation of menu names. Just a simple lookup table. typedef struct { char *from; // English name @@ -1752,11 +1717,9 @@ static garray_T menutrans_ga = GA_EMPTY_INIT_VALUE; xfree(_mt->from_noamp); \ xfree(_mt->to) -/* - * ":menutrans". - * This function is also defined without the +multi_lang feature, in which - * case the commands are ignored. - */ +// ":menutrans". +// This function is also defined without the +multi_lang feature, in which +// case the commands are ignored. void ex_menutranslate(exarg_T *eap) { char *arg = eap->arg; @@ -1766,10 +1729,8 @@ void ex_menutranslate(exarg_T *eap) ga_init(&menutrans_ga, (int)sizeof(menutrans_T), 5); } - /* - * ":menutrans clear": clear all translations. - */ - if (STRNCMP(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5))) { + // ":menutrans clear": clear all translations. + if (strncmp(arg, "clear", 5) == 0 && ends_excmd(*skipwhite(arg + 5))) { GA_DEEP_CLEAR(&menutrans_ga, menutrans_T, FREE_MENUTRANS); // Delete all "menutrans_" global variables. @@ -1800,9 +1761,7 @@ void ex_menutranslate(exarg_T *eap) } } -/* - * Find the character just after one part of a menu name. - */ +// Find the character just after one part of a menu name. static char *menu_skip_part(char *p) { while (*p != NUL && *p != '.' && !ascii_iswhite(*p)) { @@ -1814,10 +1773,8 @@ static char *menu_skip_part(char *p) return p; } -/* - * Lookup part of a menu name in the translations. - * Return a pointer to the translation or NULL if not found. - */ +// Lookup part of a menu name in the translations. +// Return a pointer to the translation or NULL if not found. static char *menutrans_lookup(char *name, int len) { menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data; @@ -1845,9 +1802,7 @@ static char *menutrans_lookup(char *name, int len) return NULL; } -/* - * Unescape the name in the translate dictionary table. - */ +// Unescape the name in the translate dictionary table. static void menu_unescape_name(char *name) { char *p; @@ -1859,10 +1814,8 @@ static void menu_unescape_name(char *name) } } -/* - * Isolate the menu name. - * Skip the menu name, and translate <Tab> into a real TAB. - */ +// Isolate the menu name. +// Skip the menu name, and translate <Tab> into a real TAB. static char *menu_translate_tab_and_shift(char *arg_start) { char *arg = arg_start; @@ -1945,7 +1898,7 @@ static void menuitem_getinfo(const char *menu_name, const vimmenu_T *menu, int m /// "menu_info()" function /// Return information about a menu (including all the child menus) -void f_menu_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_menu_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { tv_dict_alloc_ret(rettv); dict_T *const retdict = rettv->vval.v_dict; diff --git a/src/nvim/menu.h b/src/nvim/menu.h index be294a1831..32959cf35f 100644 --- a/src/nvim/menu.h +++ b/src/nvim/menu.h @@ -1,11 +1,11 @@ #ifndef NVIM_MENU_H #define NVIM_MENU_H -#include <stdbool.h> // for bool +#include <stdbool.h> -#include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/ex_cmds_defs.h" #include "nvim/menu_defs.h" -#include "nvim/types.h" // for char_u and expand_T +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "menu.h.generated.h" diff --git a/src/nvim/menu_defs.h b/src/nvim/menu_defs.h index 5fdb222bde..79b267ae49 100644 --- a/src/nvim/menu_defs.h +++ b/src/nvim/menu_defs.h @@ -1,7 +1,7 @@ #ifndef NVIM_MENU_DEFS_H #define NVIM_MENU_DEFS_H -#include <stdbool.h> // for bool +#include <stdbool.h> /// Indices into vimmenu_T->strings[] and vimmenu_T->noremap[] for each mode /// \addtogroup MENU_INDEX diff --git a/src/nvim/message.c b/src/nvim/message.c index 684cf7207c..7f29b19031 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -1,122 +1,127 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * message.c: functions for displaying messages on the command line - */ +// message.c: functions for displaying messages on the command line #include <assert.h> #include <inttypes.h> #include <stdarg.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" -#include "nvim/assert.h" +#include "nvim/buffer_defs.h" +#include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" -#include "nvim/ex_docmd.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/event/defs.h" +#include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_eval.h" -#include "nvim/ex_getln.h" #include "nvim/fileio.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/indent.h" #include "nvim/input.h" #include "nvim/keycodes.h" +#include "nvim/log.h" #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/mouse.h" -#include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/time.h" +#include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/runtime.h" +#include "nvim/screen.h" #include "nvim/strings.h" -#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/vim.h" -/* - * To be able to scroll back at the "more" and "hit-enter" prompts we need to - * store the displayed text and remember where screen lines start. - */ +// To be able to scroll back at the "more" and "hit-enter" prompts we need to +// store the displayed text and remember where screen lines start. typedef struct msgchunk_S msgchunk_T; struct msgchunk_S { msgchunk_T *sb_next; msgchunk_T *sb_prev; - char sb_eol; // TRUE when line ends after this text + char sb_eol; // true when line ends after this text int sb_msg_col; // column in which text starts int sb_attr; // text attributes - char_u sb_text[1]; // text to be displayed, actually longer + char sb_text[1]; // text to be displayed, actually longer }; // Magic chars used in confirm dialog strings -#define DLG_BUTTON_SEP '\n' -#define DLG_HOTKEY_CHAR '&' +enum { + DLG_BUTTON_SEP = '\n', + DLG_HOTKEY_CHAR = '&', +}; -static int confirm_msg_used = FALSE; // displaying confirm_msg +static int confirm_msg_used = false; // displaying confirm_msg #ifdef INCLUDE_GENERATED_DECLARATIONS # include "message.c.generated.h" #endif -static char_u *confirm_msg = NULL; // ":confirm" message -static char_u *confirm_msg_tail; // tail of confirm_msg +static char *confirm_msg = NULL; // ":confirm" message +static char *confirm_msg_tail; // tail of confirm_msg MessageHistoryEntry *first_msg_hist = NULL; MessageHistoryEntry *last_msg_hist = NULL; static int msg_hist_len = 0; static FILE *verbose_fd = NULL; -static int verbose_did_open = FALSE; +static int verbose_did_open = false; bool keep_msg_more = false; // keep_msg was set by msgmore() -/* - * When writing messages to the screen, there are many different situations. - * A number of variables is used to remember the current state: - * msg_didany true when messages were written since the last time the - * user reacted to a prompt. - * Reset: After hitting a key for the hit-return prompt, - * hitting <CR> for the command line or input(). - * Set: When any message is written to the screen. - * msg_didout true when something was written to the current line. - * Reset: When advancing to the next line, when the current - * text can be overwritten. - * Set: When any message is written to the screen. - * msg_nowait No extra delay for the last drawn message. - * Used in normal_cmd() before the mode message is drawn. - * emsg_on_display There was an error message recently. Indicates that there - * should be a delay before redrawing. - * msg_scroll The next message should not overwrite the current one. - * msg_scrolled How many lines the screen has been scrolled (because of - * messages). Used in update_screen() to scroll the screen - * back. Incremented each time the screen scrolls a line. - * msg_scrolled_ign TRUE when msg_scrolled is non-zero and msg_puts_attr() - * writes something without scrolling should not make - * need_wait_return to be set. This is a hack to make ":ts" - * work without an extra prompt. - * lines_left Number of lines available for messages before the - * more-prompt is to be given. -1 when not set. - * need_wait_return true when the hit-return prompt is needed. - * Reset: After giving the hit-return prompt, when the user - * has answered some other prompt. - * Set: When the ruler or typeahead display is overwritten, - * scrolling the screen for some message. - * keep_msg Message to be displayed after redrawing the screen, in - * main_loop(). - * This is an allocated string or NULL when not used. - */ +// When writing messages to the screen, there are many different situations. +// A number of variables is used to remember the current state: +// msg_didany true when messages were written since the last time the +// user reacted to a prompt. +// Reset: After hitting a key for the hit-return prompt, +// hitting <CR> for the command line or input(). +// Set: When any message is written to the screen. +// msg_didout true when something was written to the current line. +// Reset: When advancing to the next line, when the current +// text can be overwritten. +// Set: When any message is written to the screen. +// msg_nowait No extra delay for the last drawn message. +// Used in normal_cmd() before the mode message is drawn. +// emsg_on_display There was an error message recently. Indicates that there +// should be a delay before redrawing. +// msg_scroll The next message should not overwrite the current one. +// msg_scrolled How many lines the screen has been scrolled (because of +// messages). Used in update_screen() to scroll the screen +// back. Incremented each time the screen scrolls a line. +// msg_scrolled_ign true when msg_scrolled is non-zero and msg_puts_attr() +// writes something without scrolling should not make +// need_wait_return to be set. This is a hack to make ":ts" +// work without an extra prompt. +// lines_left Number of lines available for messages before the +// more-prompt is to be given. -1 when not set. +// need_wait_return true when the hit-return prompt is needed. +// Reset: After giving the hit-return prompt, when the user +// has answered some other prompt. +// Set: When the ruler or typeahead display is overwritten, +// scrolling the screen for some message. +// keep_msg Message to be displayed after redrawing the screen, in +// main_loop(). +// This is an allocated string or NULL when not used. // Extended msg state, currently used for external UIs with ext_messages static const char *msg_ext_kind = NULL; @@ -134,7 +139,6 @@ static bool msg_ext_history_visible = false; static bool msg_ext_keep_after_cmdline = false; static int msg_grid_pos_at_flush = 0; -static int msg_grid_scroll_discount = 0; static void ui_ext_msg_set_pos(int row, bool scrolled) { @@ -159,8 +163,7 @@ void msg_grid_set_pos(int row, bool scrolled) bool msg_use_grid(void) { - return default_grid.chars && msg_use_msgsep() - && !ui_has(kUIMessages); + return default_grid.chars && !ui_has(kUIMessages); } void msg_grid_validate(void) @@ -178,19 +181,17 @@ void msg_grid_validate(void) xfree(msg_grid.dirty_col); msg_grid.dirty_col = xcalloc((size_t)Rows, sizeof(*msg_grid.dirty_col)); - // Tricky: allow resize while pager is active - int pos = msg_scrolled ? msg_grid_pos : max_rows; + // Tricky: allow resize while pager or ex mode is active + int pos = MAX(max_rows - msg_scrolled, 0); + msg_grid.throttled = false; // don't throttle in 'cmdheight' area + msg_grid_set_pos(pos, msg_scrolled); ui_comp_put_grid(&msg_grid, pos, 0, msg_grid.rows, msg_grid.cols, false, true); ui_call_grid_resize(msg_grid.handle, msg_grid.cols, msg_grid.rows); - msg_grid.throttled = false; // don't throttle in 'cmdheight' area msg_scrolled_at_flush = msg_scrolled; msg_grid.focusable = false; msg_grid_adj.target = &msg_grid; - if (!msg_scrolled) { - msg_grid_set_pos(max_rows, false); - } } else if (!should_alloc && msg_grid.chars) { ui_comp_remove_grid(&msg_grid); grid_free(&msg_grid); @@ -204,7 +205,7 @@ void msg_grid_validate(void) msg_grid_set_pos(max_rows, false); } - if (msg_grid.chars && cmdline_row < msg_grid_pos) { + if (msg_grid.chars && !msg_scrolled && cmdline_row < msg_grid_pos) { // TODO(bfredl): this should already be the case, but fails in some // "batched" executions where compute_cmdrow() use stale positions or // something. @@ -213,9 +214,9 @@ void msg_grid_validate(void) } /// Displays the string 's' on the status line -/// When terminal not initialized (yet) mch_errmsg(..) is used. +/// When terminal not initialized (yet) os_errmsg(..) is used. /// -/// @return TRUE if wait_return not called +/// @return true if wait_return() not called int msg(char *s) { return msg_attr_keep(s, 0, false, false); @@ -251,7 +252,7 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea if (next_spec != NULL) { // Printing all char that are before the char found by strpbrk - msg_outtrans_len_attr((const char_u *)s, (int)(next_spec - s), attr); + msg_outtrans_len_attr(s, (int)(next_spec - s), attr); if (*next_spec != TAB && *need_clear) { msg_clr_eos(); @@ -265,7 +266,7 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea // Print the rest of the message. We know there is no special // character because strpbrk returned NULL if (*s != NUL) { - msg_outtrans_attr((char_u *)s, attr); + msg_outtrans_attr(s, attr); } } @@ -275,12 +276,12 @@ void msg_multiattr(HlMessage hl_msg, const char *kind, bool history) msg_start(); msg_clr_eos(); bool need_clear = false; + msg_ext_set_kind(kind); for (uint32_t i = 0; i < kv_size(hl_msg); i++) { HlMessageChunk chunk = kv_A(hl_msg, i); msg_multiline_attr((const char *)chunk.text.data, chunk.attr, true, &need_clear); } - msg_ext_set_kind(kind); if (history && kv_size(hl_msg)) { add_msg_hist_multiattr(NULL, 0, 0, true, hl_msg); } @@ -294,7 +295,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) { static int entered = 0; int retval; - char_u *buf = NULL; + char *buf = NULL; if (keep && multiline) { // Not implemented. 'multiline' is only used by nvim-added messages, @@ -305,7 +306,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) // Skip messages not match ":filter pattern". // Don't filter when there is an error. - if (!emsg_on_display && message_filtered((char_u *)s)) { + if (!emsg_on_display && message_filtered((char *)s)) { return true; } @@ -313,38 +314,36 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) set_vim_var_string(VV_STATUSMSG, s, -1); } - /* - * It is possible that displaying a messages causes a problem (e.g., - * when redrawing the window), which causes another message, etc.. To - * break this loop, limit the recursiveness to 3 levels. - */ + // It is possible that displaying a messages causes a problem (e.g., + // when redrawing the window), which causes another message, etc.. To + // break this loop, limit the recursiveness to 3 levels. if (entered >= 3) { - return TRUE; + return true; } entered++; // Add message to history (unless it's a repeated kept message or a // truncated message) - if ((const char_u *)s != keep_msg + if (s != keep_msg || (*s != '<' && last_msg_hist != NULL && last_msg_hist->msg != NULL - && STRCMP(s, last_msg_hist->msg))) { + && strcmp(s, last_msg_hist->msg) != 0)) { add_msg_hist(s, -1, attr, multiline); } // Truncate the message if needed. msg_start(); - buf = msg_strtrunc((char_u *)s, FALSE); + buf = msg_strtrunc((char *)s, false); if (buf != NULL) { - s = (const char *)buf; + s = buf; } bool need_clear = true; if (multiline) { msg_multiline_attr(s, attr, false, &need_clear); } else { - msg_outtrans_attr((char_u *)s, attr); + msg_outtrans_attr(s, attr); } if (need_clear) { msg_clr_eos(); @@ -367,9 +366,9 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) /// @return an allocated string or NULL when no truncating is done. /// /// @param force always truncate -char_u *msg_strtrunc(char_u *s, int force) +char *msg_strtrunc(char *s, int force) { - char_u *buf = NULL; + char *buf = NULL; int len; int room; @@ -377,7 +376,7 @@ char_u *msg_strtrunc(char_u *s, int force) if ((!msg_scroll && !need_wait_return && shortmess(SHM_TRUNCALL) && !exmode_active && msg_silent == 0 && !ui_has(kUIMessages)) || force) { - len = vim_strsize((char *)s); + len = vim_strsize(s); if (msg_scrolled != 0) { // Use all the columns. room = (Rows - msg_row) * Columns - 1; @@ -390,7 +389,7 @@ char_u *msg_strtrunc(char_u *s, int force) // composing chars) len = (room + 2) * 18; buf = xmalloc((size_t)len); - trunc_string((char *)s, (char *)buf, room, len); + trunc_string(s, buf, room, len); } } return buf; @@ -420,7 +419,7 @@ void trunc_string(char *s, char *buf, int room_in, int buflen) half = room / 2; // First part: Start of the string. - for (e = 0; len < half && e < buflen; ++e) { + for (e = 0; len < half && e < buflen; e++) { if (s[e] == NUL) { // text fits without truncating! buf[e] = NUL; @@ -441,10 +440,10 @@ void trunc_string(char *s, char *buf, int room_in, int buflen) } // Last part: End of the string. - half = i = (int)STRLEN(s); + half = i = (int)strlen(s); for (;;) { do { - half = half - utf_head_off((char_u *)s, (char_u *)s + half - 1) - 1; + half = half - utf_head_off(s, s + half - 1) - 1; } while (half > 0 && utf_iscomposing(utf_ptr2char(s + half))); n = ptr2cells(s + half); if (len + n > room || half == 0) { @@ -457,7 +456,7 @@ void trunc_string(char *s, char *buf, int room_in, int buflen) if (i <= e + 3) { // text fits without truncating if (s != buf) { - len = (int)STRLEN(s); + len = (int)strlen(s); if (len >= buflen) { len = buflen - 1; } @@ -471,7 +470,7 @@ void trunc_string(char *s, char *buf, int room_in, int buflen) } else if (e + 3 < buflen) { // set the middle and copy the last part memmove(buf + e, "...", (size_t)3); - len = (int)STRLEN(s + i) + 1; + len = (int)strlen(s + i) + 1; if (len >= buflen - e - 3) { len = buflen - e - 3 - 1; } @@ -483,10 +482,8 @@ void trunc_string(char *s, char *buf, int room_in, int buflen) } } -/* - * Note: Caller of smsg() and smsg_attr() must check the resulting string is - * shorter than IOSIZE!!! - */ +// Note: Caller of smsg() and smsg_attr() must check the resulting string is +// shorter than IOSIZE!!! int smsg(const char *s, ...) FUNC_ATTR_PRINTF(1, 2) @@ -494,10 +491,10 @@ int smsg(const char *s, ...) va_list arglist; va_start(arglist, s); - vim_vsnprintf((char *)IObuff, IOSIZE, s, arglist); + vim_vsnprintf(IObuff, IOSIZE, s, arglist); va_end(arglist); - return msg((char *)IObuff); + return msg(IObuff); } int smsg_attr(int attr, const char *s, ...) @@ -506,7 +503,7 @@ int smsg_attr(int attr, const char *s, ...) va_list arglist; va_start(arglist, s); - vim_vsnprintf((char *)IObuff, IOSIZE, s, arglist); + vim_vsnprintf(IObuff, IOSIZE, s, arglist); va_end(arglist); return msg_attr((const char *)IObuff, attr); } @@ -517,15 +514,13 @@ int smsg_attr_keep(int attr, const char *s, ...) va_list arglist; va_start(arglist, s); - vim_vsnprintf((char *)IObuff, IOSIZE, s, arglist); + vim_vsnprintf(IObuff, IOSIZE, s, arglist); va_end(arglist); return msg_attr_keep((const char *)IObuff, attr, true, false); } -/* - * Remember the last sourcing name/lnum used in an error message, so that it - * isn't printed each time when it didn't change. - */ +// Remember the last sourcing name/lnum used in an error message, so that it +// isn't printed each time when it didn't change. static int last_sourcing_lnum = 0; static char *last_sourcing_name = NULL; @@ -565,7 +560,7 @@ static char *get_emsg_source(void) } const char *const p = _("Error detected while processing %s:"); - const size_t buf_len = STRLEN(sname) + strlen(p) + 1; + const size_t buf_len = strlen(sname) + strlen(p) + 1; char *const buf = xmalloc(buf_len); snprintf(buf, buf_len, p, sname); xfree(tofree); @@ -608,10 +603,10 @@ void msg_source(int attr) } recursive = true; - msg_scroll = true; // this will take more than one line no_wait_return++; char *p = get_emsg_source(); if (p != NULL) { + msg_scroll = true; // this will take more than one line msg_attr(p, attr); xfree(p); } @@ -634,18 +629,18 @@ void msg_source(int attr) recursive = false; } -/// @return TRUE if not giving error messages right now: +/// @return true if not giving error messages right now: /// If "emsg_off" is set: no error messages at the moment. /// If "msg" is in 'debug': do error message but without side effects. /// If "emsg_skip" is set: never do error messages. int emsg_not_now(void) { - if ((emsg_off > 0 && vim_strchr((char *)p_debug, 'm') == NULL - && vim_strchr((char *)p_debug, 't') == NULL) + if ((emsg_off > 0 && vim_strchr(p_debug, 'm') == NULL + && vim_strchr(p_debug, 't') == NULL) || emsg_skip > 0) { - return TRUE; + return true; } - return FALSE; + return false; } static bool emsg_multiline(const char *s, bool multiline) @@ -665,7 +660,7 @@ static bool emsg_multiline(const char *s, bool multiline) bool severe = emsg_severe; emsg_severe = false; - if (!emsg_off || vim_strchr((char *)p_debug, 't') != NULL) { + if (!emsg_off || vim_strchr(p_debug, 't') != NULL) { // Cause a throw of an error exception if appropriate. Don't display // the error message in this case. (If no matching catch clause will // be found, the message will be displayed later on.) "ignore" is set @@ -673,18 +668,23 @@ static bool emsg_multiline(const char *s, bool multiline) // interrupt message). if (cause_errthrow(s, severe, &ignore)) { if (!ignore) { - did_emsg = true; + did_emsg++; } return true; } + if (in_assert_fails && emsg_assert_fails_msg == NULL) { + emsg_assert_fails_msg = xstrdup(s); + emsg_assert_fails_lnum = SOURCING_LNUM; + xfree(emsg_assert_fails_context); + emsg_assert_fails_context = xstrdup(SOURCING_NAME == NULL ? "" : SOURCING_NAME); + } + // set "v:errmsg", also when using ":silent! cmd" set_vim_var_string(VV_ERRMSG, s, -1); - /* - * When using ":silent! cmd" ignore error messages. - * But do write it to the redirection file. - */ + // When using ":silent! cmd" ignore error messages. + // But do write it to the redirection file. if (emsg_silent != 0) { if (!emsg_noredir) { msg_start(); @@ -738,14 +738,14 @@ static bool emsg_multiline(const char *s, bool multiline) } else { flush_buffers(FLUSH_MINIMAL); // flush internal buffers } - did_emsg = true; // flag for DoOneCmd() + did_emsg++; // flag for DoOneCmd() } emsg_on_display = true; // remember there is an error message attr = HL_ATTR(HLF_E); // set highlight mode for error messages if (msg_scrolled != 0) { need_wait_return = true; // needed in case emsg() is called after - } // wait_return has reset need_wait_return + } // wait_return() has reset need_wait_return // and a redraw is expected because // msg_scrolled is non-zero if (msg_ext_kind == NULL) { @@ -753,7 +753,7 @@ static bool emsg_multiline(const char *s, bool multiline) } // Display name and line number for the source of the error. - // Sets "msg_scroll". + msg_scroll = true; msg_source(attr); // Display the error message itself. @@ -764,9 +764,9 @@ static bool emsg_multiline(const char *s, bool multiline) /// emsg() - display an error message /// /// Rings the bell, if appropriate, and calls message() to do the real work -/// When terminal not initialized (yet) mch_errmsg(..) is used. +/// When terminal not initialized (yet) os_errmsg(..) is used. /// -/// @return true if wait_return not called +/// @return true if wait_return() not called bool emsg(const char *s) { return emsg_multiline(s, false); @@ -881,10 +881,10 @@ void msg_schedule_semsg(const char *const fmt, ...) { va_list ap; va_start(ap, fmt); - vim_vsnprintf((char *)IObuff, IOSIZE, fmt, ap); + vim_vsnprintf(IObuff, IOSIZE, fmt, ap); va_end(ap); - char *s = xstrdup((char *)IObuff); + char *s = xstrdup(IObuff); loop_schedule_deferred(&main_loop, event_create(msg_semsg_event, 1, s)); } @@ -900,7 +900,7 @@ char *msg_trunc_attr(char *s, bool force, int attr) // Add message to history before truncating. add_msg_hist(s, -1, attr, false); - char *ts = (char *)msg_may_trunc(force, (char_u *)s); + char *ts = msg_may_trunc(force, s); msg_hist_off = true; n = msg_attr(ts, attr); @@ -917,14 +917,16 @@ char *msg_trunc_attr(char *s, bool force, int attr) /// @return a pointer to where the truncated message starts. /// /// @note: May change the message by replacing a character with '<'. -char_u *msg_may_trunc(bool force, char_u *s) +char *msg_may_trunc(bool force, char *s) { - int room; + if (ui_has(kUIMessages)) { + return s; + } - room = (Rows - cmdline_row - 1) * Columns + sc_col - 1; + int room = (Rows - cmdline_row - 1) * Columns + sc_col - 1; if ((force || (shortmess(SHM_TRUNC) && !exmode_active)) - && (int)STRLEN(s) - room > 0 && p_ch > 0) { - int size = vim_strsize((char *)s); + && (int)strlen(s) - room > 0) { + int size = vim_strsize(s); // There may be room anyway when there are multibyte chars. if (size <= room) { @@ -932,8 +934,8 @@ char_u *msg_may_trunc(bool force, char_u *s) } int n; for (n = 0; size >= room;) { - size -= utf_ptr2cells((char *)s + n); - n += utfc_ptr2len((char *)s + n); + size -= utf_ptr2cells(s + n); + n += utfc_ptr2len(s + n); } n--; s += n; @@ -950,15 +952,6 @@ void hl_msg_free(HlMessage hl_msg) kv_destroy(hl_msg); } -#define LINE_BUFFER_SIZE 4096 - -void add_hl_msg_hist(HlMessage hl_msg) -{ - if (kv_size(hl_msg)) { - add_msg_hist_multiattr(NULL, 0, 0, true, hl_msg); - } -} - /// @param[in] len Length of s or -1. static void add_msg_hist(const char *s, int len, int attr, bool multiline) { @@ -982,7 +975,7 @@ static void add_msg_hist_multiattr(const char *s, int len, int attr, bool multil struct msg_hist *p = xmalloc(sizeof(struct msg_hist)); if (s) { if (len < 0) { - len = (int)STRLEN(s); + len = (int)strlen(s); } // remove leading and trailing newlines while (len > 0 && *s == '\n') { @@ -992,7 +985,7 @@ static void add_msg_hist_multiattr(const char *s, int len, int attr, bool multil while (len > 0 && s[len - 1] == '\n') { len--; } - p->msg = (char_u *)xmemdupz(s, (size_t)len); + p->msg = xmemdupz(s, (size_t)len); } else { p->msg = NULL; } @@ -1042,7 +1035,7 @@ void ex_messages(void *const eap_p) struct msg_hist *p; int c = 0; - if (STRCMP(eap->arg, "clear") == 0) { + if (strcmp(eap->arg, "clear") == 0) { int keep = eap->addr_count == 0 ? 0 : eap->line2; while (msg_hist_len > keep) { @@ -1086,7 +1079,7 @@ void ex_messages(void *const eap_p) HlMessageChunk chunk = kv_A(p->multiattr, i); Array content_entry = ARRAY_DICT_INIT; ADD(content_entry, INTEGER_OBJ(chunk.attr)); - ADD(content_entry, STRING_OBJ(copy_string(chunk.text))); + ADD(content_entry, STRING_OBJ(copy_string(chunk.text, NULL))); ADD(content, ARRAY_OBJ(content_entry)); } } else if (p->msg && p->msg[0]) { @@ -1109,7 +1102,7 @@ void ex_messages(void *const eap_p) if (kv_size(p->multiattr)) { msg_multiattr(p->multiattr, p->kind, false); } else if (p->msg != NULL) { - msg_attr_keep((char *)p->msg, p->attr, false, p->multiline); + msg_attr_keep(p->msg, p->attr, false, p->multiline); } } msg_hist_off = false; @@ -1131,7 +1124,7 @@ void msg_end_prompt(void) /// Wait for the user to hit a key (normally Enter) /// -/// @param redraw if true, redraw the entire screen NOT_VALID +/// @param redraw if true, redraw the entire screen UPD_NOT_VALID /// if false, do a normal redraw /// if -1, don't redraw at all void wait_return(int redraw) @@ -1143,7 +1136,7 @@ void wait_return(int redraw) FILE *save_scriptout; if (redraw == true) { - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } // If using ":silent cmd", don't wait for a return. Also don't set @@ -1156,12 +1149,10 @@ void wait_return(int redraw) return; } - /* - * When inside vgetc(), we can't wait for a typed character at all. - * With the global command (and some others) we only need one return at - * the end. Adjust cmdline_row to avoid the next message overwriting the - * last one. - */ + // When inside vgetc(), we can't wait for a typed character at all. + // With the global command (and some others) we only need one return at + // the end. Adjust cmdline_row to avoid the next message overwriting the + // last one. if (vgetc_busy > 0) { return; } @@ -1177,17 +1168,13 @@ void wait_return(int redraw) oldState = State; if (quit_more) { c = CAR; // just pretend CR was hit - quit_more = FALSE; - got_int = FALSE; + quit_more = false; + got_int = false; } else if (exmode_active) { msg_puts(" "); // make sure the cursor is on the right line c = CAR; // no need for a return in ex mode - got_int = FALSE; + got_int = false; } else { - // Make sure the hit-return prompt is on screen when 'guioptions' was - // just changed. - screenalloc(); - State = MODE_HITRETURN; setmouse(); cmdline_row = msg_row; @@ -1226,12 +1213,10 @@ void wait_return(int redraw) reg_recording = save_reg_recording; scriptout = save_scriptout; - /* - * Allow scrolling back in the messages. - * Also accept scroll-down commands when messages fill the screen, - * to avoid that typing one 'j' too many makes the messages - * disappear. - */ + // Allow scrolling back in the messages. + // Also accept scroll-down commands when messages fill the screen, + // to avoid that typing one 'j' too many makes the messages + // disappear. if (p_more) { if (c == 'b' || c == 'k' || c == 'u' || c == 'g' || c == K_UP || c == K_PAGEUP) { @@ -1247,8 +1232,8 @@ void wait_return(int redraw) } if (quit_more) { c = CAR; // just pretend CR was hit - quit_more = FALSE; - got_int = FALSE; + quit_more = false; + got_int = false; } else if (c != K_IGNORE) { c = K_IGNORE; hit_return_msg(); @@ -1268,9 +1253,7 @@ void wait_return(int redraw) || c == K_MOUSEDOWN || c == K_MOUSEUP || c == K_MOUSEMOVE); os_breakcheck(); - /* - * Avoid that the mouse-up event causes visual mode to start. - */ + // Avoid that the mouse-up event causes visual mode to start. if (c == K_LEFTMOUSE || c == K_MIDDLEMOUSE || c == K_RIGHTMOUSE || c == K_X1MOUSE || c == K_X2MOUSE) { (void)jump_to_mouse(MOUSE_SETPOS, NULL, 0); @@ -1307,7 +1290,7 @@ void wait_return(int redraw) emsg_on_display = false; // can delete error message now lines_left = -1; // reset lines_left at next msg_start() reset_last_sourcing(); - if (keep_msg != NULL && vim_strsize((char *)keep_msg) >= + if (keep_msg != NULL && vim_strsize(keep_msg) >= (Rows - cmdline_row - 1) * Columns + sc_col) { XFREE_CLEAR(keep_msg); // don't redisplay message, it's too long } @@ -1316,7 +1299,7 @@ void wait_return(int redraw) ui_refresh(); } else if (!skip_redraw) { if (redraw == true || (msg_scrolled != 0 && redraw != -1)) { - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } if (ui_has(kUIMessages)) { msg_ext_clear(true); @@ -1350,7 +1333,7 @@ void set_keep_msg(char *s, int attr) { xfree(keep_msg); if (s != NULL && msg_silent == 0) { - keep_msg = vim_strsave((char_u *)s); + keep_msg = xstrdup(s); } else { keep_msg = NULL; } @@ -1421,22 +1404,25 @@ void msg_start(void) need_fileinfo = false; } - const bool no_msg_area = !ui_has_messages(); - - if (need_clr_eos || (no_msg_area && redrawing_cmdline)) { + if (need_clr_eos || (p_ch == 0 && redrawing_cmdline)) { // Halfway an ":echo" command and getting an (error) message: clear // any text from the command. need_clr_eos = false; msg_clr_eos(); } + // if cmdheight=0, we need to scroll in the first line of msg_grid upon the screen + if (p_ch == 0 && !ui_has(kUIMessages) && !msg_scrolled) { + msg_grid_validate(); + msg_scroll_up(false, true); + msg_scrolled++; + cmdline_row = Rows - 1; + } + if (!msg_scroll && full_screen) { // overwrite last message msg_row = cmdline_row; msg_col = cmdmsg_rl ? Columns - 1 : 0; - if (no_msg_area && get_cmdprompt() == NULL) { - msg_row -= 1; - } - } else if (msg_didout || no_msg_area) { // start message on next line + } else if (msg_didout || (p_ch == 0 && !ui_has(kUIMessages))) { // start message on next line msg_putchar('\n'); did_return = true; cmdline_row = msg_row; @@ -1497,20 +1483,20 @@ void msg_outnum(long n) msg_puts(buf); } -void msg_home_replace(char_u *fname) +void msg_home_replace(char *fname) { msg_home_replace_attr(fname, 0); } -void msg_home_replace_hl(char_u *fname) +void msg_home_replace_hl(char *fname) { msg_home_replace_attr(fname, HL_ATTR(HLF_D)); } -static void msg_home_replace_attr(char_u *fname, int attr) +static void msg_home_replace_attr(char *fname, int attr) { - char *name = home_replace_save(NULL, (char *)fname); - msg_outtrans_attr((char_u *)name, attr); + char *name = home_replace_save(NULL, fname); + msg_outtrans_attr(name, attr); xfree(name); } @@ -1521,15 +1507,15 @@ static void msg_home_replace_attr(char_u *fname, int attr) /// @return the number of characters it takes on the screen. int msg_outtrans(char *str) { - return msg_outtrans_attr((char_u *)str, 0); + return msg_outtrans_attr(str, 0); } -int msg_outtrans_attr(const char_u *str, int attr) +int msg_outtrans_attr(const char *str, int attr) { - return msg_outtrans_len_attr(str, (int)STRLEN(str), attr); + return msg_outtrans_len_attr(str, (int)strlen(str), attr); } -int msg_outtrans_len(const char_u *str, int len) +int msg_outtrans_len(const char *str, int len) { return msg_outtrans_len_attr(str, len, 0); } @@ -1538,24 +1524,24 @@ int msg_outtrans_len(const char_u *str, int len) /// Handles multi-byte characters. /// /// @return pointer to the next character. -char_u *msg_outtrans_one(char_u *p, int attr) +char *msg_outtrans_one(char *p, int attr) { int l; - if ((l = utfc_ptr2len((char *)p)) > 1) { + if ((l = utfc_ptr2len(p)) > 1) { msg_outtrans_len_attr(p, l, attr); return p + l; } - msg_puts_attr((const char *)transchar_byte(*p), attr); + msg_puts_attr((const char *)transchar_byte((uint8_t)(*p)), attr); return p + 1; } -int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) +int msg_outtrans_len_attr(const char *msgstr, int len, int attr) { int retval = 0; - const char *str = (const char *)msgstr; - const char *plain_start = (const char *)msgstr; - char_u *s; + const char *str = msgstr; + const char *plain_start = msgstr; + char *s; int mb_l; int c; int save_got_int = got_int; @@ -1575,13 +1561,11 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) msg_puts_attr(" ", attr); } - /* - * Go over the string. Special characters are translated and printed. - * Normal characters are printed several at a time. - */ + // Go over the string. Special characters are translated and printed. + // Normal characters are printed several at a time. while (--len >= 0 && !got_int) { // Don't include composing chars after the end. - mb_l = utfc_ptr2len_len((char_u *)str, len + 1); + mb_l = utfc_ptr2len_len(str, len + 1); if (mb_l > 1) { c = utf_ptr2char(str); if (vim_isprintc(c)) { @@ -1601,7 +1585,7 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) len -= mb_l - 1; str += mb_l; } else { - s = transchar_byte((uint8_t)(*str)); + s = (char *)transchar_byte((uint8_t)(*str)); if (s[1] != NUL) { // Unprintable char: print the printable chars so far and the // translation of the unprintable char. @@ -1610,7 +1594,7 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) } plain_start = str + 1; msg_puts_attr((const char *)s, attr == 0 ? HL_ATTR(HLF_8) : attr); - retval += (int)STRLEN(s); + retval += (int)strlen(s); } else { retval++; } @@ -1628,12 +1612,13 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) return retval; } -void msg_make(char_u *arg) +void msg_make(char *arg) { int i; - static char_u *str = (char_u *)"eeffoc", *rs = (char_u *)"Plon#dqg#vxjduB"; + static char *str = "eeffoc"; + static char *rs = "Plon#dqg#vxjduB"; - arg = (char_u *)skipwhite((char *)arg); + arg = skipwhite(arg); for (i = 5; *arg && i >= 0; i--) { if (*arg++ != str[i]) { break; @@ -1641,7 +1626,7 @@ void msg_make(char_u *arg) } if (i < 0) { msg_putchar('\n'); - for (i = 0; rs[i]; ++i) { + for (i = 0; rs[i]; i++) { msg_putchar(rs[i] - 3); } } @@ -1653,7 +1638,7 @@ void msg_make(char_u *arg) /// If K_SPECIAL is encountered, then it is taken in conjunction with the /// following character and shown as <F1>, <S-Up> etc. Any other character /// which is not printable shown in <> form. -/// If 'from' is TRUE (lhs of a mapping), a space is shown as <Space>. +/// If 'from' is true (lhs of a mapping), a space is shown as <Space>. /// If a character is displayed in one of these special ways, is also /// highlighted (its highlight name is '8' in the p_hl variable). /// Otherwise characters are not highlighted. @@ -1662,12 +1647,12 @@ void msg_make(char_u *arg) /// /// @param from true for LHS of a mapping /// @param maxlen screen columns, 0 for unlimited -int msg_outtrans_special(const char_u *strstart, bool from, int maxlen) +int msg_outtrans_special(const char *strstart, bool from, int maxlen) { if (strstart == NULL) { return 0; // Do nothing. } - const char_u *str = strstart; + const char *str = strstart; int retval = 0; int attr = HL_ATTR(HLF_8); @@ -1678,7 +1663,7 @@ int msg_outtrans_special(const char_u *strstart, bool from, int maxlen) text = "<Space>"; str++; } else { - text = str2special((const char **)&str, from, false); + text = str2special(&str, from, false); } if (text[0] != NUL && text[1] == NUL) { // single-byte character or illegal byte @@ -1702,8 +1687,8 @@ int msg_outtrans_special(const char_u *strstart, bool from, int maxlen) /// Used for lhs or rhs of mappings. /// /// @param[in] str String to convert. -/// @param[in] replace_spaces Convert spaces into `<Space>`, normally used fo -/// lhs, but not rhs. +/// @param[in] replace_spaces Convert spaces into `<Space>`, normally used for +/// lhs of mapping and keytrans(), but not rhs. /// @param[in] replace_lt Convert `<` into `<lt>`. /// /// @return [allocated] Converted string. @@ -1725,8 +1710,8 @@ char *str2special_save(const char *const str, const bool replace_spaces, const b /// Convert character, replacing key with printable representation. /// /// @param[in,out] sp String to convert. Is advanced to the next key code. -/// @param[in] replace_spaces Convert spaces into <Space>, normally used for -/// lhs, but not rhs. +/// @param[in] replace_spaces Convert spaces into `<Space>`, normally used for +/// lhs of mapping and keytrans(), but not rhs. /// @param[in] replace_lt Convert `<` into `<lt>`. /// /// @return Converted key code, in a static buffer. Buffer is always one and the @@ -1817,20 +1802,20 @@ void str2specialbuf(const char *sp, char *buf, size_t len) } /// print line for :print or :list command -void msg_prt_line(char_u *s, int list) +void msg_prt_line(char *s, int list) { int c; int col = 0; int n_extra = 0; int c_extra = 0; int c_final = 0; - char_u *p_extra = NULL; // init to make SASC shut up + char *p_extra = NULL; // init to make SASC shut up int n; int attr = 0; - char_u *lead = NULL; + char *lead = NULL; bool in_multispace = false; int multispace_pos = 0; - char_u *trail = NULL; + char *trail = NULL; int l; if (curwin->w_p_list) { @@ -1840,7 +1825,7 @@ void msg_prt_line(char_u *s, int list) if (list) { // find start of trailing whitespace if (curwin->w_p_lcs_chars.trail) { - trail = s + STRLEN(s); + trail = s + strlen(s); while (trail > s && ascii_iswhite(trail[-1])) { trail--; } @@ -1872,16 +1857,16 @@ void msg_prt_line(char_u *s, int list) c = c_extra; } else { assert(p_extra != NULL); - c = *p_extra++; + c = (unsigned char)(*p_extra++); } - } else if ((l = utfc_ptr2len((char *)s)) > 1) { - col += utf_ptr2cells((char *)s); + } else if ((l = utfc_ptr2len(s)) > 1) { + col += utf_ptr2cells(s); char buf[MB_MAXBYTES + 1]; if (l >= MB_MAXBYTES) { xstrlcpy(buf, "?", sizeof(buf)); } else if (curwin->w_p_lcs_chars.nbsp != NUL && list - && (utf_ptr2char((char *)s) == 160 - || utf_ptr2char((char *)s) == 0x202f)) { + && (utf_ptr2char(s) == 160 + || utf_ptr2char(s) == 0x202f)) { int len = utf_char2bytes(curwin->w_p_lcs_chars.nbsp, buf); buf[len] = NUL; } else { @@ -1893,7 +1878,7 @@ void msg_prt_line(char_u *s, int list) continue; } else { attr = 0; - c = *s++; + c = (unsigned char)(*s++); in_multispace = c == ' ' && ((col > 0 && s[-2] == ' ') || *s == ' '); if (!in_multispace) { multispace_pos = 0; @@ -1919,7 +1904,7 @@ void msg_prt_line(char_u *s, int list) c = curwin->w_p_lcs_chars.nbsp; attr = HL_ATTR(HLF_0); } else if (c == NUL && list && curwin->w_p_lcs_chars.eol != NUL) { - p_extra = (char_u *)""; + p_extra = ""; c_extra = NUL; c_final = NUL; n_extra = 1; @@ -1928,10 +1913,10 @@ void msg_prt_line(char_u *s, int list) s--; } else if (c != NUL && (n = byte2cells(c)) > 1) { n_extra = n - 1; - p_extra = transchar_byte(c); + p_extra = (char *)transchar_byte(c); c_extra = NUL; c_final = NUL; - c = *p_extra++; + c = (unsigned char)(*p_extra++); // Use special coloring to be able to distinguish <hex> from // the same in plain text. attr = HL_ATTR(HLF_0); @@ -1976,13 +1961,13 @@ void msg_prt_line(char_u *s, int list) /// Use grid_puts() to output one multi-byte character. /// /// @return the pointer "s" advanced to the next character. -static char_u *screen_puts_mbyte(char_u *s, int l, int attr) +static char *screen_puts_mbyte(char *s, int l, int attr) { int cw; attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); msg_didout = true; // remember that line is not empty - cw = utf_ptr2cells((char *)s); + cw = utf_ptr2cells(s); if (cw > 1 && (cmdmsg_rl ? msg_col <= 1 : msg_col == Columns - 1)) { // Doesn't fit, print a highlighted '>' to fill it up. @@ -2022,12 +2007,12 @@ void msg_puts_title(const char *s) /// Show a message in such a way that it always fits in the line. Cut out a /// part in the middle and replace it with "..." when necessary. /// Does not handle multi-byte characters! -void msg_outtrans_long_attr(char_u *longstr, int attr) +void msg_outtrans_long_attr(char *longstr, int attr) { - msg_outtrans_long_len_attr(longstr, (int)STRLEN(longstr), attr); + msg_outtrans_long_len_attr(longstr, (int)strlen(longstr), attr); } -void msg_outtrans_long_len_attr(char_u *longstr, int len, int attr) +void msg_outtrans_long_len_attr(char *longstr, int len, int attr) { int slen = len; int room; @@ -2083,7 +2068,7 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr) overflow = true; } } else { - overflow = msg_scrolled != 0; + overflow = msg_scrolled > (p_ch == 0 ? 1 : 0); } if (overflow && !msg_scrolled_ign && strcmp(str, "\r") != 0) { @@ -2104,7 +2089,7 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr) } } if (!msg_use_printf() || (headless_mode && default_grid.chars)) { - msg_puts_display((const char_u *)str, (int)len, attr, false); + msg_puts_display(str, (int)len, attr, false); } need_fileinfo = false; @@ -2147,10 +2132,10 @@ static void msg_ext_emit_chunk(void) /// The display part of msg_puts_attr_len(). /// May be called recursively to display scroll-back text. -static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurse) +static void msg_puts_display(const char *str, int maxlen, int attr, int recurse) { - const char_u *s = str; - const char_u *t_s = str; // String from "t_s" to "s" is still todo. + const char *s = str; + const char *t_s = str; // String from "t_s" to "s" is still todo. int t_col = 0; // Screen cells todo, 0 when "t_s" not used. int l; int cw; @@ -2167,7 +2152,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs msg_ext_last_attr = attr; } // Concat pieces with the same highlight - size_t len = STRNLEN(str, maxlen); // -V781 + size_t len = strnlen(str, (size_t)maxlen); // -V781 ga_concat_len(&msg_ext_last_chunk, (char *)str, len); msg_ext_cur_len += len; return; @@ -2185,12 +2170,12 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs && (*s == '\n' || (cmdmsg_rl ? (msg_col <= 1 || (*s == TAB && msg_col <= 7) - || (utf_ptr2cells((char *)s) > 1 + || (utf_ptr2cells(s) > 1 && msg_col <= 2)) : ((*s != '\r' && msg_col + t_col >= Columns - 1) || (*s == TAB && msg_col + t_col >= ((Columns - 1) & ~7)) - || (utf_ptr2cells((char *)s) > 1 + || (utf_ptr2cells(s) > 1 && msg_col + t_col >= Columns - 2))))) { // The screen is scrolled up when at the last row (some terminals // scroll automatically, some don't. To avoid problems we scroll @@ -2206,8 +2191,8 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs } // Scroll the screen up one line. - bool has_last_char = (*s >= ' ' && !cmdmsg_rl); - msg_scroll_up(!has_last_char); + bool has_last_char = ((uint8_t)(*s) >= ' ' && !cmdmsg_rl); + msg_scroll_up(!has_last_char, false); msg_row = Rows - 2; if (msg_col >= Columns) { // can happen after screen resize @@ -2220,9 +2205,9 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs // Avoid including composing chars after the end. l = utfc_ptr2len_len(s, (int)((str + maxlen) - s)); } else { - l = utfc_ptr2len((char *)s); + l = utfc_ptr2len(s); } - s = screen_puts_mbyte((char_u *)s, l, attr); + s = screen_puts_mbyte((char *)s, l, attr); did_last_char = true; } else { did_last_char = false; @@ -2243,16 +2228,14 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs } inc_msg_scrolled(); - need_wait_return = true; // may need wait_return in main() + need_wait_return = true; // may need wait_return() in main() redraw_cmdline = true; if (cmdline_row > 0 && !exmode_active) { cmdline_row--; } - /* - * If screen is completely filled and 'more' is set then wait - * for a character. - */ + // If screen is completely filled and 'more' is set then wait + // for a character. if (lines_left > 0) { lines_left--; } @@ -2275,7 +2258,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs wrap = *s == '\n' || msg_col + t_col >= Columns - || (utf_ptr2cells((char *)s) > 1 + || (utf_ptr2cells(s) > 1 && msg_col + t_col >= Columns - 1) ; if (t_col > 0 && (wrap || *s == '\r' || *s == '\b' @@ -2311,20 +2294,20 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs } while (msg_col & 7); } else if (*s == BELL) { // beep (from ":sh") vim_beep(BO_SH); - } else if (*s >= 0x20) { // printable char - cw = utf_ptr2cells((char *)s); + } else if ((uint8_t)(*s) >= 0x20) { // printable char + cw = utf_ptr2cells(s); if (maxlen >= 0) { // avoid including composing chars after the end l = utfc_ptr2len_len(s, (int)((str + maxlen) - s)); } else { - l = utfc_ptr2len((char *)s); + l = utfc_ptr2len(s); } // When drawing from right to left or when a double-wide character // doesn't fit, draw a single character here. Otherwise collect // characters and draw them all at once later. if (cmdmsg_rl || (cw > 1 && msg_col + t_col >= Columns - 1)) { if (l > 1) { - s = screen_puts_mbyte((char_u *)s, l, attr) - 1; + s = screen_puts_mbyte((char *)s, l, attr) - 1; } else { msg_screen_putchar(*s, attr); } @@ -2344,7 +2327,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs if (t_col > 0) { t_puts(&t_col, t_s, s, attr); } - if (p_more && !recurse) { + if (p_more && !recurse && !(s == sb_str + 1 && *sb_str == '\n')) { store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, false); } @@ -2353,27 +2336,20 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs /// @return true when ":filter pattern" was used and "msg" does not match /// "pattern". -bool message_filtered(char_u *msg) +bool message_filtered(char *msg) { if (cmdmod.cmod_filter_regmatch.regprog == NULL) { return false; } - bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, (char *)msg, (colnr_T)0); + bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, msg, (colnr_T)0); return cmdmod.cmod_filter_force ? match : !match; } /// including horizontal separator int msg_scrollsize(void) { - return msg_scrolled + (int)p_ch + 1; -} - -bool msg_use_msgsep(void) -{ - // the full-screen scroll behavior doesn't really make sense with - // 'ext_multigrid' - return ((dy_flags & DY_MSGSEP) || ui_has(kUIMultigrid)); + return msg_scrolled + (int)p_ch + ((p_ch > 0 || msg_scrolled > 1) ? 1 : 0); } bool msg_do_throttle(void) @@ -2382,27 +2358,28 @@ bool msg_do_throttle(void) } /// Scroll the screen up one line for displaying the next message line. -void msg_scroll_up(bool may_throttle) +void msg_scroll_up(bool may_throttle, bool zerocmd) { if (may_throttle && msg_do_throttle()) { msg_grid.throttled = true; } msg_did_scroll = true; - if (msg_use_msgsep()) { - if (msg_grid_pos > 0) { - msg_grid_set_pos(msg_grid_pos - 1, true); - } else { - grid_del_lines(&msg_grid, 0, 1, msg_grid.rows, 0, msg_grid.cols); - memmove(msg_grid.dirty_col, msg_grid.dirty_col + 1, - (size_t)(msg_grid.rows - 1) * sizeof(*msg_grid.dirty_col)); - msg_grid.dirty_col[msg_grid.rows - 1] = 0; + if (msg_grid_pos > 0) { + msg_grid_set_pos(msg_grid_pos - 1, !zerocmd); + + // When displaying the first line with cmdheight=0, we need to draw over + // the existing last line of the screen. + if (zerocmd && msg_grid.chars) { + grid_clear_line(&msg_grid, msg_grid.line_offset[0], msg_grid.cols, false); } } else { - grid_del_lines(&msg_grid_adj, 0, 1, Rows, 0, Columns); + grid_del_lines(&msg_grid, 0, 1, msg_grid.rows, 0, msg_grid.cols); + memmove(msg_grid.dirty_col, msg_grid.dirty_col + 1, + (size_t)(msg_grid.rows - 1) * sizeof(*msg_grid.dirty_col)); + msg_grid.dirty_col[msg_grid.rows - 1] = 0; } - grid_fill(&msg_grid_adj, Rows - 1, Rows, 0, Columns, ' ', ' ', - HL_ATTR(HLF_MSG)); + grid_fill(&msg_grid_adj, Rows - 1, Rows, 0, Columns, ' ', ' ', HL_ATTR(HLF_MSG)); } /// Send throttled message output to UI clients @@ -2464,24 +2441,21 @@ void msg_reset_scroll(void) } // TODO(bfredl): some duplicate logic with update_screen(). Later on // we should properly disentangle message clear with full screen redraw. - if (msg_use_grid()) { - msg_grid.throttled = false; - // TODO(bfredl): risk for extra flicker i e with - // "nvim -o has_swap also_has_swap" - msg_grid_set_pos(Rows - (int)p_ch, false); - clear_cmdline = true; - if (msg_grid.chars) { - // non-displayed part of msg_grid is considered invalid. - for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) { - grid_clear_line(&msg_grid, msg_grid.line_offset[i], - msg_grid.cols, false); - } + msg_grid.throttled = false; + // TODO(bfredl): risk for extra flicker i e with + // "nvim -o has_swap also_has_swap" + msg_grid_set_pos(Rows - (int)p_ch, false); + clear_cmdline = true; + if (msg_grid.chars) { + // non-displayed part of msg_grid is considered invalid. + for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) { + grid_clear_line(&msg_grid, msg_grid.line_offset[i], + msg_grid.cols, false); } - } else { - redraw_all_later(NOT_VALID); } msg_scrolled = 0; msg_scrolled_at_flush = 0; + msg_grid_scroll_discount = 0; } /// Increment "msg_scrolled". @@ -2506,8 +2480,8 @@ static void inc_msg_scrolled(void) xfree(tofree); } msg_scrolled++; - if (must_redraw < VALID) { - must_redraw = VALID; + if (must_redraw < UPD_VALID) { + must_redraw = UPD_VALID; } } @@ -2557,7 +2531,7 @@ static void store_sb_text(char **sb_str, char *s, int attr, int *sb_col, int fin } mp->sb_next = NULL; } else if (finish && last_msgchunk != NULL) { - last_msgchunk->sb_eol = TRUE; + last_msgchunk->sb_eol = true; } *sb_str = s; @@ -2614,7 +2588,7 @@ void sb_text_end_cmdline(void) } /// Clear any text remembered for scrolling back. -/// When "all" is FALSE keep the last line. +/// When "all" is false keep the last line. /// Called when redrawing the screen. void clear_sb_text(int all) { @@ -2649,7 +2623,7 @@ void show_sb_text(void) vim_beep(BO_MESS); } else { do_more_prompt('G'); - wait_return(FALSE); + wait_return(false); } } @@ -2668,7 +2642,7 @@ static msgchunk_T *msg_sb_start(msgchunk_T *mps) void msg_sb_eol(void) { if (last_msgchunk != NULL) { - last_msgchunk->sb_eol = TRUE; + last_msgchunk->sb_eol = true; } } @@ -2678,7 +2652,7 @@ void msg_sb_eol(void) static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) { msgchunk_T *mp = smp; - char_u *p; + char *p; for (;;) { msg_row = row; @@ -2687,7 +2661,7 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) if (*p == '\n') { // don't display the line break p++; } - msg_puts_display(p, -1, mp->sb_attr, TRUE); + msg_puts_display(p, -1, mp->sb_attr, true); if (mp->sb_eol || mp->sb_next == NULL) { break; } @@ -2698,18 +2672,17 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) } /// Output any postponed text for msg_puts_attr_len(). -static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) +static void t_puts(int *t_col, const char *t_s, const char *s, int attr) { attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); // Output postponed text. msg_didout = true; // Remember that line is not empty. - grid_puts_len(&msg_grid_adj, (char_u *)t_s, (int)(s - t_s), msg_row, msg_col, - attr); + grid_puts_len(&msg_grid_adj, (char *)t_s, (int)(s - t_s), msg_row, msg_col, attr); msg_col += *t_col; *t_col = 0; // If the string starts with a composing character don't increment the // column position for it. - if (utf_iscomposing(utf_ptr2char((char *)t_s))) { + if (utf_iscomposing(utf_ptr2char(t_s))) { msg_col--; } if (msg_col >= Columns) { @@ -2718,7 +2691,7 @@ static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) } } -/// @return TRUE when messages should be printed to stdout/stderr: +/// @return true when messages should be printed to stdout/stderr: /// - "batch mode" ("silent mode", -es/-Es) /// - no UI and not embedded int msg_use_printf(void) @@ -2755,9 +2728,9 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) memcpy(p, s, (size_t)len); *(p + len) = '\0'; if (info_message) { - mch_msg(buf); + os_msg(buf); } else { - mch_errmsg(buf); + os_errmsg(buf); } } @@ -2786,14 +2759,14 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) /// When at hit-enter prompt "typed_char" is the already typed character, /// otherwise it's NUL. /// -/// @return TRUE when jumping ahead to "confirm_msg_tail". +/// @return true when jumping ahead to "confirm_msg_tail". static int do_more_prompt(int typed_char) { static bool entered = false; int used_typed_char = typed_char; int oldState = State; int c; - int retval = FALSE; + int retval = false; int toscroll; bool to_redraw = false; msgchunk_T *mp_last = NULL; @@ -2817,7 +2790,7 @@ static int do_more_prompt(int typed_char) // "g<": Find first line on the last page. mp_last = msg_sb_start(last_msgchunk); for (i = 0; i < Rows - 2 && mp_last != NULL - && mp_last->sb_prev != NULL; ++i) { + && mp_last->sb_prev != NULL; i++) { mp_last = msg_sb_start(mp_last->sb_prev); } } @@ -2825,12 +2798,10 @@ static int do_more_prompt(int typed_char) State = MODE_ASKMORE; setmouse(); if (typed_char == NUL) { - msg_moremsg(FALSE); + msg_moremsg(false); } for (;;) { - /* - * Get a typed character directly from the user. - */ + // Get a typed character directly from the user. if (used_typed_char != NUL) { c = used_typed_char; // was typed at hit-enter prompt used_typed_char = NUL; @@ -2898,10 +2869,10 @@ static int do_more_prompt(int typed_char) case ESC: if (confirm_msg_used) { // Jump to the choices of the dialog. - retval = TRUE; + retval = true; } else { - got_int = TRUE; - quit_more = TRUE; + got_int = true; + quit_more = true; } // When there is some more output (wrapping line) display that // without another prompt. @@ -2917,7 +2888,7 @@ static int do_more_prompt(int typed_char) break; default: // no valid response - msg_moremsg(TRUE); + msg_moremsg(true); continue; } @@ -2936,8 +2907,7 @@ static int do_more_prompt(int typed_char) } // go to start of line at top of the screen - for (i = 0; i < Rows - 2 && mp != NULL && mp->sb_prev != NULL; - ++i) { + for (i = 0; i < Rows - 2 && mp != NULL && mp->sb_prev != NULL; i++) { mp = msg_sb_start(mp->sb_prev); } @@ -2977,6 +2947,11 @@ static int do_more_prompt(int typed_char) } } else { // First display any text that we scrolled back. + // if p_ch=0 we need to allocate a line for "press enter" messages! + if (cmdline_row >= Rows && !ui_has(kUIMessages)) { + msg_scroll_up(true, false); + msg_scrolled++; + } while (toscroll > 0 && mp_last != NULL) { if (msg_do_throttle() && !msg_grid.throttled) { // Tricky: we redraw at one line higher than usual. Therefore @@ -2985,7 +2960,7 @@ static int do_more_prompt(int typed_char) msg_grid_scroll_discount++; } // scroll up, display line at bottom - msg_scroll_up(true); + msg_scroll_up(true, false); inc_msg_scrolled(); grid_fill(&msg_grid_adj, Rows - 2, Rows - 1, 0, Columns, ' ', ' ', HL_ATTR(HLF_MSG)); @@ -3029,34 +3004,39 @@ static int do_more_prompt(int typed_char) return retval; } -#if defined(WIN32) -void mch_errmsg(char *str) +#if defined(MSWIN) +/// Headless (no UI) error message handler. +static void do_msg(const char *str, bool errmsg) { + static bool did_err = false; assert(str != NULL); wchar_t *utf16str; int r = utf8_to_utf16(str, -1, &utf16str); - if (r != 0) { + if (r != 0 && !did_err) { + did_err = true; fprintf(stderr, "utf8_to_utf16 failed: %d", r); - } else { - fwprintf(stderr, L"%ls", utf16str); + ELOG("utf8_to_utf16 failed: %d", r); + } else if (r == 0) { + if (errmsg) { + fwprintf(stderr, L"%ls", utf16str); + } else { + wprintf(L"%ls", utf16str); + } xfree(utf16str); } } -/// Give a message. To be used when the UI is not initialized yet. -void mch_msg(char *str) +void os_errmsg(const char *str) { - assert(str != NULL); - wchar_t *utf16str; - int r = utf8_to_utf16(str, -1, &utf16str); - if (r != 0) { - fprintf(stderr, "utf8_to_utf16 failed: %d", r); - } else { - wprintf(L"%ls", utf16str); - xfree(utf16str); - } + do_msg(str, true); } -#endif // WIN32 + +/// Headless (no UI) message handler. +void os_msg(const char *str) +{ + do_msg(str, false); +} +#endif // MSWIN /// Put a character on the screen at the current message position and advance /// to the next position. Only for printable ASCII! @@ -3081,14 +3061,13 @@ static void msg_screen_putchar(int c, int attr) void msg_moremsg(int full) { int attr; - char_u *s = (char_u *)_("-- More --"); + char *s = _("-- More --"); attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M)); grid_puts(&msg_grid_adj, s, Rows - 1, 0, attr); if (full) { - grid_puts(&msg_grid_adj, (char_u *) - _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), - Rows - 1, vim_strsize((char *)s), attr); + grid_puts(&msg_grid_adj, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "), + Rows - 1, vim_strsize(s), attr); } } @@ -3122,7 +3101,7 @@ void repeat_message(void) /// Skip this when ":silent" was used, no need to clear for redirection. void msg_clr_eos(void) { - if (msg_silent == 0 && p_ch > 0) { + if (msg_silent == 0) { msg_clr_eos_force(); } } @@ -3144,12 +3123,10 @@ void msg_clr_eos_force(void) msg_row = msg_grid_pos; } - if (ui_has_messages()) { - grid_fill(&msg_grid_adj, msg_row, msg_row + 1, msg_startcol, msg_endcol, - ' ', ' ', HL_ATTR(HLF_MSG)); - grid_fill(&msg_grid_adj, msg_row + 1, Rows, 0, Columns, - ' ', ' ', HL_ATTR(HLF_MSG)); - } + grid_fill(&msg_grid_adj, msg_row, msg_row + 1, msg_startcol, msg_endcol, + ' ', ' ', HL_ATTR(HLF_MSG)); + grid_fill(&msg_grid_adj, msg_row + 1, Rows, 0, Columns, + ' ', ' ', HL_ATTR(HLF_MSG)); redraw_cmdline = true; // overwritten the command line if (msg_row < Rows - 1 || msg_col == (cmdmsg_rl ? Columns : 0)) { @@ -3167,17 +3144,15 @@ void msg_clr_cmdline(void) } /// end putting a message on the screen -/// call wait_return if the message does not fit in the available space +/// call wait_return() if the message does not fit in the available space /// -/// @return TRUE if wait_return not called. +/// @return true if wait_return() not called. int msg_end(void) { - /* - * If the string is larger than the window, - * or the ruler option is set and we run into it, - * we have to redraw the window. - * Do not do this if we are abandoning the file or editing the command line. - */ + // If the string is larger than the window, + // or the ruler option is set and we run into it, + // we have to redraw the window. + // Do not do this if we are abandoning the file or editing the command line. if (!exiting && need_wait_return && !(State & MODE_CMDLINE)) { wait_return(false); return false; @@ -3194,6 +3169,7 @@ int msg_end(void) void msg_ext_ui_flush(void) { if (!ui_has(kUIMessages)) { + msg_ext_kind = NULL; return; } @@ -3245,8 +3221,8 @@ void msg_ext_clear_later(void) { if (msg_ext_is_visible()) { msg_ext_need_clear = true; - if (must_redraw < VALID) { - must_redraw = VALID; + if (must_redraw < UPD_VALID) { + must_redraw = UPD_VALID; } } } @@ -3283,7 +3259,7 @@ void msg_check(void) /// @param maxlen if -1, write the whole string, otherwise up to "maxlen" bytes. static void redir_write(const char *const str, const ptrdiff_t maxlen) { - const char_u *s = (char_u *)str; + const char *s = str; static int cur_col = 0; if (maxlen == 0) { @@ -3308,7 +3284,7 @@ static void redir_write(const char *const str, const ptrdiff_t maxlen) ga_concat_len(capture_ga, " ", 1); } if (redir_reg) { - write_reg_contents(redir_reg, (char_u *)" ", 1, true); + write_reg_contents(redir_reg, " ", 1, true); } else if (redir_vname) { var_redir_str(" ", -1); } else if (redir_fd != NULL) { @@ -3321,7 +3297,7 @@ static void redir_write(const char *const str, const ptrdiff_t maxlen) } } - size_t len = maxlen == -1 ? STRLEN(s) : (size_t)maxlen; + size_t len = maxlen == -1 ? strlen(s) : (size_t)maxlen; if (capture_ga) { ga_concat_len(capture_ga, str, len); } @@ -3334,7 +3310,7 @@ static void redir_write(const char *const str, const ptrdiff_t maxlen) // Write and adjust the current column. while (*s != NUL - && (maxlen < 0 || (int)(s - (const char_u *)str) < maxlen)) { + && (maxlen < 0 || (int)(s - str) < maxlen)) { if (!redir_reg && !redir_vname && !capture_ga) { if (redir_fd != NULL) { putc(*s, redir_fd); @@ -3392,7 +3368,7 @@ void verbose_enter_scroll(void) msg_silent++; } else { // always scroll up, don't overwrite - msg_scroll = TRUE; + msg_scroll = true; } } @@ -3415,7 +3391,7 @@ void verbose_stop(void) fclose(verbose_fd); verbose_fd = NULL; } - verbose_did_open = FALSE; + verbose_did_open = false; } /// Open the file 'verbosefile'. @@ -3425,9 +3401,9 @@ int verbose_open(void) { if (verbose_fd == NULL && !verbose_did_open) { // Only give the error message once. - verbose_did_open = TRUE; + verbose_did_open = true; - verbose_fd = os_fopen((char *)p_vfile, "a"); + verbose_fd = os_fopen(p_vfile, "a"); if (verbose_fd == NULL) { semsg(_(e_notopen), p_vfile); return FAIL; @@ -3471,10 +3447,10 @@ void give_warning(char *message, bool hl) no_wait_return--; } -void give_warning2(char_u *const message, char_u *const a1, bool hl) +void give_warning2(char *const message, char *const a1, bool hl) { - vim_snprintf((char *)IObuff, IOSIZE, (char *)message, a1); - give_warning((char *)IObuff, hl); + vim_snprintf(IObuff, IOSIZE, message, a1); + give_warning(IObuff, hl); } /// Advance msg cursor to column "col". @@ -3524,13 +3500,13 @@ void msg_advance(int col) /// different letter. /// /// @param textfiel IObuff for inputdialog(), NULL otherwise -/// @param ex_cmd when TRUE pressing : accepts default and starts Ex command +/// @param ex_cmd when true pressing : accepts default and starts Ex command /// @returns 0 if cancelled, otherwise the nth button (1-indexed). -int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfltbutton, - char_u *textfield, int ex_cmd) +int do_dialog(int type, char *title, char *message, char *buttons, int dfltbutton, char *textfield, + int ex_cmd) { int retval = 0; - char_u *hotkeys; + char *hotkeys; int c; int i; @@ -3547,10 +3523,8 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl State = MODE_CONFIRM; setmouse(); - /* - * Since we wait for a keypress, don't make the - * user press RETURN as well afterwards. - */ + // Since we wait for a keypress, don't make the + // user press RETURN as well afterwards. no_wait_return++; hotkeys = msg_show_console_dialog(message, buttons, dfltbutton); @@ -3580,10 +3554,10 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl c = mb_tolower(c); retval = 1; for (i = 0; hotkeys[i]; i++) { - if (utf_ptr2char((char *)hotkeys + i) == c) { + if (utf_ptr2char(hotkeys + i) == c) { break; } - i += utfc_ptr2len((char *)hotkeys + i) - 1; + i += utfc_ptr2len(hotkeys + i) - 1; retval++; } if (hotkeys[i]) { @@ -3610,14 +3584,14 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl /// characters. Return the length of the character in bytes. /// /// @param lowercase make character lower case -static int copy_char(const char_u *from, char_u *to, bool lowercase) +static int copy_char(const char *from, char *to, bool lowercase) FUNC_ATTR_NONNULL_ALL { if (lowercase) { - int c = mb_tolower(utf_ptr2char((char *)from)); - return utf_char2bytes(c, (char *)to); + int c = mb_tolower(utf_ptr2char(from)); + return utf_char2bytes(c, to); } - int len = utfc_ptr2len((char *)from); + int len = utfc_ptr2len(from); memmove(to, from, (size_t)len); return len; } @@ -3637,7 +3611,7 @@ static int copy_char(const char_u *from, char_u *to, bool lowercase) /// corresponding button has a hotkey /// /// @return Pointer to memory allocated for storing hotkeys -static char_u *console_dialog_alloc(const char_u *message, char_u *buttons, bool has_hotkey[]) +static char *console_dialog_alloc(const char *message, char *buttons, bool has_hotkey[]) { int lenhotkey = HOTK_LEN; // count first button has_hotkey[0] = false; @@ -3645,7 +3619,7 @@ static char_u *console_dialog_alloc(const char_u *message, char_u *buttons, bool // Compute the size of memory to allocate. int len = 0; int idx = 0; - char_u *r = buttons; + char *r = buttons; while (*r) { if (*r == DLG_BUTTON_SEP) { len += 3; // '\n' -> ', '; 'x' -> '(x)' @@ -3665,9 +3639,9 @@ static char_u *console_dialog_alloc(const char_u *message, char_u *buttons, bool MB_PTR_ADV(r); } - len += (int)(STRLEN(message) + len += (int)(strlen(message) + 2 // for the NL's - + STRLEN(buttons) + + strlen(buttons) + 3); // for the ": " and NUL lenhotkey++; // for the NUL @@ -3691,11 +3665,11 @@ static char_u *console_dialog_alloc(const char_u *message, char_u *buttons, bool /// The hotkeys can be multi-byte characters, but without combining chars. /// /// @return an allocated string with hotkeys. -static char_u *msg_show_console_dialog(char_u *message, char_u *buttons, int dfltbutton) +static char *msg_show_console_dialog(char *message, char *buttons, int dfltbutton) FUNC_ATTR_NONNULL_RET { bool has_hotkey[HAS_HOTKEY_LEN] = { false }; - char_u *hotk = console_dialog_alloc(message, buttons, has_hotkey); + char *hotk = console_dialog_alloc(message, buttons, has_hotkey); copy_hotkeys_and_msg(message, buttons, dfltbutton, has_hotkey, hotk); @@ -3711,13 +3685,13 @@ static char_u *msg_show_console_dialog(char_u *message, char_u *buttons, int dfl /// @param has_hotkey An element in this array is true if corresponding button /// has a hotkey /// @param[out] hotkeys_ptr Pointer to the memory location where hotkeys will be copied -static void copy_hotkeys_and_msg(const char_u *message, char_u *buttons, int default_button_idx, - const bool has_hotkey[], char_u *hotkeys_ptr) +static void copy_hotkeys_and_msg(const char *message, char *buttons, int default_button_idx, + const bool has_hotkey[], char *hotkeys_ptr) { *confirm_msg = '\n'; STRCPY(confirm_msg + 1, message); - char_u *msgp = confirm_msg + 1 + STRLEN(message); + char *msgp = confirm_msg + 1 + strlen(message); // Define first default hotkey. Keep the hotkey string NUL // terminated to avoid reading past the end. @@ -3734,14 +3708,14 @@ static void copy_hotkeys_and_msg(const char_u *message, char_u *buttons, int def } int idx = 0; - char_u *r = buttons; + char *r = buttons; while (*r) { if (*r == DLG_BUTTON_SEP) { *msgp++ = ','; *msgp++ = ' '; // '\n' -> ', ' // Advance to next hotkey and set default hotkey - hotkeys_ptr += STRLEN(hotkeys_ptr); + hotkeys_ptr += strlen(hotkeys_ptr); hotkeys_ptr[copy_char(r + 1, hotkeys_ptr, true)] = NUL; if (default_button_idx) { @@ -3790,28 +3764,28 @@ void display_confirm_msg(void) confirm_msg_used++; if (confirm_msg != NULL) { msg_ext_set_kind("confirm"); - msg_puts_attr((const char *)confirm_msg, HL_ATTR(HLF_M)); + msg_puts_attr(confirm_msg, HL_ATTR(HLF_M)); } confirm_msg_used--; } -int vim_dialog_yesno(int type, char_u *title, char_u *message, int dflt) +int vim_dialog_yesno(int type, char *title, char *message, int dflt) { if (do_dialog(type, - title == NULL ? (char_u *)_("Question") : title, + title == NULL ? _("Question") : title, message, - (char_u *)_("&Yes\n&No"), dflt, NULL, FALSE) == 1) { + _("&Yes\n&No"), dflt, NULL, false) == 1) { return VIM_YES; } return VIM_NO; } -int vim_dialog_yesnocancel(int type, char_u *title, char_u *message, int dflt) +int vim_dialog_yesnocancel(int type, char *title, char *message, int dflt) { switch (do_dialog(type, - title == NULL ? (char_u *)_("Question") : title, + title == NULL ? _("Question") : title, message, - (char_u *)_("&Yes\n&No\n&Cancel"), dflt, NULL, FALSE)) { + _("&Yes\n&No\n&Cancel"), dflt, NULL, false)) { case 1: return VIM_YES; case 2: @@ -3820,13 +3794,13 @@ int vim_dialog_yesnocancel(int type, char_u *title, char_u *message, int dflt) return VIM_CANCEL; } -int vim_dialog_yesnoallcancel(int type, char_u *title, char_u *message, int dflt) +int vim_dialog_yesnoallcancel(int type, char *title, char *message, int dflt) { switch (do_dialog(type, - title == NULL ? (char_u *)"Question" : title, + title == NULL ? "Question" : title, message, - (char_u *)_("&Yes\n&No\nSave &All\n&Discard All\n&Cancel"), - dflt, NULL, FALSE)) { + _("&Yes\n&No\nSave &All\n&Discard All\n&Cancel"), + dflt, NULL, false)) { case 1: return VIM_YES; case 2: diff --git a/src/nvim/message.h b/src/nvim/message.h index 2de2890213..191d3b8da7 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -5,15 +5,13 @@ #include <stdbool.h> #include <stddef.h> +#include "klib/kvec.h" #include "nvim/api/private/defs.h" #include "nvim/grid_defs.h" -#include "nvim/lib/kvec.h" #include "nvim/macros.h" #include "nvim/types.h" -/* - * Types of dialogs passed to do_dialog(). - */ +// Types of dialogs passed to do_dialog(). #define VIM_GENERIC 0 #define VIM_ERROR 1 #define VIM_WARNING 2 @@ -21,9 +19,7 @@ #define VIM_QUESTION 4 #define VIM_LAST_TYPE 4 // sentinel value -/* - * Return values for functions like vim_dialogyesno() - */ +// Return values for functions like vim_dialogyesno() #define VIM_YES 2 #define VIM_NO 3 #define VIM_CANCEL 4 @@ -40,7 +36,7 @@ typedef kvec_t(HlMessageChunk) HlMessage; /// Message history for `:messages` typedef struct msg_hist { struct msg_hist *next; ///< Next message. - char_u *msg; ///< Message text. + char *msg; ///< Message text. const char *kind; ///< Message kind (for msg_ext) int attr; ///< Message highlighting. bool multiline; ///< Multiline message. @@ -70,6 +66,8 @@ EXTERN ScreenGrid msg_grid_adj INIT(= SCREEN_GRID_INIT); // value of msg_scrolled at latest msg_scroll_flush. EXTERN int msg_scrolled_at_flush INIT(= 0); +EXTERN int msg_grid_scroll_discount INIT(= 0); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "message.h.generated.h" #endif diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index a8d0b3b584..950b025e53 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -1,24 +1,47 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> #include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> #include "nvim/ascii.h" +#include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" -#include "nvim/diff.h" #include "nvim/drawscreen.h" +#include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_docmd.h" #include "nvim/fold.h" +#include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/grid.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" +#include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/menu.h" +#include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" -#include "nvim/os_unix.h" +#include "nvim/normal.h" +#include "nvim/ops.h" +#include "nvim/option.h" #include "nvim/plines.h" +#include "nvim/pos.h" +#include "nvim/screen.h" +#include "nvim/search.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/vim.h" @@ -31,8 +54,156 @@ static linenr_T orig_topline = 0; static int orig_topfill = 0; -/// Translate window coordinates to buffer position without any side effects -int get_fpos_of_mouse(pos_T *mpos) +/// Get class of a character for selection: same class means same word. +/// 0: blank +/// 1: punctuation groups +/// 2: normal word character +/// >2: multi-byte word character. +static int get_mouse_class(char *p) +{ + if (MB_BYTE2LEN((uint8_t)p[0]) > 1) { + return mb_get_class(p); + } + + const int c = (uint8_t)(*p); + if (c == ' ' || c == '\t') { + return 0; + } + if (vim_iswordc(c)) { + return 2; + } + + // There are a few special cases where we want certain combinations of + // characters to be considered as a single word. These are things like + // "->", "/ *", "*=", "+=", "&=", "<=", ">=", "!=" etc. Otherwise, each + // character is in its own class. + if (c != NUL && vim_strchr("-+*/%<>&|^!=", c) != NULL) { + return 1; + } + return c; +} + +/// Move "pos" back to the start of the word it's in. +static void find_start_of_word(pos_T *pos) +{ + char *line; + int cclass; + int col; + + line = ml_get(pos->lnum); + cclass = get_mouse_class(line + pos->col); + + while (pos->col > 0) { + col = pos->col - 1; + col -= utf_head_off(line, line + col); + if (get_mouse_class(line + col) != cclass) { + break; + } + pos->col = col; + } +} + +/// Move "pos" forward to the end of the word it's in. +/// When 'selection' is "exclusive", the position is just after the word. +static void find_end_of_word(pos_T *pos) +{ + char *line; + int cclass; + int col; + + line = ml_get(pos->lnum); + if (*p_sel == 'e' && pos->col > 0) { + pos->col--; + pos->col -= utf_head_off(line, line + pos->col); + } + cclass = get_mouse_class(line + pos->col); + while (line[pos->col] != NUL) { + col = pos->col + utfc_ptr2len(line + pos->col); + if (get_mouse_class(line + col) != cclass) { + if (*p_sel == 'e') { + pos->col = col; + } + break; + } + pos->col = col; + } +} + +/// Move the current tab to tab in same column as mouse or to end of the +/// tabline if there is no tab there. +static void move_tab_to_mouse(void) +{ + int tabnr = tab_page_click_defs[mouse_col].tabnr; + if (tabnr <= 0) { + tabpage_move(9999); + } else if (tabnr < tabpage_index(curtab)) { + tabpage_move(tabnr - 1); + } else { + tabpage_move(tabnr); + } +} + +/// Call click definition function for column "col" in the "click_defs" array for button +/// "which_button". +static void call_click_def_func(StlClickDefinition *click_defs, int col, int which_button) +{ + typval_T argv[] = { + { + .v_lock = VAR_FIXED, + .v_type = VAR_NUMBER, + .vval = { + .v_number = (varnumber_T)click_defs[col].tabnr + }, + }, + { + .v_lock = VAR_FIXED, + .v_type = VAR_NUMBER, + .vval = { + .v_number = ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK + ? 4 + : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK + ? 3 + : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK + ? 2 + : 1))) + }, + }, + { + .v_lock = VAR_FIXED, + .v_type = VAR_STRING, + .vval = { + .v_string = (which_button == MOUSE_LEFT + ? "l" + : (which_button == MOUSE_RIGHT + ? "r" + : (which_button == MOUSE_MIDDLE + ? "m" + : "?"))) + }, + }, + { + .v_lock = VAR_FIXED, + .v_type = VAR_STRING, + .vval = { + .v_string = (char[]) { + (char)(mod_mask & MOD_MASK_SHIFT ? 's' : ' '), + (char)(mod_mask & MOD_MASK_CTRL ? 'c' : ' '), + (char)(mod_mask & MOD_MASK_ALT ? 'a' : ' '), + (char)(mod_mask & MOD_MASK_META ? 'm' : ' '), + NUL + } + }, + } + }; + typval_T rettv; + (void)call_vim_function(click_defs[col].func, ARRAY_SIZE(argv), argv, &rettv); + tv_clear(&rettv); +} + +/// Translate window coordinates to buffer position without any side effects. +/// Returns IN_BUFFER and sets "mpos->col" to the column when in buffer text. +/// The column is one for the first column. +static int get_fpos_of_mouse(pos_T *mpos) { int grid = mouse_grid; int row = mouse_row; @@ -67,13 +238,724 @@ int get_fpos_of_mouse(pos_T *mpos) mpos->col = vcol2col(wp, mpos->lnum, col); - if (mpos->col > 0) { - mpos->col--; - } mpos->coladd = 0; return IN_BUFFER; } +/// Do the appropriate action for the current mouse click in the current mode. +/// Not used for Command-line mode. +/// +/// Normal and Visual Mode: +/// event modi- position visual change action +/// fier cursor window +/// left press - yes end yes +/// left press C yes end yes "^]" (2) +/// left press S yes end (popup: extend) yes "*" (2) +/// left drag - yes start if moved no +/// left relse - yes start if moved no +/// middle press - yes if not active no put register +/// middle press - yes if active no yank and put +/// right press - yes start or extend yes +/// right press S yes no change yes "#" (2) +/// right drag - yes extend no +/// right relse - yes extend no +/// +/// Insert or Replace Mode: +/// event modi- position visual change action +/// fier cursor window +/// left press - yes (cannot be active) yes +/// left press C yes (cannot be active) yes "CTRL-O^]" (2) +/// left press S yes (cannot be active) yes "CTRL-O*" (2) +/// left drag - yes start or extend (1) no CTRL-O (1) +/// left relse - yes start or extend (1) no CTRL-O (1) +/// middle press - no (cannot be active) no put register +/// right press - yes start or extend yes CTRL-O +/// right press S yes (cannot be active) yes "CTRL-O#" (2) +/// +/// (1) only if mouse pointer moved since press +/// (2) only if click is in same buffer +/// +/// @param oap operator argument, can be NULL +/// @param c K_LEFTMOUSE, etc +/// @param dir Direction to 'put' if necessary +/// @param fixindent PUT_FIXINDENT if fixing indent necessary +/// +/// @return true if start_arrow() should be called for edit mode. +bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) +{ + static bool got_click = false; // got a click some time back + + int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT + bool is_click; // If false it's a drag or release event + bool is_drag; // If true it's a drag event + int jump_flags = 0; // flags for jump_to_mouse() + pos_T start_visual; + bool moved; // Has cursor moved? + bool in_winbar; // mouse in window bar + bool in_statuscol; // mouse in status column + bool in_status_line; // mouse in status line + static bool in_tab_line = false; // mouse clicked in tab line + bool in_sep_line; // mouse in vertical separator line + int c1, c2; + pos_T save_cursor; + win_T *old_curwin = curwin; + static pos_T orig_cursor; + colnr_T leftcol, rightcol; + pos_T end_visual; + long diff; + int old_active = VIsual_active; + int old_mode = VIsual_mode; + int regname; + + save_cursor = curwin->w_cursor; + + for (;;) { + which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); + if (is_drag) { + // If the next character is the same mouse event then use that + // one. Speeds up dragging the status line. + // Note: Since characters added to the stuff buffer in the code + // below need to come before the next character, do not do this + // when the current character was stuffed. + if (!KeyStuffed && vpeekc() != NUL) { + int nc; + int save_mouse_grid = mouse_grid; + int save_mouse_row = mouse_row; + int save_mouse_col = mouse_col; + + // Need to get the character, peeking doesn't get the actual one. + nc = safe_vgetc(); + if (c == nc) { + continue; + } + vungetc(nc); + mouse_grid = save_mouse_grid; + mouse_row = save_mouse_row; + mouse_col = save_mouse_col; + } + } + break; + } + + if (c == K_MOUSEMOVE) { + // Mouse moved without a button pressed. + return false; + } + + // Ignore drag and release events if we didn't get a click. + if (is_click) { + got_click = true; + } else { + if (!got_click) { // didn't get click, ignore + return false; + } + if (!is_drag) { // release, reset got_click + got_click = false; + if (in_tab_line) { + in_tab_line = false; + return false; + } + } + } + + // CTRL right mouse button does CTRL-T + if (is_click && (mod_mask & MOD_MASK_CTRL) && which_button == MOUSE_RIGHT) { + if (State & MODE_INSERT) { + stuffcharReadbuff(Ctrl_O); + } + if (count > 1) { + stuffnumReadbuff(count); + } + stuffcharReadbuff(Ctrl_T); + got_click = false; // ignore drag&release now + return false; + } + + // CTRL only works with left mouse button + if ((mod_mask & MOD_MASK_CTRL) && which_button != MOUSE_LEFT) { + return false; + } + + // When a modifier is down, ignore drag and release events, as well as + // multiple clicks and the middle mouse button. + // Accept shift-leftmouse drags when 'mousemodel' is "popup.*". + if ((mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT + | MOD_MASK_META)) + && (!is_click + || (mod_mask & MOD_MASK_MULTI_CLICK) + || which_button == MOUSE_MIDDLE) + && !((mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)) + && mouse_model_popup() + && which_button == MOUSE_LEFT) + && !((mod_mask & MOD_MASK_ALT) + && !mouse_model_popup() + && which_button == MOUSE_RIGHT)) { + return false; + } + + // If the button press was used as the movement command for an operator (eg + // "d<MOUSE>"), or it is the middle button that is held down, ignore + // drag/release events. + if (!is_click && which_button == MOUSE_MIDDLE) { + return false; + } + + if (oap != NULL) { + regname = oap->regname; + } else { + regname = 0; + } + + // Middle mouse button does a 'put' of the selected text + if (which_button == MOUSE_MIDDLE) { + if (State == MODE_NORMAL) { + // If an operator was pending, we don't know what the user wanted to do. + // Go back to normal mode: Clear the operator and beep(). + if (oap != NULL && oap->op_type != OP_NOP) { + clearopbeep(oap); + return false; + } + + // If visual was active, yank the highlighted text and put it + // before the mouse pointer position. + // In Select mode replace the highlighted text with the clipboard. + if (VIsual_active) { + if (VIsual_select) { + stuffcharReadbuff(Ctrl_G); + stuffReadbuff("\"+p"); + } else { + stuffcharReadbuff('y'); + stuffcharReadbuff(K_MIDDLEMOUSE); + } + return false; + } + // The rest is below jump_to_mouse() + } else if ((State & MODE_INSERT) == 0) { + return false; + } + + // Middle click in insert mode doesn't move the mouse, just insert the + // contents of a register. '.' register is special, can't insert that + // with do_put(). + // Also paste at the cursor if the current mode isn't in 'mouse' (only + // happens for the GUI). + if ((State & MODE_INSERT)) { + if (regname == '.') { + insert_reg(regname, true); + } else { + if (regname == 0 && eval_has_provider("clipboard")) { + regname = '*'; + } + if ((State & REPLACE_FLAG) && !yank_register_mline(regname)) { + insert_reg(regname, true); + } else { + do_put(regname, NULL, BACKWARD, 1L, + (fixindent ? PUT_FIXINDENT : 0) | PUT_CURSEND); + + // Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r + AppendCharToRedobuff(Ctrl_R); + AppendCharToRedobuff(fixindent ? Ctrl_P : Ctrl_O); + AppendCharToRedobuff(regname == 0 ? '"' : regname); + } + } + return false; + } + } + + // When dragging or button-up stay in the same window. + if (!is_click) { + jump_flags |= MOUSE_FOCUS | MOUSE_DID_MOVE; + } + + start_visual.lnum = 0; + + if (tab_page_click_defs != NULL) { // only when initialized + // Check for clicking in the tab page line. + if (mouse_grid <= 1 && mouse_row == 0 && firstwin->w_winrow > 0) { + if (is_drag) { + if (in_tab_line) { + move_tab_to_mouse(); + } + return false; + } + + // click in a tab selects that tab page + if (is_click && cmdwin_type == 0 && mouse_col < Columns) { + in_tab_line = true; + c1 = tab_page_click_defs[mouse_col].tabnr; + switch (tab_page_click_defs[mouse_col].type) { + case kStlClickDisabled: + break; + case kStlClickTabClose: { + tabpage_T *tp; + + // Close the current or specified tab page. + if (c1 == 999) { + tp = curtab; + } else { + tp = find_tabpage(c1); + } + if (tp == curtab) { + if (first_tabpage->tp_next != NULL) { + tabpage_close(false); + } + } else if (tp != NULL) { + tabpage_close_other(tp, false); + } + break; + } + case kStlClickTabSwitch: + if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { + // double click opens new page + end_visual_mode(); + tabpage_new(); + tabpage_move(c1 == 0 ? 9999 : c1 - 1); + } else { + // Go to specified tab page, or next one if not clicking + // on a label. + goto_tabpage(c1); + + // It's like clicking on the status line of a window. + if (curwin != old_curwin) { + end_visual_mode(); + } + } + break; + case kStlClickFuncRun: + call_click_def_func(tab_page_click_defs, mouse_col, which_button); + break; + } + } + return true; + } else if (is_drag && in_tab_line) { + move_tab_to_mouse(); + return false; + } + } + + // When 'mousemodel' is "popup" or "popup_setpos", translate mouse events: + // right button up -> pop-up menu + // shift-left button -> right button + // alt-left button -> alt-right button + if (mouse_model_popup()) { + if (which_button == MOUSE_RIGHT + && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { + if (!is_click) { + // Ignore right button release events, only shows the popup + // menu on the button down event. + return false; + } + jump_flags = 0; + if (strcmp(p_mousem, "popup_setpos") == 0) { + // First set the cursor position before showing the popup + // menu. + if (VIsual_active) { + pos_T m_pos; + // set MOUSE_MAY_STOP_VIS if we are outside the + // selection or the current window (might have false + // negative here) + if (mouse_row < curwin->w_winrow + || mouse_row > (curwin->w_winrow + curwin->w_height)) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else if (get_fpos_of_mouse(&m_pos) != IN_BUFFER) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else { + if (VIsual_mode == 'V') { + if ((curwin->w_cursor.lnum <= VIsual.lnum + && (m_pos.lnum < curwin->w_cursor.lnum || VIsual.lnum < m_pos.lnum)) + || (VIsual.lnum < curwin->w_cursor.lnum + && (m_pos.lnum < VIsual.lnum || curwin->w_cursor.lnum < m_pos.lnum))) { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } else if ((ltoreq(curwin->w_cursor, VIsual) + && (lt(m_pos, curwin->w_cursor) || lt(VIsual, m_pos))) + || (lt(VIsual, curwin->w_cursor) + && (lt(m_pos, VIsual) || lt(curwin->w_cursor, m_pos)))) { + jump_flags = MOUSE_MAY_STOP_VIS; + } else if (VIsual_mode == Ctrl_V) { + getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); + getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL); + if (m_pos.col < leftcol || m_pos.col > rightcol) { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } + } + } else { + jump_flags = MOUSE_MAY_STOP_VIS; + } + } + if (jump_flags) { + jump_flags = jump_to_mouse(jump_flags, NULL, which_button); + redraw_curbuf_later(VIsual_active ? UPD_INVERTED : UPD_VALID); + update_screen(); + setcursor(); + ui_flush(); // Update before showing popup menu + } + show_popupmenu(); + got_click = false; // ignore release events + return (jump_flags & CURSOR_MOVED) != 0; + } + if (which_button == MOUSE_LEFT + && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT))) { + which_button = MOUSE_RIGHT; + mod_mask &= ~MOD_MASK_SHIFT; + } + } + + if ((State & (MODE_NORMAL | MODE_INSERT)) + && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { + if (which_button == MOUSE_LEFT) { + if (is_click) { + // stop Visual mode for a left click in a window, but not when on a status line + if (VIsual_active) { + jump_flags |= MOUSE_MAY_STOP_VIS; + } + } else { + jump_flags |= MOUSE_MAY_VIS; + } + } else if (which_button == MOUSE_RIGHT) { + if (is_click && VIsual_active) { + // Remember the start and end of visual before moving the cursor. + if (lt(curwin->w_cursor, VIsual)) { + start_visual = curwin->w_cursor; + end_visual = VIsual; + } else { + start_visual = VIsual; + end_visual = curwin->w_cursor; + } + } + jump_flags |= MOUSE_FOCUS; + jump_flags |= MOUSE_MAY_VIS; + } + } + + // If an operator is pending, ignore all drags and releases until the next mouse click. + if (!is_drag && oap != NULL && oap->op_type != OP_NOP) { + got_click = false; + oap->motion_type = kMTCharWise; + } + + // When releasing the button let jump_to_mouse() know. + if (!is_click && !is_drag) { + jump_flags |= MOUSE_RELEASED; + } + + // JUMP! + jump_flags = jump_to_mouse(jump_flags, + oap == NULL ? NULL : &(oap->inclusive), + which_button); + + moved = (jump_flags & CURSOR_MOVED); + in_winbar = (jump_flags & MOUSE_WINBAR); + in_statuscol = (jump_flags & MOUSE_STATUSCOL); + in_status_line = (jump_flags & IN_STATUS_LINE); + in_sep_line = (jump_flags & IN_SEP_LINE); + + if ((in_winbar || in_status_line || in_statuscol) && is_click) { + // Handle click event on window bar or status lin + int click_grid = mouse_grid; + int click_row = mouse_row; + int click_col = mouse_col; + win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col); + if (wp == NULL) { + return false; + } + + StlClickDefinition *click_defs = in_status_line ? wp->w_status_click_defs + : in_winbar ? wp->w_winbar_click_defs + : wp->w_statuscol_click_defs; + + if (in_status_line && global_stl_height() > 0) { + // global statusline is displayed for the current window, + // and spans the whole screen. + click_defs = curwin->w_status_click_defs; + click_col = mouse_col; + } + + if (click_defs != NULL) { + switch (click_defs[click_col].type) { + case kStlClickDisabled: + break; + case kStlClickFuncRun: + call_click_def_func(click_defs, click_col, which_button); + break; + default: + assert(false && "winbar and statusline only support %@ for clicks"); + break; + } + } + + return false; + } else if (in_winbar || in_statuscol) { + // A drag or release event in the window bar and status column has no side effects. + return false; + } + + // When jumping to another window, clear a pending operator. That's a bit + // friendlier than beeping and not jumping to that window. + if (curwin != old_curwin && oap != NULL && oap->op_type != OP_NOP) { + clearop(oap); + } + + if (mod_mask == 0 + && !is_drag + && (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN)) + && which_button == MOUSE_LEFT) { + // open or close a fold at this line + if (jump_flags & MOUSE_FOLD_OPEN) { + openFold(curwin->w_cursor, 1L); + } else { + closeFold(curwin->w_cursor, 1L); + } + // don't move the cursor if still in the same window + if (curwin == old_curwin) { + curwin->w_cursor = save_cursor; + } + } + + // Set global flag that we are extending the Visual area with mouse dragging; + // temporarily minimize 'scrolloff'. + if (VIsual_active && is_drag && get_scrolloff_value(curwin)) { + // In the very first line, allow scrolling one line + if (mouse_row == 0) { + mouse_dragging = 2; + } else { + mouse_dragging = 1; + } + } + + // When dragging the mouse above the window, scroll down. + if (is_drag && mouse_row < 0 && !in_status_line) { + scroll_redraw(false, 1L); + mouse_row = 0; + } + + if (start_visual.lnum) { // right click in visual mode + // When ALT is pressed make Visual mode blockwise. + if (mod_mask & MOD_MASK_ALT) { + VIsual_mode = Ctrl_V; + } + + // In Visual-block mode, divide the area in four, pick up the corner + // that is in the quarter that the cursor is in. + if (VIsual_mode == Ctrl_V) { + getvcols(curwin, &start_visual, &end_visual, &leftcol, &rightcol); + if (curwin->w_curswant > (leftcol + rightcol) / 2) { + end_visual.col = leftcol; + } else { + end_visual.col = rightcol; + } + if (curwin->w_cursor.lnum >= + (start_visual.lnum + end_visual.lnum) / 2) { + end_visual.lnum = start_visual.lnum; + } + + // move VIsual to the right column + start_visual = curwin->w_cursor; // save the cursor pos + curwin->w_cursor = end_visual; + coladvance(end_visual.col); + VIsual = curwin->w_cursor; + curwin->w_cursor = start_visual; // restore the cursor + } else { + // If the click is before the start of visual, change the start. + // If the click is after the end of visual, change the end. If + // the click is inside the visual, change the closest side. + if (lt(curwin->w_cursor, start_visual)) { + VIsual = end_visual; + } else if (lt(end_visual, curwin->w_cursor)) { + VIsual = start_visual; + } else { + // In the same line, compare column number + if (end_visual.lnum == start_visual.lnum) { + if (curwin->w_cursor.col - start_visual.col > + end_visual.col - curwin->w_cursor.col) { + VIsual = start_visual; + } else { + VIsual = end_visual; + } + } else { + // In different lines, compare line number + diff = (curwin->w_cursor.lnum - start_visual.lnum) - + (end_visual.lnum - curwin->w_cursor.lnum); + + if (diff > 0) { // closest to end + VIsual = start_visual; + } else if (diff < 0) { // closest to start + VIsual = end_visual; + } else { // in the middle line + if (curwin->w_cursor.col < + (start_visual.col + end_visual.col) / 2) { + VIsual = end_visual; + } else { + VIsual = start_visual; + } + } + } + } + } + } else if ((State & MODE_INSERT) && VIsual_active) { + // If Visual mode started in insert mode, execute "CTRL-O" + stuffcharReadbuff(Ctrl_O); + } + + // Middle mouse click: Put text before cursor. + if (which_button == MOUSE_MIDDLE) { + if (regname == 0 && eval_has_provider("clipboard")) { + regname = '*'; + } + if (yank_register_mline(regname)) { + if (mouse_past_bottom) { + dir = FORWARD; + } + } else if (mouse_past_eol) { + dir = FORWARD; + } + + if (fixindent) { + c1 = (dir == BACKWARD) ? '[' : ']'; + c2 = 'p'; + } else { + c1 = (dir == FORWARD) ? 'p' : 'P'; + c2 = NUL; + } + prep_redo(regname, count, NUL, c1, NUL, c2, NUL); + + // Remember where the paste started, so in edit() Insstart can be set to this position + if (restart_edit != 0) { + where_paste_started = curwin->w_cursor; + } + do_put(regname, NULL, dir, count, + (fixindent ? PUT_FIXINDENT : 0)| PUT_CURSEND); + } else if (((mod_mask & MOD_MASK_CTRL) || (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) + && bt_quickfix(curbuf)) { + // Ctrl-Mouse click or double click in a quickfix window jumps to the + // error under the mouse pointer. + if (curwin->w_llist_ref == NULL) { // quickfix window + do_cmdline_cmd(".cc"); + } else { // location list window + do_cmdline_cmd(".ll"); + } + got_click = false; // ignore drag&release now + } else if ((mod_mask & MOD_MASK_CTRL) + || (curbuf->b_help && (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)) { + // Ctrl-Mouse click (or double click in a help window) jumps to the tag + // under the mouse pointer. + if (State & MODE_INSERT) { + stuffcharReadbuff(Ctrl_O); + } + stuffcharReadbuff(Ctrl_RSB); + got_click = false; // ignore drag&release now + } else if ((mod_mask & MOD_MASK_SHIFT)) { + // Shift-Mouse click searches for the next occurrence of the word under + // the mouse pointer + if (State & MODE_INSERT || (VIsual_active && VIsual_select)) { + stuffcharReadbuff(Ctrl_O); + } + if (which_button == MOUSE_LEFT) { + stuffcharReadbuff('*'); + } else { // MOUSE_RIGHT + stuffcharReadbuff('#'); + } + } else if (in_status_line || in_sep_line) { + // Do nothing if on status line or vertical separator + // Handle double clicks otherwise + } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (MODE_NORMAL | MODE_INSERT))) { + if (is_click || !VIsual_active) { + if (VIsual_active) { + orig_cursor = VIsual; + } else { + VIsual = curwin->w_cursor; + orig_cursor = VIsual; + VIsual_active = true; + VIsual_reselect = true; + // start Select mode if 'selectmode' contains "mouse" + may_start_select('o'); + setmouse(); + } + if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { + // Double click with ALT pressed makes it blockwise. + if (mod_mask & MOD_MASK_ALT) { + VIsual_mode = Ctrl_V; + } else { + VIsual_mode = 'v'; + } + } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK) { + VIsual_mode = 'V'; + } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK) { + VIsual_mode = Ctrl_V; + } + } + // A double click selects a word or a block. + if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { + pos_T *pos = NULL; + int gc; + + if (is_click) { + // If the character under the cursor (skipping white space) is + // not a word character, try finding a match and select a (), + // {}, [], #if/#endif, etc. block. + end_visual = curwin->w_cursor; + while (gc = gchar_pos(&end_visual), ascii_iswhite(gc)) { + inc(&end_visual); + } + if (oap != NULL) { + oap->motion_type = kMTCharWise; + } + if (oap != NULL + && VIsual_mode == 'v' + && !vim_iswordc(gchar_pos(&end_visual)) + && equalpos(curwin->w_cursor, VIsual) + && (pos = findmatch(oap, NUL)) != NULL) { + curwin->w_cursor = *pos; + if (oap->motion_type == kMTLineWise) { + VIsual_mode = 'V'; + } else if (*p_sel == 'e') { + if (lt(curwin->w_cursor, VIsual)) { + VIsual.col++; + } else { + curwin->w_cursor.col++; + } + } + } + } + + if (pos == NULL && (is_click || is_drag)) { + // When not found a match or when dragging: extend to include a word. + if (lt(curwin->w_cursor, orig_cursor)) { + find_start_of_word(&curwin->w_cursor); + find_end_of_word(&VIsual); + } else { + find_start_of_word(&VIsual); + if (*p_sel == 'e' && *get_cursor_pos_ptr() != NUL) { + curwin->w_cursor.col += + utfc_ptr2len(get_cursor_pos_ptr()); + } + find_end_of_word(&curwin->w_cursor); + } + } + curwin->w_set_curswant = true; + } + if (is_click) { + redraw_curbuf_later(UPD_INVERTED); // update the inversion + } + } else if (VIsual_active && !old_active) { + if (mod_mask & MOD_MASK_ALT) { + VIsual_mode = Ctrl_V; + } else { + VIsual_mode = 'v'; + } + } + + // If Visual mode changed show it later. + if ((!VIsual_active && old_active && mode_displayed) + || (VIsual_active && p_smd && msg_silent == 0 + && (!old_active || VIsual_mode != old_mode))) { + redraw_cmdline = true; + } + + return moved; +} + /// Return true if "c" is a mouse key. bool is_mouse_key(int c) { @@ -100,6 +982,21 @@ bool is_mouse_key(int c) || c == K_X2DRAG || c == K_X2RELEASE; } + +/// @return true when 'mousemodel' is set to "popup" or "popup_setpos". +static bool mouse_model_popup(void) +{ + return p_mousem[0] == 'p'; +} + +static win_T *dragwin = NULL; ///< window being dragged + +/// Reset the window being dragged. To be called when switching tab page. +void reset_dragwin(void) +{ + dragwin = NULL; +} + /// Move the cursor to the specified row and column on the screen. /// Change current window if necessary. Returns an integer with the /// CURSOR_MOVED bit set if the cursor has moved or unset otherwise. @@ -134,9 +1031,9 @@ int jump_to_mouse(int flags, bool *inclusive, int which_button) static bool on_status_line = false; static bool on_sep_line = false; static bool on_winbar = false; + static bool on_statuscol = false; static int prev_row = -1; static int prev_col = -1; - static win_T *dragwin = NULL; // window being dragged static int did_drag = false; // drag was noticed win_T *wp, *old_curwin; @@ -177,9 +1074,12 @@ retnomove: if (on_winbar) { return IN_OTHER_WIN | MOUSE_WINBAR; } + if (on_statuscol) { + return IN_OTHER_WIN | MOUSE_STATUSCOL; + } if (flags & MOUSE_MAY_STOP_VIS) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion + redraw_curbuf_later(UPD_INVERTED); // delete the inversion } return IN_BUFFER; } @@ -211,6 +1111,10 @@ retnomove: ? wp->w_winbar_height != 0 : false; + on_statuscol = !on_status_line && !on_winbar && col < win_col_off(wp) + ? *wp->w_p_stc != NUL + : false; + on_sep_line = grid == DEFAULT_GRID_HANDLE && col >= wp->w_width ? col - wp->w_width + 1 == 1 : false; @@ -238,6 +1142,10 @@ retnomove: return IN_OTHER_WIN | MOUSE_WINBAR; } + if (on_statuscol) { + return IN_OTHER_WIN | MOUSE_STATUSCOL; + } + fdc = win_fdccol_count(wp); dragwin = NULL; @@ -279,7 +1187,7 @@ retnomove: : col >= fdc + (cmdwin_type == 0 && wp == curwin ? 0 : 1)) && (flags & MOUSE_MAY_STOP_VIS)))) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion + redraw_curbuf_later(UPD_INVERTED); // delete the inversion } if (cmdwin_type != 0 && wp != curwin) { // A click outside the command-line window: Use modeless @@ -303,17 +1211,15 @@ retnomove: // Don't use start_arrow() if we're in the same window if (curwin == old_curwin) { return IN_STATUS_LINE; - } else { - return IN_STATUS_LINE | CURSOR_MOVED; } + return IN_STATUS_LINE | CURSOR_MOVED; } if (sep_line_offset) { // In (or below) status line // Don't use start_arrow() if we're in the same window if (curwin == old_curwin) { return IN_SEP_LINE; - } else { - return IN_SEP_LINE | CURSOR_MOVED; } + return IN_SEP_LINE | CURSOR_MOVED; } curwin->w_cursor.lnum = curwin->w_topline; @@ -340,12 +1246,15 @@ retnomove: } else if (on_winbar && which_button == MOUSE_RIGHT) { // After a click on the window bar don't start Visual mode. return IN_OTHER_WIN | MOUSE_WINBAR; + } else if (on_statuscol && which_button == MOUSE_RIGHT) { + // After a click on the status column don't start Visual mode. + return IN_OTHER_WIN | MOUSE_STATUSCOL; } else { // keep_window_focus must be true // before moving the cursor for a left click, stop Visual mode if (flags & MOUSE_MAY_STOP_VIS) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion + redraw_curbuf_later(UPD_INVERTED); // delete the inversion } if (grid == 0) { @@ -374,14 +1283,14 @@ retnomove: if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) { curwin->w_topfill++; } else { - --curwin->w_topline; + curwin->w_topline--; curwin->w_topfill = 0; } } check_topfill(curwin, false); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); row = 0; } else if (row >= curwin->w_height_inner) { count = 0; @@ -410,7 +1319,7 @@ retnomove: } } check_topfill(curwin, false); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); row = curwin->w_height_inner - 1; @@ -631,14 +1540,16 @@ colnr_T vcol2col(win_T *const wp, const linenr_T lnum, const colnr_T vcol) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { // try to advance to the specified column - char_u *ptr = ml_get_buf(wp->w_buffer, lnum, false); - char_u *const line = ptr; - colnr_T count = 0; - while (count < vcol && *ptr != NUL) { - count += win_lbr_chartabsize(wp, line, ptr, count, NULL); - MB_PTR_ADV(ptr); - } - return (colnr_T)(ptr - line); + char *line = ml_get_buf(wp->w_buffer, lnum, false); + chartabsize_T cts; + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); + while (cts.cts_vcol < vcol && *cts.cts_ptr != NUL) { + cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); + MB_PTR_ADV(cts.cts_ptr); + } + clear_chartabsize_arg(&cts); + + return (colnr_T)(cts.cts_ptr - line); } /// Set UI mouse depending on current mode and 'mouse'. @@ -652,7 +1563,7 @@ void setmouse(void) // Set orig_topline. Used when jumping to another window, so that a double // click still works. -void set_mouse_topline(win_T *wp) +static void set_mouse_topline(win_T *wp) { orig_topline = wp->w_topline; orig_topfill = wp->w_topfill; @@ -664,7 +1575,7 @@ void set_mouse_topline(win_T *wp) static colnr_T scroll_line_len(linenr_T lnum) { colnr_T col = 0; - char_u *line = ml_get(lnum); + char *line = ml_get(lnum); if (*line != NUL) { for (;;) { int numchar = win_chartabsize(curwin, line, col); @@ -702,8 +1613,8 @@ static linenr_T find_longest_lnum(void) max = len; ret = lnum; } else if (len == (colnr_T)max - && abs((int)(lnum - curwin->w_cursor.lnum)) - < abs((int)(ret - curwin->w_cursor.lnum))) { + && abs(lnum - curwin->w_cursor.lnum) + < abs(ret - curwin->w_cursor.lnum)) { ret = lnum; } } @@ -769,10 +1680,10 @@ static int mouse_adjust_click(win_T *wp, int row, int col) // highlighting the second byte, not the ninth. linenr_T lnum = wp->w_cursor.lnum; - char_u *line = ml_get(lnum); - char_u *ptr = line; - char_u *ptr_end; - char_u *ptr_row_offset = line; // Where we begin adjusting `ptr_end` + char *line = ml_get(lnum); + char *ptr = line; + char *ptr_end; + char *ptr_row_offset = line; // Where we begin adjusting `ptr_end` // Find the offset where scanning should begin. int offset = wp->w_leftcol; @@ -791,7 +1702,7 @@ static int mouse_adjust_click(win_T *wp, int row, int col) vcol = 0; while (vcol < offset && *ptr != NUL) { vcol += win_chartabsize(curwin, ptr, vcol); - ptr += utfc_ptr2len((char *)ptr); + ptr += utfc_ptr2len(ptr); } ptr_row_offset = ptr; @@ -802,7 +1713,7 @@ static int mouse_adjust_click(win_T *wp, int row, int col) ptr_end = ptr_row_offset; while (vcol < col && *ptr_end != NUL) { vcol += win_chartabsize(curwin, ptr_end, vcol); - ptr_end += utfc_ptr2len((char *)ptr_end); + ptr_end += utfc_ptr2len(ptr_end); } int matchid; @@ -844,7 +1755,7 @@ static int mouse_adjust_click(win_T *wp, int row, int col) while (prev_matchid == matchid && *ptr != NUL) { INCR(); - ptr += utfc_ptr2len((char *)ptr); + ptr += utfc_ptr2len(ptr); matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line)); } @@ -852,7 +1763,7 @@ static int mouse_adjust_click(win_T *wp, int row, int col) } } - ptr += utfc_ptr2len((char *)ptr); + ptr += utfc_ptr2len(ptr); } return col + nudge; diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h index 08261e4a30..8b2d7e0acd 100644 --- a/src/nvim/mouse.h +++ b/src/nvim/mouse.h @@ -4,42 +4,54 @@ #include <stdbool.h> #include "nvim/buffer_defs.h" +#include "nvim/normal.h" #include "nvim/vim.h" - -// jump_to_mouse() returns one of first four these values, possibly with -// some of the other three added. -#define IN_UNKNOWN 0 -#define IN_BUFFER 1 -#define IN_STATUS_LINE 2 // on status or command line -#define IN_SEP_LINE 4 // on vertical separator line -#define IN_OTHER_WIN 8 // in other window but can't go there -#define CURSOR_MOVED 0x100 -#define MOUSE_FOLD_CLOSE 0x200 // clicked on '-' in fold column -#define MOUSE_FOLD_OPEN 0x400 // clicked on '+' in fold column -#define MOUSE_WINBAR 0x800 // in window toolbar - -// flags for jump_to_mouse() -#define MOUSE_FOCUS 0x01 // need to stay in this window -#define MOUSE_MAY_VIS 0x02 // may start Visual mode -#define MOUSE_DID_MOVE 0x04 // only act when mouse has moved -#define MOUSE_SETPOS 0x08 // only set current mouse position -#define MOUSE_MAY_STOP_VIS 0x10 // may stop Visual mode -#define MOUSE_RELEASED 0x20 // button was released - -// Codes for mouse button events in lower three bits: -#define MOUSE_LEFT 0x00 -#define MOUSE_MIDDLE 0x01 -#define MOUSE_RIGHT 0x02 -#define MOUSE_RELEASE 0x03 - -#define MOUSE_X1 0x300 // Mouse-button X1 (6th) -#define MOUSE_X2 0x400 // Mouse-button X2 - -// Direction for nv_mousescroll() and ins_mousescroll() -#define MSCR_DOWN 0 // DOWN must be FALSE -#define MSCR_UP 1 -#define MSCR_LEFT (-1) -#define MSCR_RIGHT (-2) +#include "nvim/window.h" + +/// jump_to_mouse() returns one of first five these values, possibly with +/// some of the other five added. +enum { + IN_UNKNOWN = 0, + IN_BUFFER = 1, + IN_STATUS_LINE = 2, ///< on status or command line + IN_SEP_LINE = 4, ///< on vertical separator line + IN_OTHER_WIN = 8, ///< in other window but can't go there + CURSOR_MOVED = 0x100, + MOUSE_FOLD_CLOSE = 0x200, ///< clicked on '-' in fold column + MOUSE_FOLD_OPEN = 0x400, ///< clicked on '+' in fold column + MOUSE_WINBAR = 0x800, ///< in window toolbar + MOUSE_STATUSCOL = 0x1000, ///< in 'statuscolumn' +}; + +/// flags for jump_to_mouse() +enum { + MOUSE_FOCUS = 0x01, ///< need to stay in this window + MOUSE_MAY_VIS = 0x02, ///< may start Visual mode + MOUSE_DID_MOVE = 0x04, ///< only act when mouse has moved + MOUSE_SETPOS = 0x08, ///< only set current mouse position + MOUSE_MAY_STOP_VIS = 0x10, ///< may stop Visual mode + MOUSE_RELEASED = 0x20, ///< button was released +}; + +enum { + // Codes for mouse button events in lower three bits: + MOUSE_LEFT = 0x00, + MOUSE_MIDDLE = 0x01, + MOUSE_RIGHT = 0x02, + MOUSE_RELEASE = 0x03, + + // mouse buttons that are handled like a key press + MOUSE_X1 = 0x300, ///< Mouse-button X1 (6th) + MOUSE_X2 = 0x400, ///< Mouse-button X2 +}; + +/// Direction for nv_mousescroll() and ins_mousescroll() +enum { + MSCR_DOWN = 0, ///< DOWN must be false + MSCR_UP = 1, + MSCR_LEFT = -1, + MSCR_RIGHT = -2, +}; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mouse.h.generated.h" diff --git a/src/nvim/move.c b/src/nvim/move.c index 1ed7acd012..3af26b910e 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -1,20 +1,19 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * move.c: Functions for moving the cursor and scrolling text. - * - * There are two ways to move the cursor: - * 1. Move the cursor directly, the text is scrolled to keep the cursor in the - * window. - * 2. Scroll the text, the cursor is moved into the text visible in the - * window. - * The 'scrolloff' option makes this a bit complicated. - */ +// move.c: Functions for moving the cursor and scrolling text. +// +// There are two ways to move the cursor: +// 1. Move the cursor directly, the text is scrolled to keep the cursor in the +// window. +// 2. Scroll the text, the cursor is moved into the text visible in the +// window. +// The 'scrolloff' option makes this a bit complicated. #include <assert.h> -#include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stddef.h> #include "nvim/ascii.h" #include "nvim/buffer.h" @@ -23,18 +22,30 @@ #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/window.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" +#include "nvim/macros.h" #include "nvim/mbyte.h" -#include "nvim/memline.h" +#include "nvim/memline_defs.h" +#include "nvim/message.h" +#include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/vim.h" #include "nvim/window.h" typedef struct { @@ -47,19 +58,15 @@ typedef struct { # include "move.c.generated.h" #endif -/* - * Compute wp->w_botline for the current wp->w_topline. Can be called after - * wp->w_topline changed. - */ +// Compute wp->w_botline for the current wp->w_topline. Can be called after +// wp->w_topline changed. static void comp_botline(win_T *wp) { linenr_T lnum; int done; - /* - * If w_cline_row is valid, start there. - * Otherwise have to start at w_topline. - */ + // If w_cline_row is valid, start there. + // Otherwise have to start at w_topline. check_cursor_moved(wp); if (wp->w_valid & VALID_CROW) { lnum = wp->w_cursor.lnum; @@ -105,7 +112,7 @@ void redraw_for_cursorline(win_T *wp) if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible() && (wp->w_p_rnu || win_cursorline_standout(wp))) { // win_line() will redraw the number column and cursorline only. - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } } @@ -118,11 +125,11 @@ static void redraw_for_cursorcolumn(win_T *wp) if ((wp->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) { if (wp->w_p_cuc || ((HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC)) && using_hlsearch())) { // When 'cursorcolumn' is set or "CurSearch" is in use - // need to redraw with SOME_VALID. - redraw_later(wp, SOME_VALID); + // need to redraw with UPD_SOME_VALID. + redraw_later(wp, UPD_SOME_VALID); } else if (wp->w_p_cul && (wp->w_p_culopt_flags & CULOPT_SCRLINE)) { - // When 'cursorlineopt' contains "screenline" need to redraw with VALID. - redraw_later(wp, VALID); + // When 'cursorlineopt' contains "screenline" need to redraw with UPD_VALID. + redraw_later(wp, UPD_VALID); } } // If the cursor moves horizontally when 'concealcursor' is active, then the @@ -133,21 +140,7 @@ static void redraw_for_cursorcolumn(win_T *wp) } } -/* - * Update curwin->w_topline and redraw if necessary. - * Used to update the screen before printing a message. - */ -void update_topline_redraw(void) -{ - update_topline(curwin); - if (must_redraw) { - update_screen(0); - } -} - -/* - * Update curwin->w_topline to move the cursor onto the screen. - */ +// Update curwin->w_topline to move the cursor onto the screen. void update_topline(win_T *wp) { linenr_T old_topline; @@ -157,12 +150,16 @@ void update_topline(win_T *wp) long *so_ptr = wp->w_p_so >= 0 ? &wp->w_p_so : &p_so; long save_so = *so_ptr; + // Cursor is updated instead when this is true for 'splitkeep'. + if (skip_update_topline) { + return; + } + // If there is no valid screen and when the window height is zero just use // the cursor line. if (!default_grid.chars || wp->w_height_inner == 0) { wp->w_topline = wp->w_cursor.lnum; wp->w_botline = wp->w_topline; - wp->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; wp->w_viewport_invalid = true; wp->w_scbind_pos = 1; return; @@ -184,7 +181,7 @@ void update_topline(win_T *wp) // If the buffer is empty, always set topline to 1. if (buf_is_empty(curbuf)) { // special case - file is empty if (wp->w_topline != 1) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } wp->w_topline = 1; wp->w_botline = 2; @@ -250,14 +247,12 @@ void update_topline(win_T *wp) } } - /* - * If the cursor is below the bottom of the window, scroll the window - * to put the cursor on the window. - * When w_botline is invalid, recompute it first, to avoid a redraw later. - * If w_botline was approximated, we might need a redraw later in a few - * cases, but we don't want to spend (a lot of) time recomputing w_botline - * for every small change. - */ + // If the cursor is below the bottom of the window, scroll the window + // to put the cursor on the window. + // When w_botline is invalid, recompute it first, to avoid a redraw later. + // If w_botline was approximated, we might need a redraw later in a few + // cases, but we don't want to spend (a lot of) time recomputing w_botline + // for every small change. if (check_botline) { if (!(wp->w_valid & VALID_BOTLINE_AP)) { validate_botline(wp); @@ -328,17 +323,15 @@ void update_topline(win_T *wp) wp->w_viewport_invalid = true; win_check_anchored_floats(wp); - /* - * Need to redraw when topline changed. - */ + // Need to redraw when topline changed. if (wp->w_topline != old_topline || wp->w_topfill != old_topfill) { dollar_vcol = -1; if (wp->w_skipcol != 0) { wp->w_skipcol = 0; - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } else { - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } // May need to set w_skipcol when cursor in w_topline. if (wp->w_cursor.lnum == wp->w_topline) { @@ -349,22 +342,9 @@ void update_topline(win_T *wp) *so_ptr = save_so; } -/* - * Update win->w_topline to move the cursor onto the screen. - */ -void update_topline_win(win_T *win) -{ - switchwin_T switchwin; - switch_win(&switchwin, win, NULL, true); - update_topline(curwin); - restore_win(&switchwin, true); -} - -/* - * Return the scrolljump value to use for the current window. - * When 'scrolljump' is positive use it as-is. - * When 'scrolljump' is negative use it as a percentage of the window height. - */ +// Return the scrolljump value to use for the current window. +// When 'scrolljump' is positive use it as-is. +// When 'scrolljump' is negative use it as a percentage of the window height. static int scrolljump_value(void) { long result = p_sj >= 0 ? p_sj : (curwin->w_height_inner * -p_sj) / 100; @@ -372,10 +352,8 @@ static int scrolljump_value(void) return (int)result; } -/* - * Return true when there are not 'scrolloff' lines above the cursor for the - * current window. - */ +// Return true when there are not 'scrolloff' lines above the cursor for the +// current window. static bool check_top_offset(void) { long so = get_scrolloff_value(curwin); @@ -403,18 +381,23 @@ static bool check_top_offset(void) return false; } +/// Update w_curswant. +void update_curswant_force(void) +{ + validate_virtcol(); + curwin->w_curswant = curwin->w_virtcol; + curwin->w_set_curswant = false; +} + +/// Update w_curswant if w_set_curswant is set. void update_curswant(void) { if (curwin->w_set_curswant) { - validate_virtcol(); - curwin->w_curswant = curwin->w_virtcol; - curwin->w_set_curswant = false; + update_curswant_force(); } } -/* - * Check if the cursor has moved. Set the w_valid flag accordingly. - */ +// Check if the cursor has moved. Set the w_valid flag accordingly. void check_cursor_moved(win_T *wp) { if (wp->w_cursor.lnum != wp->w_valid_cursor.lnum) { @@ -435,11 +418,9 @@ void check_cursor_moved(win_T *wp) } } -/* - * Call this function when some window settings have changed, which require - * the cursor position, botline and topline to be recomputed and the window to - * be redrawn. E.g, when changing the 'wrap' option or folding. - */ +// Call this function when some window settings have changed, which require +// the cursor position, botline and topline to be recomputed and the window to +// be redrawn. E.g, when changing the 'wrap' option or folding. void changed_window_setting(void) { changed_window_setting_win(curwin); @@ -450,12 +431,10 @@ void changed_window_setting_win(win_T *wp) wp->w_lines_valid = 0; changed_line_abv_curs_win(wp); wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP|VALID_TOPLINE); - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } -/* - * Set wp->w_topline to a certain number. - */ +// Set wp->w_topline to a certain number. void set_topline(win_T *wp, linenr_T lnum) { linenr_T prev_topline = wp->w_topline; @@ -472,14 +451,12 @@ void set_topline(win_T *wp, linenr_T lnum) } wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_TOPLINE); // Don't set VALID_TOPLINE here, 'scrolloff' needs to be checked. - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } -/* - * Call this function when the length of the cursor line (in screen - * characters) has changed, and the change is before the cursor. - * Need to take care of w_botline separately! - */ +// Call this function when the length of the cursor line (in screen +// characters) has changed, and the change is before the cursor. +// Need to take care of w_botline separately! void changed_cline_bef_curs(void) { curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL @@ -492,11 +469,9 @@ void changed_cline_bef_curs_win(win_T *wp) |VALID_CHEIGHT|VALID_TOPLINE); } -/* - * Call this function when the length of a line (in screen characters) above - * the cursor have changed. - * Need to take care of w_botline separately! - */ +// Call this function when the length of a line (in screen characters) above +// the cursor have changed. +// Need to take care of w_botline separately! void changed_line_abv_curs(void) { curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL|VALID_CROW @@ -509,9 +484,7 @@ void changed_line_abv_curs_win(win_T *wp) |VALID_CHEIGHT|VALID_TOPLINE); } -/* - * Make sure the value of curwin->w_botline is valid. - */ +// Make sure the value of curwin->w_botline is valid. void validate_botline(win_T *wp) { if (!(wp->w_valid & VALID_BOTLINE)) { @@ -519,9 +492,7 @@ void validate_botline(win_T *wp) } } -/* - * Mark curwin->w_botline as invalid (because of some change in the buffer). - */ +// Mark curwin->w_botline as invalid (because of some change in the buffer). void invalidate_botline(void) { curwin->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP); @@ -537,9 +508,7 @@ void approximate_botline_win(win_T *wp) wp->w_valid &= ~VALID_BOTLINE; } -/* - * Return true if curwin->w_wrow and curwin->w_wcol are valid. - */ +// Return true if curwin->w_wrow and curwin->w_wcol are valid. int cursor_valid(void) { check_cursor_moved(curwin); @@ -547,10 +516,8 @@ int cursor_valid(void) (VALID_WROW|VALID_WCOL); } -/* - * Validate cursor position. Makes sure w_wrow and w_wcol are valid. - * w_topline must be valid, you may need to call update_topline() first! - */ +// Validate cursor position. Makes sure w_wrow and w_wcol are valid. +// w_topline must be valid, you may need to call update_topline() first! void validate_cursor(void) { check_cursor_moved(curwin); @@ -559,10 +526,8 @@ void validate_cursor(void) } } -/* - * Compute wp->w_cline_row and wp->w_cline_height, based on the current value - * of wp->w_topline. - */ +// Compute wp->w_cline_row and wp->w_cline_height, based on the current value +// of wp->w_topline. static void curs_rows(win_T *wp) { // Check if wp->w_lines[].wl_size is invalid @@ -571,7 +536,7 @@ static void curs_rows(win_T *wp) || wp->w_lines[0].wl_lnum > wp->w_topline); int i = 0; wp->w_cline_row = 0; - for (linenr_T lnum = wp->w_topline; lnum < wp->w_cursor.lnum; ++i) { + for (linenr_T lnum = wp->w_topline; lnum < wp->w_cursor.lnum; i++) { bool valid = false; if (!all_invalid && i < wp->w_lines_valid) { if (wp->w_lines[i].wl_lnum < lnum || !wp->w_lines[i].wl_valid) { @@ -587,7 +552,7 @@ static void curs_rows(win_T *wp) valid = true; } } else if (wp->w_lines[i].wl_lnum > lnum) { - --i; // hold at inserted lines + i--; // hold at inserted lines } } if (valid && (lnum != wp->w_topline || !win_may_fill(wp))) { @@ -633,77 +598,77 @@ static void curs_rows(win_T *wp) wp->w_valid |= VALID_CROW|VALID_CHEIGHT; } -/* - * Validate curwin->w_virtcol only. - */ +// Validate curwin->w_virtcol only. void validate_virtcol(void) { validate_virtcol_win(curwin); } -/* - * Validate wp->w_virtcol only. - */ +// Validate wp->w_virtcol only. void validate_virtcol_win(win_T *wp) { check_cursor_moved(wp); - if (!(wp->w_valid & VALID_VIRTCOL)) { - getvvcol(wp, &wp->w_cursor, NULL, &(wp->w_virtcol), NULL); - redraw_for_cursorcolumn(wp); - wp->w_valid |= VALID_VIRTCOL; + + if (wp->w_valid & VALID_VIRTCOL) { + return; } + + getvvcol(wp, &wp->w_cursor, NULL, &(wp->w_virtcol), NULL); + redraw_for_cursorcolumn(wp); + wp->w_valid |= VALID_VIRTCOL; } -/* - * Validate curwin->w_cline_height only. - */ +// Validate curwin->w_cline_height only. void validate_cheight(void) { check_cursor_moved(curwin); - if (!(curwin->w_valid & VALID_CHEIGHT)) { - curwin->w_cline_height = plines_win_full(curwin, curwin->w_cursor.lnum, - NULL, &curwin->w_cline_folded, - true); - curwin->w_valid |= VALID_CHEIGHT; + + if (curwin->w_valid & VALID_CHEIGHT) { + return; } + + curwin->w_cline_height = plines_win_full(curwin, curwin->w_cursor.lnum, + NULL, &curwin->w_cline_folded, + true); + curwin->w_valid |= VALID_CHEIGHT; } -/* - * Validate w_wcol and w_virtcol only. - */ +// Validate w_wcol and w_virtcol only. void validate_cursor_col(void) { validate_virtcol(); - if (!(curwin->w_valid & VALID_WCOL)) { - colnr_T col = curwin->w_virtcol; - colnr_T off = curwin_col_off(); - col += off; - int width = curwin->w_width_inner - off + curwin_col_off2(); - - // long line wrapping, adjust curwin->w_wrow - if (curwin->w_p_wrap && col >= (colnr_T)curwin->w_width_inner - && width > 0) { - // use same formula as what is used in curs_columns() - col -= ((col - curwin->w_width_inner) / width + 1) * width; - } - if (col > (int)curwin->w_leftcol) { - col -= curwin->w_leftcol; - } else { - col = 0; - } - curwin->w_wcol = col; - curwin->w_valid |= VALID_WCOL; + if (curwin->w_valid & VALID_WCOL) { + return; } + + colnr_T col = curwin->w_virtcol; + colnr_T off = curwin_col_off(); + col += off; + int width = curwin->w_width_inner - off + curwin_col_off2(); + + // long line wrapping, adjust curwin->w_wrow + if (curwin->w_p_wrap && col >= (colnr_T)curwin->w_width_inner + && width > 0) { + // use same formula as what is used in curs_columns() + col -= ((col - curwin->w_width_inner) / width + 1) * width; + } + if (col > (int)curwin->w_leftcol) { + col -= curwin->w_leftcol; + } else { + col = 0; + } + curwin->w_wcol = col; + + curwin->w_valid |= VALID_WCOL; } -/* - * Compute offset of a window, occupied by absolute or relative line number, - * fold column and sign column (these don't move when scrolling horizontally). - */ +// Compute offset of a window, occupied by absolute or relative line number, +// fold column and sign column (these don't move when scrolling horizontally). int win_col_off(win_T *wp) { - return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0) + return ((wp->w_p_nu || wp->w_p_rnu || (*wp->w_p_stc != NUL)) ? + (number_width(wp) + (*wp->w_p_stc == NUL)) : 0) + (cmdwin_type == 0 || wp != curwin ? 0 : 1) + win_fdccol_count(wp) + (win_signcol_count(wp) * win_signcol_width(wp)); @@ -714,11 +679,9 @@ int curwin_col_off(void) return win_col_off(curwin); } -/* - * Return the difference in column offset for the second screen line of a - * wrapped line. It's 8 if 'number' or 'relativenumber' is on and 'n' is in - * 'cpoptions'. - */ +// Return the difference in column offset for the second screen line of a +// wrapped line. It's 8 if 'number' or 'relativenumber' is on and 'n' is in +// 'cpoptions'. int win_col_off2(win_T *wp) { if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) != NULL) { @@ -746,9 +709,7 @@ void curs_columns(win_T *wp, int may_scroll) long so = get_scrolloff_value(wp); long siso = get_sidescrolloff_value(wp); - /* - * First make sure that w_topline is valid (after moving the cursor). - */ + // First make sure that w_topline is valid (after moving the cursor). update_topline(wp); // Next make sure that w_cline_row is valid. @@ -756,9 +717,7 @@ void curs_columns(win_T *wp, int may_scroll) curs_rows(wp); } - /* - * Compute the number of virtual columns. - */ + // Compute the number of virtual columns. if (wp->w_cline_folded) { // In a folded line the cursor is always in the first column startcol = wp->w_virtcol = endcol = wp->w_leftcol; @@ -801,7 +760,7 @@ void curs_columns(win_T *wp, int may_scroll) // When cursor wraps to first char of next line in Insert // mode, the 'showbreak' string isn't shown, backup to first // column - char *const sbr = (char *)get_showbreak_value(wp); + char *const sbr = get_showbreak_value(wp); if (*sbr && *get_cursor_pos_ptr() == NUL && wp->w_wcol == vim_strsize(sbr)) { wp->w_wcol = 0; @@ -847,7 +806,7 @@ void curs_columns(win_T *wp, int may_scroll) wp->w_leftcol = new_leftcol; win_check_anchored_floats(wp); // screen has to be redrawn with new wp->w_leftcol - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } } wp->w_wcol -= wp->w_leftcol; @@ -871,8 +830,7 @@ void curs_columns(win_T *wp, int may_scroll) if ((wp->w_wrow >= wp->w_height_inner || ((prev_skipcol > 0 || wp->w_wrow + so >= wp->w_height_inner) - && (plines = - plines_win_nofill(wp, wp->w_cursor.lnum, false)) - 1 + && (plines = plines_win_nofill(wp, wp->w_cursor.lnum, false)) - 1 >= wp->w_height_inner)) && wp->w_height_inner != 0 && wp->w_cursor.lnum == wp->w_topline @@ -954,7 +912,7 @@ void curs_columns(win_T *wp, int may_scroll) wp->w_skipcol = 0; } if (prev_skipcol != wp->w_skipcol) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } redraw_for_cursorcolumn(curwin); @@ -980,11 +938,16 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, int rowoff = 0; colnr_T coloff = 0; bool visible_row = false; - - if (pos->lnum >= wp->w_topline && pos->lnum < wp->w_botline) { - row = plines_m_win(wp, wp->w_topline, pos->lnum - 1) + 1; + bool is_folded = false; + + if (pos->lnum >= wp->w_topline && pos->lnum <= wp->w_botline) { + linenr_T lnum = pos->lnum; + is_folded = hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL); + row = plines_m_win(wp, wp->w_topline, lnum - 1) + 1; + // Add filler lines above this buffer line. + row += win_get_fill(wp, lnum); visible_row = true; - } else if (pos->lnum < wp->w_topline) { + } else if (!local || pos->lnum < wp->w_topline) { row = 0; } else { row = wp->w_height_inner; @@ -993,46 +956,108 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, bool existing_row = (pos->lnum > 0 && pos->lnum <= wp->w_buffer->b_ml.ml_line_count); - if ((local && existing_row) || visible_row) { - colnr_T off; - colnr_T col; - int width; - - getvcol(wp, pos, &scol, &ccol, &ecol); - - // similar to what is done in validate_cursor_col() - col = scol; - off = win_col_off(wp); - col += off; - width = wp->w_width - off + win_col_off2(wp); - - // long line wrapping, adjust row - if (wp->w_p_wrap && col >= (colnr_T)wp->w_width && width > 0) { - // use same formula as what is used in curs_columns() - rowoff = visible_row ? ((col - wp->w_width) / width + 1) : 0; - col -= rowoff * width; - } + if ((local || visible_row) && existing_row) { + const colnr_T off = win_col_off(wp); + if (is_folded) { + row += local ? 0 : wp->w_winrow + wp->w_winrow_off; + coloff = (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1 + off; + } else { + getvcol(wp, pos, &scol, &ccol, &ecol); + + // similar to what is done in validate_cursor_col() + colnr_T col = scol; + col += off; + int width = wp->w_width - off + win_col_off2(wp); + + // long line wrapping, adjust row + if (wp->w_p_wrap && col >= (colnr_T)wp->w_width && width > 0) { + // use same formula as what is used in curs_columns() + rowoff = visible_row ? ((col - wp->w_width) / width + 1) : 0; + col -= rowoff * width; + } - col -= wp->w_leftcol; + col -= wp->w_leftcol; - if (col >= 0 && col < wp->w_width) { - coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1; - } else { - scol = ccol = ecol = 0; - // character is left or right of the window - if (local) { - coloff = col < 0 ? -1 : wp->w_width_inner + 1; + if (col >= 0 && col < wp->w_width && row + rowoff <= wp->w_height) { + coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1; + row += local ? 0 : wp->w_winrow + wp->w_winrow_off; } else { - row = 0; + // character is left, right or below of the window + scol = ccol = ecol = 0; + if (local) { + coloff = col < 0 ? -1 : wp->w_width_inner + 1; + } else { + row = rowoff = 0; + } } } } - *rowp = (local ? 0 : wp->w_winrow + wp->w_winrow_off) + row + rowoff; + *rowp = row + rowoff; *scolp = scol + coloff; *ccolp = ccol + coloff; *ecolp = ecol + coloff; } +/// "screenpos({winid}, {lnum}, {col})" function +void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + tv_dict_alloc_ret(rettv); + dict_T *dict = rettv->vval.v_dict; + + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + return; + } + + pos_T pos = { + .lnum = (linenr_T)tv_get_number(&argvars[1]), + .col = (colnr_T)tv_get_number(&argvars[2]) - 1, + .coladd = 0 + }; + if (pos.lnum > wp->w_buffer->b_ml.ml_line_count) { + semsg(_(e_invalid_line_number_nr), pos.lnum); + return; + } + int row = 0; + int scol = 0, ccol = 0, ecol = 0; + textpos2screenpos(wp, &pos, &row, &scol, &ccol, &ecol, false); + + tv_dict_add_nr(dict, S_LEN("row"), row); + tv_dict_add_nr(dict, S_LEN("col"), scol); + tv_dict_add_nr(dict, S_LEN("curscol"), ccol); + tv_dict_add_nr(dict, S_LEN("endcol"), ecol); +} + +/// "virtcol2col({winid}, {lnum}, {col})" function +void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) +{ + rettv->vval.v_number = -1; + + if (tv_check_for_number_arg(argvars, 0) == FAIL + || tv_check_for_number_arg(argvars, 1) == FAIL + || tv_check_for_number_arg(argvars, 2) == FAIL) { + return; + } + + win_T *wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL) { + return; + } + + bool error = false; + linenr_T lnum = (linenr_T)tv_get_number_chk(&argvars[1], &error); + if (error || lnum < 0 || lnum > wp->w_buffer->b_ml.ml_line_count) { + return; + } + + int screencol = (int)tv_get_number_chk(&argvars[2], &error); + if (error || screencol < 0) { + return; + } + + rettv->vval.v_number = vcol2col(wp, lnum, screencol); +} + /// Scroll the current window down by "line_count" logical lines. "CTRL-Y" /// /// @param line_count number of lines to scroll @@ -1053,7 +1078,7 @@ bool scrolldown(long line_count, int byfold) if (curwin->w_topline == 1) { break; } - --curwin->w_topline; + curwin->w_topline--; curwin->w_topfill = 0; // A sequence of folded lines only counts for one logical line linenr_T first; @@ -1068,7 +1093,7 @@ bool scrolldown(long line_count, int byfold) done += plines_win_nofill(curwin, curwin->w_topline, true); } } - --curwin->w_botline; // approximate w_botline + curwin->w_botline--; // approximate w_botline invalidate_botline(); } curwin->w_wrow += done; // keep w_wrow updated @@ -1079,10 +1104,8 @@ bool scrolldown(long line_count, int byfold) } check_topfill(curwin, true); - /* - * Compute the row number of the last row of the cursor line - * and move the cursor onto the displayed part of the window. - */ + // Compute the row number of the last row of the cursor line + // and move the cursor onto the displayed part of the window. int wrow = curwin->w_wrow; if (curwin->w_p_wrap && curwin->w_width_inner != 0) { validate_virtcol(); @@ -1130,7 +1153,7 @@ bool scrollup(long line_count, int byfold) linenr_T lnum = curwin->w_topline; while (line_count--) { if (curwin->w_topfill > 0) { - --curwin->w_topfill; + curwin->w_topfill--; } else { if (byfold) { (void)hasFolding(lnum, NULL, &lnum); @@ -1187,7 +1210,7 @@ void check_topfill(win_T *wp, bool down) int n = plines_win_nofill(wp, wp->w_topline, true); if (wp->w_topfill + n > wp->w_height_inner) { if (down && wp->w_topline > 1) { - --wp->w_topline; + wp->w_topline--; wp->w_topfill = 0; } else { wp->w_topfill = wp->w_height_inner - n; @@ -1200,10 +1223,8 @@ void check_topfill(win_T *wp, bool down) win_check_anchored_floats(curwin); } -/* - * Use as many filler lines as possible for w_topline. Make sure w_topline - * is still visible. - */ +// Use as many filler lines as possible for w_topline. Make sure w_topline +// is still visible. static void max_topfill(void) { int n = plines_win_nofill(curwin, curwin->w_topline, true); @@ -1217,10 +1238,8 @@ static void max_topfill(void) } } -/* - * Scroll the screen one line down, but don't do it if it would move the - * cursor off the screen. - */ +// Scroll the screen one line down, but don't do it if it would move the +// cursor off the screen. void scrolldown_clamp(void) { int can_fill = (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)); @@ -1249,22 +1268,20 @@ void scrolldown_clamp(void) } if (end_row < curwin->w_height_inner - get_scrolloff_value(curwin)) { if (can_fill) { - ++curwin->w_topfill; + curwin->w_topfill++; check_topfill(curwin, true); } else { - --curwin->w_topline; + curwin->w_topline--; curwin->w_topfill = 0; } (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); - --curwin->w_botline; // approximate w_botline + curwin->w_botline--; // approximate w_botline curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE); } } -/* - * Scroll the screen one line up, but don't do it if it would move the cursor - * off the screen. - */ +// Scroll the screen one line up, but don't do it if it would move the cursor +// off the screen. void scrollup_clamp(void) { if (curwin->w_topline == curbuf->b_ml.ml_line_count @@ -1296,12 +1313,10 @@ void scrollup_clamp(void) } } -/* - * Add one line above "lp->lnum". This can be a filler line, a closed fold or - * a (wrapped) text line. Uses and sets "lp->fill". - * Returns the height of the added line in "lp->height". - * Lines above the first one are incredibly high: MAXCOL. - */ +// Add one line above "lp->lnum". This can be a filler line, a closed fold or +// a (wrapped) text line. Uses and sets "lp->fill". +// Returns the height of the added line in "lp->height". +// Lines above the first one are incredibly high: MAXCOL. static void topline_back(win_T *wp, lineoff_T *lp) { if (lp->fill < win_get_fill(wp, lp->lnum)) { @@ -1309,7 +1324,7 @@ static void topline_back(win_T *wp, lineoff_T *lp) lp->fill++; lp->height = 1; } else { - --lp->lnum; + lp->lnum--; lp->fill = 0; if (lp->lnum < 1) { lp->height = MAXCOL; @@ -1322,12 +1337,10 @@ static void topline_back(win_T *wp, lineoff_T *lp) } } -/* - * Add one line below "lp->lnum". This can be a filler line, a closed fold or - * a (wrapped) text line. Uses and sets "lp->fill". - * Returns the height of the added line in "lp->height". - * Lines below the last one are incredibly high. - */ +// Add one line below "lp->lnum". This can be a filler line, a closed fold or +// a (wrapped) text line. Uses and sets "lp->fill". +// Returns the height of the added line in "lp->height". +// Lines below the last one are incredibly high. static void botline_forw(win_T *wp, lineoff_T *lp) { if (lp->fill < win_get_fill(wp, lp->lnum + 1)) { @@ -1335,7 +1348,7 @@ static void botline_forw(win_T *wp, lineoff_T *lp) lp->fill++; lp->height = 1; } else { - ++lp->lnum; + lp->lnum++; lp->fill = 0; assert(wp->w_buffer != 0); if (lp->lnum > wp->w_buffer->b_ml.ml_line_count) { @@ -1349,11 +1362,9 @@ static void botline_forw(win_T *wp, lineoff_T *lp) } } -/* - * Switch from including filler lines below lp->lnum to including filler - * lines above loff.lnum + 1. This keeps pointing to the same line. - * When there are no filler lines nothing changes. - */ +// Switch from including filler lines below lp->lnum to including filler +// lines above loff.lnum + 1. This keeps pointing to the same line. +// When there are no filler lines nothing changes. static void botline_topline(lineoff_T *lp) { if (lp->fill > 0) { @@ -1362,11 +1373,9 @@ static void botline_topline(lineoff_T *lp) } } -/* - * Switch from including filler lines above lp->lnum to including filler - * lines below loff.lnum - 1. This keeps pointing to the same line. - * When there are no filler lines nothing changes. - */ +// Switch from including filler lines above lp->lnum to including filler +// lines below loff.lnum - 1. This keeps pointing to the same line. +// When there are no filler lines nothing changes. static void topline_botline(lineoff_T *lp) { if (lp->fill > 0) { @@ -1375,11 +1384,9 @@ static void topline_botline(lineoff_T *lp) } } -/* - * Recompute topline to put the cursor at the top of the window. - * Scroll at least "min_scroll" lines. - * If "always" is true, always set topline (for "zt"). - */ +// Recompute topline to put the cursor at the top of the window. +// Scroll at least "min_scroll" lines. +// If "always" is true, always set topline (for "zt"). void scroll_cursor_top(int min_scroll, int always) { int scrolled = 0; @@ -1394,13 +1401,11 @@ void scroll_cursor_top(int min_scroll, int always) off = mouse_dragging - 1; } - /* - * Decrease topline until: - * - it has become 1 - * - (part of) the cursor line is moved off the screen or - * - moved at least 'scrolljump' lines and - * - at least 'scrolloff' lines above and below the cursor - */ + // Decrease topline until: + // - it has become 1 + // - (part of) the cursor line is moved off the screen or + // - moved at least 'scrolljump' lines and + // - at least 'scrolloff' lines above and below the cursor validate_cheight(); int used = curwin->w_cline_height; // includes filler lines above if (curwin->w_cursor.lnum < curwin->w_topline) { @@ -1421,10 +1426,8 @@ void scroll_cursor_top(int min_scroll, int always) // Hide filler lines above cursor line by adding them to "extra". int extra = win_get_fill(curwin, curwin->w_cursor.lnum); - /* - * Check if the lines from "top" to "bot" fit in the window. If they do, - * set new_topline and advance "top" and "bot" to include more lines. - */ + // Check if the lines from "top" to "bot" fit in the window. If they do, + // set new_topline and advance "top" and "bot" to include more lines. while (top > 0) { int i = hasFolding(top, &top, NULL) ? 1 // count one logical line for a sequence of folded lines @@ -1445,9 +1448,7 @@ void scroll_cursor_top(int min_scroll, int always) scrolled += i; } - /* - * If scrolling is needed, scroll at least 'sj' lines. - */ + // If scrolling is needed, scroll at least 'sj' lines. if ((new_topline >= curwin->w_topline || scrolled > min_scroll) && extra >= off) { break; @@ -1459,18 +1460,14 @@ void scroll_cursor_top(int min_scroll, int always) bot++; } - /* - * If we don't have enough space, put cursor in the middle. - * This makes sure we get the same position when using "k" and "j" - * in a small window. - */ + // If we don't have enough space, put cursor in the middle. + // This makes sure we get the same position when using "k" and "j" + // in a small window. if (used > curwin->w_height_inner) { scroll_cursor_halfway(false); } else { - /* - * If "always" is false, only adjust topline to a lower value, higher - * value may happen with wrapping lines - */ + // If "always" is false, only adjust topline to a lower value, higher + // value may happen with wrapping lines if (new_topline < curwin->w_topline || always) { curwin->w_topline = new_topline; } @@ -1495,10 +1492,8 @@ void scroll_cursor_top(int min_scroll, int always) } } -/* - * Set w_empty_rows and w_filler_rows for window "wp", having used up "used" - * screen lines for text lines. - */ +// Set w_empty_rows and w_filler_rows for window "wp", having used up "used" +// screen lines for text lines. void set_empty_rows(win_T *wp, int used) { wp->w_filler_rows = 0; @@ -1577,13 +1572,11 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) } } - /* - * Stop counting lines to scroll when - * - hitting start of the file - * - scrolled nothing or at least 'sj' lines - * - at least 'so' lines below the cursor - * - lines between botline and cursor have been counted - */ + // Stop counting lines to scroll when + // - hitting start of the file + // - scrolled nothing or at least 'sj' lines + // - at least 'so' lines below the cursor + // - lines between botline and cursor have been counted if (!hasFolding(curwin->w_cursor.lnum, &loff.lnum, &boff.lnum)) { loff.lnum = cln; boff.lnum = cln; @@ -1673,21 +1666,17 @@ void scroll_cursor_bot(int min_scroll, int set_topbot) } } - /* - * Scroll up if the cursor is off the bottom of the screen a bit. - * Otherwise put it at 1/2 of the screen. - */ + // Scroll up if the cursor is off the bottom of the screen a bit. + // Otherwise put it at 1/2 of the screen. if (line_count >= curwin->w_height_inner && line_count > min_scroll) { scroll_cursor_halfway(false); } else { scrollup(line_count, true); } - /* - * If topline didn't change we need to restore w_botline and w_empty_rows - * (we changed them). - * If topline did change, update_screen() will set botline. - */ + // If topline didn't change we need to restore w_botline and w_empty_rows + // (we changed them). + // If topline did change, update_screen() will set botline. if (curwin->w_topline == old_topline && set_topbot) { curwin->w_botline = old_botline; curwin->w_empty_rows = old_empty_rows; @@ -1726,7 +1715,7 @@ void scroll_cursor_halfway(int atend) } below += boff.height; } else { - ++below; // count a "~" line + below++; // count a "~" line if (atend) { used++; } @@ -1760,18 +1749,14 @@ void scroll_cursor_halfway(int atend) curwin->w_valid |= VALID_TOPLINE; } -/* - * Correct the cursor position so that it is in a part of the screen at least - * 'so' lines from the top and bottom, if possible. - * If not possible, put it at the same position as scroll_cursor_halfway(). - * When called topline must be valid! - */ +// Correct the cursor position so that it is in a part of the screen at least +// 'so' lines from the top and bottom, if possible. +// If not possible, put it at the same position as scroll_cursor_halfway(). +// When called topline must be valid! void cursor_correct(void) { - /* - * How many lines we would like to have above/below the cursor depends on - * whether the first/last line of the file is on screen. - */ + // How many lines we would like to have above/below the cursor depends on + // whether the first/last line of the file is on screen. int above_wanted = (int)get_scrolloff_value(curwin); int below_wanted = (int)get_scrolloff_value(curwin); if (mouse_dragging > 0) { @@ -1795,10 +1780,8 @@ void cursor_correct(void) } } - /* - * If there are sufficient file-lines above and below the cursor, we can - * return now. - */ + // If there are sufficient file-lines above and below the cursor, we can + // return now. linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number if (cln >= curwin->w_topline + above_wanted && cln < curwin->w_botline - below_wanted @@ -1806,12 +1789,10 @@ void cursor_correct(void) return; } - /* - * Narrow down the area where the cursor can be put by taking lines from - * the top and the bottom until: - * - the desired context lines are found - * - the lines from the top is past the lines from the bottom - */ + // Narrow down the area where the cursor can be put by taking lines from + // the top and the bottom until: + // - the desired context lines are found + // - the lines from the top is past the lines from the bottom linenr_T topline = curwin->w_topline; linenr_T botline = curwin->w_botline - 1; // count filler lines as context @@ -1860,11 +1841,9 @@ void cursor_correct(void) curwin->w_viewport_invalid = true; } -/* - * move screen 'count' pages up or down and update screen - * - * return FAIL for failure, OK otherwise - */ +// move screen 'count' pages up or down and update screen +// +// return FAIL for failure, OK otherwise int onepage(Direction dir, long count) { long n; @@ -1899,7 +1878,7 @@ int onepage(Direction dir, long count) if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) { // Vi compatible scrolling if (p_window <= 2) { - ++curwin->w_topline; + curwin->w_topline++; } else { curwin->w_topline += (linenr_T)p_window - 2; } @@ -1935,7 +1914,7 @@ int onepage(Direction dir, long count) if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) { // Vi compatible scrolling (sort of) if (p_window <= 2) { - --curwin->w_topline; + curwin->w_topline--; } else { curwin->w_topline -= (linenr_T)p_window - 2; } @@ -2000,7 +1979,7 @@ int onepage(Direction dir, long count) max_topfill(); } if (curwin->w_topfill == loff.fill) { - --curwin->w_topline; + curwin->w_topline--; curwin->w_topfill = 0; } comp_botline(curwin); @@ -2040,22 +2019,20 @@ int onepage(Direction dir, long count) } } - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); return retval; } -/* - * Decide how much overlap to use for page-up or page-down scrolling. - * This is symmetric, so that doing both keeps the same lines displayed. - * Three lines are examined: - * - * before CTRL-F after CTRL-F / before CTRL-B - * etc. l1 - * l1 last but one line ------------ - * l2 last text line l2 top text line - * ------------- l3 second text line - * l3 etc. - */ +// Decide how much overlap to use for page-up or page-down scrolling. +// This is symmetric, so that doing both keeps the same lines displayed. +// Three lines are examined: +// +// before CTRL-F after CTRL-F / before CTRL-B +// etc. l1 +// l1 last but one line ------------ +// l2 last text line l2 top text line +// ------------- l3 second text line +// l3 etc. static void get_scroll_overlap(lineoff_T *lp, int dir) { int min_height = curwin->w_height_inner - 2; @@ -2125,9 +2102,7 @@ void halfpage(bool flag, linenr_T Prenum) validate_botline(curwin); int room = curwin->w_empty_rows + curwin->w_filler_rows; if (flag) { - /* - * scroll the text up - */ + // scroll the text up while (n > 0 && curwin->w_botline <= curbuf->b_ml.ml_line_count) { if (curwin->w_topfill > 0) { i = 1; @@ -2144,7 +2119,7 @@ void halfpage(bool flag, linenr_T Prenum) curwin->w_topfill = win_get_fill(curwin, curwin->w_topline); if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; curwin->w_valid &= ~(VALID_VIRTCOL|VALID_CHEIGHT|VALID_WCOL); } @@ -2177,7 +2152,7 @@ void halfpage(bool flag, linenr_T Prenum) && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { (void)hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum); - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; } } else { curwin->w_cursor.lnum += n; @@ -2185,9 +2160,7 @@ void halfpage(bool flag, linenr_T Prenum) check_cursor_lnum(); } } else { - /* - * scroll the text down - */ + // scroll the text down while (n > 0 && curwin->w_topline > 1) { if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) { i = 1; @@ -2199,7 +2172,7 @@ void halfpage(bool flag, linenr_T Prenum) if (n < 0 && scrolled > 0) { break; } - --curwin->w_topline; + curwin->w_topline--; (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); curwin->w_topfill = 0; } @@ -2207,7 +2180,7 @@ void halfpage(bool flag, linenr_T Prenum) VALID_BOTLINE|VALID_BOTLINE_AP); scrolled += i; if (curwin->w_cursor.lnum > 1) { - --curwin->w_cursor.lnum; + curwin->w_cursor.lnum--; curwin->w_valid &= ~(VALID_VIRTCOL|VALID_CHEIGHT|VALID_WCOL); } } @@ -2218,7 +2191,7 @@ void halfpage(bool flag, linenr_T Prenum) curwin->w_cursor.lnum = 1; } else if (hasAnyFolding(curwin)) { while (--n >= 0 && curwin->w_cursor.lnum > 1) { - --curwin->w_cursor.lnum; + curwin->w_cursor.lnum--; (void)hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); } @@ -2232,7 +2205,7 @@ void halfpage(bool flag, linenr_T Prenum) check_topfill(curwin, !flag); cursor_correct(); beginline(BL_SOL | BL_FIX); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } void do_check_cursorbind(void) @@ -2247,9 +2220,7 @@ void do_check_cursorbind(void) int old_VIsual_select = VIsual_select; int old_VIsual_active = VIsual_active; - /* - * loop through the cursorbound windows - */ + // loop through the cursorbound windows VIsual_select = VIsual_active = 0; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { curwin = wp; @@ -2284,7 +2255,7 @@ void do_check_cursorbind(void) } // Correct cursor for multi-byte character. mb_adjust_cursor(); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); // Only scroll when 'scrollbind' hasn't done this. if (!curwin->w_p_scb) { @@ -2294,9 +2265,7 @@ void do_check_cursorbind(void) } } - /* - * reset current-window - */ + // reset current-window VIsual_select = old_VIsual_select; VIsual_active = old_VIsual_active; curwin = old_curwin; diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 1ca68979f4..d60e18590f 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -1,36 +1,43 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> #include <inttypes.h> -#include <msgpack.h> +#include <msgpack/object.h> +#include <msgpack/pack.h> +#include <msgpack/sbuffer.h> +#include <msgpack/unpack.h> #include <stdbool.h> -#include <string.h> +#include <stdio.h> +#include <stdlib.h> #include <uv.h> +#include "klib/kvec.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" #include "nvim/api/ui.h" -#include "nvim/api/vim.h" -#include "nvim/ascii.h" #include "nvim/channel.h" -#include "nvim/eval.h" -#include "nvim/event/libuv_process.h" +#include "nvim/event/defs.h" #include "nvim/event/loop.h" +#include "nvim/event/process.h" #include "nvim/event/rstream.h" -#include "nvim/event/socket.h" +#include "nvim/event/stream.h" #include "nvim/event/wstream.h" -#include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/main.h" #include "nvim/map.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/msgpack_rpc/unpacker.h" #include "nvim/os/input.h" -#include "nvim/os_unix.h" +#include "nvim/rbuffer.h" +#include "nvim/types.h" #include "nvim/ui.h" -#include "nvim/vim.h" +#include "nvim/ui_client.h" #if MIN_LOG_LEVEL > LOGLVL_DBG # define log_client_msg(...) @@ -94,7 +101,6 @@ bool rpc_send_event(uint64_t id, const char *name, Array args) Channel *channel = NULL; if (id && (!(channel = find_rpc_channel(id)))) { - api_free_array(args); return false; } @@ -130,6 +136,7 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem uint32_t request_id = rpc->next_request_id++; // Send the msgpack-rpc request send_request(channel, request_id, method_name, args); + api_free_array(args); // Push the frame ChannelCallFrame frame = { request_id, false, false, NIL, NULL }; @@ -158,7 +165,7 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem } // frame.result was allocated in an arena - arena_mem_free(frame.result_mem, &rpc->unpacker->reuse_blk); + arena_mem_free(frame.result_mem); frame.result_mem = NULL; } @@ -244,8 +251,8 @@ static void parse_msgpack(Channel *channel) ui_client_event_raw_line(p->grid_line_event); } else if (p->ui_handler.fn != NULL && p->result.type == kObjectTypeArray) { p->ui_handler.fn(p->result.data.array); - arena_mem_free(arena_finish(&p->arena), &p->reuse_blk); } + arena_mem_free(arena_finish(&p->arena)); } else if (p->type == kMessageTypeResponse) { ChannelCallFrame *frame = kv_last(channel->rpc.call_stack); if (p->request_id != frame->request_id) { @@ -293,9 +300,9 @@ static void handle_request(Channel *channel, Unpacker *p, Array args) assert(p->type == kMessageTypeRequest || p->type == kMessageTypeNotification); if (!p->handler.fn) { - send_error(channel, p->type, p->request_id, p->unpack_error.msg); + send_error(channel, p->handler, p->type, p->request_id, p->unpack_error.msg); api_clear_error(&p->unpack_error); - arena_mem_free(arena_finish(&p->arena), &p->reuse_blk); + arena_mem_free(arena_finish(&p->arena)); return; } @@ -304,7 +311,8 @@ static void handle_request(Channel *channel, Unpacker *p, Array args) evdata->channel = channel; evdata->handler = p->handler; evdata->args = args; - evdata->used_mem = arena_finish(&p->arena); + evdata->used_mem = p->arena; + p->arena = (Arena)ARENA_EMPTY; evdata->request_id = p->request_id; channel_incref(channel); if (p->handler.fast) { @@ -344,24 +352,27 @@ static void request_event(void **argv) // channel was closed, abort any pending requests goto free_ret; } - Object result = handler.fn(channel->id, e->args, &error); + + Object result = handler.fn(channel->id, e->args, &e->used_mem, &error); if (e->type == kMessageTypeRequest || ERROR_SET(&error)) { // Send the response. msgpack_packer response; msgpack_packer_init(&response, &out_buffer, msgpack_sbuffer_write); channel_write(channel, serialize_response(channel->id, + e->handler, e->type, e->request_id, &error, result, &out_buffer)); - } else { + } + if (!handler.arena_return) { api_free_object(result); } free_ret: - // e->args is allocated in an arena - arena_mem_free(e->used_mem, &channel->rpc.unpacker->reuse_blk); + // e->args (and possibly result) are allocated in an arena + arena_mem_free(arena_finish(&e->used_mem)); channel_decref(channel); xfree(e); api_clear_error(&error); @@ -431,11 +442,13 @@ static void internal_read_event(void **argv) wstream_release_wbuffer(buffer); } -static void send_error(Channel *chan, MessageType type, uint32_t id, char *err) +static void send_error(Channel *chan, MsgpackRpcRequestHandler handler, MessageType type, + uint32_t id, char *err) { Error e = ERROR_INIT; api_set_error(&e, kErrorTypeException, "%s", err); channel_write(chan, serialize_response(chan->id, + handler, type, id, &e, @@ -479,7 +492,6 @@ static void broadcast_event(const char *name, Array args) }); if (!kv_size(subscribed)) { - api_free_array(args); goto end; } @@ -534,26 +546,8 @@ void rpc_close(Channel *channel) channel_decref(channel); if (channel->streamtype == kChannelStreamStdio - || channel->id == ui_client_channel_id) { - multiqueue_put(main_loop.fast_events, exit_event, 0); - } -} - -static void exit_delay_cb(uv_timer_t *handle) -{ - uv_timer_stop(&main_loop.exit_delay_timer); - multiqueue_put(main_loop.fast_events, exit_event, 0); -} - -static void exit_event(void **argv) -{ - if (exit_need_delay) { - uv_timer_start(&main_loop.exit_delay_timer, exit_delay_cb, 0, 0); - return; - } - - if (!exiting) { - os_exit(0); + || (channel->id == ui_client_channel_id && channel->streamtype != kChannelStreamProc)) { + exit_from_channel(0); } } @@ -599,22 +593,30 @@ static WBuffer *serialize_request(uint64_t channel_id, uint32_t request_id, cons refcount, xfree); msgpack_sbuffer_clear(sbuffer); - api_free_array(args); return rv; } -static WBuffer *serialize_response(uint64_t channel_id, MessageType type, uint32_t response_id, - Error *err, Object arg, msgpack_sbuffer *sbuffer) +static WBuffer *serialize_response(uint64_t channel_id, MsgpackRpcRequestHandler handler, + MessageType type, uint32_t response_id, Error *err, Object arg, + msgpack_sbuffer *sbuffer) { msgpack_packer pac; msgpack_packer_init(&pac, sbuffer, msgpack_sbuffer_write); if (ERROR_SET(err) && type == kMessageTypeNotification) { - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(err->type)); - ADD(args, STRING_OBJ(cstr_to_string(err->msg))); - msgpack_rpc_serialize_request(0, cstr_as_string("nvim_error_event"), - args, &pac); - api_free_array(args); + if (handler.fn == handle_nvim_paste) { + // TODO(bfredl): this is pretty much ad-hoc. maybe TUI and UI:s should be + // allowed to ask nvim to just scream directly in the users face + // instead of sending nvim_error_event, in general. + semsg("paste: %s", err->msg); + api_clear_error(err); + } else { + Array args = ARRAY_DICT_INIT; + ADD(args, INTEGER_OBJ(err->type)); + ADD(args, STRING_OBJ(cstr_to_string(err->msg))); + msgpack_rpc_serialize_request(0, cstr_as_string("nvim_error_event"), + args, &pac); + api_free_array(args); + } } else { msgpack_rpc_serialize_response(response_id, err, arg, &pac); } @@ -624,7 +626,6 @@ static WBuffer *serialize_response(uint64_t channel_id, MessageType type, uint32 1, // responses only go though 1 channel xfree); msgpack_sbuffer_clear(sbuffer); - api_free_object(arg); return rv; } @@ -642,7 +643,7 @@ void rpc_set_client_info(uint64_t id, Dictionary info) Dictionary rpc_client_info(Channel *chan) { - return copy_dictionary(chan->rpc.info); + return copy_dictionary(chan->rpc.info, NULL); } const char *rpc_client_name(Channel *chan) diff --git a/src/nvim/msgpack_rpc/channel.h b/src/nvim/msgpack_rpc/channel.h index ac7911bb2c..ce5806930c 100644 --- a/src/nvim/msgpack_rpc/channel.h +++ b/src/nvim/msgpack_rpc/channel.h @@ -6,8 +6,10 @@ #include "nvim/api/private/defs.h" #include "nvim/channel.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/process.h" #include "nvim/event/socket.h" +#include "nvim/macros.h" #include "nvim/vim.h" #define METHOD_MAXLEN 512 diff --git a/src/nvim/msgpack_rpc/channel_defs.h b/src/nvim/msgpack_rpc/channel_defs.h index e622ebddf5..404e68329a 100644 --- a/src/nvim/msgpack_rpc/channel_defs.h +++ b/src/nvim/msgpack_rpc/channel_defs.h @@ -27,7 +27,7 @@ typedef struct { MsgpackRpcRequestHandler handler; Array args; uint32_t request_id; - ArenaMem used_mem; + Arena used_mem; } RequestEvent; typedef struct { diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index 488321be42..5f0f03dd69 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -1,21 +1,25 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <inttypes.h> -#include <msgpack.h> +#include <msgpack/object.h> +#include <msgpack/sbuffer.h> +#include <msgpack/unpack.h> +#include <msgpack/zone.h> #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> -#include "nvim/api/private/dispatch.h" +#include "klib/kvec.h" +#include "msgpack/pack.h" #include "nvim/api/private/helpers.h" #include "nvim/assert.h" -#include "nvim/lib/kvec.h" -#include "nvim/log.h" +#include "nvim/event/wstream.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/helpers.h" -#include "nvim/vim.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "keysets.generated.h" +# include "keysets.generated.h" // IWYU pragma: export # include "msgpack_rpc/helpers.c.generated.h" #endif @@ -35,6 +39,8 @@ typedef struct { size_t idx; } MPToAPIObjectStackItem; +// uncrustify:off + /// Convert type used by msgpack parser to Nvim API type. /// /// @param[in] obj Msgpack value to convert. @@ -95,9 +101,8 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) dest = conv(((String) { \ .size = obj->via.attr.size, \ .data = (obj->via.attr.ptr == NULL || obj->via.attr.size == 0 \ - ? xmemdupz("", 0) \ - : xmemdupz(obj->via.attr.ptr, obj->via.attr.size)), \ - })); \ + ? xmemdupz("", 0) \ + : xmemdupz(obj->via.attr.ptr, obj->via.attr.size)), })); \ break; \ } STR_CASE(MSGPACK_OBJECT_STR, str, cur.mobj, *cur.aobj, STRING_OBJ) @@ -115,7 +120,7 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) .mobj = &cur.mobj->via.array.ptr[idx], .aobj = &cur.aobj->data.array.items[idx], .container = false, - })); + })); } } else { *cur.aobj = ARRAY_OBJ(((Array) { @@ -124,7 +129,7 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) .items = (size > 0 ? xcalloc(size, sizeof(*cur.aobj->data.array.items)) : NULL), - })); + })); cur.container = true; kv_last(stack) = cur; } @@ -168,7 +173,7 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) .mobj = &cur.mobj->via.map.ptr[idx].val, .aobj = &cur.aobj->data.dictionary.items[idx].value, .container = false, - })); + })); } } } else { @@ -178,7 +183,7 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) .items = (size > 0 ? xcalloc(size, sizeof(*cur.aobj->data.dictionary.items)) : NULL), - })); + })); cur.container = true; kv_last(stack) = cur; } @@ -368,7 +373,7 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res) kvi_push(stack, ((APIToMPObjectStackItem) { .aobj = &cur.aobj->data.array.items[idx], .container = false, - })); + })); } } else { msgpack_pack_array(res, size); @@ -386,12 +391,11 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res) const size_t idx = cur.idx; cur.idx++; kv_last(stack) = cur; - msgpack_rpc_from_string(cur.aobj->data.dictionary.items[idx].key, - res); + msgpack_rpc_from_string(cur.aobj->data.dictionary.items[idx].key, res); kvi_push(stack, ((APIToMPObjectStackItem) { .aobj = &cur.aobj->data.dictionary.items[idx].value, .container = false, - })); + })); } } else { msgpack_pack_map(res, size); @@ -408,6 +412,8 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res) kvi_destroy(stack); } +// uncrustify:on + void msgpack_rpc_from_array(Array result, msgpack_packer *res) FUNC_ATTR_NONNULL_ARG(2) { diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index b252f0998e..1d75c208be 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -1,25 +1,22 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <assert.h> #include <inttypes.h> -#include <stdlib.h> +#include <stdbool.h> +#include <stdio.h> #include <string.h> +#include <uv.h> -#include "nvim/ascii.h" +#include "nvim/channel.h" #include "nvim/eval.h" #include "nvim/event/socket.h" -#include "nvim/fileio.h" #include "nvim/garray.h" #include "nvim/log.h" #include "nvim/main.h" #include "nvim/memory.h" -#include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" #include "nvim/os/os.h" -#include "nvim/path.h" -#include "nvim/strings.h" -#include "nvim/vim.h" +#include "nvim/os/stdpaths_defs.h" #define MAX_CONNECTIONS 32 #define ENV_LISTEN "NVIM_LISTEN_ADDRESS" // deprecated @@ -94,7 +91,7 @@ char *server_address_new(const char *name) { static uint32_t count = 0; char fmt[ADDRESS_MAX_SIZE]; -#ifdef WIN32 +#ifdef MSWIN int r = snprintf(fmt, sizeof(fmt), "\\\\.\\pipe\\%s.%" PRIu64 ".%" PRIu32, name ? name : "nvim", os_get_pid(), count++); #else @@ -104,7 +101,7 @@ char *server_address_new(const char *name) xfree(dir); #endif if ((size_t)r >= sizeof(fmt)) { - ELOG("truncated server address"); + ELOG("truncated server address: %.40s...", fmt); } return xstrdup(fmt); } @@ -173,7 +170,7 @@ int server_start(const char *addr) ((SocketWatcher **)watchers.ga_data)[watchers.ga_len++] = watcher; // Update v:servername, if not set. - if (STRLEN(get_vim_var_str(VV_SEND_SERVER)) == 0) { + if (strlen(get_vim_var_str(VV_SEND_SERVER)) == 0) { set_vservername(&watchers); } @@ -216,7 +213,7 @@ bool server_stop(char *endpoint) watchers.ga_len--; // Bump v:servername to the next available server, if any. - if (strequal(addr, (char *)get_vim_var_str(VV_SEND_SERVER))) { + if (strequal(addr, get_vim_var_str(VV_SEND_SERVER))) { set_vservername(&watchers); } diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c index c8e9fdd4c3..44a16beb48 100644 --- a/src/nvim/msgpack_rpc/unpacker.c +++ b/src/nvim/msgpack_rpc/unpacker.c @@ -1,9 +1,17 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> +#include <stdbool.h> +#include <stdlib.h> + +#include "klib/kvec.h" +#include "mpack/conv.h" #include "nvim/api/private/helpers.h" -#include "nvim/log.h" +#include "nvim/ascii.h" +#include "nvim/macros.h" #include "nvim/memory.h" +#include "nvim/msgpack_rpc/channel_defs.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/msgpack_rpc/unpacker.h" #include "nvim/ui_client.h" @@ -178,16 +186,14 @@ void unpacker_init(Unpacker *p) mpack_parser_init(&p->parser, 0); p->parser.data.p = p; mpack_tokbuf_init(&p->reader); - p->unpack_error = (Error)ERROR_INIT; + p->unpack_error = ERROR_INIT; p->arena = (Arena)ARENA_EMPTY; - p->reuse_blk = NULL; } void unpacker_teardown(Unpacker *p) { - arena_mem_free(p->reuse_blk, NULL); - arena_mem_free(arena_finish(&p->arena), NULL); + arena_mem_free(arena_finish(&p->arena)); } bool unpacker_parse_header(Unpacker *p) @@ -292,7 +298,7 @@ error: // // When method is "grid_line", we furthermore decode a cell at a time like: // -// <0>[2, "redraw", <10>[{11}["grid_line", <13>[g, r, c, [<14>[cell], <14>[cell], ...]], ...], <11>[...], ...]] +// <0>[2, "redraw", <10>[{11}["grid_line", <14>[g, r, c, [<15>[cell], <15>[cell], ...]], ...], <11>[...], ...]] // // where [cell] is [char, repeat, attr], where 'repeat' and 'attr' is optional @@ -308,21 +314,23 @@ bool unpacker_advance(Unpacker *p) p->state = 10; } else { p->state = p->type == kMessageTypeResponse ? 1 : 2; - arena_start(&p->arena, &p->reuse_blk); + p->arena = (Arena)ARENA_EMPTY; } } - if (p->state >= 10 && p->state != 12) { + if (p->state >= 10 && p->state != 13) { if (!unpacker_parse_redraw(p)) { return false; } - if (p->state == 14) { + if (p->state == 15) { // grid_line event already unpacked goto done; } else { + assert(p->state == 12); // unpack other ui events using mpack_parse() - arena_start(&p->arena, &p->reuse_blk); + p->arena = (Arena)ARENA_EMPTY; + p->state = 13; } } @@ -349,11 +357,11 @@ done: case 2: p->state = 0; return true; - case 12: - case 14: + case 13: + case 15: p->ncalls--; if (p->ncalls > 0) { - p->state = (p->state == 14) ? 13 : 12; + p->state = (p->state == 15) ? 14 : 12; } else if (p->nevents > 0) { p->state = 11; } else { @@ -374,6 +382,7 @@ bool unpacker_parse_redraw(Unpacker *p) size_t size = p->read_size; GridLineEvent *g = p->grid_line_event; +// -V:NEXT_TYPE:501 #define NEXT_TYPE(tok, typ) \ result = mpack_rtoken(&data, &size, &tok); \ if (result == MPACK_EOF) { \ @@ -416,19 +425,19 @@ redo: if (p->ui_handler.fn != ui_client_event_grid_line) { p->state = 12; if (p->grid_line_event) { - arena_mem_free(arena_finish(&p->arena), &p->reuse_blk); + arena_mem_free(arena_finish(&p->arena)); p->grid_line_event = NULL; } return true; } else { - p->state = 13; - arena_start(&p->arena, &p->reuse_blk); + p->state = 14; + p->arena = (Arena)ARENA_EMPTY; p->grid_line_event = arena_alloc(&p->arena, sizeof *p->grid_line_event, true); g = p->grid_line_event; } FALLTHROUGH; - case 13: + case 14: NEXT_TYPE(tok, MPACK_TOKEN_ARRAY); int eventarrsize = (int)tok.length; if (eventarrsize != 4) { @@ -449,10 +458,10 @@ redo: p->read_ptr = data; p->read_size = size; - p->state = 14; + p->state = 15; FALLTHROUGH; - case 14: + case 15: assert(g->icell < g->ncells); NEXT_TYPE(tok, MPACK_TOKEN_ARRAY); @@ -506,6 +515,9 @@ redo: } goto redo; + case 12: + return true; + default: abort(); } diff --git a/src/nvim/msgpack_rpc/unpacker.h b/src/nvim/msgpack_rpc/unpacker.h index f39439be63..b8b2d38d3b 100644 --- a/src/nvim/msgpack_rpc/unpacker.h +++ b/src/nvim/msgpack_rpc/unpacker.h @@ -7,10 +7,14 @@ #include "mpack/mpack_core.h" #include "mpack/object.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" +#include "nvim/grid_defs.h" #include "nvim/memory.h" #include "nvim/msgpack_rpc/channel_defs.h" +#include "nvim/types.h" +#include "nvim/ui_client.h" struct Unpacker { mpack_parser_t parser; @@ -32,8 +36,6 @@ struct Unpacker { Error unpack_error; Arena arena; - // one length free-list of reusable blocks - ArenaMem reuse_blk; int nevents; int ncalls; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 2c7dadad0b..58a18ca5a8 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -8,14 +8,21 @@ // #include <assert.h> +#include <ctype.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <time.h> +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cmdhist.h" @@ -25,8 +32,6 @@ #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/eval/userfunc.h" -#include "nvim/event/loop.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" @@ -34,18 +39,18 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/help.h" -#include "nvim/indent.h" +#include "nvim/highlight_defs.h" #include "nvim/keycodes.h" -#include "nvim/log.h" -#include "nvim/main.h" +#include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/menu.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" @@ -57,14 +62,19 @@ #include "nvim/plines.h" #include "nvim/profile.h" #include "nvim/quickfix.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/spell.h" #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" +#include "nvim/textformat.h" +#include "nvim/textobject.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -143,8 +153,7 @@ static const struct nv_cmd { nv_func_T cmd_func; ///< function for this command uint16_t cmd_flags; ///< NV_ flags int16_t cmd_arg; ///< value for ca.arg -} nv_cmds[] = -{ +} nv_cmds[] = { { NUL, nv_error, 0, 0 }, { Ctrl_A, nv_addsub, 0, 0 }, { Ctrl_B, nv_page, NV_STS, BACKWARD }, @@ -442,12 +451,34 @@ static int find_command(int cmdchar) /// message, return true. static bool check_text_locked(oparg_T *oap) { - if (text_locked()) { + if (!text_locked()) { + return false; + } + + if (oap != NULL) { clearopbeep(oap); - text_locked_msg(); + } + text_locked_msg(); + return true; +} + +/// If text is locked, "curbuf->b_ro_locked" or "allbuf_lock" is set: +/// Give an error message, possibly beep and return true. +/// "oap" may be NULL. +static bool check_text_or_curbuf_locked(oparg_T *oap) +{ + if (check_text_locked(oap)) { return true; } - return false; + + if (!curbuf_locked()) { + return false; + } + + if (oap != NULL) { + clearop(oap); + } + return true; } /// Normal state entry point. This is called on: @@ -523,7 +554,7 @@ static bool normal_handle_special_visual_command(NormalState *s) && (nv_cmds[s->idx].cmd_flags & NV_STS) && !(mod_mask & MOD_MASK_SHIFT)) { end_visual_mode(); - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } // Keys that work different when 'keymodel' contains "startsel" @@ -611,6 +642,7 @@ static bool normal_need_redraw_mode_message(NormalState *s) && stuff_empty() && typebuf_typed() && emsg_silent == 0 + && !in_assert_fails && !did_wait_return && s->oa.op_type == OP_NOP); } @@ -627,18 +659,18 @@ static void normal_redraw_mode_message(NormalState *s) // If need to redraw, and there is a "keep_msg", redraw before the // delay if (must_redraw && keep_msg != NULL && !emsg_on_display) { - char_u *kmsg; + char *kmsg; kmsg = keep_msg; keep_msg = NULL; // Showmode() will clear keep_msg, but we want to use it anyway. // First update w_topline. setcursor(); - update_screen(0); + update_screen(); // now reset it, otherwise it's put in the history again keep_msg = kmsg; - kmsg = vim_strsave(keep_msg); + kmsg = xstrdup(keep_msg); msg_attr((const char *)kmsg, keep_msg_attr); xfree(kmsg); } @@ -1097,8 +1129,7 @@ static int normal_execute(VimState *state, int key) goto finish; } - if ((nv_cmds[s->idx].cmd_flags & NV_NCW) - && (check_text_locked(&s->oa) || curbuf_locked())) { + if ((nv_cmds[s->idx].cmd_flags & NV_NCW) && check_text_or_curbuf_locked(&s->oa)) { // this command is not allowed now s->command_finished = true; goto finish; @@ -1130,6 +1161,7 @@ static int normal_execute(VimState *state, int key) if (s->need_flushbuf) { ui_flush(); } + if (s->ca.cmdchar != K_IGNORE && s->ca.cmdchar != K_EVENT) { did_cursorhold = false; } @@ -1220,8 +1252,7 @@ static void normal_check_interrupt(NormalState *s) static void normal_check_window_scrolled(NormalState *s) { if (!finish_op) { - // Trigger Scroll if the viewport changed. - may_trigger_winscrolled(); + may_trigger_win_scrolled_resized(); } } @@ -1281,10 +1312,10 @@ static void normal_redraw(NormalState *s) validate_cursor(); if (VIsual_active) { - redraw_curbuf_later(INVERTED); // update inverted part - update_screen(0); + redraw_curbuf_later(UPD_INVERTED); // update inverted part + update_screen(); } else if (must_redraw) { - update_screen(0); + update_screen(); } else if (redraw_cmdline || clear_cmdline || redraw_mode) { showmode(); } @@ -1300,7 +1331,7 @@ static void normal_redraw(NormalState *s) // Display message after redraw. If an external message is still visible, // it contains the kept message already. if (keep_msg != NULL && !msg_ext_is_visible()) { - char_u *const p = vim_strsave(keep_msg); + char *const p = xstrdup(keep_msg); // msg_start() will set keep_msg to NULL, make a copy // first. Don't reset keep_msg, msg_attr_keep() uses it to @@ -1387,6 +1418,9 @@ static int normal_check(VimState *state) fclose(time_fd); time_fd = NULL; } + // After the first screen update may start triggering WinScrolled + // autocmd events. Store all the scroll positions and sizes now. + may_make_initial_scroll_size_snapshot(); } // May perform garbage collection when waiting for a character, but @@ -1431,856 +1465,6 @@ static void set_vcount_ca(cmdarg_T *cap, bool *set_prevcount) *set_prevcount = false; // only set v:prevcount once } -/// Move the current tab to tab in same column as mouse or to end of the -/// tabline if there is no tab there. -static void move_tab_to_mouse(void) -{ - int tabnr = tab_page_click_defs[mouse_col].tabnr; - if (tabnr <= 0) { - tabpage_move(9999); - } else if (tabnr < tabpage_index(curtab)) { - tabpage_move(tabnr - 1); - } else { - tabpage_move(tabnr); - } -} - -/// Call click definition function for column "col" in the "click_defs" array for button -/// "which_button". -static void call_click_def_func(StlClickDefinition *click_defs, int col, int which_button) -{ - typval_T argv[] = { - { - .v_lock = VAR_FIXED, - .v_type = VAR_NUMBER, - .vval = { - .v_number = (varnumber_T)click_defs[col].tabnr - }, - }, - { - .v_lock = VAR_FIXED, - .v_type = VAR_NUMBER, - .vval = { - .v_number = ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK - ? 4 - : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK - ? 3 - : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK - ? 2 - : 1))) - }, - }, - { - .v_lock = VAR_FIXED, - .v_type = VAR_STRING, - .vval = { - .v_string = (which_button == MOUSE_LEFT - ? "l" - : (which_button == MOUSE_RIGHT - ? "r" - : (which_button == MOUSE_MIDDLE - ? "m" - : "?"))) - }, - }, - { - .v_lock = VAR_FIXED, - .v_type = VAR_STRING, - .vval = { - .v_string = (char[]) { - (char)(mod_mask & MOD_MASK_SHIFT ? 's' : ' '), - (char)(mod_mask & MOD_MASK_CTRL ? 'c' : ' '), - (char)(mod_mask & MOD_MASK_ALT ? 'a' : ' '), - (char)(mod_mask & MOD_MASK_META ? 'm' : ' '), - NUL - } - }, - } - }; - typval_T rettv; - (void)call_vim_function(click_defs[col].func, ARRAY_SIZE(argv), argv, &rettv); - tv_clear(&rettv); -} - -/// Do the appropriate action for the current mouse click in the current mode. -/// Not used for Command-line mode. -/// -/// Normal and Visual Mode: -/// event modi- position visual change action -/// fier cursor window -/// left press - yes end yes -/// left press C yes end yes "^]" (2) -/// left press S yes end (popup: extend) yes "*" (2) -/// left drag - yes start if moved no -/// left relse - yes start if moved no -/// middle press - yes if not active no put register -/// middle press - yes if active no yank and put -/// right press - yes start or extend yes -/// right press S yes no change yes "#" (2) -/// right drag - yes extend no -/// right relse - yes extend no -/// -/// Insert or Replace Mode: -/// event modi- position visual change action -/// fier cursor window -/// left press - yes (cannot be active) yes -/// left press C yes (cannot be active) yes "CTRL-O^]" (2) -/// left press S yes (cannot be active) yes "CTRL-O*" (2) -/// left drag - yes start or extend (1) no CTRL-O (1) -/// left relse - yes start or extend (1) no CTRL-O (1) -/// middle press - no (cannot be active) no put register -/// right press - yes start or extend yes CTRL-O -/// right press S yes (cannot be active) yes "CTRL-O#" (2) -/// -/// (1) only if mouse pointer moved since press -/// (2) only if click is in same buffer -/// -/// @param oap operator argument, can be NULL -/// @param c K_LEFTMOUSE, etc -/// @param dir Direction to 'put' if necessary -/// @param fixindent PUT_FIXINDENT if fixing indent necessary -/// -/// @return true if start_arrow() should be called for edit mode. -bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) -{ - static bool got_click = false; // got a click some time back - - int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT - bool is_click; // If false it's a drag or release event - bool is_drag; // If true it's a drag event - int jump_flags = 0; // flags for jump_to_mouse() - pos_T start_visual; - bool moved; // Has cursor moved? - bool in_winbar; // mouse in window bar - bool in_status_line; // mouse in status line - static bool in_tab_line = false; // mouse clicked in tab line - bool in_sep_line; // mouse in vertical separator line - int c1, c2; - pos_T save_cursor; - win_T *old_curwin = curwin; - static pos_T orig_cursor; - colnr_T leftcol, rightcol; - pos_T end_visual; - long diff; - int old_active = VIsual_active; - int old_mode = VIsual_mode; - int regname; - - save_cursor = curwin->w_cursor; - - for (;;) { - which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); - if (is_drag) { - // If the next character is the same mouse event then use that - // one. Speeds up dragging the status line. - // Note: Since characters added to the stuff buffer in the code - // below need to come before the next character, do not do this - // when the current character was stuffed. - if (!KeyStuffed && vpeekc() != NUL) { - int nc; - int save_mouse_grid = mouse_grid; - int save_mouse_row = mouse_row; - int save_mouse_col = mouse_col; - - // Need to get the character, peeking doesn't get the actual one. - nc = safe_vgetc(); - if (c == nc) { - continue; - } - vungetc(nc); - mouse_grid = save_mouse_grid; - mouse_row = save_mouse_row; - mouse_col = save_mouse_col; - } - } - break; - } - - if (c == K_MOUSEMOVE) { - // Mouse moved without a button pressed. - return false; - } - - // Ignore drag and release events if we didn't get a click. - if (is_click) { - got_click = true; - } else { - if (!got_click) { // didn't get click, ignore - return false; - } - if (!is_drag) { // release, reset got_click - got_click = false; - if (in_tab_line) { - in_tab_line = false; - return false; - } - } - } - - // CTRL right mouse button does CTRL-T - if (is_click && (mod_mask & MOD_MASK_CTRL) && which_button == MOUSE_RIGHT) { - if (State & MODE_INSERT) { - stuffcharReadbuff(Ctrl_O); - } - if (count > 1) { - stuffnumReadbuff(count); - } - stuffcharReadbuff(Ctrl_T); - got_click = false; // ignore drag&release now - return false; - } - - // CTRL only works with left mouse button - if ((mod_mask & MOD_MASK_CTRL) && which_button != MOUSE_LEFT) { - return false; - } - - // When a modifier is down, ignore drag and release events, as well as - // multiple clicks and the middle mouse button. - // Accept shift-leftmouse drags when 'mousemodel' is "popup.*". - if ((mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT - | MOD_MASK_META)) - && (!is_click - || (mod_mask & MOD_MASK_MULTI_CLICK) - || which_button == MOUSE_MIDDLE) - && !((mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)) - && mouse_model_popup() - && which_button == MOUSE_LEFT) - && !((mod_mask & MOD_MASK_ALT) - && !mouse_model_popup() - && which_button == MOUSE_RIGHT)) { - return false; - } - - // If the button press was used as the movement command for an operator (eg - // "d<MOUSE>"), or it is the middle button that is held down, ignore - // drag/release events. - if (!is_click && which_button == MOUSE_MIDDLE) { - return false; - } - - if (oap != NULL) { - regname = oap->regname; - } else { - regname = 0; - } - - // Middle mouse button does a 'put' of the selected text - if (which_button == MOUSE_MIDDLE) { - if (State == MODE_NORMAL) { - // If an operator was pending, we don't know what the user wanted to do. - // Go back to normal mode: Clear the operator and beep(). - if (oap != NULL && oap->op_type != OP_NOP) { - clearopbeep(oap); - return false; - } - - // If visual was active, yank the highlighted text and put it - // before the mouse pointer position. - // In Select mode replace the highlighted text with the clipboard. - if (VIsual_active) { - if (VIsual_select) { - stuffcharReadbuff(Ctrl_G); - stuffReadbuff("\"+p"); - } else { - stuffcharReadbuff('y'); - stuffcharReadbuff(K_MIDDLEMOUSE); - } - return false; - } - // The rest is below jump_to_mouse() - } else if ((State & MODE_INSERT) == 0) { - return false; - } - - // Middle click in insert mode doesn't move the mouse, just insert the - // contents of a register. '.' register is special, can't insert that - // with do_put(). - // Also paste at the cursor if the current mode isn't in 'mouse' (only - // happens for the GUI). - if ((State & MODE_INSERT)) { - if (regname == '.') { - insert_reg(regname, true); - } else { - if (regname == 0 && eval_has_provider("clipboard")) { - regname = '*'; - } - if ((State & REPLACE_FLAG) && !yank_register_mline(regname)) { - insert_reg(regname, true); - } else { - do_put(regname, NULL, BACKWARD, 1L, - (fixindent ? PUT_FIXINDENT : 0) | PUT_CURSEND); - - // Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r - AppendCharToRedobuff(Ctrl_R); - AppendCharToRedobuff(fixindent ? Ctrl_P : Ctrl_O); - AppendCharToRedobuff(regname == 0 ? '"' : regname); - } - } - return false; - } - } - - // When dragging or button-up stay in the same window. - if (!is_click) { - jump_flags |= MOUSE_FOCUS | MOUSE_DID_MOVE; - } - - start_visual.lnum = 0; - - // Check for clicking in the tab page line. - if (mouse_grid <= 1 && mouse_row == 0 && firstwin->w_winrow > 0) { - if (is_drag) { - if (in_tab_line) { - move_tab_to_mouse(); - } - return false; - } - - // click in a tab selects that tab page - if (is_click - && cmdwin_type == 0 - && mouse_col < Columns) { - in_tab_line = true; - c1 = tab_page_click_defs[mouse_col].tabnr; - switch (tab_page_click_defs[mouse_col].type) { - case kStlClickDisabled: - break; - case kStlClickTabClose: { - tabpage_T *tp; - - // Close the current or specified tab page. - if (c1 == 999) { - tp = curtab; - } else { - tp = find_tabpage(c1); - } - if (tp == curtab) { - if (first_tabpage->tp_next != NULL) { - tabpage_close(false); - } - } else if (tp != NULL) { - tabpage_close_other(tp, false); - } - break; - } - case kStlClickTabSwitch: - if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { - // double click opens new page - end_visual_mode(); - tabpage_new(); - tabpage_move(c1 == 0 ? 9999 : c1 - 1); - } else { - // Go to specified tab page, or next one if not clicking - // on a label. - goto_tabpage(c1); - - // It's like clicking on the status line of a window. - if (curwin != old_curwin) { - end_visual_mode(); - } - } - break; - case kStlClickFuncRun: - call_click_def_func(tab_page_click_defs, mouse_col, which_button); - break; - } - } - return true; - } else if (is_drag && in_tab_line) { - move_tab_to_mouse(); - return false; - } - - // When 'mousemodel' is "popup" or "popup_setpos", translate mouse events: - // right button up -> pop-up menu - // shift-left button -> right button - // alt-left button -> alt-right button - if (mouse_model_popup()) { - if (which_button == MOUSE_RIGHT - && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { - if (!is_click) { - // Ignore right button release events, only shows the popup - // menu on the button down event. - return false; - } - jump_flags = 0; - if (STRCMP(p_mousem, "popup_setpos") == 0) { - // First set the cursor position before showing the popup - // menu. - if (VIsual_active) { - pos_T m_pos; - // set MOUSE_MAY_STOP_VIS if we are outside the - // selection or the current window (might have false - // negative here) - if (mouse_row < curwin->w_winrow - || mouse_row > (curwin->w_winrow + curwin->w_height)) { - jump_flags = MOUSE_MAY_STOP_VIS; - } else if (get_fpos_of_mouse(&m_pos) != IN_BUFFER) { - jump_flags = MOUSE_MAY_STOP_VIS; - } else { - if ((lt(curwin->w_cursor, VIsual) - && (lt(m_pos, curwin->w_cursor) || lt(VIsual, m_pos))) - || (lt(VIsual, curwin->w_cursor) - && (lt(m_pos, VIsual) || lt(curwin->w_cursor, m_pos)))) { - jump_flags = MOUSE_MAY_STOP_VIS; - } else if (VIsual_mode == Ctrl_V) { - getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); - getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL); - if (m_pos.col < leftcol || m_pos.col > rightcol) { - jump_flags = MOUSE_MAY_STOP_VIS; - } - } - } - } else { - jump_flags = MOUSE_MAY_STOP_VIS; - } - } - if (jump_flags) { - jump_flags = jump_to_mouse(jump_flags, NULL, which_button); - redraw_curbuf_later(VIsual_active ? INVERTED : VALID); - update_screen(0); - setcursor(); - ui_flush(); // Update before showing popup menu - } - show_popupmenu(); - got_click = false; // ignore release events - return (jump_flags & CURSOR_MOVED) != 0; - } - if (which_button == MOUSE_LEFT - && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT))) { - which_button = MOUSE_RIGHT; - mod_mask &= ~MOD_MASK_SHIFT; - } - } - - if ((State & (MODE_NORMAL | MODE_INSERT)) - && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { - if (which_button == MOUSE_LEFT) { - if (is_click) { - // stop Visual mode for a left click in a window, but not when on a status line - if (VIsual_active) { - jump_flags |= MOUSE_MAY_STOP_VIS; - } - } else { - jump_flags |= MOUSE_MAY_VIS; - } - } else if (which_button == MOUSE_RIGHT) { - if (is_click && VIsual_active) { - // Remember the start and end of visual before moving the cursor. - if (lt(curwin->w_cursor, VIsual)) { - start_visual = curwin->w_cursor; - end_visual = VIsual; - } else { - start_visual = VIsual; - end_visual = curwin->w_cursor; - } - } - jump_flags |= MOUSE_FOCUS; - jump_flags |= MOUSE_MAY_VIS; - } - } - - // If an operator is pending, ignore all drags and releases until the next mouse click. - if (!is_drag && oap != NULL && oap->op_type != OP_NOP) { - got_click = false; - oap->motion_type = kMTCharWise; - } - - // When releasing the button let jump_to_mouse() know. - if (!is_click && !is_drag) { - jump_flags |= MOUSE_RELEASED; - } - - // JUMP! - jump_flags = jump_to_mouse(jump_flags, - oap == NULL ? NULL : &(oap->inclusive), - which_button); - - moved = (jump_flags & CURSOR_MOVED); - in_winbar = (jump_flags & MOUSE_WINBAR); - in_status_line = (jump_flags & IN_STATUS_LINE); - in_sep_line = (jump_flags & IN_SEP_LINE); - - if ((in_winbar || in_status_line) && is_click) { - // Handle click event on window bar or status lin - int click_grid = mouse_grid; - int click_row = mouse_row; - int click_col = mouse_col; - win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col); - if (wp == NULL) { - return false; - } - - StlClickDefinition *click_defs = in_status_line ? wp->w_status_click_defs - : wp->w_winbar_click_defs; - - if (in_status_line && global_stl_height() > 0) { - // global statusline is displayed for the current window, - // and spans the whole screen. - click_defs = curwin->w_status_click_defs; - click_col = mouse_col; - } - - if (click_defs != NULL) { - switch (click_defs[click_col].type) { - case kStlClickDisabled: - break; - case kStlClickFuncRun: - call_click_def_func(click_defs, click_col, which_button); - break; - default: - assert(false && "winbar and statusline only support %@ for clicks"); - break; - } - } - - return false; - } else if (in_winbar) { - // A drag or release event in the window bar has no side effects. - return false; - } - - // When jumping to another window, clear a pending operator. That's a bit - // friendlier than beeping and not jumping to that window. - if (curwin != old_curwin && oap != NULL && oap->op_type != OP_NOP) { - clearop(oap); - } - - if (mod_mask == 0 - && !is_drag - && (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN)) - && which_button == MOUSE_LEFT) { - // open or close a fold at this line - if (jump_flags & MOUSE_FOLD_OPEN) { - openFold(curwin->w_cursor, 1L); - } else { - closeFold(curwin->w_cursor, 1L); - } - // don't move the cursor if still in the same window - if (curwin == old_curwin) { - curwin->w_cursor = save_cursor; - } - } - - // Set global flag that we are extending the Visual area with mouse dragging; - // temporarily minimize 'scrolloff'. - if (VIsual_active && is_drag && get_scrolloff_value(curwin)) { - // In the very first line, allow scrolling one line - if (mouse_row == 0) { - mouse_dragging = 2; - } else { - mouse_dragging = 1; - } - } - - // When dragging the mouse above the window, scroll down. - if (is_drag && mouse_row < 0 && !in_status_line) { - scroll_redraw(false, 1L); - mouse_row = 0; - } - - if (start_visual.lnum) { // right click in visual mode - // When ALT is pressed make Visual mode blockwise. - if (mod_mask & MOD_MASK_ALT) { - VIsual_mode = Ctrl_V; - } - - // In Visual-block mode, divide the area in four, pick up the corner - // that is in the quarter that the cursor is in. - if (VIsual_mode == Ctrl_V) { - getvcols(curwin, &start_visual, &end_visual, &leftcol, &rightcol); - if (curwin->w_curswant > (leftcol + rightcol) / 2) { - end_visual.col = leftcol; - } else { - end_visual.col = rightcol; - } - if (curwin->w_cursor.lnum >= - (start_visual.lnum + end_visual.lnum) / 2) { - end_visual.lnum = start_visual.lnum; - } - - // move VIsual to the right column - start_visual = curwin->w_cursor; // save the cursor pos - curwin->w_cursor = end_visual; - coladvance(end_visual.col); - VIsual = curwin->w_cursor; - curwin->w_cursor = start_visual; // restore the cursor - } else { - // If the click is before the start of visual, change the start. - // If the click is after the end of visual, change the end. If - // the click is inside the visual, change the closest side. - if (lt(curwin->w_cursor, start_visual)) { - VIsual = end_visual; - } else if (lt(end_visual, curwin->w_cursor)) { - VIsual = start_visual; - } else { - // In the same line, compare column number - if (end_visual.lnum == start_visual.lnum) { - if (curwin->w_cursor.col - start_visual.col > - end_visual.col - curwin->w_cursor.col) { - VIsual = start_visual; - } else { - VIsual = end_visual; - } - } else { - // In different lines, compare line number - diff = (curwin->w_cursor.lnum - start_visual.lnum) - - (end_visual.lnum - curwin->w_cursor.lnum); - - if (diff > 0) { // closest to end - VIsual = start_visual; - } else if (diff < 0) { // closest to start - VIsual = end_visual; - } else { // in the middle line - if (curwin->w_cursor.col < - (start_visual.col + end_visual.col) / 2) { - VIsual = end_visual; - } else { - VIsual = start_visual; - } - } - } - } - } - } else if ((State & MODE_INSERT) && VIsual_active) { - // If Visual mode started in insert mode, execute "CTRL-O" - stuffcharReadbuff(Ctrl_O); - } - - // Middle mouse click: Put text before cursor. - if (which_button == MOUSE_MIDDLE) { - if (regname == 0 && eval_has_provider("clipboard")) { - regname = '*'; - } - if (yank_register_mline(regname)) { - if (mouse_past_bottom) { - dir = FORWARD; - } - } else if (mouse_past_eol) { - dir = FORWARD; - } - - if (fixindent) { - c1 = (dir == BACKWARD) ? '[' : ']'; - c2 = 'p'; - } else { - c1 = (dir == FORWARD) ? 'p' : 'P'; - c2 = NUL; - } - prep_redo(regname, count, NUL, c1, NUL, c2, NUL); - - // Remember where the paste started, so in edit() Insstart can be set to this position - if (restart_edit != 0) { - where_paste_started = curwin->w_cursor; - } - do_put(regname, NULL, dir, count, - (fixindent ? PUT_FIXINDENT : 0)| PUT_CURSEND); - } else if (((mod_mask & MOD_MASK_CTRL) || (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) - && bt_quickfix(curbuf)) { - // Ctrl-Mouse click or double click in a quickfix window jumps to the - // error under the mouse pointer. - if (curwin->w_llist_ref == NULL) { // quickfix window - do_cmdline_cmd(".cc"); - } else { // location list window - do_cmdline_cmd(".ll"); - } - got_click = false; // ignore drag&release now - } else if ((mod_mask & MOD_MASK_CTRL) - || (curbuf->b_help && (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)) { - // Ctrl-Mouse click (or double click in a help window) jumps to the tag - // under the mouse pointer. - if (State & MODE_INSERT) { - stuffcharReadbuff(Ctrl_O); - } - stuffcharReadbuff(Ctrl_RSB); - got_click = false; // ignore drag&release now - } else if ((mod_mask & MOD_MASK_SHIFT)) { - // Shift-Mouse click searches for the next occurrence of the word under - // the mouse pointer - if (State & MODE_INSERT || (VIsual_active && VIsual_select)) { - stuffcharReadbuff(Ctrl_O); - } - if (which_button == MOUSE_LEFT) { - stuffcharReadbuff('*'); - } else { // MOUSE_RIGHT - stuffcharReadbuff('#'); - } - } else if (in_status_line || in_sep_line) { - // Do nothing if on status line or vertical separator - // Handle double clicks otherwise - } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (MODE_NORMAL | MODE_INSERT))) { - if (is_click || !VIsual_active) { - if (VIsual_active) { - orig_cursor = VIsual; - } else { - VIsual = curwin->w_cursor; - orig_cursor = VIsual; - VIsual_active = true; - VIsual_reselect = true; - // start Select mode if 'selectmode' contains "mouse" - may_start_select('o'); - setmouse(); - } - if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { - // Double click with ALT pressed makes it blockwise. - if (mod_mask & MOD_MASK_ALT) { - VIsual_mode = Ctrl_V; - } else { - VIsual_mode = 'v'; - } - } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK) { - VIsual_mode = 'V'; - } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK) { - VIsual_mode = Ctrl_V; - } - } - // A double click selects a word or a block. - if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { - pos_T *pos = NULL; - int gc; - - if (is_click) { - // If the character under the cursor (skipping white space) is - // not a word character, try finding a match and select a (), - // {}, [], #if/#endif, etc. block. - end_visual = curwin->w_cursor; - while (gc = gchar_pos(&end_visual), ascii_iswhite(gc)) { - inc(&end_visual); - } - if (oap != NULL) { - oap->motion_type = kMTCharWise; - } - if (oap != NULL - && VIsual_mode == 'v' - && !vim_iswordc(gchar_pos(&end_visual)) - && equalpos(curwin->w_cursor, VIsual) - && (pos = findmatch(oap, NUL)) != NULL) { - curwin->w_cursor = *pos; - if (oap->motion_type == kMTLineWise) { - VIsual_mode = 'V'; - } else if (*p_sel == 'e') { - if (lt(curwin->w_cursor, VIsual)) { - VIsual.col++; - } else { - curwin->w_cursor.col++; - } - } - } - } - - if (pos == NULL && (is_click || is_drag)) { - // When not found a match or when dragging: extend to include a word. - if (lt(curwin->w_cursor, orig_cursor)) { - find_start_of_word(&curwin->w_cursor); - find_end_of_word(&VIsual); - } else { - find_start_of_word(&VIsual); - if (*p_sel == 'e' && *get_cursor_pos_ptr() != NUL) { - curwin->w_cursor.col += - utfc_ptr2len((char *)get_cursor_pos_ptr()); - } - find_end_of_word(&curwin->w_cursor); - } - } - curwin->w_set_curswant = true; - } - if (is_click) { - redraw_curbuf_later(INVERTED); // update the inversion - } - } else if (VIsual_active && !old_active) { - if (mod_mask & MOD_MASK_ALT) { - VIsual_mode = Ctrl_V; - } else { - VIsual_mode = 'v'; - } - } - - // If Visual mode changed show it later. - if ((!VIsual_active && old_active && mode_displayed) - || (VIsual_active && p_smd && msg_silent == 0 - && (!old_active || VIsual_mode != old_mode))) { - redraw_cmdline = true; - } - - return moved; -} - -/// Move "pos" back to the start of the word it's in. -static void find_start_of_word(pos_T *pos) -{ - char_u *line; - int cclass; - int col; - - line = ml_get(pos->lnum); - cclass = get_mouse_class(line + pos->col); - - while (pos->col > 0) { - col = pos->col - 1; - col -= utf_head_off(line, line + col); - if (get_mouse_class(line + col) != cclass) { - break; - } - pos->col = col; - } -} - -/// Move "pos" forward to the end of the word it's in. -/// When 'selection' is "exclusive", the position is just after the word. -static void find_end_of_word(pos_T *pos) -{ - char_u *line; - int cclass; - int col; - - line = ml_get(pos->lnum); - if (*p_sel == 'e' && pos->col > 0) { - pos->col--; - pos->col -= utf_head_off(line, line + pos->col); - } - cclass = get_mouse_class(line + pos->col); - while (line[pos->col] != NUL) { - col = pos->col + utfc_ptr2len((char *)line + pos->col); - if (get_mouse_class(line + col) != cclass) { - if (*p_sel == 'e') { - pos->col = col; - } - break; - } - pos->col = col; - } -} - -/// Get class of a character for selection: same class means same word. -/// 0: blank -/// 1: punctuation groups -/// 2: normal word character -/// >2: multi-byte word character. -static int get_mouse_class(char_u *p) -{ - if (MB_BYTE2LEN(p[0]) > 1) { - return mb_get_class(p); - } - - const int c = *p; - if (c == ' ' || c == '\t') { - return 0; - } - if (vim_iswordc(c)) { - return 2; - } - - // There are a few special cases where we want certain combinations of - // characters to be considered as a single word. These are things like - // "->", "/ *", "*=", "+=", "&=", "<=", ">=", "!=" etc. Otherwise, each - // character is in its own class. - if (c != NUL && vim_strchr("-+*/%<>&|^!=", c) != NULL) { - return 1; - } - return c; -} - /// End Visual mode. /// This function should ALWAYS be called to end Visual mode, except from /// do_pending_operator(). @@ -2311,7 +1495,7 @@ void reset_VIsual_and_resel(void) { if (VIsual_active) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion later + redraw_curbuf_later(UPD_INVERTED); // delete the inversion later } VIsual_reselect = false; } @@ -2321,7 +1505,7 @@ void reset_VIsual(void) { if (VIsual_active) { end_visual_mode(); - redraw_curbuf_later(INVERTED); // delete the inversion later + redraw_curbuf_later(UPD_INVERTED); // delete the inversion later VIsual_reselect = false; } } @@ -2342,8 +1526,7 @@ void restore_visual_mode(void) /// @param dir the direction of searching, is either FORWARD or BACKWARD /// @param *colp is in/decremented if "ptr[-dir]" should also be included. /// @param bnp points to a counter for square brackets. -static bool find_is_eval_item(const char_u *const ptr, int *const colp, int *const bnp, - const int dir) +static bool find_is_eval_item(const char *const ptr, int *const colp, int *const bnp, const int dir) { // Accept everything inside []. if ((*ptr == ']' && dir == BACKWARD) || (*ptr == '[' && dir == FORWARD)) { @@ -2414,7 +1597,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text // if i == 0: try to find an identifier // if i == 1: try to find any non-white text - char_u *ptr = ml_get_buf(wp->w_buffer, lnum, false); + char *ptr = ml_get_buf(wp->w_buffer, lnum, false); for (i = (find_type & FIND_IDENT) ? 0 : 1; i < 2; i++) { // 1. skip to start of identifier/text col = startcol; @@ -2427,7 +1610,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text if (this_class != 0 && (i == 1 || this_class != 1)) { break; } - col += utfc_ptr2len((char *)ptr + col); + col += utfc_ptr2len(ptr + col); } // When starting on a ']' count it, so that we include the '['. @@ -2438,7 +1621,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text // // Remember class of character under cursor. if ((find_type & FIND_EVAL) && ptr[col] == ']') { - this_class = mb_get_class((char_u *)"a"); + this_class = mb_get_class("a"); } else { this_class = mb_get_class(ptr + col); } @@ -2477,7 +1660,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text return 0; } ptr += col; - *text = (char *)ptr; + *text = ptr; if (textcol != NULL) { *textcol = col; } @@ -2495,7 +1678,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text || ((find_type & FIND_EVAL) && col <= (int)startcol && find_is_eval_item(ptr + col, &col, &bn, FORWARD)))) { - col += utfc_ptr2len((char *)ptr + col); + col += utfc_ptr2len(ptr + col); } assert(col >= 0); @@ -2620,9 +1803,7 @@ void may_clear_cmdline(void) } // Routines for displaying a partly typed command -#define SHOWCMD_BUFLEN (SHOWCMD_COLS + 1 + 30) -static char_u showcmd_buf[SHOWCMD_BUFLEN]; -static char_u old_showcmd_buf[SHOWCMD_BUFLEN]; // For push_showcmd() +static char old_showcmd_buf[SHOWCMD_BUFLEN]; // For push_showcmd() static bool showcmd_is_clear = true; static bool showcmd_visual = false; @@ -2652,8 +1833,8 @@ void clear_showcmd(void) lines = bot - top + 1; if (VIsual_mode == Ctrl_V) { - char_u *const saved_sbr = p_sbr; - char_u *const saved_w_sbr = curwin->w_p_sbr; + char *const saved_sbr = p_sbr; + char *const saved_w_sbr = curwin->w_p_sbr; // Make 'sbr' empty for a moment to get the correct size. p_sbr = empty_option; @@ -2661,12 +1842,12 @@ void clear_showcmd(void) getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); p_sbr = saved_sbr; curwin->w_p_sbr = saved_w_sbr; - snprintf((char *)showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64 "x%" PRId64, + snprintf(showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64 "x%" PRId64, (int64_t)lines, (int64_t)rightcol - leftcol + 1); } else if (VIsual_mode == 'V' || VIsual.lnum != curwin->w_cursor.lnum) { - snprintf((char *)showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64, (int64_t)lines); + snprintf(showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64, (int64_t)lines); } else { - char_u *s, *e; + char *s, *e; int l; int bytes = 0; int chars = 0; @@ -2679,7 +1860,7 @@ void clear_showcmd(void) e = ml_get_pos(&VIsual); } while ((*p_sel != 'e') ? s <= e : s < e) { - l = utfc_ptr2len((char *)s); + l = utfc_ptr2len(s); if (l == 0) { bytes++; chars++; @@ -2690,9 +1871,9 @@ void clear_showcmd(void) s += l; } if (bytes == chars) { - sprintf((char *)showcmd_buf, "%d", chars); + snprintf(showcmd_buf, SHOWCMD_BUFLEN, "%d", chars); } else { - sprintf((char *)showcmd_buf, "%d-%d", chars, bytes); + snprintf(showcmd_buf, SHOWCMD_BUFLEN, "%d-%d", chars, bytes); } } int limit = ui_has(kUIMessages) ? SHOWCMD_BUFLEN - 1 : SHOWCMD_COLS; @@ -2716,8 +1897,6 @@ void clear_showcmd(void) /// @return true if output has been written (and setcursor() has been called). bool add_to_showcmd(int c) { - char_u *p; - int i; static int ignore[] = { K_IGNORE, K_LEFTMOUSE, K_LEFTDRAG, K_LEFTRELEASE, K_MOUSEMOVE, @@ -2740,19 +1919,19 @@ bool add_to_showcmd(int c) // Ignore keys that are scrollbar updates and mouse clicks if (IS_SPECIAL(c)) { - for (i = 0; ignore[i] != 0; i++) { + for (int i = 0; ignore[i] != 0; i++) { if (ignore[i] == c) { return false; } } } - p = transchar(c); + char *p = transchar(c); if (*p == ' ') { STRCPY(p, "<20>"); } - size_t old_len = STRLEN(showcmd_buf); - size_t extra_len = STRLEN(p); + size_t old_len = strlen(showcmd_buf); + size_t extra_len = strlen(p); size_t limit = ui_has(kUIMessages) ? SHOWCMD_BUFLEN - 1 : SHOWCMD_COLS; if (old_len + extra_len > limit) { size_t overflow = old_len + extra_len - limit; @@ -2784,7 +1963,7 @@ static void del_from_showcmd(int len) return; } - old_len = (int)STRLEN(showcmd_buf); + old_len = (int)strlen(showcmd_buf); if (len > old_len) { len = old_len; } @@ -2817,13 +1996,23 @@ void pop_showcmd(void) static void display_showcmd(void) { - if (!ui_has_messages()) { + int len = (int)strlen(showcmd_buf); + showcmd_is_clear = (len == 0); + + if (*p_sloc == 's') { + win_redr_status(curwin); + setcursor(); // put cursor back where it belongs + return; + } + if (*p_sloc == 't') { + draw_tabline(); + setcursor(); // put cursor back where it belongs + return; + } + // 'showcmdloc' is "last" or empty + if (p_ch == 0 && !ui_has(kUIMessages)) { return; } - - int len; - len = (int)STRLEN(showcmd_buf); - showcmd_is_clear = (len == 0); if (ui_has(kUIMessages)) { MAXSIZE_TEMP_ARRAY(content, 1); @@ -2831,7 +2020,7 @@ static void display_showcmd(void) if (len > 0) { // placeholder for future highlight support ADD_C(chunk, INTEGER_OBJ(0)); - ADD_C(chunk, STRING_OBJ(cstr_as_string((char *)showcmd_buf))); + ADD_C(chunk, STRING_OBJ(cstr_as_string(showcmd_buf))); ADD_C(content, ARRAY_OBJ(chunk)); } ui_call_msg_showcmd(content); @@ -2848,7 +2037,7 @@ static void display_showcmd(void) } // clear the rest of an old message by outputting up to SHOWCMD_COLS spaces - grid_puts(&msg_grid_adj, (char_u *)" " + len, showcmd_row, + grid_puts(&msg_grid_adj, (char *)" " + len, showcmd_row, sc_col + len, HL_ATTR(HLF_MSG)); grid_puts_line_flush(false); @@ -2956,7 +2145,7 @@ void check_scrollbind(linenr_T topline_diff, long leftcol_diff) } } - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); cursor_correct(); curwin->w_redr_status = true; } @@ -3022,17 +2211,19 @@ static void nv_addsub(cmdarg_T *cap) /// CTRL-F, CTRL-B, etc: Scroll page up or down. static void nv_page(cmdarg_T *cap) { - if (!checkclearop(cap->oap)) { - if (mod_mask & MOD_MASK_CTRL) { - // <C-PageUp>: tab page back; <C-PageDown>: tab page forward - if (cap->arg == BACKWARD) { - goto_tabpage(-(int)cap->count1); - } else { - goto_tabpage((int)cap->count0); - } + if (checkclearop(cap->oap)) { + return; + } + + if (mod_mask & MOD_MASK_CTRL) { + // <C-PageUp>: tab page back; <C-PageDown>: tab page forward + if (cap->arg == BACKWARD) { + goto_tabpage(-(int)cap->count1); } else { - (void)onepage(cap->arg, cap->count1); + goto_tabpage((int)cap->count0); } + } else { + (void)onepage(cap->arg, cap->count1); } } @@ -3044,22 +2235,23 @@ static void nv_gd(oparg_T *oap, int nchar, int thisblock) size_t len; char *ptr; if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0 - || !find_decl((char_u *)ptr, len, nchar == 'd', thisblock, SEARCH_START)) { + || !find_decl(ptr, len, nchar == 'd', thisblock, SEARCH_START)) { clearopbeep(oap); - } else { - if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) { - foldOpenCursor(); - } - // clear any search statistics - if (messaging() && !msg_silent && !shortmess(SHM_SEARCHCOUNT)) { - clear_cmdline = true; - } + return; + } + + if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) { + foldOpenCursor(); + } + // clear any search statistics + if (messaging() && !msg_silent && !shortmess(SHM_SEARCHCOUNT)) { + clear_cmdline = true; } } /// @return true if line[offset] is not inside a C-style comment or string, /// false otherwise. -static bool is_ident(char_u *line, int offset) +static bool is_ident(const char *line, int offset) { bool incomment = false; int instring = 0; @@ -3067,11 +2259,11 @@ static bool is_ident(char_u *line, int offset) for (int i = 0; i < offset && line[i] != NUL; i++) { if (instring != 0) { - if (prev != '\\' && line[i] == instring) { + if (prev != '\\' && (uint8_t)line[i] == instring) { instring = 0; } } else if ((line[i] == '"' || line[i] == '\'') && !incomment) { - instring = line[i]; + instring = (uint8_t)line[i]; } else { if (incomment) { if (prev == '*' && line[i] == '/') { @@ -3084,7 +2276,7 @@ static bool is_ident(char_u *line, int offset) } } - prev = line[i]; + prev = (uint8_t)line[i]; } return incomment == false && instring == 0; @@ -3098,9 +2290,9 @@ static bool is_ident(char_u *line, int offset) /// @param flags_arg flags passed to searchit() /// /// @return fail when not found. -bool find_decl(char_u *ptr, size_t len, bool locally, bool thisblock, int flags_arg) +bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_arg) { - char_u *pat; + char *pat; pos_T old_pos; pos_T par_pos; pos_T found_pos; @@ -3116,7 +2308,7 @@ bool find_decl(char_u *ptr, size_t len, bool locally, bool thisblock, int flags_ // Put "\V" before the pattern to avoid that the special meaning of "." // and "~" causes trouble. assert(len <= INT_MAX); - sprintf((char *)pat, vim_iswordp(ptr) ? "\\V\\<%.*s\\>" : "\\V%.*s", + sprintf(pat, vim_iswordp(ptr) ? "\\V\\<%.*s\\>" : "\\V%.*s", // NOLINT(runtime/printf) (int)len, ptr); old_pos = curwin->w_cursor; save_p_ws = p_ws; @@ -3134,7 +2326,7 @@ bool find_decl(char_u *ptr, size_t len, bool locally, bool thisblock, int flags_ } else { par_pos = curwin->w_cursor; while (curwin->w_cursor.lnum > 1 - && *skipwhite((char *)get_cursor_line_ptr()) != NUL) { + && *skipwhite(get_cursor_line_ptr()) != NUL) { curwin->w_cursor.lnum--; } } @@ -3171,7 +2363,7 @@ bool find_decl(char_u *ptr, size_t len, bool locally, bool thisblock, int flags_ } break; } - if (get_leader_len((char *)get_cursor_line_ptr(), NULL, false, true) > 0) { + if (get_leader_len(get_cursor_line_ptr(), NULL, false, true) > 0) { // Ignore this line, continue at start of next line. curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; @@ -3358,10 +2550,10 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) validate_virtcol(); colnr_T virtcol = curwin->w_virtcol; if (virtcol > (colnr_T)width1 && *get_showbreak_value(curwin) != NUL) { - virtcol -= vim_strsize((char *)get_showbreak_value(curwin)); + virtcol -= vim_strsize(get_showbreak_value(curwin)); } - int c = utf_ptr2char((char *)get_cursor_pos_ptr()); + int c = utf_ptr2char(get_cursor_pos_ptr()); if (dir == FORWARD && virtcol < curwin->w_curswant && (curwin->w_curswant <= (colnr_T)width1) && !vim_isprintc(c) && c > 255) { @@ -3410,7 +2602,7 @@ static void nv_mousescroll(cmdarg_T *cap) if (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN) { if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { (void)onepage(cap->arg ? FORWARD : BACKWARD, 1L); - } else { + } else if (p_mousescroll_vert > 0) { cap->count1 = p_mousescroll_vert; cap->count0 = p_mousescroll_vert; nv_scroll_line(cap); @@ -3490,7 +2682,7 @@ void scroll_redraw(int up, long count) if (moved) { curwin->w_viewport_invalid = true; } - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); } /// Get the count specified after a 'z' command. Only the 'z<CR>', 'zl', 'zh', @@ -3514,6 +2706,7 @@ static bool nv_z_get_count(cmdarg_T *cap, int *nchar_arg) no_mapping--; allow_keys--; (void)add_to_showcmd(nchar); + if (nchar == K_DEL || nchar == K_KDEL) { n /= 10; } else if (ascii_isdigit(nchar)) { @@ -3554,6 +2747,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar) no_mapping--; allow_keys--; (void)add_to_showcmd(nchar); + if (vim_strchr("gGwW", nchar) == NULL) { clearopbeep(cap->oap); return OK; @@ -3579,7 +2773,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar) len = spell_move_to(curwin, FORWARD, true, true, NULL); emsg_off--; if (len != 0 && curwin->w_cursor.col <= pos.col) { - ptr = (char *)ml_get_pos(&curwin->w_cursor); + ptr = ml_get_pos(&curwin->w_cursor); } curwin->w_cursor = pos; } @@ -3588,7 +2782,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar) return FAIL; } assert(len <= INT_MAX); - spell_add_word((char_u *)ptr, (int)len, + spell_add_word(ptr, (int)len, nchar == 'w' || nchar == 'W' ? SPELL_ADD_BAD : SPELL_ADD_GOOD, (nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1, undo); @@ -3605,7 +2799,7 @@ static void nv_zet(cmdarg_T *cap) long old_fdl = curwin->w_p_fdl; int old_fen = curwin->w_p_fen; - int l_p_siso = (int)get_sidescrolloff_value(curwin); + int siso = (int)get_sidescrolloff_value(curwin); if (ascii_isdigit(nchar) && !nv_z_get_count(cap, &nchar)) { return; @@ -3656,7 +2850,7 @@ static void nv_zet(cmdarg_T *cap) case 't': scroll_cursor_top(0, true); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); set_fraction(curwin); break; @@ -3667,7 +2861,7 @@ static void nv_zet(cmdarg_T *cap) case 'z': scroll_cursor_halfway(true); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); set_fraction(curwin); break; @@ -3690,7 +2884,7 @@ static void nv_zet(cmdarg_T *cap) case 'b': scroll_cursor_bot(0, true); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); set_fraction(curwin); break; @@ -3735,14 +2929,14 @@ static void nv_zet(cmdarg_T *cap) } else { getvcol(curwin, &curwin->w_cursor, &col, NULL, NULL); } - if (col > l_p_siso) { - col -= l_p_siso; + if (col > siso) { + col -= siso; } else { col = 0; } if (curwin->w_leftcol != col) { curwin->w_leftcol = col; - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } } break; @@ -3756,14 +2950,14 @@ static void nv_zet(cmdarg_T *cap) getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col); } n = curwin->w_width_inner - curwin_col_off(); - if (col + l_p_siso < n) { + if (col + siso < n) { col = 0; } else { - col = col + l_p_siso - n + 1; + col = col + siso - n + 1; } if (curwin->w_leftcol != col) { curwin->w_leftcol = col; - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } } break; @@ -4024,44 +3218,45 @@ static void nv_colon(cmdarg_T *cap) if (VIsual_active && !is_cmdkey && !is_lua) { nv_operator(cap); - } else { - if (cap->oap->op_type != OP_NOP) { - // Using ":" as a movement is charwise exclusive. - cap->oap->motion_type = kMTCharWise; - cap->oap->inclusive = false; - } else if (cap->count0 && !is_cmdkey && !is_lua) { - // translate "count:" into ":.,.+(count - 1)" - stuffcharReadbuff('.'); - if (cap->count0 > 1) { - stuffReadbuff(",.+"); - stuffnumReadbuff(cap->count0 - 1L); - } - } + return; + } - // When typing, don't type below an old message - if (KeyTyped) { - compute_cmdrow(); + if (cap->oap->op_type != OP_NOP) { + // Using ":" as a movement is charwise exclusive. + cap->oap->motion_type = kMTCharWise; + cap->oap->inclusive = false; + } else if (cap->count0 && !is_cmdkey && !is_lua) { + // translate "count:" into ":.,.+(count - 1)" + stuffcharReadbuff('.'); + if (cap->count0 > 1) { + stuffReadbuff(",.+"); + stuffnumReadbuff(cap->count0 - 1L); } + } - if (is_lua) { - cmd_result = map_execute_lua(); - } else { - // get a command line and execute it - cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, - cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); - } + // When typing, don't type below an old message + if (KeyTyped) { + compute_cmdrow(); + } - if (cmd_result == false) { - // The Ex command failed, do not execute the operator. - clearop(cap->oap); - } else if (cap->oap->op_type != OP_NOP - && (cap->oap->start.lnum > curbuf->b_ml.ml_line_count - || cap->oap->start.col > - (colnr_T)STRLEN(ml_get(cap->oap->start.lnum)) - || did_emsg)) { - // The start of the operator has become invalid by the Ex command. - clearopbeep(cap->oap); - } + if (is_lua) { + cmd_result = map_execute_lua(); + } else { + // get a command line and execute it + cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, + cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); + } + + if (cmd_result == false) { + // The Ex command failed, do not execute the operator. + clearop(cap->oap); + } else if (cap->oap->op_type != OP_NOP + && (cap->oap->start.lnum > curbuf->b_ml.ml_line_count + || cap->oap->start.col > + (colnr_T)strlen(ml_get(cap->oap->start.lnum)) + || did_emsg)) { + // The start of the operator has become invalid by the Ex command. + clearopbeep(cap->oap); } } @@ -4092,14 +3287,16 @@ static void nv_ctrlh(cmdarg_T *cap) /// CTRL-L: clear screen and redraw. static void nv_clear(cmdarg_T *cap) { - if (!checkclearop(cap->oap)) { - // Clear all syntax states to force resyncing. - syn_stack_free_all(curwin->w_s); - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - wp->w_s->b_syn_slow = false; - } - redraw_later(curwin, CLEAR); + if (checkclearop(cap->oap)) { + return; } + + // Clear all syntax states to force resyncing. + syn_stack_free_all(curwin->w_s); + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + wp->w_s->b_syn_slow = false; + } + redraw_later(curwin, UPD_CLEAR); } /// CTRL-O: In Select mode: switch to Visual mode for one command. @@ -4130,21 +3327,23 @@ static void nv_hat(cmdarg_T *cap) /// "Z" commands. static void nv_Zet(cmdarg_T *cap) { - if (!checkclearopq(cap->oap)) { - switch (cap->nchar) { - // "ZZ": equivalent to ":x". - case 'Z': - do_cmdline_cmd("x"); - break; + if (checkclearopq(cap->oap)) { + return; + } - // "ZQ": equivalent to ":q!" (Elvis compatible). - case 'Q': - do_cmdline_cmd("q!"); - break; + switch (cap->nchar) { + // "ZZ": equivalent to ":x". + case 'Z': + do_cmdline_cmd("x"); + break; - default: - clearopbeep(cap->oap); - } + // "ZQ": equivalent to ":q!" (Elvis compatible). + case 'Q': + do_cmdline_cmd("q!"); + break; + + default: + clearopbeep(cap->oap); } } @@ -4164,7 +3363,7 @@ void do_nv_ident(int c1, int c2) /// 'K' normal-mode command. Get the command to lookup the keyword under the /// cursor. -static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, char **ptr_arg, +static size_t nv_K_getcmd(cmdarg_T *cap, char *kp, bool kp_help, bool kp_ex, char **ptr_arg, size_t n, char *buf, size_t buf_size) { if (kp_help) { @@ -4201,8 +3400,8 @@ static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, c // When a count is given, turn it into a range. Is this // really what we want? - bool isman = (STRCMP(kp, "man") == 0); - bool isman_s = (STRCMP(kp, "man -s") == 0); + bool isman = (strcmp(kp, "man") == 0); + bool isman_s = (strcmp(kp, "man -s") == 0); if (cap->count0 != 0 && !(isman || isman_s)) { snprintf(buf, buf_size, ".,.+%" PRId64, (int64_t)(cap->count0 - 1)); } @@ -4216,7 +3415,7 @@ static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, c } STRCAT(buf, " "); if (cap->count0 != 0 && (isman || isman_s)) { - snprintf(buf + STRLEN(buf), buf_size - STRLEN(buf), "%" PRId64, + snprintf(buf + strlen(buf), buf_size - strlen(buf), "%" PRId64, (int64_t)cap->count0); STRCAT(buf, " "); } @@ -4234,12 +3433,12 @@ static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, c static void nv_ident(cmdarg_T *cap) { char *ptr = NULL; - char_u *p; + char *p; size_t n = 0; // init for GCC int cmdchar; bool g_cmd; // "g" command bool tag_cmd = false; - char_u *aux_ptr; + char *aux_ptr; if (cap->cmdchar == 'g') { // "g*", "g#", "g]" and "gCTRL-]" cmdchar = cap->nchar; @@ -4275,15 +3474,14 @@ static void nv_ident(cmdarg_T *cap) // Allocate buffer to put the command in. Inserting backslashes can // double the length of the word. p_kp / curbuf->b_p_kp could be added // and some numbers. - char_u *kp = *curbuf->b_p_kp == NUL ? p_kp : curbuf->b_p_kp; // 'keywordprg' - assert(*kp != NUL); // option.c:do_set() should default to ":help" if empty. - bool kp_ex = (*kp == ':'); // 'keywordprg' is an ex command - bool kp_help = (STRCMP(kp, ":he") == 0 || STRCMP(kp, ":help") == 0); + char *kp = *curbuf->b_p_kp == NUL ? p_kp : curbuf->b_p_kp; // 'keywordprg' + bool kp_help = (*kp == NUL || strcmp(kp, ":he") == 0 || strcmp(kp, ":help") == 0); if (kp_help && *skipwhite(ptr) == NUL) { emsg(_(e_noident)); // found white space only return; } - size_t buf_size = n * 2 + 30 + STRLEN(kp); + bool kp_ex = (*kp == ':'); // 'keywordprg' is an ex command + size_t buf_size = n * 2 + 30 + strlen(kp); char *buf = xmalloc(buf_size); buf[0] = NUL; @@ -4295,9 +3493,9 @@ static void nv_ident(cmdarg_T *cap) // Call setpcmark() first, so "*``" puts the cursor back where // it was. setpcmark(); - curwin->w_cursor.col = (colnr_T)(ptr - (char *)get_cursor_line_ptr()); + curwin->w_cursor.col = (colnr_T)(ptr - get_cursor_line_ptr()); - if (!g_cmd && vim_iswordp((char_u *)ptr)) { + if (!g_cmd && vim_iswordp(ptr)) { STRCPY(buf, "\\<"); } no_smartcase = true; // don't use 'smartcase' now @@ -4312,11 +3510,7 @@ static void nv_ident(cmdarg_T *cap) case ']': tag_cmd = true; - if (p_cst) { - STRCPY(buf, "cstag "); - } else { - STRCPY(buf, "ts "); - } + STRCPY(buf, "ts "); break; default: @@ -4337,45 +3531,45 @@ static void nv_ident(cmdarg_T *cap) ptr = xstrnsave(ptr, n); if (kp_ex) { // Escape the argument properly for an Ex command - p = (char_u *)vim_strsave_fnameescape((const char *)ptr, VSE_NONE); + p = vim_strsave_fnameescape((const char *)ptr, VSE_NONE); } else { // Escape the argument properly for a shell command - p = vim_strsave_shellescape((char_u *)ptr, true, true); + p = vim_strsave_shellescape(ptr, true, true); } xfree(ptr); - char *newbuf = xrealloc(buf, STRLEN(buf) + STRLEN(p) + 1); + char *newbuf = xrealloc(buf, strlen(buf) + strlen(p) + 1); buf = newbuf; STRCAT(buf, p); xfree(p); } else { if (cmdchar == '*') { - aux_ptr = (char_u *)(p_magic ? "/.*~[^$\\" : "/^$\\"); + aux_ptr = (magic_isset() ? "/.*~[^$\\" : "/^$\\"); } else if (cmdchar == '#') { - aux_ptr = (char_u *)(p_magic ? "/?.*~[^$\\" : "/?^$\\"); + aux_ptr = (magic_isset() ? "/?.*~[^$\\" : "/?^$\\"); } else if (tag_cmd) { if (curbuf->b_help) { // ":help" handles unescaped argument - aux_ptr = (char_u *)""; + aux_ptr = ""; } else { - aux_ptr = (char_u *)"\\|\"\n["; + aux_ptr = "\\|\"\n["; } } else { - aux_ptr = (char_u *)"\\|\"\n*?["; + aux_ptr = "\\|\"\n*?["; } - p = (char_u *)buf + STRLEN(buf); + p = buf + strlen(buf); while (n-- > 0) { // put a backslash before \ and some others - if (vim_strchr((char *)aux_ptr, *ptr) != NULL) { + if (vim_strchr(aux_ptr, (uint8_t)(*ptr)) != NULL) { *p++ = '\\'; } // When current byte is a part of multibyte character, copy all // bytes of that character. const size_t len = (size_t)(utfc_ptr2len(ptr) - 1); for (size_t i = 0; i < len && n > 0; i++, n--) { - *p++ = (char_u)(*ptr++); + *p++ = *ptr++; } - *p++ = (char_u)(*ptr++); + *p++ = *ptr++; } *p = NUL; } @@ -4383,16 +3577,15 @@ static void nv_ident(cmdarg_T *cap) // Execute the command. if (cmdchar == '*' || cmdchar == '#') { if (!g_cmd - && vim_iswordp(mb_prevptr(get_cursor_line_ptr(), (char_u *)ptr))) { + && vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr))) { STRCAT(buf, "\\>"); } // put pattern in search history init_history(); - add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL); + add_to_history(HIST_SEARCH, buf, true, NUL); - (void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0, - NULL); + (void)normal_search(cap, cmdchar == '*' ? '/' : '?', buf, 0, NULL); } else { g_tag_at_cursor = true; do_cmdline_cmd(buf); @@ -4427,14 +3620,14 @@ bool get_visual_text(cmdarg_T *cap, char **pp, size_t *lenp) return false; } if (VIsual_mode == 'V') { - *pp = (char *)get_cursor_line_ptr(); - *lenp = STRLEN(*pp); + *pp = get_cursor_line_ptr(); + *lenp = strlen(*pp); } else { if (lt(curwin->w_cursor, VIsual)) { - *pp = (char *)ml_get_pos(&curwin->w_cursor); + *pp = ml_get_pos(&curwin->w_cursor); *lenp = (size_t)VIsual.col - (size_t)curwin->w_cursor.col + 1; } else { - *pp = (char *)ml_get_pos(&VIsual); + *pp = ml_get_pos(&VIsual); *lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1; } if (**pp == NUL) { @@ -4453,7 +3646,7 @@ bool get_visual_text(cmdarg_T *cap, char **pp, size_t *lenp) static void nv_tagpop(cmdarg_T *cap) { if (!checkclearopq(cap->oap)) { - do_tag((char_u *)"", DT_POP, (int)cap->count1, false, true); + do_tag("", DT_POP, (int)cap->count1, false, true); } } @@ -4480,7 +3673,9 @@ static void nv_scroll(cmdarg_T *cap) && curwin->w_cursor.lnum > curwin->w_topline; n--) { (void)hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); - curwin->w_cursor.lnum--; + if (curwin->w_cursor.lnum > curwin->w_topline) { + curwin->w_cursor.lnum--; + } } } else { curwin->w_cursor.lnum -= (linenr_T)cap->count1 - 1; @@ -4566,9 +3761,9 @@ static void nv_right(cmdarg_T *cap) // <Space> wraps to next line if 'whichwrap' has 's'. // 'l' wraps to next line if 'whichwrap' has 'l'. // CURS_RIGHT wraps to next line if 'whichwrap' has '>'. - if (((cap->cmdchar == ' ' && vim_strchr((char *)p_ww, 's') != NULL) - || (cap->cmdchar == 'l' && vim_strchr((char *)p_ww, 'l') != NULL) - || (cap->cmdchar == K_RIGHT && vim_strchr((char *)p_ww, '>') != NULL)) + if (((cap->cmdchar == ' ' && vim_strchr(p_ww, 's') != NULL) + || (cap->cmdchar == 'l' && vim_strchr(p_ww, 'l') != NULL) + || (cap->cmdchar == K_RIGHT && vim_strchr(p_ww, '>') != NULL)) && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { // When deleting we also count the NL as a character. // Set cap->oap->inclusive when last char in the line is @@ -4602,7 +3797,7 @@ static void nv_right(cmdarg_T *cap) if (virtual_active()) { oneright(); } else { - curwin->w_cursor.col += utfc_ptr2len((char *)get_cursor_pos_ptr()); + curwin->w_cursor.col += utfc_ptr2len(get_cursor_pos_ptr()); } } } @@ -4636,9 +3831,9 @@ static void nv_left(cmdarg_T *cap) // 'h' wraps to previous line if 'whichwrap' has 'h'. // CURS_LEFT wraps to previous line if 'whichwrap' has '<'. if ((((cap->cmdchar == K_BS || cap->cmdchar == Ctrl_H) - && vim_strchr((char *)p_ww, 'b') != NULL) - || (cap->cmdchar == 'h' && vim_strchr((char *)p_ww, 'h') != NULL) - || (cap->cmdchar == K_LEFT && vim_strchr((char *)p_ww, '<') != NULL)) + && vim_strchr(p_ww, 'b') != NULL) + || (cap->cmdchar == 'h' && vim_strchr(p_ww, 'h') != NULL) + || (cap->cmdchar == K_LEFT && vim_strchr(p_ww, '<') != NULL)) && curwin->w_cursor.lnum > 1) { curwin->w_cursor.lnum--; coladvance(MAXCOL); @@ -4650,10 +3845,10 @@ static void nv_left(cmdarg_T *cap) // Don't adjust op_end now, otherwise it won't work. if ((cap->oap->op_type == OP_DELETE || cap->oap->op_type == OP_CHANGE) && !LINEEMPTY(curwin->w_cursor.lnum)) { - char_u *cp = get_cursor_pos_ptr(); + char *cp = get_cursor_pos_ptr(); if (*cp != NUL) { - curwin->w_cursor.col += utfc_ptr2len((char *)cp); + curwin->w_cursor.col += utfc_ptr2len(cp); } cap->retval |= CA_NO_ADJ_OP_END; } @@ -4679,13 +3874,14 @@ static void nv_up(cmdarg_T *cap) // <S-Up> is page up cap->arg = BACKWARD; nv_page(cap); - } else { - cap->oap->motion_type = kMTLineWise; - if (cursor_up(cap->count1, cap->oap->op_type == OP_NOP) == false) { - clearopbeep(cap->oap); - } else if (cap->arg) { - beginline(BL_WHITE | BL_FIX); - } + return; + } + + cap->oap->motion_type = kMTLineWise; + if (cursor_up(cap->count1, cap->oap->op_type == OP_NOP) == false) { + clearopbeep(cap->oap); + } else if (cap->arg) { + beginline(BL_WHITE | BL_FIX); } } @@ -4725,14 +3921,10 @@ static void nv_down(cmdarg_T *cap) /// Grab the file name under the cursor and edit it. static void nv_gotofile(cmdarg_T *cap) { - char_u *ptr; + char *ptr; linenr_T lnum = -1; - if (check_text_locked(cap->oap)) { - return; - } - if (curbuf_locked()) { - clearop(cap->oap); + if (check_text_or_curbuf_locked(cap->oap)) { return; } @@ -4744,7 +3936,7 @@ static void nv_gotofile(cmdarg_T *cap) (void)autowrite(curbuf, false); } setpcmark(); - if (do_ecmd(0, (char *)ptr, NULL, NULL, ECMD_LAST, + if (do_ecmd(0, ptr, NULL, NULL, ECMD_LAST, buf_hide(curbuf) ? ECMD_HIDE : 0, curwin) == OK && cap->nchar == 'F' && lnum >= 0) { curwin->w_cursor.lnum = lnum; @@ -4841,9 +4033,8 @@ static void nv_next(cmdarg_T *cap) /// @param opt extra flags for do_search() /// /// @return 0 for failure, 1 for found, 2 for found and line offset added. -static int normal_search(cmdarg_T *cap, int dir, char_u *pat, int opt, int *wrapped) +static int normal_search(cmdarg_T *cap, int dir, char *pat, int opt, int *wrapped) { - int i; searchit_arg_T sia; cap->oap->motion_type = kMTCharWise; @@ -4852,8 +4043,8 @@ static int normal_search(cmdarg_T *cap, int dir, char_u *pat, int opt, int *wrap curwin->w_set_curswant = true; CLEAR_FIELD(sia); - i = do_search(cap->oap, dir, dir, pat, cap->count1, - opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); + int i = do_search(cap->oap, dir, dir, pat, cap->count1, + opt | SEARCH_OPT | SEARCH_ECHO | SEARCH_MSG, &sia); if (wrapped != NULL) { *wrapped = sia.sa_wrapped; } @@ -4892,22 +4083,23 @@ static void nv_csearch(cmdarg_T *cap) cap->oap->motion_type = kMTCharWise; if (IS_SPECIAL(cap->nchar) || searchc(cap, t_cmd) == false) { clearopbeep(cap->oap); - } else { - curwin->w_set_curswant = true; - // Include a Tab for "tx" and for "dfx". - if (gchar_cursor() == TAB && virtual_active() && cap->arg == FORWARD - && (t_cmd || cap->oap->op_type != OP_NOP)) { - colnr_T scol, ecol; + return; + } - getvcol(curwin, &curwin->w_cursor, &scol, NULL, &ecol); - curwin->w_cursor.coladd = ecol - scol; - } else { - curwin->w_cursor.coladd = 0; - } - adjust_for_sel(cap); - if ((fdo_flags & FDO_HOR) && KeyTyped && cap->oap->op_type == OP_NOP) { - foldOpenCursor(); - } + curwin->w_set_curswant = true; + // Include a Tab for "tx" and for "dfx". + if (gchar_cursor() == TAB && virtual_active() && cap->arg == FORWARD + && (t_cmd || cap->oap->op_type != OP_NOP)) { + colnr_T scol, ecol; + + getvcol(curwin, &curwin->w_cursor, &scol, NULL, &ecol); + curwin->w_cursor.coladd = ecol - scol; + } else { + curwin->w_cursor.coladd = 0; + } + adjust_for_sel(cap); + if ((fdo_flags & FDO_HOR) && KeyTyped && cap->oap->op_type == OP_NOP) { + foldOpenCursor(); } } @@ -5061,7 +4253,7 @@ static void nv_brackets(cmdarg_T *cap) } else { // Make a copy, if the line was changed it will be freed. ptr = xstrnsave(ptr, len); - find_pattern_in_path((char_u *)ptr, 0, len, true, + find_pattern_in_path(ptr, 0, len, true, cap->count0 == 0 ? !isupper(cap->nchar) : false, (((cap->nchar & 0xf) == ('d' & 0xf)) ? FIND_DEFINE @@ -5154,9 +4346,8 @@ static void nv_brackets(cmdarg_T *cap) cap->nchar == 's', false, NULL) == 0) { clearopbeep(cap->oap); break; - } else { - curwin->w_set_curswant = true; } + curwin->w_set_curswant = true; } if (cap->oap->op_type == OP_NOP && (fdo_flags & FDO_SEARCH) && KeyTyped) { foldOpenCursor(); @@ -5230,25 +4421,28 @@ static void nv_brace(cmdarg_T *cap) cap->oap->inclusive = false; curwin->w_set_curswant = true; - if (findsent(cap->arg, cap->count1) == false) { + if (findsent(cap->arg, cap->count1) == FAIL) { clearopbeep(cap->oap); - } else { - // Don't leave the cursor on the NUL past end of line. - adjust_cursor(cap->oap); - curwin->w_cursor.coladd = 0; - if ((fdo_flags & FDO_BLOCK) && KeyTyped && cap->oap->op_type == OP_NOP) { - foldOpenCursor(); - } + return; + } + + // Don't leave the cursor on the NUL past end of line. + adjust_cursor(cap->oap); + curwin->w_cursor.coladd = 0; + if ((fdo_flags & FDO_BLOCK) && KeyTyped && cap->oap->op_type == OP_NOP) { + foldOpenCursor(); } } /// "m" command: Mark a position. static void nv_mark(cmdarg_T *cap) { - if (!checkclearop(cap->oap)) { - if (setmark(cap->nchar) == false) { - clearopbeep(cap->oap); - } + if (checkclearop(cap->oap)) { + return; + } + + if (setmark(cap->nchar) == false) { + clearopbeep(cap->oap); } } @@ -5262,11 +4456,12 @@ static void nv_findpar(cmdarg_T *cap) curwin->w_set_curswant = true; if (!findpar(&cap->oap->inclusive, cap->arg, cap->count1, NUL, false)) { clearopbeep(cap->oap); - } else { - curwin->w_cursor.coladd = 0; - if ((fdo_flags & FDO_BLOCK) && KeyTyped && cap->oap->op_type == OP_NOP) { - foldOpenCursor(); - } + return; + } + + curwin->w_cursor.coladd = 0; + if ((fdo_flags & FDO_BLOCK) && KeyTyped && cap->oap->op_type == OP_NOP) { + foldOpenCursor(); } } @@ -5287,20 +4482,22 @@ static void nv_undo(cmdarg_T *cap) /// <Undo> command. static void nv_kundo(cmdarg_T *cap) { - if (!checkclearopq(cap->oap)) { - if (bt_prompt(curbuf)) { - clearopbeep(cap->oap); - return; - } - u_undo((int)cap->count1); - curwin->w_set_curswant = true; + if (checkclearopq(cap->oap)) { + return; } + + if (bt_prompt(curbuf)) { + clearopbeep(cap->oap); + return; + } + u_undo((int)cap->count1); + curwin->w_set_curswant = true; } /// Handle the "r" command. static void nv_replace(cmdarg_T *cap) { - char_u *ptr; + char *ptr; int had_ctrl_v; if (checkclearop(cap->oap)) { @@ -5364,7 +4561,7 @@ static void nv_replace(cmdarg_T *cap) // Abort if not enough characters to replace. ptr = get_cursor_pos_ptr(); - if (STRLEN(ptr) < (unsigned)cap->count1 + if (strlen(ptr) < (unsigned)cap->count1 || (mb_charlen(ptr) < cap->count1)) { clearopbeep(cap->oap); return; @@ -5505,15 +4702,20 @@ static void nv_Replace(cmdarg_T *cap) VIsual_mode_orig = VIsual_mode; // remember original area for gv VIsual_mode = 'V'; nv_operator(cap); - } else if (!checkclearopq(cap->oap)) { - if (!MODIFIABLE(curbuf)) { - emsg(_(e_modifiable)); - } else { - if (virtual_active()) { - coladvance(getviscol()); - } - invoke_edit(cap, false, cap->arg ? 'V' : 'R', false); + return; + } + + if (checkclearopq(cap->oap)) { + return; + } + + if (!MODIFIABLE(curbuf)) { + emsg(_(e_modifiable)); + } else { + if (virtual_active()) { + coladvance(getviscol()); } + invoke_edit(cap, false, cap->arg ? 'V' : 'R', false); } } @@ -5524,20 +4726,25 @@ static void nv_vreplace(cmdarg_T *cap) cap->cmdchar = 'r'; cap->nchar = cap->extra_char; nv_replace(cap); // Do same as "r" in Visual mode for now - } else if (!checkclearopq(cap->oap)) { - if (!MODIFIABLE(curbuf)) { - emsg(_(e_modifiable)); - } else { - if (cap->extra_char == Ctrl_V) { // get another character - cap->extra_char = get_literal(false); - } - stuffcharReadbuff(cap->extra_char); - stuffcharReadbuff(ESC); - if (virtual_active()) { - coladvance(getviscol()); - } - invoke_edit(cap, true, 'v', false); + return; + } + + if (checkclearopq(cap->oap)) { + return; + } + + if (!MODIFIABLE(curbuf)) { + emsg(_(e_modifiable)); + } else { + if (cap->extra_char == Ctrl_V) { // get another character + cap->extra_char = get_literal(false); } + stuffcharReadbuff(cap->extra_char); + stuffcharReadbuff(ESC); + if (virtual_active()) { + coladvance(getviscol()); + } + invoke_edit(cap, true, 'v', false); } } @@ -5552,7 +4759,7 @@ static void n_swapchar(cmdarg_T *cap) return; } - if (LINEEMPTY(curwin->w_cursor.lnum) && vim_strchr((char *)p_ww, '~') == NULL) { + if (LINEEMPTY(curwin->w_cursor.lnum) && vim_strchr(p_ww, '~') == NULL) { clearopbeep(cap->oap); return; } @@ -5568,7 +4775,7 @@ static void n_swapchar(cmdarg_T *cap) did_change |= swapchar(cap->oap->op_type, &curwin->w_cursor); inc_cursor(); if (gchar_cursor() == NUL) { - if (vim_strchr((char *)p_ww, '~') != NULL + if (vim_strchr(p_ww, '~') != NULL && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; @@ -5624,7 +4831,7 @@ static MarkMoveRes nv_mark_move_to(cmdarg_T *cap, MarkMove flags, fmark_T *fm) /// Handle commands that are operators in Visual mode. static void v_visop(cmdarg_T *cap) { - static char_u trans[] = "YyDdCcxdXdAAIIrr"; + static char trans[] = "YyDdCcxdXdAAIIrr"; // Uppercase means linewise, except in block mode, then "D" deletes till // the end of the line, and "C" replaces till EOL @@ -5636,7 +4843,7 @@ static void v_visop(cmdarg_T *cap) curwin->w_curswant = MAXCOL; } } - cap->cmdchar = (uint8_t)(*(vim_strchr((char *)trans, cap->cmdchar) + 1)); + cap->cmdchar = (uint8_t)(*(vim_strchr(trans, cap->cmdchar) + 1)); nv_operator(cap); } @@ -5695,6 +4902,10 @@ static void nv_gomark(cmdarg_T *cap) { int name; MarkMove flags = jop_flags & JOP_VIEW ? kMarkSetView : 0; // flags for moving to the mark + if (cap->oap->op_type != OP_NOP) { + // When there is a pending operator, do not restore the view as this is usually unexpected. + flags = 0; + } MarkMoveRes move_res = 0; // Result from moving to the mark const bool old_KeyTyped = KeyTyped; // getting file may reset it @@ -5734,42 +4945,44 @@ static void nv_pcmark(cmdarg_T *cap) MarkMoveRes move_res = 0; // Result from moving to the mark const bool old_KeyTyped = KeyTyped; // getting file may reset it. - if (!checkclearopq(cap->oap)) { - if (cap->cmdchar == TAB && mod_mask == MOD_MASK_CTRL) { - if (!goto_tabpage_lastused()) { - clearopbeep(cap->oap); - } - return; - } + if (checkclearopq(cap->oap)) { + return; + } - if (cap->cmdchar == 'g') { - fm = get_changelist(curbuf, curwin, (int)cap->count1); - } else { - fm = get_jumplist(curwin, (int)cap->count1); - flags |= KMarkNoContext | kMarkJumpList; - } - // Changelist and jumplist have their own error messages. Therefore avoid - // calling nv_mark_move_to() when not found to avoid incorrect error - // messages. - if (fm != NULL) { - move_res = nv_mark_move_to(cap, flags, fm); - } else if (cap->cmdchar == 'g') { - if (curbuf->b_changelistlen == 0) { - emsg(_("E664: changelist is empty")); - } else if (cap->count1 < 0) { - emsg(_("E662: At start of changelist")); - } else { - emsg(_("E663: At end of changelist")); - } - } else { + if (cap->cmdchar == TAB && mod_mask == MOD_MASK_CTRL) { + if (!goto_tabpage_lastused()) { clearopbeep(cap->oap); } - if (cap->oap->op_type == OP_NOP - && (move_res & kMarkSwitchedBuf || move_res & kMarkChangedLine) - && (fdo_flags & FDO_MARK) - && old_KeyTyped) { - foldOpenCursor(); + return; + } + + if (cap->cmdchar == 'g') { + fm = get_changelist(curbuf, curwin, (int)cap->count1); + } else { + fm = get_jumplist(curwin, (int)cap->count1); + flags |= KMarkNoContext | kMarkJumpList; + } + // Changelist and jumplist have their own error messages. Therefore avoid + // calling nv_mark_move_to() when not found to avoid incorrect error + // messages. + if (fm != NULL) { + move_res = nv_mark_move_to(cap, flags, fm); + } else if (cap->cmdchar == 'g') { + if (curbuf->b_changelistlen == 0) { + emsg(_("E664: changelist is empty")); + } else if (cap->count1 < 0) { + emsg(_("E662: At start of changelist")); + } else { + emsg(_("E663: At end of changelist")); } + } else { + clearopbeep(cap->oap); + } + if (cap->oap->op_type == OP_NOP + && (move_res & kMarkSwitchedBuf || move_res & kMarkChangedLine) + && (fdo_flags & FDO_MARK) + && old_KeyTyped) { + foldOpenCursor(); } } @@ -5819,7 +5032,7 @@ static void nv_visual(cmdarg_T *cap) showmode(); may_trigger_modechanged(); } - redraw_curbuf_later(INVERTED); // update the inversion + redraw_curbuf_later(UPD_INVERTED); // update the inversion } else { // start Visual mode if (cap->count0 > 0 && resel_VIsual_mode != NUL) { // use previously selected part @@ -5844,10 +5057,12 @@ static void nv_visual(cmdarg_T *cap) VIsual_mode = resel_VIsual_mode; if (VIsual_mode == 'v') { if (resel_VIsual_line_count <= 1) { - validate_virtcol(); + update_curswant_force(); assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX); - curwin->w_curswant = (curwin->w_virtcol - + resel_VIsual_vcol * (int)cap->count0 - 1); + curwin->w_curswant += resel_VIsual_vcol * (int)cap->count0; + if (*p_sel != 'e') { + curwin->w_curswant--; + } } else { curwin->w_curswant = resel_VIsual_vcol; } @@ -5857,15 +5072,14 @@ static void nv_visual(cmdarg_T *cap) curwin->w_curswant = MAXCOL; coladvance(MAXCOL); } else if (VIsual_mode == Ctrl_V) { - validate_virtcol(); + update_curswant_force(); assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX); - curwin->w_curswant = (curwin->w_virtcol - + resel_VIsual_vcol * (int)cap->count0 - 1); + curwin->w_curswant += resel_VIsual_vcol * (int)cap->count0 - 1; coladvance(curwin->w_curswant); } else { curwin->w_set_curswant = true; } - redraw_curbuf_later(INVERTED); // show the inversion + redraw_curbuf_later(UPD_INVERTED); // show the inversion } else { if (!cap->arg) { // start Select mode when 'selectmode' contains "cmd" @@ -5900,7 +5114,7 @@ void start_selection(void) void may_start_select(int c) { VIsual_select = (c == 'o' || (stuff_empty() && typebuf_typed())) - && vim_strchr((char *)p_slm, c) != NULL; + && vim_strchr(p_slm, c) != NULL; } /// Start Visual mode "c". @@ -5931,7 +5145,7 @@ static void n_start_visual_mode(int c) } // Only need to redraw this line, unless still need to redraw an old // Visual area (when 'lazyredraw' is set). - if (curwin->w_redr_type < INVERTED) { + if (curwin->w_redr_type < UPD_INVERTED) { curwin->w_old_cursor_lnum = curwin->w_cursor.lnum; curwin->w_old_visual_lnum = curwin->w_cursor.lnum; } @@ -6018,7 +5232,7 @@ static void nv_gv_cmd(cmdarg_T *cap) may_start_select('c'); } setmouse(); - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); showmode(); } @@ -6071,7 +5285,7 @@ static void nv_g_underscore_cmd(cmdarg_T *cap) return; } - char_u *ptr = get_cursor_line_ptr(); + char *ptr = get_cursor_line_ptr(); // In Visual mode we may end up after the line. if (curwin->w_cursor.col > 0 && ptr[curwin->w_cursor.col] == NUL) { @@ -6109,9 +5323,7 @@ static void nv_g_dollar_cmd(cmdarg_T *cap) coladvance((colnr_T)i); // Make sure we stick in this column. - validate_virtcol(); - curwin->w_curswant = curwin->w_virtcol; - curwin->w_set_curswant = false; + update_curswant_force(); if (curwin->w_cursor.col > 0 && curwin->w_p_wrap) { // Check for landing on a character that got split at // the end of the line. We do not want to advance to @@ -6142,9 +5354,7 @@ static void nv_g_dollar_cmd(cmdarg_T *cap) } // Make sure we stick in this column. - validate_virtcol(); - curwin->w_curswant = curwin->w_virtcol; - curwin->w_set_curswant = false; + update_curswant_force(); } } @@ -6154,7 +5364,7 @@ static void nv_gi_cmd(cmdarg_T *cap) if (curbuf->b_last_insert.mark.lnum != 0) { curwin->w_cursor = curbuf->b_last_insert.mark; check_cursor_lnum(); - int i = (int)STRLEN(get_cursor_line_ptr()); + int i = (int)strlen(get_cursor_line_ptr()); if (curwin->w_cursor.col > (colnr_T)i) { if (virtual_active()) { curwin->w_cursor.coladd += curwin->w_cursor.col - i; @@ -6503,43 +5713,46 @@ static void nv_g_cmd(cmdarg_T *cap) /// Handle "o" and "O" commands. static void n_opencmd(cmdarg_T *cap) { - if (!checkclearopq(cap->oap)) { - if (cap->cmdchar == 'O') { - // Open above the first line of a folded sequence of lines - (void)hasFolding(curwin->w_cursor.lnum, - &curwin->w_cursor.lnum, NULL); - } else { - // Open below the last line of a folded sequence of lines - (void)hasFolding(curwin->w_cursor.lnum, - NULL, &curwin->w_cursor.lnum); - } - if (u_save((linenr_T)(curwin->w_cursor.lnum - - (cap->cmdchar == 'O' ? 1 : 0)), - (linenr_T)(curwin->w_cursor.lnum + - (cap->cmdchar == 'o' ? 1 : 0)) - ) - && open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD, - has_format_option(FO_OPEN_COMS) ? OPENLINE_DO_COM : 0, - 0, NULL)) { - if (win_cursorline_standout(curwin)) { - // force redraw of cursorline - curwin->w_valid &= ~VALID_CROW; - } - invoke_edit(cap, false, cap->cmdchar, true); + if (checkclearopq(cap->oap)) { + return; + } + + if (cap->cmdchar == 'O') { + // Open above the first line of a folded sequence of lines + (void)hasFolding(curwin->w_cursor.lnum, + &curwin->w_cursor.lnum, NULL); + } else { + // Open below the last line of a folded sequence of lines + (void)hasFolding(curwin->w_cursor.lnum, + NULL, &curwin->w_cursor.lnum); + } + if (u_save((linenr_T)(curwin->w_cursor.lnum - + (cap->cmdchar == 'O' ? 1 : 0)), + (linenr_T)(curwin->w_cursor.lnum + + (cap->cmdchar == 'o' ? 1 : 0))) + && open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD, + has_format_option(FO_OPEN_COMS) ? OPENLINE_DO_COM : 0, + 0, NULL)) { + if (win_cursorline_standout(curwin)) { + // force redraw of cursorline + curwin->w_valid &= ~VALID_CROW; } + invoke_edit(cap, false, cap->cmdchar, true); } } /// "." command: redo last change. static void nv_dot(cmdarg_T *cap) { - if (!checkclearopq(cap->oap)) { - // If "restart_edit" is true, the last but one command is repeated - // instead of the last command (inserting text). This is used for - // CTRL-O <.> in insert mode. - if (start_redo(cap->count0, restart_edit != 0 && !arrow_used) == false) { - clearopbeep(cap->oap); - } + if (checkclearopq(cap->oap)) { + return; + } + + // If "restart_edit" is true, the last but one command is repeated + // instead of the last command (inserting text). This is used for + // CTRL-O <.> in insert mode. + if (start_redo(cap->count0, restart_edit != 0 && !arrow_used) == false) { + clearopbeep(cap->oap); } } @@ -6563,10 +5776,12 @@ static void nv_redo_or_register(cmdarg_T *cap) return; } - if (!checkclearopq(cap->oap)) { - u_redo((int)cap->count1); - curwin->w_set_curswant = true; + if (checkclearopq(cap->oap)) { + return; } + + u_redo((int)cap->count1); + curwin->w_set_curswant = true; } /// Handle "U" command. @@ -6578,10 +5793,15 @@ static void nv_Undo(cmdarg_T *cap) cap->cmdchar = 'g'; cap->nchar = 'U'; nv_operator(cap); - } else if (!checkclearopq(cap->oap)) { - u_undoline(); - curwin->w_set_curswant = true; + return; + } + + if (checkclearopq(cap->oap)) { + return; } + + u_undoline(); + curwin->w_set_curswant = true; } /// '~' command: If tilde is not an operator and Visual is off: swap case of a @@ -6837,7 +6057,7 @@ bool unadjust_for_sel(void) mark_mb_adjustpos(curbuf, pp); } else if (pp->lnum > 1) { pp->lnum--; - pp->col = (colnr_T)STRLEN(ml_get(pp->lnum)); + pp->col = (colnr_T)strlen(ml_get(pp->lnum)); return true; } } @@ -6901,7 +6121,7 @@ static void nv_normal(cmdarg_T *cap) } if (VIsual_active) { end_visual_mode(); // stop Visual - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } } else { clearopbeep(cap->oap); @@ -6920,10 +6140,7 @@ static void nv_esc(cmdarg_T *cap) && cap->oap->regname == 0); if (cap->arg) { // true for CTRL-C - if (restart_edit == 0 - && cmdwin_type == 0 - && !VIsual_active - && no_reason) { + if (restart_edit == 0 && cmdwin_type == 0 && !VIsual_active && no_reason) { if (anyBufIsChanged()) { msg(_("Type :qa! and press <Enter> to abandon all changes" " and exit Nvim")); @@ -6955,7 +6172,7 @@ static void nv_esc(cmdarg_T *cap) end_visual_mode(); // stop Visual check_cursor_col(); // make sure cursor is not beyond EOL curwin->w_set_curswant = true; - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } else if (no_reason) { vim_beep(BO_ESC); } @@ -6974,7 +6191,7 @@ void set_cursor_for_append_to_line(void) coladvance(MAXCOL); State = save_State; } else { - curwin->w_cursor.col += (colnr_T)STRLEN(get_cursor_pos_ptr()); + curwin->w_cursor.col += (colnr_T)strlen(get_cursor_pos_ptr()); } } @@ -7067,7 +6284,7 @@ static void nv_object(cmdarg_T *cap) { bool flag; bool include; - char_u *mps_save; + char *mps_save; if (cap->cmdchar == 'i') { include = false; // "ix" = inner object: exclude white space @@ -7076,7 +6293,7 @@ static void nv_object(cmdarg_T *cap) } // Make sure (), [], {} and <> are in 'matchpairs' mps_save = curbuf->b_p_mps; - curbuf->b_p_mps = (char_u *)"(:),{:},[:],<:>"; + curbuf->b_p_mps = "(:),{:},[:],<:>"; switch (cap->nchar) { case 'w': // "aw" = a word @@ -7147,16 +6364,21 @@ static void nv_record(cmdarg_T *cap) cap->cmdchar = 'g'; cap->nchar = 'q'; nv_operator(cap); - } else if (!checkclearop(cap->oap)) { - if (cap->nchar == ':' || cap->nchar == '/' || cap->nchar == '?') { - stuffcharReadbuff(cap->nchar); - stuffcharReadbuff(K_CMDWIN); - } else { - // (stop) recording into a named register, unless executing a - // register. - if (reg_executing == 0 && do_record(cap->nchar) == FAIL) { - clearopbeep(cap->oap); - } + return; + } + + if (checkclearop(cap->oap)) { + return; + } + + if (cap->nchar == ':' || cap->nchar == '/' || cap->nchar == '?') { + stuffcharReadbuff(cap->nchar); + stuffcharReadbuff(K_CMDWIN); + } else { + // (stop) recording into a named register, unless executing a + // register. + if (reg_executing == 0 && do_record(cap->nchar) == FAIL) { + clearopbeep(cap->oap); } } } @@ -7198,24 +6420,29 @@ static void nv_join(cmdarg_T *cap) { if (VIsual_active) { // join the visual lines nv_operator(cap); - } else if (!checkclearop(cap->oap)) { - if (cap->count0 <= 1) { - cap->count0 = 2; // default for join is two lines! - } - if (curwin->w_cursor.lnum + cap->count0 - 1 > - curbuf->b_ml.ml_line_count) { - // can't join when on the last line - if (cap->count0 <= 2) { - clearopbeep(cap->oap); - return; - } - cap->count0 = curbuf->b_ml.ml_line_count - curwin->w_cursor.lnum + 1; - } + return; + } - prep_redo(cap->oap->regname, cap->count0, - NUL, cap->cmdchar, NUL, NUL, cap->nchar); - do_join((size_t)cap->count0, cap->nchar == NUL, true, true, true); + if (checkclearop(cap->oap)) { + return; + } + + if (cap->count0 <= 1) { + cap->count0 = 2; // default for join is two lines! } + if (curwin->w_cursor.lnum + cap->count0 - 1 > + curbuf->b_ml.ml_line_count) { + // can't join when on the last line + if (cap->count0 <= 2) { + clearopbeep(cap->oap); + return; + } + cap->count0 = curbuf->b_ml.ml_line_count - curwin->w_cursor.lnum + 1; + } + + prep_redo(cap->oap->regname, cap->count0, + NUL, cap->cmdchar, NUL, NUL, cap->nchar); + do_join((size_t)cap->count0, cap->nchar == NUL, true, true, true); } /// "P", "gP", "p" and "gp" commands. @@ -7245,117 +6472,121 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent) } else { clearopbeep(cap->oap); } - } else if (bt_prompt(curbuf) && !prompt_curpos_editable()) { - clearopbeep(cap->oap); - } else { - if (fix_indent) { - dir = (cap->cmdchar == ']' && cap->nchar == 'p') - ? FORWARD : BACKWARD; - flags |= PUT_FIXINDENT; - } else { - dir = (cap->cmdchar == 'P' - || ((cap->cmdchar == 'g' || cap->cmdchar == 'z') - && cap->nchar == 'P')) ? BACKWARD : FORWARD; - } - prep_redo_cmd(cap); - if (cap->cmdchar == 'g') { - flags |= PUT_CURSEND; - } else if (cap->cmdchar == 'z') { - flags |= PUT_BLOCK_INNER; - } - - if (VIsual_active) { - // Putting in Visual mode: The put text replaces the selected - // text. First delete the selected text, then put the new text. - // Need to save and restore the registers that the delete - // overwrites if the old contents is being put. - was_visual = true; - regname = cap->oap->regname; - bool keep_registers = cap->cmdchar == 'P'; - // '+' and '*' could be the same selection - bool clipoverwrite = (regname == '+' || regname == '*') && (cb_flags & CB_UNNAMEDMASK); - if (regname == 0 || regname == '"' || clipoverwrite - || ascii_isdigit(regname) || regname == '-') { - // The delete might overwrite the register we want to put, save it first - savereg = copy_register(regname); - } + return; + } - // To place the cursor correctly after a blockwise put, and to leave the - // text in the correct position when putting over a selection with - // 'virtualedit' and past the end of the line, we use the 'c' operator in - // do_put(), which requires the visual selection to still be active. - if (!VIsual_active || VIsual_mode == 'V' || regname != '.') { - // Now delete the selected text. Avoid messages here. - cap->cmdchar = 'd'; - cap->nchar = NUL; - cap->oap->regname = keep_registers ? '_' : NUL; - msg_silent++; - nv_operator(cap); - do_pending_operator(cap, 0, false); - empty = (curbuf->b_ml.ml_flags & ML_EMPTY); - msg_silent--; + if (bt_prompt(curbuf) && !prompt_curpos_editable()) { + clearopbeep(cap->oap); + return; + } - // delete PUT_LINE_BACKWARD; - cap->oap->regname = regname; - } + if (fix_indent) { + dir = (cap->cmdchar == ']' && cap->nchar == 'p') + ? FORWARD : BACKWARD; + flags |= PUT_FIXINDENT; + } else { + dir = (cap->cmdchar == 'P' + || ((cap->cmdchar == 'g' || cap->cmdchar == 'z') + && cap->nchar == 'P')) ? BACKWARD : FORWARD; + } + prep_redo_cmd(cap); + if (cap->cmdchar == 'g') { + flags |= PUT_CURSEND; + } else if (cap->cmdchar == 'z') { + flags |= PUT_BLOCK_INNER; + } - // When deleted a linewise Visual area, put the register as - // lines to avoid it joined with the next line. When deletion was - // charwise, split a line when putting lines. - if (VIsual_mode == 'V') { - flags |= PUT_LINE; - } else if (VIsual_mode == 'v') { - flags |= PUT_LINE_SPLIT; - } - if (VIsual_mode == Ctrl_V && dir == FORWARD) { - flags |= PUT_LINE_FORWARD; - } - dir = BACKWARD; - if ((VIsual_mode != 'V' - && curwin->w_cursor.col < curbuf->b_op_start.col) - || (VIsual_mode == 'V' - && curwin->w_cursor.lnum < curbuf->b_op_start.lnum)) { - // cursor is at the end of the line or end of file, put - // forward. - dir = FORWARD; - } - // May have been reset in do_put(). - VIsual_active = true; + if (VIsual_active) { + // Putting in Visual mode: The put text replaces the selected + // text. First delete the selected text, then put the new text. + // Need to save and restore the registers that the delete + // overwrites if the old contents is being put. + was_visual = true; + regname = cap->oap->regname; + bool keep_registers = cap->cmdchar == 'P'; + // '+' and '*' could be the same selection + bool clipoverwrite = (regname == '+' || regname == '*') && (cb_flags & CB_UNNAMEDMASK); + if (regname == 0 || regname == '"' || clipoverwrite + || ascii_isdigit(regname) || regname == '-') { + // The delete might overwrite the register we want to put, save it first + savereg = copy_register(regname); + } + + // To place the cursor correctly after a blockwise put, and to leave the + // text in the correct position when putting over a selection with + // 'virtualedit' and past the end of the line, we use the 'c' operator in + // do_put(), which requires the visual selection to still be active. + if (!VIsual_active || VIsual_mode == 'V' || regname != '.') { + // Now delete the selected text. Avoid messages here. + cap->cmdchar = 'd'; + cap->nchar = NUL; + cap->oap->regname = keep_registers ? '_' : NUL; + msg_silent++; + nv_operator(cap); + do_pending_operator(cap, 0, false); + empty = (curbuf->b_ml.ml_flags & ML_EMPTY); + msg_silent--; + + // delete PUT_LINE_BACKWARD; + cap->oap->regname = regname; + } + + // When deleted a linewise Visual area, put the register as + // lines to avoid it joined with the next line. When deletion was + // charwise, split a line when putting lines. + if (VIsual_mode == 'V') { + flags |= PUT_LINE; + } else if (VIsual_mode == 'v') { + flags |= PUT_LINE_SPLIT; + } + if (VIsual_mode == Ctrl_V && dir == FORWARD) { + flags |= PUT_LINE_FORWARD; + } + dir = BACKWARD; + if ((VIsual_mode != 'V' + && curwin->w_cursor.col < curbuf->b_op_start.col) + || (VIsual_mode == 'V' + && curwin->w_cursor.lnum < curbuf->b_op_start.lnum)) { + // cursor is at the end of the line or end of file, put + // forward. + dir = FORWARD; } - do_put(cap->oap->regname, savereg, dir, cap->count1, flags); + // May have been reset in do_put(). + VIsual_active = true; + } + do_put(cap->oap->regname, savereg, dir, cap->count1, flags); - // If a register was saved, free it - if (savereg != NULL) { - free_register(savereg); - xfree(savereg); - } + // If a register was saved, free it + if (savereg != NULL) { + free_register(savereg); + xfree(savereg); + } - // What to reselect with "gv"? Selecting the just put text seems to - // be the most useful, since the original text was removed. - if (was_visual) { - curbuf->b_visual.vi_start = curbuf->b_op_start; - curbuf->b_visual.vi_end = curbuf->b_op_end; - // need to adjust cursor position - if (*p_sel == 'e') { - inc(&curbuf->b_visual.vi_end); - } + // What to reselect with "gv"? Selecting the just put text seems to + // be the most useful, since the original text was removed. + if (was_visual) { + curbuf->b_visual.vi_start = curbuf->b_op_start; + curbuf->b_visual.vi_end = curbuf->b_op_end; + // need to adjust cursor position + if (*p_sel == 'e') { + inc(&curbuf->b_visual.vi_end); } + } - // When all lines were selected and deleted do_put() leaves an empty - // line that needs to be deleted now. - if (empty && *ml_get(curbuf->b_ml.ml_line_count) == NUL) { - ml_delete(curbuf->b_ml.ml_line_count, true); - deleted_lines(curbuf->b_ml.ml_line_count + 1, 1); + // When all lines were selected and deleted do_put() leaves an empty + // line that needs to be deleted now. + if (empty && *ml_get(curbuf->b_ml.ml_line_count) == NUL) { + ml_delete(curbuf->b_ml.ml_line_count, true); + deleted_lines(curbuf->b_ml.ml_line_count + 1, 1); - // If the cursor was in that line, move it to the end of the last - // line. - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - coladvance(MAXCOL); - } + // If the cursor was in that line, move it to the end of the last + // line. + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance(MAXCOL); } - auto_format(false, true); } + auto_format(false, true); } /// "o" and "O" commands. @@ -7399,12 +6630,6 @@ static void nv_event(cmdarg_T *cap) } } -/// @return true when 'mousemodel' is set to "popup" or "popup_setpos". -static bool mouse_model_popup(void) -{ - return p_mousem[0] == 'p'; -} - void normal_cmd(oparg_T *oap, bool toplevel) { NormalState s; diff --git a/src/nvim/normal.h b/src/nvim/normal.h index 9bda70eacd..bed1a40b97 100644 --- a/src/nvim/normal.h +++ b/src/nvim/normal.h @@ -3,7 +3,8 @@ #include <stdbool.h> -#include "nvim/buffer_defs.h" // for win_T +#include "nvim/buffer_defs.h" +#include "nvim/macros.h" #include "nvim/pos.h" // Values for find_ident_under_cursor() @@ -22,9 +23,7 @@ typedef enum { kMTUnknown = -1, ///< Unknown or invalid motion type } MotionType; -/* - * Arguments for operators. - */ +// Arguments for operators. typedef struct oparg_S { int op_type; // current pending operator type int regname; // register to use for the operator @@ -53,11 +52,9 @@ typedef struct oparg_S { // block } oparg_T; -/* - * Arguments for Normal mode commands. - */ +// Arguments for Normal mode commands. typedef struct cmdarg_S { - oparg_T *oap; // Operator arguments + oparg_T *oap; // Operator arguments int prechar; // prefix character (optional, always 'g') int cmdchar; // command character int nchar; // next command character (optional) @@ -69,13 +66,19 @@ typedef struct cmdarg_S { long count1; // count before command, default 1 int arg; // extra argument from nv_cmds[] int retval; // return: CA_* values - char_u *searchbuf; // return: pointer to search pattern or NULL + char *searchbuf; // return: pointer to search pattern or NULL } cmdarg_T; // values for retval: #define CA_COMMAND_BUSY 1 // skip restarting edit() once #define CA_NO_ADJ_OP_END 2 // don't adjust operator end +// columns needed by shown command +#define SHOWCMD_COLS 10 +// 'showcmd' buffer shared between normal.c and statusline.c +#define SHOWCMD_BUFLEN (SHOWCMD_COLS + 1 + 30) +EXTERN char showcmd_buf[SHOWCMD_BUFLEN]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "normal.h.generated.h" #endif diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 3be4536f16..435ca106ab 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1,18 +1,22 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * ops.c: implementation of various operators: op_shift, op_delete, op_tilde, - * op_change, op_yank, do_put, do_join - */ +// ops.c: implementation of various operators: op_shift, op_delete, op_tilde, +// op_change, op_yank, do_put, do_join #include <assert.h> +#include <ctype.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include "nvim/api/private/defs.h" #include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" @@ -21,17 +25,19 @@ #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" -#include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/fold.h" +#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/indent.h" #include "nvim/indent_c.h" -#include "nvim/lib/kvec.h" -#include "nvim/log.h" +#include "nvim/keycodes.h" #include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -45,12 +51,14 @@ #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/time.h" -#include "nvim/path.h" #include "nvim/plines.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/terminal.h" +#include "nvim/textformat.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -66,25 +74,23 @@ static bool clipboard_delay_update = false; // delay clipboard update static bool clipboard_needs_update = false; // clipboard was updated static bool clipboard_didwarn = false; -/* - * structure used by block_prep, op_delete and op_yank for blockwise operators - * also op_change, op_shift, op_insert, op_replace - AKelly - */ +// structure used by block_prep, op_delete and op_yank for blockwise operators +// also op_change, op_shift, op_insert, op_replace - AKelly struct block_def { int startspaces; // 'extra' cols before first char int endspaces; // 'extra' cols after last char int textlen; // chars in block - char_u *textstart; // pointer to 1st char (partially) in block + char *textstart; // pointer to 1st char (partially) in block colnr_T textcol; // index of chars (partially) in block colnr_T start_vcol; // start col of 1st char wholly inside block colnr_T end_vcol; // start col of 1st char wholly after block - int is_short; // TRUE if line is too short to fit in block - int is_MAX; // TRUE if curswant==MAXCOL when starting - int is_oneChar; // TRUE if block within one character + int is_short; // true if line is too short to fit in block + int is_MAX; // true if curswant==MAXCOL when starting + int is_oneChar; // true if block within one character int pre_whitesp; // screen cols of ws before block int pre_whitesp_c; // chars of ws before block colnr_T end_char_vcols; // number of vcols of post-block char - colnr_T start_char_vcols; // number of vcols of pre-block char + colnr_T start_char_vcols; // number of vcols of pre-block char }; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -95,13 +101,10 @@ struct block_def { #define OPF_LINES 1 // operator always works on lines #define OPF_CHANGE 2 // operator changes text -/* - * The names of operators. - * IMPORTANT: Index must correspond with defines in vim.h!!! - * The third field indicates whether the operator always works on lines. - */ -static char opchars[][3] = -{ +// The names of operators. +// IMPORTANT: Index must correspond with defines in vim.h!!! +// The third field indicates whether the operator always works on lines. +static char opchars[][3] = { { NUL, NUL, 0 }, // OP_NOP { 'd', NUL, OPF_CHANGE }, // OP_DELETE { 'y', NUL, 0 }, // OP_YANK @@ -181,13 +184,13 @@ int get_op_type(int char1, int char2) return i; } -/// @return TRUE if operator "op" always works on whole lines. +/// @return true if operator "op" always works on whole lines. int op_on_lines(int op) { return opchars[op][2] & OPF_LINES; } -/// @return TRUE if operator "op" changes text. +/// @return true if operator "op" changes text. int op_is_change(int op) { return opchars[op][2] & OPF_CHANGE; @@ -224,7 +227,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount) } for (i = oap->line_count - 1; i >= 0; i--) { - first_char = *get_cursor_line_ptr(); + first_char = (uint8_t)(*get_cursor_line_ptr()); if (first_char == NUL) { // empty line curwin->w_cursor.col = 0; } else if (oap->motion_type == kMTBlockWise) { @@ -234,7 +237,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount) // isn't set or 'cindent' isn't set or '#' isn't in 'cino'. shift_line(oap->op_type == OP_LSHIFT, p_sr, amount, false); } - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; } if (oap->motion_type == kMTBlockWise) { @@ -244,7 +247,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount) curwin->w_cursor.lnum = oap->start.lnum; beginline(BL_SOL | BL_FIX); // shift_line() may have set cursor.col } else { - --curwin->w_cursor.lnum; // put cursor on last line, for ":>" + curwin->w_cursor.lnum--; // put cursor on last line, for ":>" } // The cursor line is not in a closed fold foldOpenCursor(); @@ -261,17 +264,17 @@ void op_shift(oparg_T *oap, int curs_top, int amount) "%" PRId64 " line %sed %d times", amount); char *msg_line_plural = NGETTEXT("%" PRId64 " lines %sed %d time", "%" PRId64 " lines %sed %d times", amount); - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, NGETTEXT(msg_line_single, msg_line_plural, oap->line_count), (int64_t)oap->line_count, op, amount); - msg_attr_keep((char *)IObuff, 0, true, false); + msg_attr_keep(IObuff, 0, true, false); } if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { // Set "'[" and "']" marks. curbuf->b_op_start = oap->start; curbuf->b_op_end.lnum = oap->end.lnum; - curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); + curbuf->b_op_end.col = (colnr_T)strlen(ml_get(oap->end.lnum)); if (curbuf->b_op_end.col > 0) { curbuf->b_op_end.col--; } @@ -332,7 +335,7 @@ static void shift_block(oparg_T *oap, int amount) { const bool left = (oap->op_type == OP_LSHIFT); const int oldstate = State; - char_u *newp; + char *newp; const int oldcol = curwin->w_cursor.col; const int sw_val = (int)get_sw_value_indent(curbuf); const int ts_val = (int)curbuf->b_p_ts; @@ -355,34 +358,40 @@ static void shift_block(oparg_T *oap, int amount) return; // multiplication overflow } - char_u *const oldp = get_cursor_line_ptr(); + char *const oldp = get_cursor_line_ptr(); int startcol, oldlen, newlen; if (!left) { - /* - * 1. Get start vcol - * 2. Total ws vcols - * 3. Divvy into TABs & spp - * 4. Construct new string - */ + // 1. Get start vcol + // 2. Total ws vcols + // 3. Divvy into TABs & spp + // 4. Construct new string total += bd.pre_whitesp; // all virtual WS up to & incl a split TAB colnr_T ws_vcol = bd.start_vcol - bd.pre_whitesp; - char_u *old_textstart = bd.textstart; + char *old_textstart = bd.textstart; if (bd.startspaces) { - if (utfc_ptr2len((char *)bd.textstart) == 1) { + if (utfc_ptr2len(bd.textstart) == 1) { bd.textstart++; } else { ws_vcol = 0; bd.startspaces = 0; } } - for (; ascii_iswhite(*bd.textstart);) { - // TODO(fmoralesc): is passing bd.textstart for start of the line OK? - incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, bd.start_vcol); + + // 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); total += incr; - bd.start_vcol += incr; + cts.cts_vcol += incr; } + bd.textstart = cts.cts_ptr; + bd.start_vcol = cts.cts_vcol; + clear_chartabsize_arg(&cts); + // OK, now total=all the VWS reqd, and textstart points at the 1st // non-ws char in the block. if (!curbuf->b_p_et) { @@ -394,10 +403,10 @@ static void shift_block(oparg_T *oap, int amount) // if we're splitting a TAB, allow for it int col_pre = bd.pre_whitesp_c - (bd.startspaces != 0); bd.textcol -= col_pre; - const int len = (int)STRLEN(bd.textstart) + 1; + const int len = (int)strlen(bd.textstart) + 1; int col = bd.textcol + i + j + len; assert(col >= 0); - newp = (char_u *)xmalloc((size_t)col); + newp = xmalloc((size_t)col); memset(newp, NUL, (size_t)col); memmove(newp, oldp, (size_t)bd.textcol); startcol = bd.textcol; @@ -410,22 +419,20 @@ static void shift_block(oparg_T *oap, int amount) } else { // left colnr_T destination_col; // column to which text in block will // be shifted - char_u *verbatim_copy_end; // end of the part of the line which is + char *verbatim_copy_end; // end of the part of the line which is // copied verbatim colnr_T verbatim_copy_width; // the (displayed) width of this part // of line size_t fill; // nr of spaces that replace a TAB size_t new_line_len; // the length of the line after the // block shift - char_u *non_white = bd.textstart; + char *non_white = bd.textstart; - /* - * Firstly, let's find the first non-whitespace character that is - * displayed after the block's start column and the character's column - * number. Also, let's calculate the width of all the whitespace - * characters that are displayed in the block and precede the searched - * non-whitespace character. - */ + // Firstly, let's find the first non-whitespace character that is + // displayed after the block's start column and the character's column + // number. Also, let's calculate the width of all the whitespace + // characters that are displayed in the block and precede the searched + // non-whitespace character. // If "bd.startspaces" is set, "bd.textstart" points to the character, // the part of which is displayed at the block's beginning. Let's start @@ -437,10 +444,16 @@ 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; - while (ascii_iswhite(*non_white)) { - incr = lbr_chartabsize_adv(bd.textstart, &non_white, non_white_col); - non_white_col += incr; + 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; } + 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. @@ -461,17 +474,19 @@ static void shift_block(oparg_T *oap, int amount) if (bd.startspaces) { verbatim_copy_width -= bd.start_char_vcols; } - while (verbatim_copy_width < destination_col) { - char_u *line = verbatim_copy_end; - - // TODO: is passing verbatim_copy_end for start of the line OK? - incr = lbr_chartabsize(line, verbatim_copy_end, verbatim_copy_width); - if (verbatim_copy_width + incr > destination_col) { + 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) { break; } - verbatim_copy_width += incr; - MB_PTR_ADV(verbatim_copy_end); + cts.cts_vcol += incr; + MB_PTR_ADV(cts.cts_ptr); } + verbatim_copy_width = cts.cts_vcol; + verbatim_copy_end = cts.cts_ptr; + clear_chartabsize_arg(&cts); // 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 @@ -485,9 +500,9 @@ static void shift_block(oparg_T *oap, int amount) // - the beginning of the original line up to "verbatim_copy_end", // - "fill" number of spaces, // - the rest of the line, pointed to by non_white. - new_line_len = verbatim_diff + fill + STRLEN(non_white) + 1; + new_line_len = verbatim_diff + fill + strlen(non_white) + 1; - newp = (char_u *)xmalloc(new_line_len); + newp = xmalloc(new_line_len); startcol = (int)verbatim_diff; oldlen = bd.textcol + (int)(non_white - bd.textstart) - (int)verbatim_diff; newlen = (int)fill; @@ -496,7 +511,7 @@ static void shift_block(oparg_T *oap, int amount) STRMOVE(newp + verbatim_diff + fill, non_white); } // replace the line - ml_replace(curwin->w_cursor.lnum, (char *)newp, false); + ml_replace(curwin->w_cursor.lnum, newp, false); changed_bytes(curwin->w_cursor.lnum, bd.textcol); extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum - 1, startcol, oldlen, newlen, @@ -508,14 +523,14 @@ static void shift_block(oparg_T *oap, int amount) /// Insert string "s" (b_insert ? before : after) block :AKelly /// Caller must prepare for undo. -static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def *bdp) +static void block_insert(oparg_T *oap, char *s, int b_insert, struct block_def *bdp) { int ts_val; int count = 0; // extra spaces to replace a cut TAB int spaces = 0; // non-zero if cutting a TAB colnr_T offset; // pointer along new line - size_t s_len = STRLEN(s); - char_u *newp, *oldp; // new, old lines + size_t s_len = strlen(s); + char *newp, *oldp; // new, old lines linenr_T lnum; // loop var int oldstate = State; State = MODE_INSERT; // don't want MODE_REPLACE for State @@ -563,7 +578,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def assert(count >= 0); // Make sure the allocated size matches what is actually copied below. - newp = xmalloc(STRLEN(oldp) + (size_t)spaces + s_len + newp = xmalloc(strlen(oldp) + (size_t)spaces + s_len + (spaces > 0 && !bdp->is_short ? (size_t)ts_val - (size_t)spaces : 0) + (size_t)count + 1); @@ -600,7 +615,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def } STRMOVE(newp + offset, oldp); - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); extmark_splice_cols(curbuf, (int)lnum - 1, startcol, skipped, offset - startcol, kExtmarkUndo); @@ -621,7 +636,7 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def void op_reindent(oparg_T *oap, Indenter how) { long i = 0; - char_u *l; + char *l; int amount; linenr_T first_changed = 0; linenr_T last_changed = 0; @@ -651,7 +666,7 @@ void op_reindent(oparg_T *oap, Indenter how) // indented, unless there is only one line. if (i != oap->line_count - 1 || oap->line_count == 1 || how != get_lisp_indent) { - l = (char_u *)skipwhite((char *)get_cursor_line_ptr()); + l = skipwhite(get_cursor_line_ptr()); if (*l == NUL) { // empty or blank line amount = 0; } else { @@ -674,15 +689,15 @@ void op_reindent(oparg_T *oap, Indenter how) curwin->w_cursor.lnum = start_lnum; beginline(BL_SOL | BL_FIX); - /* Mark changed lines so that they will be redrawn. When Visual - * highlighting was present, need to continue until the last line. When - * there is no change still need to remove the Visual highlighting. */ + // Mark changed lines so that they will be redrawn. When Visual + // highlighting was present, need to continue until the last line. When + // there is no change still need to remove the Visual highlighting. if (last_changed != 0) { changed_lines(first_changed, 0, oap->is_VIsual ? start_lnum + (linenr_T)oap->line_count : last_changed + 1, 0L, true); } else if (oap->is_VIsual) { - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } if (oap->line_count > p_report) { @@ -698,17 +713,15 @@ void op_reindent(oparg_T *oap, Indenter how) } } -/* - * Keep the last expression line here, for repeating. - */ -static char_u *expr_line = NULL; +// Keep the last expression line here, for repeating. +static char *expr_line = NULL; /// Get an expression for the "\"=expr1" or "CTRL-R =expr1" /// /// @return '=' when OK, NUL otherwise. int get_expr_register(void) { - char_u *new_line; + char *new_line; new_line = getcmdline('=', 0L, 0, true); if (new_line == NULL) { @@ -724,7 +737,7 @@ int get_expr_register(void) /// Set the expression for the '=' register. /// Argument must be an allocated string. -void set_expr_line(char_u *new_line) +void set_expr_line(char *new_line) { xfree(expr_line); expr_line = new_line; @@ -733,40 +746,40 @@ void set_expr_line(char_u *new_line) /// Get the result of the '=' register expression. /// /// @return a pointer to allocated memory, or NULL for failure. -char_u *get_expr_line(void) +char *get_expr_line(void) { - char_u *expr_copy; - char_u *rv; + char *expr_copy; + char *rv; static int nested = 0; if (expr_line == NULL) { return NULL; } - /* Make a copy of the expression, because evaluating it may cause it to be - * changed. */ - expr_copy = vim_strsave(expr_line); + // Make a copy of the expression, because evaluating it may cause it to be + // changed. + expr_copy = xstrdup(expr_line); - /* When we are invoked recursively limit the evaluation to 10 levels. - * Then return the string as-is. */ + // When we are invoked recursively limit the evaluation to 10 levels. + // Then return the string as-is. if (nested >= 10) { return expr_copy; } nested++; - rv = (char_u *)eval_to_string((char *)expr_copy, NULL, true); + rv = eval_to_string(expr_copy, NULL, true); nested--; xfree(expr_copy); return rv; } /// Get the '=' register expression itself, without evaluating it. -char_u *get_expr_line_src(void) +char *get_expr_line_src(void) { if (expr_line == NULL) { return NULL; } - return vim_strsave(expr_line); + return xstrdup(expr_line); } /// @return whether `regname` is a valid name of a yank register. @@ -889,9 +902,8 @@ bool yank_register_mline(int regname) /// @return FAIL for failure, OK otherwise. int do_record(int c) { - char_u *p; + char *p; static int regname; - static bool changed_cmdheight = false; yankreg_T *old_y_previous; int retval; @@ -902,18 +914,11 @@ int do_record(int c) retval = FAIL; } else { reg_recording = c; + // TODO(bfredl): showmode based messaging is currently missing with cmdheight=0 showmode(); regname = c; retval = OK; - if (!ui_has_messages()) { - // Enable macro indicator temporarily - set_option_value("ch", 1L, NULL, 0); - update_screen(VALID); - - changed_cmdheight = true; - } - apply_autocmds(EVENT_RECORDINGENTER, NULL, NULL, false, curbuf); } } else { // stop recording @@ -922,10 +927,10 @@ int do_record(int c) dict_T *dict = get_v_event(&save_v_event); // The recorded text contents. - p = get_recorded(); + p = (char *)get_recorded(); if (p != NULL) { // Remove escaping for K_SPECIAL in multi-byte chars. - vim_unescape_ks(p); + vim_unescape_ks((char_u *)p); (void)tv_dict_add_str(dict, S_LEN("regcontents"), (const char *)p); } @@ -943,7 +948,7 @@ int do_record(int c) restore_v_event(dict, &save_v_event); reg_recorded = reg_recording; reg_recording = 0; - if (ui_has(kUIMessages)) { + if (p_ch == 0 || ui_has(kUIMessages)) { showmode(); } else { msg(""); @@ -959,12 +964,6 @@ int do_record(int c) y_previous = old_y_previous; } - - if (changed_cmdheight) { - // Restore cmdheight - set_option_value("ch", 0L, NULL, 0); - redraw_all_later(CLEAR); - } } return retval; } @@ -983,7 +982,7 @@ static void set_yreg_additional_data(yankreg_T *reg, dict_T *additional_data) /// uppercase). "p" must have been allocated. /// /// @return FAIL for failure, OK otherwise -static int stuff_yank(int regname, char_u *p) +static int stuff_yank(int regname, char *p) { // check for read-only register if (regname != 0 && !valid_yank_reg(regname, true)) { @@ -997,18 +996,18 @@ static int stuff_yank(int regname, char_u *p) yankreg_T *reg = get_yank_register(regname, YREG_YANK); if (is_append_register(regname) && reg->y_array != NULL) { char **pp = &(reg->y_array[reg->y_size - 1]); - char_u *lp = xmalloc(STRLEN(*pp) + STRLEN(p) + 1); + char *lp = xmalloc(strlen(*pp) + strlen(p) + 1); STRCPY(lp, *pp); // TODO(philix): use xstpcpy() in stuff_yank() STRCAT(lp, p); xfree(p); xfree(*pp); - *pp = (char *)lp; + *pp = lp; } else { free_register(reg); set_yreg_additional_data(reg, NULL); - reg->y_array = xmalloc(sizeof(char_u *)); - reg->y_array[0] = (char *)p; + reg->y_array = xmalloc(sizeof(char *)); + reg->y_array[0] = p; reg->y_size = 1; reg->y_type = kMTCharWise; } @@ -1029,22 +1028,22 @@ static int execreg_lastc = NUL; /// with a \. Lines that start with a comment "\ character are ignored. /// @returns the concatenated line. The index of the line that should be /// processed next is returned in idx. -static char_u *execreg_line_continuation(char **lines, size_t *idx) +static char *execreg_line_continuation(char **lines, size_t *idx) { size_t i = *idx; assert(i > 0); const size_t cmd_end = i; garray_T ga; - ga_init(&ga, (int)sizeof(char_u), 400); + ga_init(&ga, (int)sizeof(char), 400); - char_u *p; + char *p; // search backwards to find the first line of this command. // Any line not starting with \ or "\ is the start of the // command. while (--i > 0) { - p = (char_u *)skipwhite(lines[i]); + p = skipwhite(lines[i]); if (*p != '\\' && (p[0] != '"' || p[1] != '\\' || p[2] != ' ')) { break; } @@ -1054,18 +1053,18 @@ static char_u *execreg_line_continuation(char **lines, size_t *idx) // join all the lines ga_concat(&ga, lines[cmd_start]); for (size_t j = cmd_start + 1; j <= cmd_end; j++) { - p = (char_u *)skipwhite(lines[j]); + p = skipwhite(lines[j]); if (*p == '\\') { // Adjust the growsize to the current length to // speed up concatenating many lines. if (ga.ga_len > 400) { ga_set_growsize(&ga, MIN(ga.ga_len, 8000)); } - ga_concat(&ga, (char *)(p + 1)); + ga_concat(&ga, p + 1); } } ga_append(&ga, NUL); - char_u *str = vim_strsave(ga.ga_data); + char *str = xstrdup(ga.ga_data); ga_clear(&ga); *idx = i; @@ -1081,7 +1080,7 @@ static char_u *execreg_line_continuation(char **lines, size_t *idx) /// @return FAIL for failure, OK otherwise int do_execreg(int regname, int colon, int addcr, int silent) { - char_u *p; + char *p; int retval = OK; if (regname == '@') { // repeat previous one @@ -1111,14 +1110,14 @@ int do_execreg(int regname, int colon, int addcr, int silent) XFREE_CLEAR(new_last_cmdline); // Escape all control characters with a CTRL-V p = vim_strsave_escaped_ext(last_cmdline, - (char_u *)"\001\002\003\004\005\006\007" + "\001\002\003\004\005\006\007" "\010\011\012\013\014\015\016\017" "\020\021\022\023\024\025\026\027" "\030\031\032\033\034\035\036\037", Ctrl_V, false); // When in Visual mode "'<,'>" will be prepended to the command. // Remove it when it's already there. - if (VIsual_active && STRNCMP(p, "'<,'>", 5) == 0) { + if (VIsual_active && strncmp(p, "'<,'>", 5) == 0) { retval = put_in_typebuf(p + 5, true, true, silent); } else { retval = put_in_typebuf(p, true, true, silent); @@ -1148,9 +1147,7 @@ int do_execreg(int regname, int colon, int addcr, int silent) // Disallow remapping for ":@r". int remap = colon ? REMAP_NONE : REMAP_YES; - /* - * Insert lines into typeahead buffer, from last one to first one. - */ + // Insert lines into typeahead buffer, from last one to first one. put_reedit_in_typebuf(silent); char *escaped; for (size_t i = reg->y_size; i-- > 0;) { // from y_size - 1 to 0 included @@ -1162,16 +1159,16 @@ int do_execreg(int regname, int colon, int addcr, int silent) } // Handle line-continuation for :@<register> - char_u *str = (char_u *)reg->y_array[i]; + char *str = reg->y_array[i]; bool free_str = false; if (colon && i > 0) { - p = (char_u *)skipwhite((char *)str); + p = skipwhite(str); if (*p == '\\' || (p[0] == '"' && p[1] == '\\' && p[2] == ' ')) { str = execreg_line_continuation(reg->y_array, &i); free_str = true; } } - escaped = vim_strsave_escape_ks((char *)str); + escaped = vim_strsave_escape_ks(str); if (free_str) { xfree(str); } @@ -1196,18 +1193,20 @@ static void put_reedit_in_typebuf(int silent) { char_u buf[3]; - if (restart_edit != NUL) { - if (restart_edit == 'V') { - buf[0] = 'g'; - buf[1] = 'R'; - buf[2] = NUL; - } else { - buf[0] = (char_u)(restart_edit == 'I' ? 'i' : restart_edit); - buf[1] = NUL; - } - if (ins_typebuf((char *)buf, REMAP_NONE, 0, true, silent) == OK) { - restart_edit = NUL; - } + if (restart_edit == NUL) { + return; + } + + if (restart_edit == 'V') { + buf[0] = 'g'; + buf[1] = 'R'; + buf[2] = NUL; + } else { + buf[0] = (char_u)(restart_edit == 'I' ? 'i' : restart_edit); + buf[1] = NUL; + } + if (ins_typebuf((char *)buf, REMAP_NONE, 0, true, silent) == OK) { + restart_edit = NUL; } } @@ -1217,7 +1216,7 @@ static void put_reedit_in_typebuf(int silent) /// @param esc when true then it is to be taken literally: Escape K_SPECIAL /// characters and no remapping. /// @param colon add ':' before the line -static int put_in_typebuf(char_u *s, bool esc, bool colon, int silent) +static int put_in_typebuf(char *s, bool esc, bool colon, int silent) { int retval = OK; @@ -1229,9 +1228,9 @@ static int put_in_typebuf(char_u *s, bool esc, bool colon, int silent) char *p; if (esc) { - p = vim_strsave_escape_ks((char *)s); + p = vim_strsave_escape_ks(s); } else { - p = (char *)s; + p = s; } if (p == NULL) { retval = FAIL; @@ -1260,11 +1259,9 @@ int insert_reg(int regname, bool literally_arg) bool allocated; const bool literally = literally_arg || is_literal_register(regname); - /* - * It is possible to get into an endless loop by having CTRL-R a in - * register a and then, in insert mode, doing CTRL-R a. - * If you hit CTRL-C, the loop will be broken here. - */ + // It is possible to get into an endless loop by having CTRL-R a in + // register a and then, in insert mode, doing CTRL-R a. + // If you hit CTRL-C, the loop will be broken here. os_breakcheck(); if (got_int) { return FAIL; @@ -1311,34 +1308,6 @@ int insert_reg(int regname, bool literally_arg) return retval; } -/// Stuff a string into the typeahead buffer, such that edit() will insert it -/// literally ("literally" true) or interpret is as typed characters. -static void stuffescaped(const char *arg, bool literally) -{ - while (*arg != NUL) { - // Stuff a sequence of normal ASCII characters, that's fast. Also - // stuff K_SPECIAL to get the effect of a special key when "literally" - // is true. - const char *const start = arg; - while ((*arg >= ' ' && *arg < DEL) || ((uint8_t)(*arg) == K_SPECIAL - && !literally)) { - arg++; - } - if (arg > start) { - stuffReadbuffLen(start, (arg - start)); - } - - // stuff a single special character - if (*arg != NUL) { - const int c = mb_cptr2char_adv((const char_u **)&arg); - if (literally && ((c < ' ' && c != TAB) || c == DEL)) { - stuffcharReadbuff(Ctrl_V); - } - stuffcharReadbuff(c); - } - } -} - /// If "regname" is a special register, return true and store a pointer to its /// value in "argp". /// @@ -1365,7 +1334,7 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg) return true; case '=': // result of expression - *argp = (char *)get_expr_line(); + *argp = get_expr_line(); *allocated = true; return true; @@ -1373,18 +1342,18 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg) if (last_cmdline == NULL && errmsg) { emsg(_(e_nolastcmd)); } - *argp = (char *)last_cmdline; + *argp = last_cmdline; return true; case '/': // last search-pattern if (last_search_pat() == NULL && errmsg) { emsg(_(e_noprevre)); } - *argp = (char *)last_search_pat(); + *argp = last_search_pat(); return true; case '.': // last inserted text - *argp = (char *)get_last_insert_save(); + *argp = get_last_insert_save(); *allocated = true; if (*argp == NULL && errmsg) { emsg(_(e_noinstext)); @@ -1396,9 +1365,8 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg) if (!errmsg) { return false; } - *argp - = (char *)file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0), - 1L, NULL); + *argp = file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0), + 1L, NULL); *allocated = true; return true; @@ -1419,7 +1387,7 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg) return false; } - *argp = (char *)ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum, false); + *argp = ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum, false); return true; case '_': // black hole: always empty @@ -1451,15 +1419,15 @@ bool cmdline_paste_reg(int regname, bool literally_arg, bool remcr) } for (size_t i = 0; i < reg->y_size; i++) { - cmdline_paste_str((char_u *)reg->y_array[i], literally); + cmdline_paste_str(reg->y_array[i], literally); // Insert ^M between lines, unless `remcr` is true. if (i < reg->y_size - 1 && !remcr) { - cmdline_paste_str((char_u *)"\r", literally); + cmdline_paste_str("\r", literally); } - /* Check for CTRL-C, in case someone tries to paste a few thousand - * lines and gets bored. */ + // Check for CTRL-C, in case someone tries to paste a few thousand + // lines and gets bored. os_breakcheck(); if (got_int) { return FAIL; @@ -1488,8 +1456,8 @@ int op_delete(oparg_T *oap) { int n; linenr_T lnum; - char_u *ptr; - char_u *newp, *oldp; + char *ptr; + char *newp, *oldp; struct block_def bd = { 0 }; linenr_T old_lcount = curbuf->b_ml.ml_line_count; @@ -1514,11 +1482,9 @@ int op_delete(oparg_T *oap) mb_adjust_opend(oap); - /* - * Imitate the strange Vi behaviour: If the delete spans more than one - * line and motion_type == kMTCharWise and the result is a blank line, make the - * delete linewise. Don't do this for the change command or Visual mode. - */ + // Imitate the strange Vi behaviour: If the delete spans more than one + // line and motion_type == kMTCharWise and the result is a blank line, make the + // delete linewise. Don't do this for the change command or Visual mode. if (oap->motion_type == kMTCharWise && !oap->is_VIsual && oap->line_count > 1 @@ -1528,16 +1494,14 @@ int op_delete(oparg_T *oap) if (*ptr != NUL) { ptr += oap->inclusive; } - ptr = (char_u *)skipwhite((char *)ptr); + ptr = skipwhite(ptr); if (*ptr == NUL && inindent(0)) { oap->motion_type = kMTLineWise; } } - /* - * Check for trying to delete (e.g. "D") in an empty line. - * Note: For the change operator it is ok. - */ + // Check for trying to delete (e.g. "D") in an empty line. + // Note: For the change operator it is ok. if (oap->motion_type != kMTLineWise && oap->line_count == 1 && oap->op_type == OP_DELETE @@ -1555,11 +1519,9 @@ int op_delete(oparg_T *oap) return OK; } - /* - * Do a yank of whatever we're about to delete. - * If a yank register was specified, put the deleted text into that - * register. For the black hole register '_' don't yank anything. - */ + // Do a yank of whatever we're about to delete. + // If a yank register was specified, put the deleted text into that + // register. For the black hole register '_' don't yank anything. if (oap->regname != '_') { yankreg_T *reg = NULL; int did_yank = false; @@ -1577,6 +1539,7 @@ int op_delete(oparg_T *oap) // Put deleted text into register 1 and shift number registers if the // delete contains a line break, or when using a specific operator (Vi // compatible) + if (oap->motion_type == kMTLineWise || oap->line_count > 1 || oap->use_reg_one) { shift_delete_registers(is_append_register(oap->regname)); reg = &y_regs[1]; @@ -1602,9 +1565,7 @@ int op_delete(oparg_T *oap) } } - /* - * block mode delete - */ + // block mode delete if (oap->motion_type == kMTBlockWise) { if (u_save((linenr_T)(oap->start.lnum - 1), (linenr_T)(oap->end.lnum + 1)) == FAIL) { @@ -1628,7 +1589,7 @@ int op_delete(oparg_T *oap) // Thus the number of characters may increase! n = bd.textlen - bd.startspaces - bd.endspaces; oldp = ml_get(lnum); - newp = (char_u *)xmalloc(STRLEN(oldp) - (size_t)n + 1); + newp = xmalloc(strlen(oldp) - (size_t)n + 1); // copy up to deleted part memmove(newp, oldp, (size_t)bd.textcol); // insert spaces @@ -1638,7 +1599,7 @@ int op_delete(oparg_T *oap) oldp += bd.textcol + bd.textlen; STRMOVE(newp + bd.textcol + bd.startspaces + bd.endspaces, oldp); // replace the line - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); extmark_splice_cols(curbuf, (int)lnum - 1, bd.textcol, bd.textlen, bd.startspaces + bd.endspaces, @@ -1672,7 +1633,7 @@ int op_delete(oparg_T *oap) beginline(0); // cursor in column 0 } - int old_len = (int)STRLEN(ml_get(curwin->w_cursor.lnum)); + int old_len = (int)strlen(ml_get(curwin->w_cursor.lnum)); truncate_line(false); // delete the rest of the line extmark_splice_cols(curbuf, @@ -1745,8 +1706,8 @@ int op_delete(oparg_T *oap) if (virtual_op) { // fix up things for virtualedit-delete: // break the tabs which are going to get in our way - char_u *curline = get_cursor_line_ptr(); - int len = (int)STRLEN(curline); + char *curline = get_cursor_line_ptr(); + int len = (int)strlen(curline); if (oap->end.coladd != 0 && (int)oap->end.col >= len - 1 @@ -1825,17 +1786,19 @@ setmarks: /// Used for deletion. static void mb_adjust_opend(oparg_T *oap) { - if (oap->inclusive) { - char *p = (char *)ml_get(oap->end.lnum); - oap->end.col += utf_cp_tail_off(p, p + oap->end.col); + if (!oap->inclusive) { + return; } + + char *p = ml_get(oap->end.lnum); + oap->end.col += utf_cp_tail_off(p, p + oap->end.col); } /// Put character 'c' at position 'lp' static inline void pbyte(pos_T lp, int c) { assert(c <= UCHAR_MAX); - *(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c; + *(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char)c; if (!curbuf_splice_pending) { extmark_splice_cols(curbuf, (int)lp.lnum - 1, lp.col, 1, 1, kExtmarkUndo); } @@ -1859,10 +1822,10 @@ static int op_replace(oparg_T *oap, int c) { int n, numc; int num_chars; - char_u *newp, *oldp; + char *newp, *oldp; colnr_T oldlen; struct block_def bd; - char_u *after_p = NULL; + char *after_p = NULL; int had_ctrl_v_cr = false; if ((curbuf->b_ml.ml_flags & ML_EMPTY) || oap->empty) { @@ -1883,9 +1846,7 @@ static int op_replace(oparg_T *oap, int c) return FAIL; } - /* - * block mode replace - */ + // block mode replace if (oap->motion_type == kMTBlockWise) { bd.is_MAX = (curwin->w_curswant == MAXCOL); for (; curwin->w_cursor.lnum <= oap->end.lnum; curwin->w_cursor.lnum++) { @@ -1937,7 +1898,7 @@ static int op_replace(oparg_T *oap, int c) numc *= utf_char2len(c); oldp = get_cursor_line_ptr(); - oldlen = (int)STRLEN(oldp); + oldlen = (int)strlen(oldp); size_t newp_size = (size_t)bd.textcol + (size_t)bd.startspaces; if (had_ctrl_v_cr || (c != '\r' && c != '\n')) { @@ -1963,7 +1924,7 @@ static int op_replace(oparg_T *oap, int c) // strlen(newp) at this point int newp_len = bd.textcol + bd.startspaces; while (--num_chars >= 0) { - newp_len += utf_char2bytes(c, (char *)newp + newp_len); + newp_len += utf_char2bytes(c, newp + newp_len); } if (!bd.is_short) { // insert post-spaces @@ -1976,16 +1937,16 @@ static int op_replace(oparg_T *oap, int c) } else { // Replacing with \r or \n means splitting the line. after_p_len = (size_t)col; - after_p = (char_u *)xmalloc(after_p_len); + after_p = xmalloc(after_p_len); memmove(after_p, oldp, after_p_len); newrows = 1; } // replace the line - ml_replace(curwin->w_cursor.lnum, (char *)newp, false); + ml_replace(curwin->w_cursor.lnum, newp, false); curbuf_splice_pending++; linenr_T baselnum = curwin->w_cursor.lnum; if (after_p != NULL) { - ml_append(curwin->w_cursor.lnum++, (char *)after_p, (int)after_p_len, false); + ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false); appended_lines_mark(curwin->w_cursor.lnum, 1L); oap->end.lnum++; xfree(after_p); @@ -2000,9 +1961,9 @@ static int op_replace(oparg_T *oap, int c) if (oap->motion_type == kMTLineWise) { oap->start.col = 0; curwin->w_cursor.col = 0; - oap->end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); + oap->end.col = (colnr_T)strlen(ml_get(oap->end.lnum)); if (oap->end.col) { - --oap->end.col; + oap->end.col--; } } else if (!oap->inclusive) { dec(&(oap->end)); @@ -2011,10 +1972,12 @@ static int op_replace(oparg_T *oap, int c) // TODO(bfredl): we could batch all the splicing // done on the same line, at least while (ltoreq(curwin->w_cursor, oap->end)) { + bool done = false; + n = gchar_cursor(); if (n != NUL) { int new_byte_len = utf_char2len(c); - int old_byte_len = utfc_ptr2len((char *)get_cursor_pos_ptr()); + int old_byte_len = utfc_ptr2len(get_cursor_pos_ptr()); if (new_byte_len > 1 || old_byte_len > 1) { // This is slow, but it handles replacing a single-byte @@ -2023,6 +1986,7 @@ static int op_replace(oparg_T *oap, int c) oap->end.col += new_byte_len - old_byte_len; } replace_character(c); + done = true; } else { if (n == TAB) { int end_vcol = 0; @@ -2038,9 +2002,14 @@ static int op_replace(oparg_T *oap, int c) getvpos(&oap->end, end_vcol); } } - pbyte(curwin->w_cursor, c); + // with "coladd" set may move to just after a TAB + if (gchar_cursor() != NUL) { + pbyte(curwin->w_cursor, c); + done = true; + } } - } else if (virtual_op && curwin->w_cursor.lnum == oap->end.lnum) { + } + if (!done && virtual_op && curwin->w_cursor.lnum == oap->end.lnum) { int virtcols = oap->end.coladd; if (curwin->w_cursor.lnum == oap->start.lnum @@ -2090,7 +2059,7 @@ void op_tilde(oparg_T *oap) { pos_T pos; struct block_def bd; - int did_change = FALSE; + int did_change = false; if (u_save((linenr_T)(oap->start.lnum - 1), (linenr_T)(oap->end.lnum + 1)) == FAIL) { @@ -2114,9 +2083,9 @@ void op_tilde(oparg_T *oap) if (oap->motion_type == kMTLineWise) { oap->start.col = 0; pos.col = 0; - oap->end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); + oap->end.col = (colnr_T)strlen(ml_get(oap->end.lnum)); if (oap->end.col) { - --oap->end.col; + oap->end.col--; } } else if (!oap->inclusive) { dec(&(oap->end)); @@ -2129,7 +2098,7 @@ void op_tilde(oparg_T *oap) for (;;) { did_change |= swapchars(oap->op_type, &pos, pos.lnum == oap->end.lnum ? oap->end.col + 1 : - (int)STRLEN(ml_get_pos(&pos))); + (int)strlen(ml_get_pos(&pos))); if (ltoreq(oap->end, pos) || inc(&pos) == -1) { break; } @@ -2143,7 +2112,7 @@ void op_tilde(oparg_T *oap) if (!did_change && oap->is_VIsual) { // No change: need to remove the Visual selection - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { @@ -2165,14 +2134,14 @@ void op_tilde(oparg_T *oap) /// @param length is rounded up to include the whole last multi-byte character. /// Also works correctly when the number of bytes changes. /// -/// @return TRUE if some character was changed. +/// @return true if some character was changed. static int swapchars(int op_type, pos_T *pos, int length) FUNC_ATTR_NONNULL_ALL { int did_change = 0; for (int todo = length; todo > 0; todo--) { - const int len = utfc_ptr2len((char *)ml_get_pos(pos)); + const int len = utfc_ptr2len(ml_get_pos(pos)); // we're counting bytes, not characters if (len > 0) { @@ -2235,7 +2204,7 @@ bool swapchar(int op_type, pos_T *pos) curwin->w_cursor = *pos; // don't use del_char(), it also removes composing chars - del_bytes(utf_ptr2len((char *)get_cursor_pos_ptr()), false, false); + del_bytes(utf_ptr2len(get_cursor_pos_ptr()), false, false); ins_char(nc); curwin->w_cursor = sp; } else { @@ -2250,7 +2219,7 @@ bool swapchar(int op_type, pos_T *pos) void op_insert(oparg_T *oap, long count1) { long ins_len, pre_textlen = 0; - char_u *firstline, *ins_text; + char *firstline, *ins_text; colnr_T ind_pre_col = 0, ind_post_col; int ind_pre_vcol = 0, ind_post_vcol = 0; struct block_def bd; @@ -2262,7 +2231,8 @@ void op_insert(oparg_T *oap, long count1) // vis block is still marked. Get rid of it now. curwin->w_cursor.lnum = oap->start.lnum; - update_screen(INVERTED); + redraw_curbuf_later(UPD_INVERTED); + update_screen(); if (oap->motion_type == kMTBlockWise) { // When 'virtualedit' is used, need to insert the extra spaces before @@ -2280,9 +2250,9 @@ void op_insert(oparg_T *oap, long count1) } curwin->w_ve_flags = VE_ALL; coladvance_force(oap->op_type == OP_APPEND - ? oap->end_vcol + 1 : getviscol()); + ? oap->end_vcol + 1 : getviscol()); if (oap->op_type == OP_APPEND) { - --curwin->w_cursor.col; + curwin->w_cursor.col--; } curwin->w_ve_flags = old_ve_flags; } @@ -2296,17 +2266,17 @@ void op_insert(oparg_T *oap, long count1) if (oap->op_type == OP_APPEND) { firstline += bd.textlen; } - pre_textlen = (long)STRLEN(firstline); + pre_textlen = (long)strlen(firstline); } if (oap->op_type == OP_APPEND) { if (oap->motion_type == kMTBlockWise && curwin->w_cursor.coladd == 0) { // Move the cursor to the character right of the block. - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; while (*get_cursor_pos_ptr() != NUL && (curwin->w_cursor.col < bd.textcol + bd.textlen)) { - ++curwin->w_cursor.col; + curwin->w_cursor.col++; } if (bd.is_short && !bd.is_MAX) { // First line was too short, make it longer and adjust the @@ -2412,19 +2382,17 @@ void op_insert(oparg_T *oap, long count1) if (oap->op_type == OP_APPEND) { pre_textlen += bd2.textlen - bd.textlen; if (bd2.endspaces) { - --bd2.textlen; + bd2.textlen--; } } bd.textcol = bd2.textcol; bd.textlen = bd2.textlen; } - /* - * Subsequent calls to ml_get() flush the firstline data - take a - * copy of the required string. - */ + // Subsequent calls to ml_get() flush the firstline data - take a + // copy of the required string. firstline = ml_get(oap->start.lnum); - const size_t len = STRLEN(firstline); + const size_t len = strlen(firstline); colnr_T add = bd.textcol; colnr_T offset = 0; // offset when cursor was moved in insert mode if (oap->op_type == OP_APPEND) { @@ -2446,9 +2414,9 @@ void op_insert(oparg_T *oap, long count1) } else { firstline += add; } - ins_len = (long)STRLEN(firstline) - pre_textlen - offset; + ins_len = (long)strlen(firstline) - pre_textlen - offset; if (pre_textlen >= 0 && ins_len > 0) { - ins_text = vim_strnsave(firstline, (size_t)ins_len); + ins_text = xstrnsave(firstline, (size_t)ins_len); // block handled here if (u_save(oap->start.lnum, (linenr_T)(oap->end.lnum + 1)) == OK) { block_insert(oap, ins_text, (oap->op_type == OP_INSERT), &bd); @@ -2463,7 +2431,7 @@ void op_insert(oparg_T *oap, long count1) /// handle a change operation /// -/// @return TRUE if edit() returns because of a CTRL-O command +/// @return true if edit() returns because of a CTRL-O command int op_change(oparg_T *oap) { colnr_T l; @@ -2473,10 +2441,10 @@ int op_change(oparg_T *oap) long ins_len; long pre_textlen = 0; long pre_indent = 0; - char_u *newp; - char_u *firstline; - char_u *ins_text; - char_u *oldp; + char *newp; + char *firstline; + char *ins_text; + char *oldp; struct block_def bd; l = oap->start.col; @@ -2489,10 +2457,10 @@ int op_change(oparg_T *oap) // save for undo if (curbuf->b_ml.ml_flags & ML_EMPTY) { if (u_save_cursor() == FAIL) { - return FALSE; + return false; } } else if (op_delete(oap) == FAIL) { - return FALSE; + return false; } if ((l > curwin->w_cursor.col) && !LINEEMPTY(curwin->w_cursor.lnum) @@ -2509,7 +2477,7 @@ int op_change(oparg_T *oap) coladvance_force(getviscol()); } firstline = ml_get(oap->start.lnum); - pre_textlen = (long)STRLEN(firstline); + pre_textlen = (long)strlen(firstline); pre_indent = (long)getwhitecols(firstline); bd.textcol = curwin->w_cursor.col; } @@ -2518,13 +2486,17 @@ int op_change(oparg_T *oap) fix_indent(); } - retval = edit(NUL, FALSE, (linenr_T)1); + // Reset finish_op now, don't want it set inside edit(). + const bool save_finish_op = finish_op; + finish_op = false; + + retval = edit(NUL, false, (linenr_T)1); - /* - * In Visual block mode, handle copying the new text to all lines of the - * block. - * Don't repeat the insert when Insert mode ended with CTRL-C. - */ + finish_op = save_finish_op; + + // In Visual block mode, handle copying the new text to all lines of the + // block. + // Don't repeat the insert when Insert mode ended with CTRL-C. if (oap->motion_type == kMTBlockWise && oap->start.lnum != oap->end.lnum && !got_int) { // Auto-indenting may have changed the indent. If the cursor was past @@ -2537,12 +2509,12 @@ int op_change(oparg_T *oap) bd.textcol += (colnr_T)(new_indent - pre_indent); } - ins_len = (long)STRLEN(firstline) - pre_textlen; + ins_len = (long)strlen(firstline) - pre_textlen; if (ins_len > 0) { // Subsequent calls to ml_get() flush the firstline data - take a // copy of the inserted text. - ins_text = (char_u *)xmalloc((size_t)(ins_len + 1)); - STRLCPY(ins_text, firstline + bd.textcol, ins_len + 1); + ins_text = xmalloc((size_t)(ins_len + 1)); + xstrlcpy(ins_text, firstline + bd.textcol, (size_t)ins_len + 1); for (linenr = oap->start.lnum + 1; linenr <= oap->end.lnum; linenr++) { block_prep(oap, &bd, linenr, true); @@ -2558,7 +2530,7 @@ int op_change(oparg_T *oap) vpos.coladd = 0; } oldp = ml_get(linenr); - newp = xmalloc(STRLEN(oldp) + (size_t)vpos.coladd + newp = xmalloc(strlen(oldp) + (size_t)vpos.coladd + (size_t)ins_len + 1); // copy up to block start memmove(newp, oldp, (size_t)bd.textcol); @@ -2569,7 +2541,7 @@ int op_change(oparg_T *oap) offset += ins_len; oldp += bd.textcol; STRMOVE(newp + offset, oldp); - ml_replace(linenr, (char *)newp, false); + ml_replace(linenr, newp, false); extmark_splice_cols(curbuf, (int)linenr - 1, bd.textcol, 0, vpos.coladd + (int)ins_len, kExtmarkUndo); } @@ -2604,12 +2576,14 @@ void free_register(yankreg_T *reg) FUNC_ATTR_NONNULL_ALL { set_yreg_additional_data(reg, NULL); - if (reg->y_array != NULL) { - for (size_t i = reg->y_size; i-- > 0;) { // from y_size - 1 to 0 included - xfree(reg->y_array[i]); - } - XFREE_CLEAR(reg->y_array); + if (reg->y_array == NULL) { + return; } + + for (size_t i = reg->y_size; i-- > 0;) { // from y_size - 1 to 0 included + xfree(reg->y_array[i]); + } + XFREE_CLEAR(reg->y_array); } /// Yanks the text between "oap->start" and "oap->end" into a yank register. @@ -2649,8 +2623,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) MotionType yank_type = oap->motion_type; size_t yanklines = (size_t)oap->line_count; linenr_T yankendlnum = oap->end.lnum; - char_u *p; - char_u *pnew; + char *p; + char *pnew; struct block_def bd; yankreg_T *curr = reg; // copy of current register @@ -2701,7 +2675,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) break; case kMTLineWise: - reg->y_array[y_idx] = (char *)vim_strsave(ml_get(lnum)); + reg->y_array[y_idx] = xstrdup(ml_get(lnum)); break; case kMTCharWise: { @@ -2717,8 +2691,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) if (virtual_op) { getvcol(curwin, &oap->start, &cs, NULL, &ce); if (ce != cs && oap->start.coladd > 0) { - /* Part of a tab selected -- but don't - * double-count it. */ + // Part of a tab selected -- but don't double-count it. bd.startspaces = (ce - cs + 1) - oap->start.coladd; startcol++; @@ -2751,7 +2724,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) } } if (endcol == MAXCOL) { - endcol = (colnr_T)STRLEN(p); + endcol = (colnr_T)strlen(p); } if (startcol > endcol || is_oneChar) { @@ -2770,7 +2743,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) } if (curr != reg) { // append the new block to the old block - new_ptr = xmalloc(sizeof(char_u *) * (curr->y_size + reg->y_size)); + new_ptr = xmalloc(sizeof(char *) * (curr->y_size + reg->y_size)); for (j = 0; j < curr->y_size; j++) { new_ptr[j] = curr->y_array[j]; } @@ -2786,13 +2759,13 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) // the new block, unless being Vi compatible. if (curr->y_type == kMTCharWise && vim_strchr(p_cpo, CPO_REGAPPEND) == NULL) { - pnew = xmalloc(STRLEN(curr->y_array[curr->y_size - 1]) - + STRLEN(reg->y_array[0]) + 1); + pnew = xmalloc(strlen(curr->y_array[curr->y_size - 1]) + + strlen(reg->y_array[0]) + 1); STRCPY(pnew, curr->y_array[--j]); STRCAT(pnew, reg->y_array[0]); xfree(curr->y_array[j]); xfree(reg->y_array[0]); - curr->y_array[j++] = (char *)pnew; + curr->y_array[j++] = pnew; y_idx = 1; } else { y_idx = 0; @@ -2819,7 +2792,10 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) } // redisplay now, so message is not deleted - update_topline_redraw(); + update_topline(curwin); + if (must_redraw) { + update_screen(); + } if (yank_type == kMTBlockWise) { smsg(NGETTEXT("block of %" PRId64 " line yanked%s", "block of %" PRId64 " lines yanked%s", yanklines), @@ -2855,8 +2831,8 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx, } int size = bd->startspaces + bd->endspaces + bd->textlen; assert(size >= 0); - char_u *pnew = xmallocz((size_t)size); - reg->y_array[y_idx] = (char *)pnew; + char *pnew = xmallocz((size_t)size); + reg->y_array[y_idx] = pnew; memset(pnew, ' ', (size_t)bd->startspaces); pnew += bd->startspaces; memmove(pnew, bd->textstart, (size_t)bd->textlen); @@ -2944,9 +2920,9 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) /// @param dir BACKWARD for 'P', FORWARD for 'p' void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) { - char_u *ptr; - char_u *newp; - char_u *oldp; + char *ptr; + char *newp; + char *oldp; int yanklen; size_t totlen = 0; // init for gcc linenr_T lnum = 0; @@ -2983,10 +2959,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) curbuf->b_op_start = curwin->w_cursor; // default for '[ mark curbuf->b_op_end = curwin->w_cursor; // default for '] mark - /* - * Using inserted text works differently, because the register includes - * special characters (newlines, etc.). - */ + // Using inserted text works differently, because the register includes + // special characters (newlines, etc.). if (regname == '.' && !reg) { bool non_linewise_vis = (VIsual_active && VIsual_mode != 'V'); @@ -3035,14 +3009,14 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // curwin->w_cursor.col marks the byte position of the cursor in the // currunt line. It increases up to a max of - // STRLEN(ml_get(curwin->w_cursor.lnum)). With 'virtualedit' and the + // strlen(ml_get(curwin->w_cursor.lnum)). With 'virtualedit' and the // cursor past the end of the line, curwin->w_cursor.coladd is // incremented instead of curwin->w_cursor.col. - char_u *cursor_pos = get_cursor_pos_ptr(); + char *cursor_pos = get_cursor_pos_ptr(); bool one_past_line = (*cursor_pos == NUL); bool eol = false; if (!one_past_line) { - eol = (*(cursor_pos + utfc_ptr2len((char *)cursor_pos)) == NUL); + eol = (*(cursor_pos + utfc_ptr2len(cursor_pos)) == NUL); } bool ve_allows = (cur_ve_flags == VE_ALL || cur_ve_flags == VE_ONEMORE); @@ -3066,10 +3040,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) return; } - /* - * For special registers '%' (file name), '#' (alternate file name) and - * ':' (last command line), etc. we have to create a fake yank register. - */ + // For special registers '%' (file name), '#' (alternate file name) and + // ':' (last command line), etc. we have to create a fake yank register. if (!reg && get_spec_reg(regname, &insert_string, &allocated, true)) { if (insert_string == NULL) { return; @@ -3087,18 +3059,18 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (insert_string != NULL) { y_type = kMTCharWise; if (regname == '=') { - /* For the = register we need to split the string at NL - * characters. - * Loop twice: count the number of lines and save them. */ + // For the = register we need to split the string at NL + // characters. + // Loop twice: count the number of lines and save them. for (;;) { y_size = 0; - ptr = (char_u *)insert_string; + ptr = insert_string; while (ptr != NULL) { if (y_array != NULL) { - y_array[y_size] = (char *)ptr; + y_array[y_size] = ptr; } y_size++; - ptr = (char_u *)vim_strchr((char *)ptr, '\n'); + ptr = vim_strchr(ptr, '\n'); if (ptr != NULL) { if (y_array != NULL) { *ptr = NUL; @@ -3114,7 +3086,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (y_array != NULL) { break; } - y_array = xmalloc(y_size * sizeof(char_u *)); + y_array = xmalloc(y_size * sizeof(char *)); } } else { y_size = 1; // use fake one-line yank register @@ -3146,12 +3118,12 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (u_save_cursor() == FAIL) { goto end; } - char_u *p = get_cursor_pos_ptr(); + char *p = get_cursor_pos_ptr(); if (dir == FORWARD && *p != NUL) { MB_PTR_ADV(p); } - ptr = vim_strsave(p); - ml_append(curwin->w_cursor.lnum, (char *)ptr, (colnr_T)0, false); + ptr = xstrdup(p); + ml_append(curwin->w_cursor.lnum, ptr, (colnr_T)0, false); xfree(ptr); oldp = get_cursor_line_ptr(); @@ -3159,8 +3131,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (dir == FORWARD && *p != NUL) { MB_PTR_ADV(p); } - ptr = vim_strnsave(oldp, (size_t)(p - oldp)); - ml_replace(curwin->w_cursor.lnum, (char *)ptr, false); + ptr = xstrnsave(oldp, (size_t)(p - oldp)); + ml_replace(curwin->w_cursor.lnum, ptr, false); nr_lines++; dir = FORWARD; } @@ -3179,7 +3151,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (y_size == 0 || y_array == NULL) { semsg(_("E353: Nothing in register %s"), - regname == 0 ? (char_u *)"\"" : transchar(regname)); + regname == 0 ? "\"" : transchar(regname)); goto end; } @@ -3219,7 +3191,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) goto end; } - yanklen = (int)STRLEN(y_array[0]); + yanklen = (int)strlen(y_array[0]); if (cur_ve_flags == VE_ALL && y_type == kMTCharWise) { if (gchar_cursor() == TAB) { @@ -3242,9 +3214,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) lnum = curwin->w_cursor.lnum; col = curwin->w_cursor.col; - /* - * Block mode - */ + // Block mode if (y_type == kMTBlockWise) { int c = gchar_cursor(); colnr_T endcol2 = 0; @@ -3257,7 +3227,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } // move to start of next multi-byte character - curwin->w_cursor.col += utfc_ptr2len((char *)get_cursor_pos_ptr()); + curwin->w_cursor.col += utfc_ptr2len(get_cursor_pos_ptr()); col++; } else { getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2); @@ -3306,42 +3276,52 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } // get the old line and advance to the position to insert at oldp = get_cursor_line_ptr(); - oldlen = STRLEN(oldp); - for (ptr = oldp; vcol < col && *ptr;) { + 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(oldp, &ptr, vcol); - vcol += incr; + incr = lbr_chartabsize_adv(&cts); + cts.cts_vcol += incr; } + vcol = cts.cts_vcol; + ptr = cts.cts_ptr; bd.textcol = (colnr_T)(ptr - oldp); + clear_chartabsize_arg(&cts); shortline = (vcol < col) || (vcol == col && !*ptr); - if (vcol < col) { // line too short, padd with spaces + if (vcol < col) { // line too short, pad with spaces bd.startspaces = col - vcol; } else if (vcol > col) { bd.endspaces = vcol - col; bd.startspaces = incr - bd.endspaces; - --bd.textcol; + bd.textcol--; delcount = 1; bd.textcol -= utf_head_off(oldp, oldp + bd.textcol); if (oldp[bd.textcol] != TAB) { - /* Only a Tab can be split into spaces. Other - * characters will have to be moved to after the - * block, causing misalignment. */ + // Only a Tab can be split into spaces. Other + // characters will have to be moved to after the + // block, causing misalignment. delcount = 0; bd.endspaces = 0; } } - yanklen = (int)STRLEN(y_array[i]); + yanklen = (int)strlen(y_array[i]); if ((flags & PUT_BLOCK_INNER) == 0) { // 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(NULL, (char_u *)(&y_array[i][j]), 0); + spaces -= lbr_chartabsize(&cts); + cts.cts_ptr++; + cts.cts_vcol = 0; } + clear_chartabsize_arg(&cts); if (spaces < 0) { spaces = 0; } @@ -3355,9 +3335,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) break; } - totlen = (size_t)(count * (yanklen + spaces) - + bd.startspaces + bd.endspaces); - newp = (char_u *)xmalloc(totlen + oldlen + 1); + totlen = (size_t)(count * (yanklen + spaces) + bd.startspaces + bd.endspaces); + newp = xmalloc(totlen + oldlen + 1); // copy part up to cursor to new line ptr = newp; @@ -3390,11 +3369,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) int columns = (int)oldlen - bd.textcol - delcount + 1; assert(columns >= 0); memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns); - ml_replace(curwin->w_cursor.lnum, (char *)newp, false); + ml_replace(curwin->w_cursor.lnum, newp, false); extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum - 1, bd.textcol, delcount, (int)totlen + lines_appended, kExtmarkUndo); - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; if (i == 0) { curwin->w_cursor.col += bd.startspaces; } @@ -3410,6 +3389,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // adjust '] mark curbuf->b_op_end.lnum = curwin->w_cursor.lnum - 1; curbuf->b_op_end.col = bd.textcol + (colnr_T)totlen - 1; + if (curbuf->b_op_end.col < 0) { + curbuf->b_op_end.col = 0; + } curbuf->b_op_end.coladd = 0; if (flags & PUT_CURSEND) { colnr_T len; @@ -3418,7 +3400,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) curwin->w_cursor.col++; // in Insert mode we might be after the NUL, correct for that - len = (colnr_T)STRLEN(get_cursor_line_ptr()); + len = (colnr_T)strlen(get_cursor_line_ptr()); if (curwin->w_cursor.col > len) { curwin->w_cursor.col = len; } @@ -3431,7 +3413,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // if type is kMTCharWise, FORWARD is the same as BACKWARD on the next // char if (dir == FORWARD && gchar_cursor() != NUL) { - int bytelen = utfc_ptr2len((char *)get_cursor_pos_ptr()); + int bytelen = utfc_ptr2len(get_cursor_pos_ptr()); // put it on the next of the multi-byte character. col += bytelen; @@ -3482,7 +3464,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) totlen = (size_t)(count * yanklen); do { oldp = ml_get(lnum); - oldlen = STRLEN(oldp); + oldlen = strlen(oldp); if (lnum > start_lnum) { pos_T pos = { .lnum = lnum, @@ -3497,7 +3479,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) lnum++; continue; } - newp = (char_u *)xmalloc(totlen + oldlen + 1); + newp = xmalloc(totlen + oldlen + 1); memmove(newp, oldp, (size_t)col); ptr = newp + col; for (i = 0; i < (size_t)count; i++) { @@ -3505,7 +3487,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) ptr += yanklen; } STRMOVE(ptr, oldp + col); - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); // compute the byte offset for the last character first_byte_off = utf_head_off(newp, ptr - 1); @@ -3553,22 +3535,22 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) // Then append y_array[0] to first line. lnum = new_cursor.lnum; ptr = ml_get(lnum) + col; - totlen = STRLEN(y_array[y_size - 1]); - newp = (char_u *)xmalloc((size_t)(STRLEN(ptr) + totlen + 1)); + totlen = strlen(y_array[y_size - 1]); + newp = xmalloc((size_t)(strlen(ptr) + totlen + 1)); STRCPY(newp, y_array[y_size - 1]); STRCAT(newp, ptr); // insert second line - ml_append(lnum, (char *)newp, (colnr_T)0, false); + ml_append(lnum, newp, (colnr_T)0, false); new_lnum++; xfree(newp); oldp = ml_get(lnum); - newp = (char_u *)xmalloc((size_t)col + (size_t)yanklen + 1); + newp = xmalloc((size_t)col + (size_t)yanklen + 1); // copy first part of line memmove(newp, oldp, (size_t)col); // append to first line memmove(newp + col, y_array[0], (size_t)yanklen + 1); - ml_replace(lnum, (char *)newp, false); + ml_replace(lnum, newp, false); curwin->w_cursor.lnum = lnum; i = 1; @@ -3588,7 +3570,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) curwin->w_cursor.lnum = lnum; ptr = ml_get(lnum); if (cnt == count && i == y_size - 1) { - lendiff = (int)STRLEN(ptr); + lendiff = (int)strlen(ptr); } if (*ptr == '#' && preprocs_left()) { indent = 0; // Leave # lines at start @@ -3605,7 +3587,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) curwin->w_cursor = old_pos; // remember how many chars were removed if (cnt == count && i == y_size - 1) { - lendiff -= (int)STRLEN(ml_get(lnum)); + lendiff -= (int)strlen(ml_get(lnum)); } } } @@ -3615,9 +3597,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (y_type == kMTCharWise || (y_type == kMTLineWise && flags & PUT_LINE_SPLIT)) { for (i = 0; i < y_size - 1; i++) { - totsize += (bcount_t)STRLEN(y_array[i]) + 1; + totsize += (bcount_t)strlen(y_array[i]) + 1; } - lastsize = (int)STRLEN(y_array[y_size - 1]); + lastsize = (int)strlen(y_array[y_size - 1]); totsize += lastsize; } if (y_type == kMTCharWise) { @@ -3661,13 +3643,13 @@ error: // Put the '] mark on the first byte of the last inserted character. // Correct the length for change in indent. curbuf->b_op_end.lnum = new_lnum; - len = STRLEN(y_array[y_size - 1]); + len = strlen(y_array[y_size - 1]); col = (colnr_T)len - lendiff; if (col > 1) { curbuf->b_op_end.col = col - 1; if (len > 0) { - curbuf->b_op_end.col -= utf_head_off((char_u *)y_array[y_size - 1], - (char_u *)y_array[y_size - 1] + len - 1); + curbuf->b_op_end.col -= utf_head_off(y_array[y_size - 1], + y_array[y_size - 1] + len - 1); } } else { curbuf->b_op_end.col = 0; @@ -3698,7 +3680,7 @@ error: // put cursor on first non-blank in first inserted line curwin->w_cursor.col = 0; if (dir == FORWARD) { - ++curwin->w_cursor.lnum; + curwin->w_cursor.lnum++; } beginline(BL_WHITE | BL_FIX); } else { // put cursor on first inserted character @@ -3708,7 +3690,7 @@ error: } msgmore(nr_lines); - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; end: if (cmdmod.cmod_flags & CMOD_LOCKMARKS) { @@ -3722,7 +3704,7 @@ end: xfree(y_array); } - VIsual_active = FALSE; + VIsual_active = false; // If the cursor is past the end of the line put it at the end. adjust_cursor_eol(); @@ -3734,24 +3716,27 @@ void adjust_cursor_eol(void) { unsigned int cur_ve_flags = get_ve_flags(); - if (curwin->w_cursor.col > 0 - && gchar_cursor() == NUL - && (cur_ve_flags & VE_ONEMORE) == 0 - && !(restart_edit || (State & MODE_INSERT))) { - // Put the cursor on the last character in the line. - dec_cursor(); + const bool adj_cursor = (curwin->w_cursor.col > 0 + && gchar_cursor() == NUL + && (cur_ve_flags & VE_ONEMORE) == 0 + && !(restart_edit || (State & MODE_INSERT))); + if (!adj_cursor) { + return; + } + + // Put the cursor on the last character in the line. + dec_cursor(); - if (cur_ve_flags == VE_ALL) { - colnr_T scol, ecol; + if (cur_ve_flags == VE_ALL) { + colnr_T scol, ecol; - // Coladd is set to the width of the last character. - getvcol(curwin, &curwin->w_cursor, &scol, NULL, &ecol); - curwin->w_cursor.coladd = ecol - scol + 1; - } + // Coladd is set to the width of the last character. + getvcol(curwin, &curwin->w_cursor, &scol, NULL, &ecol); + curwin->w_cursor.coladd = ecol - scol + 1; } } -/// @return TRUE if lines starting with '#' should be left aligned. +/// @return true if lines starting with '#' should be left aligned. int preprocs_left(void) { return ((curbuf->b_p_si && !curbuf->b_p_cin) @@ -3786,10 +3771,10 @@ int get_unname_register(void) /// ":dis" and ":registers": Display the contents of the yank registers. void ex_display(exarg_T *eap) { - char_u *p; + char *p; yankreg_T *yb; int name; - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; int clen; int type; @@ -3811,7 +3796,7 @@ void ex_display(exarg_T *eap) type = 'b'; break; } - if (arg != NULL && vim_strchr((char *)arg, name) == NULL) { + if (arg != NULL && vim_strchr(arg, name) == NULL) { continue; // did not ask for this register } @@ -3837,7 +3822,7 @@ void ex_display(exarg_T *eap) bool do_show = false; for (size_t j = 0; !do_show && j < yb->y_size; j++) { - do_show = !message_filtered((char_u *)yb->y_array[j]); + do_show = !message_filtered(yb->y_array[j]); } if (do_show || yb->y_size == 0) { @@ -3855,9 +3840,9 @@ void ex_display(exarg_T *eap) msg_puts_attr("^J", attr); n -= 2; } - for (p = (char_u *)yb->y_array[j]; - *p != NUL && (n -= ptr2cells((char *)p)) >= 0; p++) { // -V1019 - clen = utfc_ptr2len((char *)p); + for (p = yb->y_array[j]; + *p != NUL && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 + clen = utfc_ptr2len(p); msg_outtrans_len(p, clen); p += clen - 1; } @@ -3865,22 +3850,21 @@ void ex_display(exarg_T *eap) if (n > 1 && yb->y_type == kMTLineWise) { msg_puts_attr("^J", attr); } - ui_flush(); // show one line at a time } os_breakcheck(); } } // display last inserted text - if ((p = get_last_insert()) != NULL - && (arg == NULL || vim_strchr((char *)arg, '.') != NULL) && !got_int + if ((p = (char *)get_last_insert()) != NULL + && (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int && !message_filtered(p)) { msg_puts("\n c \". "); dis_msg(p, true); } // display last command line - if (last_cmdline != NULL && (arg == NULL || vim_strchr((char *)arg, ':') != NULL) + if (last_cmdline != NULL && (arg == NULL || vim_strchr(arg, ':') != NULL) && !got_int && !message_filtered(last_cmdline)) { msg_puts("\n c \": "); dis_msg(last_cmdline, false); @@ -3888,33 +3872,33 @@ void ex_display(exarg_T *eap) // display current file name if (curbuf->b_fname != NULL - && (arg == NULL || vim_strchr((char *)arg, '%') != NULL) && !got_int - && !message_filtered((char_u *)curbuf->b_fname)) { + && (arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int + && !message_filtered(curbuf->b_fname)) { msg_puts("\n c \"% "); - dis_msg((char_u *)curbuf->b_fname, false); + dis_msg(curbuf->b_fname, false); } // display alternate file name - if ((arg == NULL || vim_strchr((char *)arg, '%') != NULL) && !got_int) { + if ((arg == NULL || vim_strchr(arg, '%') != NULL) && !got_int) { char *fname; linenr_T dummy; - if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered((char_u *)fname)) { + if (buflist_name_nr(0, &fname, &dummy) != FAIL && !message_filtered(fname)) { msg_puts("\n c \"# "); - dis_msg((char_u *)fname, false); + dis_msg(fname, false); } } // display last search pattern if (last_search_pat() != NULL - && (arg == NULL || vim_strchr((char *)arg, '/') != NULL) && !got_int + && (arg == NULL || vim_strchr(arg, '/') != NULL) && !got_int && !message_filtered(last_search_pat())) { msg_puts("\n c \"/ "); dis_msg(last_search_pat(), false); } // display last used expression - if (expr_line != NULL && (arg == NULL || vim_strchr((char *)arg, '=') != NULL) + if (expr_line != NULL && (arg == NULL || vim_strchr(arg, '=') != NULL) && !got_int && !message_filtered(expr_line)) { msg_puts("\n c \"= "); dis_msg(expr_line, false); @@ -3925,7 +3909,7 @@ void ex_display(exarg_T *eap) /// truncate at end of screen line /// /// @param skip_esc if true, ignore trailing ESC -static void dis_msg(const char_u *p, bool skip_esc) +static void dis_msg(const char *p, bool skip_esc) FUNC_ATTR_NONNULL_ALL { int n; @@ -3934,8 +3918,8 @@ static void dis_msg(const char_u *p, bool skip_esc) n = Columns - 6; while (*p != NUL && !(*p == ESC && skip_esc && *(p + 1) == NUL) - && (n -= ptr2cells((char *)p)) >= 0) { - if ((l = utfc_ptr2len((char *)p)) > 1) { + && (n -= ptr2cells(p)) >= 0) { + if ((l = utfc_ptr2len(p)) > 1) { msg_outtrans_len(p, l); p += l; } else { @@ -3956,11 +3940,11 @@ static void dis_msg(const char_u *p, bool skip_esc) /// @param include_space - whether to skip space following the comment leader /// @param[out] is_comment - whether the current line ends with an unclosed /// comment. -char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_comment) +char *skip_comment(char *line, bool process, bool include_space, bool *is_comment) { char *comment_flags = NULL; int lead_len; - int leader_offset = get_last_leader_offset((char *)line, &comment_flags); + int leader_offset = get_last_leader_offset(line, &comment_flags); *is_comment = false; if (leader_offset != -1) { @@ -3982,7 +3966,7 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co return line; } - lead_len = get_leader_len((char *)line, &comment_flags, false, include_space); + lead_len = get_leader_len(line, &comment_flags, false, include_space); if (lead_len == 0) { return line; @@ -4011,8 +3995,8 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co } /// @param count number of lines (minimal 2) to join at cursor position. -/// @param save_undo when TRUE, save lines for undo first. -/// @param use_formatoptions set to FALSE when e.g. processing backspace and comment +/// @param save_undo when true, save lines for undo first. +/// @param use_formatoptions set to false when e.g. processing backspace and comment /// leaders should not be removed. /// @param setmark when true, sets the '[ and '] mark, else, the caller is expected /// to set those marks. @@ -4020,11 +4004,11 @@ char_u *skip_comment(char_u *line, bool process, bool include_space, bool *is_co /// @return FAIL for failure, OK otherwise int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions, bool setmark) { - char_u *curr = NULL; - char_u *curr_start = NULL; - char_u *cend; - char_u *newp; - char_u *spaces; // number of spaces inserted before a line + char *curr = NULL; + char *curr_start = NULL; + char *cend; + char *newp; + char *spaces; // number of spaces inserted before a line int endcurr1 = NUL; int endcurr2 = NUL; int currsize = 0; // size of the current line @@ -4033,7 +4017,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions colnr_T col = 0; int ret = OK; int *comments = NULL; - int remove_comments = (use_formatoptions == TRUE) + int remove_comments = (use_formatoptions == true) && has_format_option(FO_REMOVE_COMS); bool prev_was_comment = false; assert(count >= 1); @@ -4053,18 +4037,18 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions // Don't move anything, just compute the final line length // and setup the array of space strings lengths for (t = 0; t < (linenr_T)count; t++) { - curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t)); + curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t)); + curr = curr_start; if (t == 0 && setmark && (cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { // Set the '[ mark. curwin->w_buffer->b_op_start.lnum = curwin->w_cursor.lnum; - curwin->w_buffer->b_op_start.col = (colnr_T)STRLEN(curr); + curwin->w_buffer->b_op_start.col = (colnr_T)strlen(curr); } if (remove_comments) { // We don't want to remove the comment leader if the // previous line is not a comment. if (t > 0 && prev_was_comment) { - char_u *new_curr = skip_comment(curr, true, insert_space, - &prev_was_comment); + char *new_curr = skip_comment(curr, true, insert_space, &prev_was_comment); comments[t] = (int)(new_curr - curr); curr = new_curr; } else { @@ -4073,26 +4057,26 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions } if (insert_space && t > 0) { - curr = (char_u *)skipwhite((char *)curr); + curr = skipwhite(curr); if (*curr != NUL && *curr != ')' && sumsize != 0 && endcurr1 != TAB && (!has_format_option(FO_MBYTE_JOIN) - || (utf_ptr2char((char *)curr) < 0x100 && endcurr1 < 0x100)) + || (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100)) && (!has_format_option(FO_MBYTE_JOIN2) - || (utf_ptr2char((char *)curr) < 0x100 && !utf_eat_space(endcurr1)) + || (utf_ptr2char(curr) < 0x100 && !utf_eat_space(endcurr1)) || (endcurr1 < 0x100 - && !utf_eat_space(utf_ptr2char((char *)curr))))) { + && !utf_eat_space(utf_ptr2char(curr))))) { // don't add a space if the line is ending in a space if (endcurr1 == ' ') { endcurr1 = endcurr2; } else { - ++spaces[t]; + spaces[t]++; } // Extra space when 'joinspaces' set and line ends in '.', '?', or '!'. if (p_js && (endcurr1 == '.' || endcurr1 == '?' || endcurr1 == '!')) { - ++spaces[t]; + spaces[t]++; } } } @@ -4104,16 +4088,16 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions 0, spaces[t], spaces[t], kExtmarkUndo); } - currsize = (int)STRLEN(curr); + currsize = (int)strlen(curr); sumsize += currsize + spaces[t]; endcurr1 = endcurr2 = NUL; if (insert_space && currsize > 0) { cend = curr + currsize; MB_PTR_BACK(curr, cend); - endcurr1 = utf_ptr2char((char *)cend); + endcurr1 = utf_ptr2char(cend); if (cend > curr) { MB_PTR_BACK(curr, cend); - endcurr2 = utf_ptr2char((char *)cend); + endcurr2 = utf_ptr2char(cend); } } line_breakcheck(); @@ -4127,17 +4111,15 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions col = sumsize - currsize - spaces[count - 1]; // allocate the space for the new line - newp = (char_u *)xmalloc((size_t)sumsize + 1); + newp = xmalloc((size_t)sumsize + 1); cend = newp + sumsize; *cend = 0; - /* - * Move affected lines to the new long one. - * - * Move marks from each deleted line to the joined line, adjusting the - * column. This is not Vi compatible, but Vi deletes the marks, thus that - * should not really be a problem. - */ + // Move affected lines to the new long one. + // + // Move marks from each deleted line to the joined line, adjusting the + // column. This is not Vi compatible, but Vi deletes the marks, thus that + // should not really be a problem. curbuf_splice_pending++; @@ -4163,17 +4145,18 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions break; } - curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t - 1)); + curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t - 1)); + curr = curr_start; if (remove_comments) { curr += comments[t - 1]; } if (insert_space && t > 1) { - curr = (char_u *)skipwhite((char *)curr); + curr = skipwhite(curr); } - currsize = (int)STRLEN(curr); + currsize = (int)strlen(curr); } - ml_replace(curwin->w_cursor.lnum, (char *)newp, false); + ml_replace(curwin->w_cursor.lnum, newp, false); if (setmark && (cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { // Set the '] mark. @@ -4186,11 +4169,9 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions changed_lines(curwin->w_cursor.lnum, currsize, curwin->w_cursor.lnum + 1, 0L, true); - /* - * Delete following lines. To do this we move the cursor there - * briefly, and then move it back. After del_lines() the cursor may - * have moved up (last line deleted), so the current lnum is kept in t. - */ + // Delete following lines. To do this we move the cursor there + // briefly, and then move it back. After del_lines() the cursor may + // have moved up (last line deleted), so the current lnum is kept in t. t = curwin->w_cursor.lnum; curwin->w_cursor.lnum++; del_lines((long)count - 1, false); @@ -4198,17 +4179,15 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions curbuf_splice_pending--; curbuf->deleted_bytes2 = 0; - /* - * Set the cursor column: - * Vi compatible: use the column of the first join - * vim: use the column of the last join - */ + // Set the cursor column: + // Vi compatible: use the column of the first join + // vim: use the column of the last join curwin->w_cursor.col = (vim_strchr(p_cpo, CPO_JOINCOL) != NULL ? currsize : col); check_cursor_col(); curwin->w_cursor.coladd = 0; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; theend: xfree(spaces); @@ -4218,530 +4197,29 @@ theend: return ret; } -/// @return TRUE if the two comment leaders given are the same. -/// -/// @param lnum The first line. White-space is ignored. -/// -/// @note the whole of 'leader1' must match 'leader2_len' characters from 'leader2'. -static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, int leader2_len, - char_u *leader2_flags) +/// Reset 'linebreak' and take care of side effects. +/// @return the previous value, to be passed to restore_lbr(). +static bool reset_lbr(void) { - int idx1 = 0, idx2 = 0; - char_u *p; - char_u *line1; - char_u *line2; - - if (leader1_len == 0) { - return leader2_len == 0; - } - - /* - * If first leader has 'f' flag, the lines can be joined only if the - * second line does not have a leader. - * If first leader has 'e' flag, the lines can never be joined. - * If first leader has 's' flag, the lines can only be joined if there is - * some text after it and the second line has the 'm' flag. - */ - if (leader1_flags != NULL) { - for (p = leader1_flags; *p && *p != ':'; ++p) { - if (*p == COM_FIRST) { - return leader2_len == 0; - } - if (*p == COM_END) { - return FALSE; - } - if (*p == COM_START) { - if (*(ml_get(lnum) + leader1_len) == NUL) { - return FALSE; - } - if (leader2_flags == NULL || leader2_len == 0) { - return FALSE; - } - for (p = leader2_flags; *p && *p != ':'; ++p) { - if (*p == COM_MIDDLE) { - return TRUE; - } - } - return FALSE; - } - } - } - - /* - * Get current line and next line, compare the leaders. - * The first line has to be saved, only one line can be locked at a time. - */ - line1 = vim_strsave(ml_get(lnum)); - for (idx1 = 0; ascii_iswhite(line1[idx1]); idx1++) {} - line2 = ml_get(lnum + 1); - for (idx2 = 0; idx2 < leader2_len; ++idx2) { - if (!ascii_iswhite(line2[idx2])) { - if (line1[idx1++] != line2[idx2]) { - break; - } - } else { - while (ascii_iswhite(line1[idx1])) { - idx1++; - } - } + if (!curwin->w_p_lbr) { + return false; } - xfree(line1); - - return idx2 == leader2_len && idx1 == leader1_len; + // changing 'linebreak' may require w_virtcol to be updated + curwin->w_p_lbr = false; + curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); + return true; } -/// Implementation of the format operator 'gq'. -/// -/// @param keep_cursor keep cursor on same text char -static void op_format(oparg_T *oap, int keep_cursor) +/// Restore 'linebreak' and take care of side effects. +static void restore_lbr(bool lbr_saved) { - linenr_T old_line_count = curbuf->b_ml.ml_line_count; - - // Place the cursor where the "gq" or "gw" command was given, so that "u" - // can put it back there. - curwin->w_cursor = oap->cursor_start; - - if (u_save((linenr_T)(oap->start.lnum - 1), - (linenr_T)(oap->end.lnum + 1)) == FAIL) { + if (curwin->w_p_lbr || !lbr_saved) { return; } - curwin->w_cursor = oap->start; - - if (oap->is_VIsual) { - // When there is no change: need to remove the Visual selection - redraw_curbuf_later(INVERTED); - } - - if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { - // Set '[ mark at the start of the formatted area - curbuf->b_op_start = oap->start; - } - - // For "gw" remember the cursor position and put it back below (adjusted - // for joined and split lines). - if (keep_cursor) { - saved_cursor = oap->cursor_start; - } - - format_lines((linenr_T)oap->line_count, keep_cursor); - - /* - * Leave the cursor at the first non-blank of the last formatted line. - * If the cursor was moved one line back (e.g. with "Q}") go to the next - * line, so "." will do the next lines. - */ - if (oap->end_adjusted && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - ++curwin->w_cursor.lnum; - } - beginline(BL_WHITE | BL_FIX); - old_line_count = curbuf->b_ml.ml_line_count - old_line_count; - msgmore(old_line_count); - - if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { - // put '] mark on the end of the formatted area - curbuf->b_op_end = curwin->w_cursor; - } - - if (keep_cursor) { - curwin->w_cursor = saved_cursor; - saved_cursor.lnum = 0; - - // formatting may have made the cursor position invalid - check_cursor(); - } - - if (oap->is_VIsual) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_old_cursor_lnum != 0) { - // When lines have been inserted or deleted, adjust the end of - // the Visual area to be redrawn. - if (wp->w_old_cursor_lnum > wp->w_old_visual_lnum) { - wp->w_old_cursor_lnum += old_line_count; - } else { - wp->w_old_visual_lnum += old_line_count; - } - } - } - } -} - -/// Implementation of the format operator 'gq' for when using 'formatexpr'. -static void op_formatexpr(oparg_T *oap) -{ - if (oap->is_VIsual) { - // When there is no change: need to remove the Visual selection - redraw_curbuf_later(INVERTED); - } - - if (fex_format(oap->start.lnum, oap->line_count, NUL) != 0) { - // As documented: when 'formatexpr' returns non-zero fall back to - // internal formatting. - op_format(oap, false); - } -} - -/// @param c character to be inserted -int fex_format(linenr_T lnum, long count, int c) -{ - int use_sandbox = was_set_insecurely(curwin, "formatexpr", OPT_LOCAL); - int r; - char_u *fex; - - /* - * Set v:lnum to the first line number and v:count to the number of lines. - * Set v:char to the character to be inserted (can be NUL). - */ - set_vim_var_nr(VV_LNUM, (varnumber_T)lnum); - set_vim_var_nr(VV_COUNT, (varnumber_T)count); - set_vim_var_char(c); - - // Make a copy, the option could be changed while calling it. - fex = vim_strsave(curbuf->b_p_fex); - // Evaluate the function. - if (use_sandbox) { - sandbox++; - } - r = (int)eval_to_number((char *)fex); - if (use_sandbox) { - sandbox--; - } - - set_vim_var_string(VV_CHAR, NULL, -1); - xfree(fex); - - return r; -} - -/// @param line_count number of lines to format, starting at the cursor position. -/// when negative, format until the end of the paragraph. -/// -/// Lines after the cursor line are saved for undo, caller must have saved the -/// first line. -/// -/// @param avoid_fex don't use 'formatexpr' -void format_lines(linenr_T line_count, int avoid_fex) -{ - bool is_not_par; // current line not part of parag. - bool next_is_not_par; // next line not part of paragraph - bool is_end_par; // at end of paragraph - bool prev_is_end_par = false; // prev. line not part of parag. - bool next_is_start_par = false; - int leader_len = 0; // leader len of current line - int next_leader_len; // leader len of next line - char_u *leader_flags = NULL; // flags for leader of current line - char_u *next_leader_flags = NULL; // flags for leader of next line - bool advance = true; - int second_indent = -1; // indent for second line (comment aware) - bool first_par_line = true; - int smd_save; - long count; - bool need_set_indent = true; // set indent of next paragraph - linenr_T first_line = curwin->w_cursor.lnum; - bool force_format = false; - const int old_State = State; - - // length of a line to force formatting: 3 * 'tw' - const int max_len = comp_textwidth(true) * 3; - - // check for 'q', '2' and '1' in 'formatoptions' - const bool do_comments = has_format_option(FO_Q_COMS); // format comments - int do_comments_list = 0; // format comments with 'n' or '2' - const bool do_second_indent = has_format_option(FO_Q_SECOND); - const bool do_number_indent = has_format_option(FO_Q_NUMBER); - const bool do_trail_white = has_format_option(FO_WHITE_PAR); - - // Get info about the previous and current line. - if (curwin->w_cursor.lnum > 1) { - is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1, - &leader_len, &leader_flags, do_comments); - } else { - is_not_par = true; - } - next_is_not_par = fmt_check_par(curwin->w_cursor.lnum, - &next_leader_len, &next_leader_flags, do_comments - ); - is_end_par = (is_not_par || next_is_not_par); - if (!is_end_par && do_trail_white) { - is_end_par = !ends_in_white(curwin->w_cursor.lnum - 1); - } - - curwin->w_cursor.lnum--; - for (count = line_count; count != 0 && !got_int; --count) { - /* - * Advance to next paragraph. - */ - if (advance) { - curwin->w_cursor.lnum++; - prev_is_end_par = is_end_par; - is_not_par = next_is_not_par; - leader_len = next_leader_len; - leader_flags = next_leader_flags; - } - - /* - * The last line to be formatted. - */ - if (count == 1 || curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { - next_is_not_par = true; - next_leader_len = 0; - next_leader_flags = NULL; - } else { - next_is_not_par = fmt_check_par(curwin->w_cursor.lnum + 1, - &next_leader_len, &next_leader_flags, do_comments - ); - if (do_number_indent) { - next_is_start_par = - (get_number_indent(curwin->w_cursor.lnum + 1) > 0); - } - } - advance = true; - is_end_par = (is_not_par || next_is_not_par || next_is_start_par); - if (!is_end_par && do_trail_white) { - is_end_par = !ends_in_white(curwin->w_cursor.lnum); - } - - /* - * Skip lines that are not in a paragraph. - */ - if (is_not_par) { - if (line_count < 0) { - break; - } - } else { - /* - * For the first line of a paragraph, check indent of second line. - * Don't do this for comments and empty lines. - */ - if (first_par_line - && (do_second_indent || do_number_indent) - && prev_is_end_par - && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - if (do_second_indent && !LINEEMPTY(curwin->w_cursor.lnum + 1)) { - if (leader_len == 0 && next_leader_len == 0) { - // no comment found - second_indent = - get_indent_lnum(curwin->w_cursor.lnum + 1); - } else { - second_indent = next_leader_len; - do_comments_list = 1; - } - } else if (do_number_indent) { - if (leader_len == 0 && next_leader_len == 0) { - // no comment found - second_indent = - get_number_indent(curwin->w_cursor.lnum); - } else { - // get_number_indent() is now "comment aware"... - second_indent = - get_number_indent(curwin->w_cursor.lnum); - do_comments_list = 1; - } - } - } - - /* - * When the comment leader changes, it's the end of the paragraph. - */ - if (curwin->w_cursor.lnum >= curbuf->b_ml.ml_line_count - || !same_leader(curwin->w_cursor.lnum, - leader_len, leader_flags, - next_leader_len, - next_leader_flags)) { - // Special case: If the next line starts with a line comment - // and this line has a line comment after some text, the - // paragraph doesn't really end. - if (next_leader_flags == NULL - || STRNCMP(next_leader_flags, "://", 3) != 0 - || check_linecomment(get_cursor_line_ptr()) == MAXCOL) { - is_end_par = true; - } - } - - /* - * If we have got to the end of a paragraph, or the line is - * getting long, format it. - */ - if (is_end_par || force_format) { - if (need_set_indent) { - int indent = 0; // amount of indent needed - - // Replace indent in first line of a paragraph with minimal - // number of tabs and spaces, according to current options. - // For the very first formatted line keep the current - // indent. - if (curwin->w_cursor.lnum == first_line) { - indent = get_indent(); - } else if (curbuf->b_p_lisp) { - indent = get_lisp_indent(); - } else { - if (cindent_on()) { - indent = *curbuf->b_p_inde != NUL ? get_expr_indent() : get_c_indent(); - } else { - indent = get_indent(); - } - } - (void)set_indent(indent, SIN_CHANGED); - } - - // put cursor on last non-space - State = MODE_NORMAL; // don't go past end-of-line - coladvance(MAXCOL); - while (curwin->w_cursor.col && ascii_isspace(gchar_cursor())) { - dec_cursor(); - } - - // do the formatting, without 'showmode' - State = MODE_INSERT; // for open_line() - smd_save = p_smd; - p_smd = FALSE; - insertchar(NUL, INSCHAR_FORMAT - + (do_comments ? INSCHAR_DO_COM : 0) - + (do_comments && do_comments_list - ? INSCHAR_COM_LIST : 0) - + (avoid_fex ? INSCHAR_NO_FEX : 0), second_indent); - State = old_State; - p_smd = smd_save; - second_indent = -1; - // at end of par.: need to set indent of next par. - need_set_indent = is_end_par; - if (is_end_par) { - // When called with a negative line count, break at the - // end of the paragraph. - if (line_count < 0) { - break; - } - first_par_line = true; - } - force_format = false; - } - - /* - * When still in same paragraph, join the lines together. But - * first delete the leader from the second line. - */ - if (!is_end_par) { - advance = false; - curwin->w_cursor.lnum++; - curwin->w_cursor.col = 0; - if (line_count < 0 && u_save_cursor() == FAIL) { - break; - } - if (next_leader_len > 0) { - (void)del_bytes(next_leader_len, false, false); - mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, - (long)-next_leader_len, 0); - } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND - int indent = (int)getwhitecols_curline(); - - if (indent > 0) { - (void)del_bytes(indent, false, false); - mark_col_adjust(curwin->w_cursor.lnum, - (colnr_T)0, 0L, (long)-indent, 0); - } - } - curwin->w_cursor.lnum--; - if (do_join(2, TRUE, FALSE, FALSE, false) == FAIL) { - beep_flush(); - break; - } - first_par_line = false; - // If the line is getting long, format it next time - if (STRLEN(get_cursor_line_ptr()) > (size_t)max_len) { - force_format = true; - } else { - force_format = false; - } - } - } - line_breakcheck(); - } -} - -/// @return TRUE if line "lnum" ends in a white character. -static int ends_in_white(linenr_T lnum) -{ - char_u *s = ml_get(lnum); - size_t l; - - if (*s == NUL) { - return FALSE; - } - l = STRLEN(s) - 1; - return ascii_iswhite(s[l]); -} - -/// Blank lines, and lines containing only the comment leader, are left -/// untouched by the formatting. The function returns TRUE in this -/// case. It also returns TRUE when a line starts with the end of a comment -/// ('e' in comment flags), so that this line is skipped, and not joined to the -/// previous line. A new paragraph starts after a blank line, or when the -/// comment leader changes. -static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags, int do_comments) -{ - char_u *flags = NULL; // init for GCC - char_u *ptr; - - ptr = ml_get(lnum); - if (do_comments) { - *leader_len = get_leader_len((char *)ptr, (char **)leader_flags, false, true); - } else { - *leader_len = 0; - } - - if (*leader_len > 0) { - /* - * Search for 'e' flag in comment leader flags. - */ - flags = *leader_flags; - while (*flags && *flags != ':' && *flags != COM_END) { - flags++; - } - } - return *skipwhite((char *)ptr + *leader_len) == NUL - || (*leader_len > 0 && *flags == COM_END) - || startPS(lnum, NUL, FALSE); -} - -/// Used for auto-formatting. -/// -/// @return TRUE when a paragraph starts in line "lnum". -/// FALSE when the previous line is in the same paragraph. -int paragraph_start(linenr_T lnum) -{ - char_u *p; - int leader_len = 0; // leader len of current line - char_u *leader_flags = NULL; // flags for leader of current line - int next_leader_len = 0; // leader len of next line - char_u *next_leader_flags = NULL; // flags for leader of next line - - if (lnum <= 1) { - return TRUE; // start of the file - } - p = ml_get(lnum - 1); - if (*p == NUL) { - return TRUE; // after empty line - } - const bool do_comments = has_format_option(FO_Q_COMS); // format comments - if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) { - return true; // after non-paragraph line - } - - if (fmt_check_par(lnum, &next_leader_len, &next_leader_flags, do_comments)) { - return true; // "lnum" is not a paragraph line - } - - if (has_format_option(FO_WHITE_PAR) && !ends_in_white(lnum - 1)) { - return TRUE; // missing trailing space in previous line. - } - if (has_format_option(FO_Q_NUMBER) && (get_number_indent(lnum) > 0)) { - return TRUE; // numbered item starts in "lnum". - } - if (!same_leader(lnum - 1, leader_len, leader_flags, - next_leader_len, next_leader_flags)) { - return TRUE; // change of comment leader. - } - return FALSE; + // changing 'linebreak' may require w_virtcol to be updated + curwin->w_p_lbr = true; + curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); } /// prepare a few things for block mode yank/delete/tilde @@ -4758,15 +4236,14 @@ int paragraph_start(linenr_T lnum) static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool is_del) { int incr = 0; - char_u *pend; - char_u *pstart; - char_u *line; - char_u *prev_pstart; - char_u *prev_pend; - const int lbr_saved = curwin->w_p_lbr; - + char *pend; + char *pstart; + char *line; + char *prev_pstart; + char *prev_pend; // Avoid a problem with unwanted linebreaks in block mode. - curwin->w_p_lbr = false; + const bool lbr_saved = reset_lbr(); + bdp->startspaces = 0; bdp->endspaces = 0; bdp->textlen = 0; @@ -4780,22 +4257,28 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool bdp->start_char_vcols = 0; line = ml_get(lnum); - pstart = line; prev_pstart = line; - while (bdp->start_vcol < oap->start_vcol && *pstart) { + + 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(line, pstart, bdp->start_vcol); - bdp->start_vcol += incr; - if (ascii_iswhite(*pstart)) { + incr = lbr_chartabsize(&cts); + cts.cts_vcol += incr; + if (ascii_iswhite(*cts.cts_ptr)) { bdp->pre_whitesp += incr; bdp->pre_whitesp_c++; } else { bdp->pre_whitesp = 0; bdp->pre_whitesp_c = 0; } - prev_pstart = pstart; - MB_PTR_ADV(pstart); + prev_pstart = cts.cts_ptr; + MB_PTR_ADV(cts.cts_ptr); } + bdp->start_vcol = cts.cts_vcol; + pstart = cts.cts_ptr; + clear_chartabsize_arg(&cts); + bdp->start_char_vcols = incr; if (bdp->start_vcol < oap->start_vcol) { // line too short bdp->end_vcol = bdp->start_vcol; @@ -4831,13 +4314,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); prev_pend = pend; - while (bdp->end_vcol <= oap->end_vcol && *pend != NUL) { + 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 = pend; - incr = lbr_chartabsize_adv(line, &pend, bdp->end_vcol); - bdp->end_vcol += incr; + 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); + if (bdp->end_vcol <= oap->end_vcol && (!is_del || oap->op_type == OP_APPEND @@ -4869,7 +4357,7 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool } bdp->textcol = (colnr_T)(pstart - line); bdp->textstart = pstart; - curwin->w_p_lbr = lbr_saved; + restore_lbr(lbr_saved); } /// Handle the add/subtract operator. @@ -4921,20 +4409,20 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) } else if (oap->motion_type == kMTLineWise) { curwin->w_cursor.col = 0; pos.col = 0; - length = (colnr_T)STRLEN(ml_get(pos.lnum)); + length = (colnr_T)strlen(ml_get(pos.lnum)); } else { // oap->motion_type == kMTCharWise if (pos.lnum == oap->start.lnum && !oap->inclusive) { dec(&(oap->end)); } - length = (colnr_T)STRLEN(ml_get(pos.lnum)); + length = (colnr_T)strlen(ml_get(pos.lnum)); pos.col = 0; if (pos.lnum == oap->start.lnum) { pos.col += oap->start.col; length -= oap->start.col; } if (pos.lnum == oap->end.lnum) { - length = (int)STRLEN(ml_get(oap->end.lnum)); + length = (int)strlen(ml_get(oap->end.lnum)); if (oap->end.col >= length) { oap->end.col = length - 1; } @@ -4962,7 +4450,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) if (!change_cnt && oap->is_VIsual) { // No change: need to remove the Visual selection - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); } // Set '[ mark if something changed. Keep the last end @@ -4990,13 +4478,13 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) { int col; - char_u *buf1 = NULL; - char_u buf2[NUMBUFLEN]; + char *buf1 = NULL; + char buf2[NUMBUFLEN]; int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin static bool hexupper = false; // 0xABC uvarnumber_T n; uvarnumber_T oldn; - char_u *ptr; + char *ptr; int c; int todel; int firstdigit; @@ -5010,12 +4498,12 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) pos_T endpos; colnr_T save_coladd = 0; - const bool do_hex = vim_strchr((char *)curbuf->b_p_nf, 'x') != NULL; // "heX" - const bool do_oct = vim_strchr((char *)curbuf->b_p_nf, 'o') != NULL; // "Octal" - const bool do_bin = vim_strchr((char *)curbuf->b_p_nf, 'b') != NULL; // "Bin" - const bool do_alpha = vim_strchr((char *)curbuf->b_p_nf, 'p') != NULL; // "alPha" + const bool do_hex = vim_strchr(curbuf->b_p_nf, 'x') != NULL; // "heX" + const bool do_oct = vim_strchr(curbuf->b_p_nf, 'o') != NULL; // "Octal" + const bool do_bin = vim_strchr(curbuf->b_p_nf, 'b') != NULL; // "Bin" + const bool do_alpha = vim_strchr(curbuf->b_p_nf, 'p') != NULL; // "alPha" // "Unsigned" - const bool do_unsigned = vim_strchr((char *)curbuf->b_p_nf, 'u') != NULL; + const bool do_unsigned = vim_strchr(curbuf->b_p_nf, 'u') != NULL; if (virtual_active()) { save_coladd = pos->coladd; @@ -5026,7 +4514,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) ptr = ml_get(pos->lnum); col = pos->col; - if (*ptr == NUL || col + !!save_coladd >= (int)STRLEN(ptr)) { + if (*ptr == NUL || col + !!save_coladd >= (int)strlen(ptr)) { goto theend; } @@ -5098,7 +4586,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) if (visual) { while (ptr[col] != NUL && length > 0 && !ascii_isdigit(ptr[col]) && !(do_alpha && ASCII_ISALPHA(ptr[col]))) { - int mb_len = utfc_ptr2len((char *)ptr + col); + int mb_len = utfc_ptr2len(ptr + col); col += mb_len; length -= mb_len; @@ -5117,7 +4605,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } // If a number was found, and saving for undo works, replace the number. - firstdigit = ptr[col]; + firstdigit = (uint8_t)ptr[col]; if (!ascii_isdigit(firstdigit) && !(do_alpha && ASCII_ISALPHA(firstdigit))) { beep_flush(); goto theend; @@ -5166,7 +4654,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) // get the number value (unsigned) if (visual && VIsual_mode != 'V') { maxlen = (curbuf->b_visual.vi_curswant == MAXCOL - ? (int)STRLEN(ptr) - col + ? (int)strlen(ptr) - col : length); } @@ -5271,7 +4759,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) length--; } if (pre == 'b' || pre == 'B' || pre == 'x' || pre == 'X') { - *ptr++ = (char_u)pre; + *ptr++ = (char)pre; length--; } @@ -5293,15 +4781,15 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) buf2[i] = '\0'; } else if (pre == 0) { - vim_snprintf((char *)buf2, ARRAY_SIZE(buf2), "%" PRIu64, (uint64_t)n); + vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIu64, (uint64_t)n); } else if (pre == '0') { - vim_snprintf((char *)buf2, ARRAY_SIZE(buf2), "%" PRIo64, (uint64_t)n); + vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIo64, (uint64_t)n); } else if (hexupper) { - vim_snprintf((char *)buf2, ARRAY_SIZE(buf2), "%" PRIX64, (uint64_t)n); + vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIX64, (uint64_t)n); } else { - vim_snprintf((char *)buf2, ARRAY_SIZE(buf2), "%" PRIx64, (uint64_t)n); + vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIx64, (uint64_t)n); } - length -= (int)STRLEN(buf2); + length -= (int)strlen(buf2); // Adjust number of zeros to the new number of digits, so the // total length of the number remains the same. @@ -5412,13 +4900,13 @@ void format_reg_type(MotionType reg_type, colnr_T reg_width, char *buf, size_t b /// Otherwise just return `s`. /// /// @return a void * for use in get_reg_contents(). -static void *get_reg_wrap_one_line(char_u *s, int flags) +static void *get_reg_wrap_one_line(char *s, int flags) { if (!(flags & kGRegList)) { return s; } list_T *const list = tv_list_alloc(1); - tv_list_append_allocated_string(list, (char *)s); + tv_list_append_allocated_string(list, s); return list; } @@ -5460,9 +4948,9 @@ void *get_reg_contents(int regname, int flags) return NULL; } if (allocated) { - return get_reg_wrap_one_line((char_u *)retval, flags); + return get_reg_wrap_one_line(retval, flags); } - return get_reg_wrap_one_line(vim_strsave((char_u *)retval), flags); + return get_reg_wrap_one_line(xstrdup(retval), flags); } yankreg_T *reg = get_yank_register(regname, YREG_PASTE); @@ -5479,16 +4967,12 @@ void *get_reg_contents(int regname, int flags) return list; } - /* - * Compute length of resulting string. - */ + // Compute length of resulting string. size_t len = 0; for (size_t i = 0; i < reg->y_size; i++) { - len += STRLEN(reg->y_array[i]); - /* - * Insert a newline between lines and after last line if - * y_type is kMTLineWise. - */ + len += strlen(reg->y_array[i]); + // Insert a newline between lines and after last line if + // y_type is kMTLineWise. if (reg->y_type == kMTLineWise || i < reg->y_size - 1) { len++; } @@ -5496,18 +4980,14 @@ void *get_reg_contents(int regname, int flags) retval = xmalloc(len + 1); - /* - * Copy the lines of the yank register into the string. - */ + // Copy the lines of the yank register into the string. len = 0; for (size_t i = 0; i < reg->y_size; i++) { STRCPY(retval + len, reg->y_array[i]); - len += STRLEN(retval + len); + len += strlen(retval + len); - /* - * Insert a NL between lines and after the last line if y_type is - * kMTLineWise. - */ + // Insert a NL between lines and after the last line if y_type is + // kMTLineWise. if (reg->y_type == kMTLineWise || i < reg->y_size - 1) { retval[len++] = '\n'; } @@ -5548,7 +5028,7 @@ static void finish_write_reg(int name, yankreg_T *reg, yankreg_T *old_y_previous /// store `str` in register `name` /// /// @see write_reg_contents_ex -void write_reg_contents(int name, const char_u *str, ssize_t len, int must_append) +void write_reg_contents(int name, const char *str, ssize_t len, int must_append) { write_reg_contents_ex(name, str, len, must_append, kMTUnknown, 0L); } @@ -5557,9 +5037,9 @@ void write_reg_contents_lst(int name, char **strings, bool must_append, MotionTy colnr_T block_len) { if (name == '/' || name == '=') { - char_u *s = (char_u *)strings[0]; + char *s = strings[0]; if (strings[0] == NULL) { - s = (char_u *)""; + s = ""; } else if (strings[1] != NULL) { emsg(_("E883: search pattern and expression register may not " "contain two or more lines")); @@ -5579,7 +5059,7 @@ void write_reg_contents_lst(int name, char **strings, bool must_append, MotionTy return; } - str_to_reg(reg, yank_type, (char *)strings, STRLEN((char_u *)strings), + str_to_reg(reg, yank_type, (char *)strings, strlen((char *)strings), block_len, true); finish_write_reg(name, reg, old_y_previous); } @@ -5602,16 +5082,16 @@ void write_reg_contents_lst(int name, char **strings, bool must_append, MotionTy /// is an uppercase letter. /// @param yank_type The motion type (kMTUnknown to auto detect) /// @param block_len width of visual block -void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_append, +void write_reg_contents_ex(int name, const char *str, ssize_t len, bool must_append, MotionType yank_type, colnr_T block_len) { if (len < 0) { - len = (ssize_t)STRLEN(str); + len = (ssize_t)strlen(str); } // Special case: '/' search pattern if (name == '/') { - set_last_search_pat(str, RE_SEARCH, TRUE, TRUE); + set_last_search_pat(str, RE_SEARCH, true, true); return; } @@ -5619,14 +5099,14 @@ void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_a buf_T *buf; if (ascii_isdigit(*str)) { - int num = atoi((char *)str); + int num = atoi(str); buf = buflist_findnr(num); if (buf == NULL) { semsg(_(e_nobufnr), (int64_t)num); } } else { - buf = buflist_findnr(buflist_findpat((char *)str, (char *)str + STRLEN(str), + buf = buflist_findnr(buflist_findpat(str, str + strlen(str), true, false, false)); } if (buf == NULL) { @@ -5643,7 +5123,7 @@ void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_a if (must_append && expr_line) { // append has been specified and expr_line already exists, so we'll // append the new string to expr_line. - size_t exprlen = STRLEN(expr_line); + size_t exprlen = strlen(expr_line); totlen += exprlen; offset = exprlen; @@ -5667,7 +5147,7 @@ void write_reg_contents_ex(int name, const char_u *str, ssize_t len, bool must_a if (!(reg = init_write_reg(name, &old_y_previous, must_append))) { return; } - str_to_reg(reg, yank_type, (char *)str, (size_t)len, block_len, false); + str_to_reg(reg, yank_type, str, (size_t)len, block_len, false); finish_write_reg(name, reg, old_y_previous); } @@ -5701,18 +5181,18 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, // Count the number of lines within the string if (str_list) { - for (char_u **ss = (char_u **)str; *ss != NULL; ++ss) { + for (char **ss = (char **)str; *ss != NULL; ss++) { newlines++; } } else { newlines = memcnt(str, '\n', len); if (yank_type == kMTCharWise || len == 0 || str[len - 1] != '\n') { extraline = 1; - ++newlines; // count extra newline at the end + newlines++; // count extra newline at the end } if (y_ptr->y_size > 0 && y_ptr->y_type == kMTCharWise) { append = true; - --newlines; // uncount newline when appending first line + newlines--; // uncount newline when appending first line } } @@ -5723,7 +5203,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, } // Grow the register array to hold the pointers to the new lines. - char **pp = xrealloc(y_ptr->y_array, (y_ptr->y_size + newlines) * sizeof(char_u *)); + char **pp = xrealloc(y_ptr->y_array, (y_ptr->y_size + newlines) * sizeof(char *)); y_ptr->y_array = pp; size_t lnum = y_ptr->y_size; // The current line number. @@ -5733,8 +5213,8 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, // Find the end of each line and save it into the array. if (str_list) { - for (char_u **ss = (char_u **)str; *ss != NULL; ++ss, ++lnum) { - size_t ss_len = STRLEN(*ss); + for (char **ss = (char **)str; *ss != NULL; ss++, lnum++) { + size_t ss_len = strlen(*ss); pp[lnum] = xmemdupz(*ss, ss_len); if (ss_len > maxlen) { maxlen = ss_len; @@ -5742,18 +5222,17 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, } } else { size_t line_len; - for (const char_u *start = (char_u *)str, *end = (char_u *)str + len; + for (const char *start = str, *end = str + len; start < end + extraline; start += line_len + 1, lnum++) { assert(end - start >= 0); - line_len = (size_t)((char_u *)xmemscan(start, '\n', - (size_t)(end - start)) - start); + line_len = (size_t)((char *)xmemscan(start, '\n', (size_t)(end - start)) - start); if (line_len > maxlen) { maxlen = line_len; } // When appending, copy the previous line and free it after. - size_t extra = append ? STRLEN(pp[--lnum]) : 0; + size_t extra = append ? strlen(pp[--lnum]) : 0; char *s = xmallocz(line_len + extra); if (extra > 0) { memcpy(s, pp[lnum], extra); @@ -5799,8 +5278,8 @@ void clear_oparg(oparg_T *oap) /// line, stopping if it encounters an end-of-line (NUL byte). In that /// case, eol_size will be added to the character count to account for /// the size of the EOL character. -static varnumber_T line_count_info(char_u *line, varnumber_T *wc, varnumber_T *cc, - varnumber_T limit, int eol_size) +static varnumber_T line_count_info(char *line, varnumber_T *wc, varnumber_T *cc, varnumber_T limit, + int eol_size) { varnumber_T i; varnumber_T words = 0; @@ -5817,7 +5296,7 @@ static varnumber_T line_count_info(char_u *line, varnumber_T *wc, varnumber_T *c is_word = 1; } chars++; - i += utfc_ptr2len((char *)line + i); + i += utfc_ptr2len(line + i); } if (is_word) { @@ -5841,9 +5320,9 @@ static varnumber_T line_count_info(char_u *line, varnumber_T *wc, varnumber_T *c /// @param dict when not NULL, store the info there instead of showing it. void cursor_pos_info(dict_T *dict) { - char_u *p; - char_u buf1[50]; - char_u buf2[40]; + char *p; + char buf1[50]; + char buf2[40]; linenr_T lnum; varnumber_T byte_count = 0; varnumber_T bom_count = 0; @@ -5883,12 +5362,12 @@ void cursor_pos_info(dict_T *dict) max_pos = VIsual; } if (*p_sel == 'e' && max_pos.col > 0) { - --max_pos.col; + max_pos.col--; } if (l_VIsual_mode == Ctrl_V) { - char_u *const saved_sbr = p_sbr; - char_u *const saved_w_sbr = curwin->w_p_sbr; + char *const saved_sbr = p_sbr; + char *const saved_w_sbr = curwin->w_p_sbr; // Make 'sbr' empty for a moment to get the correct size. p_sbr = empty_option; @@ -5896,8 +5375,7 @@ void cursor_pos_info(dict_T *dict) oparg.is_VIsual = true; oparg.motion_type = kMTBlockWise; oparg.op_type = OP_NOP; - getvcols(curwin, &min_pos, &max_pos, - &oparg.start_vcol, &oparg.end_vcol); + getvcols(curwin, &min_pos, &max_pos, &oparg.start_vcol, &oparg.end_vcol); p_sbr = saved_sbr; curwin->w_p_sbr = saved_w_sbr; if (curwin->w_curswant == MAXCOL) { @@ -5913,7 +5391,7 @@ void cursor_pos_info(dict_T *dict) line_count_selected = max_pos.lnum - min_pos.lnum + 1; } - for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) { + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) { // Check for a CTRL-C every 100000 characters. if (byte_count > last_check) { os_breakcheck(); @@ -5926,7 +5404,7 @@ void cursor_pos_info(dict_T *dict) // Do extra processing for VIsual mode. if (l_VIsual_active && lnum >= min_pos.lnum && lnum <= max_pos.lnum) { - char_u *s = NULL; + char *s = NULL; long len = 0L; switch (l_VIsual_mode) { @@ -5958,7 +5436,7 @@ void cursor_pos_info(dict_T *dict) if (lnum == curbuf->b_ml.ml_line_count && !curbuf->b_p_eol && (curbuf->b_p_bin || !curbuf->b_p_fixeol) - && (long)STRLEN(s) < len) { + && (long)strlen(s) < len) { byte_count_cursor -= eol_size; } } @@ -5990,7 +5468,7 @@ void cursor_pos_info(dict_T *dict) getvcols(curwin, &min_pos, &max_pos, &min_pos.col, &max_pos.col); int64_t cols; STRICT_SUB(oparg.end_vcol + 1, oparg.start_vcol, &cols, int64_t); - vim_snprintf((char *)buf1, sizeof(buf1), _("%" PRId64 " Cols; "), + vim_snprintf(buf1, sizeof(buf1), _("%" PRId64 " Cols; "), cols); } else { buf1[0] = NUL; @@ -5998,7 +5476,7 @@ void cursor_pos_info(dict_T *dict) if (char_count_cursor == byte_count_cursor && char_count == byte_count) { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Selected %s%" PRId64 " of %" PRId64 " Lines;" " %" PRId64 " of %" PRId64 " Words;" " %" PRId64 " of %" PRId64 " Bytes"), @@ -6007,7 +5485,7 @@ void cursor_pos_info(dict_T *dict) (int64_t)word_count_cursor, (int64_t)word_count, (int64_t)byte_count_cursor, (int64_t)byte_count); } else { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Selected %s%" PRId64 " of %" PRId64 " Lines;" " %" PRId64 " of %" PRId64 " Words;" " %" PRId64 " of %" PRId64 " Chars;" @@ -6021,28 +5499,28 @@ void cursor_pos_info(dict_T *dict) } else { p = get_cursor_line_ptr(); validate_virtcol(); - col_print((char *)buf1, sizeof(buf1), (int)curwin->w_cursor.col + 1, + col_print(buf1, sizeof(buf1), (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); - col_print((char *)buf2, sizeof(buf2), (int)STRLEN(p), linetabsize(p)); + col_print((char *)buf2, sizeof(buf2), (int)strlen(p), linetabsize(p)); if (char_count_cursor == byte_count_cursor && char_count == byte_count) { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Col %s of %s; Line %" PRId64 " of %" PRId64 ";" " Word %" PRId64 " of %" PRId64 ";" " Byte %" PRId64 " of %" PRId64 ""), - (char *)buf1, (char *)buf2, + buf1, buf2, (int64_t)curwin->w_cursor.lnum, (int64_t)curbuf->b_ml.ml_line_count, (int64_t)word_count_cursor, (int64_t)word_count, (int64_t)byte_count_cursor, (int64_t)byte_count); } else { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Col %s of %s; Line %" PRId64 " of %" PRId64 ";" " Word %" PRId64 " of %" PRId64 ";" " Char %" PRId64 " of %" PRId64 ";" " Byte %" PRId64 " of %" PRId64 ""), - (char *)buf1, (char *)buf2, + buf1, buf2, (int64_t)curwin->w_cursor.lnum, (int64_t)curbuf->b_ml.ml_line_count, (int64_t)word_count_cursor, (int64_t)word_count, @@ -6054,19 +5532,19 @@ void cursor_pos_info(dict_T *dict) bom_count = bomb_size(); if (dict == NULL && bom_count > 0) { - const size_t len = STRLEN(IObuff); - vim_snprintf((char *)IObuff + len, IOSIZE - len, + const size_t len = strlen(IObuff); + vim_snprintf(IObuff + len, IOSIZE - len, _("(+%" PRId64 " for BOM)"), (int64_t)bom_count); } if (dict == NULL) { // Don't shorten this message, the user asked for it. p = p_shm; - p_shm = (char_u *)""; + p_shm = ""; if (p_ch < 1) { msg_start(); msg_scroll = true; } - msg((char *)IObuff); + msg(IObuff); p_shm = p; } } @@ -6101,13 +5579,22 @@ static void op_colon(oparg_T *oap) } else { stuffnumReadbuff((long)oap->start.lnum); } - if (oap->end.lnum != oap->start.lnum) { + + // When using !! on a closed fold the range ".!" works best to operate + // on, it will be made the whole closed fold later. + linenr_T endOfStartFold = oap->start.lnum; + (void)hasFolding(oap->start.lnum, NULL, &endOfStartFold); + if (oap->end.lnum != oap->start.lnum && oap->end.lnum != endOfStartFold) { + // Make it a range with the end line. stuffcharReadbuff(','); if (oap->end.lnum == curwin->w_cursor.lnum) { stuffcharReadbuff('.'); } else if (oap->end.lnum == curbuf->b_ml.ml_line_count) { stuffcharReadbuff('$'); - } else if (oap->start.lnum == curwin->w_cursor.lnum) { + } else if (oap->start.lnum == curwin->w_cursor.lnum + // do not use ".+number" for a closed fold, it would count + // folded lines twice + && !hasFolding(oap->end.lnum, NULL, NULL)) { stuffReadbuff(".+"); stuffnumReadbuff(oap->line_count - 1); } else { @@ -6139,10 +5626,11 @@ static void op_colon(oparg_T *oap) static Callback opfunc_cb; /// Process the 'operatorfunc' option value. -/// @return OK or FAIL -int set_operatorfunc_option(void) +void set_operatorfunc_option(char **errmsg) { - return option_set_callback_func(p_opfunc, &opfunc_cb); + if (option_set_callback_func(p_opfunc, &opfunc_cb) == FAIL) { + *errmsg = e_invarg; + } } #if defined(EXITFREE) @@ -6152,12 +5640,17 @@ void free_operatorfunc_option(void) } #endif +/// Mark the global 'operatorfunc' callback with "copyID" so that it is not +/// garbage collected. +bool set_ref_in_opfunc(int copyID) +{ + return set_ref_in_callback(&opfunc_cb, copyID, NULL, NULL); +} + /// Handle the "g@" operator: call 'operatorfunc'. static void op_function(const oparg_T *oap) FUNC_ATTR_NONNULL_ALL { - const TriState save_virtual_op = virtual_op; - const bool save_finish_op = finish_op; const pos_T orig_start = curbuf->b_op_start; const pos_T orig_end = curbuf->b_op_end; @@ -6184,9 +5677,11 @@ static void op_function(const oparg_T *oap) // Reset virtual_op so that 'virtualedit' can be changed in the // function. + const TriState save_virtual_op = virtual_op; virtual_op = kNone; // Reset finish_op so that mode() returns the right value. + const bool save_finish_op = finish_op; finish_op = false; typval_T rettv; @@ -6303,10 +5798,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) const bool redo_yank = vim_strchr(p_cpo, CPO_YANK) != NULL && !gui_yank; // Avoid a problem with unwanted linebreaks in block mode - if (curwin->w_p_lbr) { - curwin->w_valid &= ~VALID_VIRTCOL; - } - curwin->w_p_lbr = false; + (void)reset_lbr(); oap->is_VIsual = VIsual_active; if (oap->motion_force == 'V') { oap->motion_type = kMTLineWise; @@ -6353,7 +5845,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) // If 'cpoptions' does not contain 'r', insert the search // pattern to really repeat the same command. if (vim_strchr(p_cpo, CPO_REDO) == NULL) { - AppendToRedobuffLit((char *)cap->searchbuf, -1); + AppendToRedobuffLit(cap->searchbuf, -1); } AppendToRedobuff(NL_STR); } else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) { @@ -6363,7 +5855,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (repeat_cmdline == NULL) { ResetRedobuff(); } else { - AppendToRedobuffLit((char *)repeat_cmdline, -1); + AppendToRedobuffLit(repeat_cmdline, -1); AppendToRedobuff(NL_STR); XFREE_CLEAR(repeat_cmdline); } @@ -6413,10 +5905,10 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (lt(VIsual, curwin->w_cursor)) { VIsual.col = 0; curwin->w_cursor.col = - (colnr_T)STRLEN(ml_get(curwin->w_cursor.lnum)); + (colnr_T)strlen(ml_get(curwin->w_cursor.lnum)); } else { curwin->w_cursor.col = 0; - VIsual.col = (colnr_T)STRLEN(ml_get(VIsual.lnum)); + VIsual.col = (colnr_T)strlen(ml_get(VIsual.lnum)); } VIsual_mode = 'v'; } else if (VIsual_mode == 'v') { @@ -6446,7 +5938,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) || oap->motion_type == kMTLineWise) && hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { - curwin->w_cursor.col = (colnr_T)STRLEN(get_cursor_line_ptr()); + curwin->w_cursor.col = (colnr_T)strlen(get_cursor_line_ptr()); } } oap->end = curwin->w_cursor; @@ -6464,7 +5956,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) curwin->w_cursor.col = 0; } if (hasFolding(oap->start.lnum, NULL, &oap->start.lnum)) { - oap->start.col = (colnr_T)STRLEN(ml_get(oap->start.lnum)); + oap->start.col = (colnr_T)strlen(ml_get(oap->start.lnum)); } } oap->end = oap->start; @@ -6595,15 +6087,15 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) || oap->op_type == OP_FILTER) && oap->motion_force == NUL) { // Make sure redrawing is correct. - curwin->w_p_lbr = lbr_saved; - redraw_curbuf_later(INVERTED); + restore_lbr(lbr_saved); + redraw_curbuf_later(UPD_INVERTED); } } } // Include the trailing byte of a multi-byte char. if (oap->inclusive) { - const int l = utfc_ptr2len((char *)ml_get_pos(&oap->end)); + const int l = utfc_ptr2len(ml_get_pos(&oap->end)); if (l > 1) { oap->end.col += l - 1; } @@ -6627,8 +6119,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) // 'modifiable is off or creating a fold. if (oap->is_VIsual && (oap->empty || !MODIFIABLE(curbuf) || oap->op_type == OP_FOLD)) { - curwin->w_p_lbr = lbr_saved; - redraw_curbuf_later(INVERTED); + restore_lbr(lbr_saved); + redraw_curbuf_later(UPD_INVERTED); } // If the end of an operator is in column one while oap->motion_type @@ -6648,7 +6140,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (inindent(0)) { oap->motion_type = kMTLineWise; } else { - oap->end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); + oap->end.col = (colnr_T)strlen(ml_get(oap->end.lnum)); if (oap->end.col) { oap->end.col--; oap->inclusive = true; @@ -6703,7 +6195,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) CancelRedo(); } } else { - curwin->w_p_lbr = lbr_saved; + restore_lbr(lbr_saved); oap->excl_tr_ws = cap->cmdchar == 'z'; (void)op_yank(oap, !gui_yank); } @@ -6727,10 +6219,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) restart_edit = 0; // Restore linebreak, so that when the user edits it looks as before. - curwin->w_p_lbr = lbr_saved; + restore_lbr(lbr_saved); - // Reset finish_op now, don't want it set inside edit(). - finish_op = false; if (op_change(oap)) { // will call edit() cap->retval |= CA_COMMAND_BUSY; } @@ -6754,7 +6244,11 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) // If 'equalprg' is empty, do the indenting internally. if (oap->op_type == OP_INDENT && *get_equalprg() == NUL) { if (curbuf->b_p_lisp) { - op_reindent(oap, get_lisp_indent); + if (use_indentexpr_for_lisp()) { + op_reindent(oap, get_expr_indent); + } else { + op_reindent(oap, get_lisp_indent); + } break; } op_reindent(oap, @@ -6798,9 +6292,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) case OP_FUNCTION: { redo_VIsual_T save_redo_VIsual = redo_VIsual; - // Restore linebreak, so that when the user edits it looks as - // before. - curwin->w_p_lbr = lbr_saved; + // Restore linebreak, so that when the user edits it looks as before. + restore_lbr(lbr_saved); // call 'operatorfunc' op_function(oap); @@ -6824,12 +6317,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) restart_edit = 0; // Restore linebreak, so that when the user edits it looks as before. - curwin->w_p_lbr = lbr_saved; + restore_lbr(lbr_saved); op_insert(oap, cap->count1); // Reset linebreak, so that formatting works correctly. - curwin->w_p_lbr = false; + (void)reset_lbr(); // TODO(brammool): when inserting in several lines, should format all // the lines. @@ -6850,7 +6343,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) CancelRedo(); } else { // Restore linebreak, so that when the user edits it looks as before. - curwin->w_p_lbr = lbr_saved; + restore_lbr(lbr_saved); op_replace(oap, cap->nchar); } @@ -6888,7 +6381,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) CancelRedo(); } else { VIsual_active = true; - curwin->w_p_lbr = lbr_saved; + restore_lbr(lbr_saved); op_addsub(oap, (linenr_T)cap->count1, redo_VIsual.rv_arg); VIsual_active = false; } @@ -6903,7 +6396,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (!p_sol && oap->motion_type == kMTLineWise && !oap->end_adjusted && (oap->op_type == OP_LSHIFT || oap->op_type == OP_RSHIFT || oap->op_type == OP_DELETE)) { - curwin->w_p_lbr = false; + (void)reset_lbr(); coladvance(curwin->w_curswant = old_col); } } else { @@ -6912,7 +6405,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) clearop(oap); motion_force = NUL; } - curwin->w_p_lbr = lbr_saved; + restore_lbr(lbr_saved); } /// Check if the default register (used in an unnamed paste) should be a @@ -6950,7 +6443,7 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing) } if (!eval_has_provider("clipboard")) { - if (batch_change_count == 1 && !quiet + if (batch_change_count <= 1 && !quiet && (!clipboard_didwarn || (explicit_cb_reg && !redirecting()))) { clipboard_didwarn = true; // Do NOT error (emsg()) here--if it interrupts :redir we get into @@ -7042,7 +6535,7 @@ bool prepare_yankreg_from_object(yankreg_T *reg, String regtype, size_t lines) void finish_yankreg_from_object(yankreg_T *reg, bool clipboard_adjust) { - if (reg->y_size > 0 && STRLEN(reg->y_array[reg->y_size - 1]) == 0) { + if (reg->y_size > 0 && strlen(reg->y_array[reg->y_size - 1]) == 0) { // a known-to-be charwise yank might have a final linebreak // but otherwise there is no line after the final newline if (reg->y_type != kMTCharWise) { @@ -7063,7 +6556,7 @@ void finish_yankreg_from_object(yankreg_T *reg, bool clipboard_adjust) if (reg->y_type == kMTBlockWise) { size_t maxlen = 0; for (size_t i = 0; i < reg->y_size; i++) { - size_t rowlen = STRLEN(reg->y_array[i]); + size_t rowlen = strlen(reg->y_array[i]); if (rowlen > maxlen) { maxlen = rowlen; } @@ -7106,8 +6599,8 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) if (TV_LIST_ITEM_TV(tv_list_last(res))->v_type != VAR_STRING) { goto err; } - char_u *regtype = (char_u *)TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string; - if (regtype == NULL || STRLEN(regtype) > 1) { + char *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string; + if (regtype == NULL || strlen(regtype) > 1) { goto err; } switch (regtype[0]) { @@ -7150,7 +6643,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) reg->y_array[tv_idx++] = xstrdupnul((const char *)TV_LIST_ITEM_TV(li)->vval.v_string); }); - if (reg->y_size > 0 && STRLEN(reg->y_array[reg->y_size - 1]) == 0) { + if (reg->y_size > 0 && strlen(reg->y_array[reg->y_size - 1]) == 0) { // a known-to-be charwise yank might have a final linebreak // but otherwise there is no line after the final newline if (reg->y_type != kMTCharWise) { @@ -7169,7 +6662,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) if (reg->y_type == kMTBlockWise) { size_t maxlen = 0; for (size_t i = 0; i < reg->y_size; i++) { - size_t rowlen = STRLEN(reg->y_array[i]); + size_t rowlen = strlen(reg->y_array[i]); if (rowlen > maxlen) { maxlen = rowlen; } @@ -7419,13 +6912,13 @@ bcount_t get_region_bytecount(buf_T *buf, linenr_T start_lnum, linenr_T end_lnum return end_col - start_col; } const char *first = (const char *)ml_get_buf(buf, start_lnum, false); - bcount_t deleted_bytes = (bcount_t)STRLEN(first) - start_col + 1; + bcount_t deleted_bytes = (bcount_t)strlen(first) - start_col + 1; for (linenr_T i = 1; i <= end_lnum - start_lnum - 1; i++) { if (start_lnum + i > max_lnum) { return deleted_bytes; } - deleted_bytes += (bcount_t)STRLEN(ml_get_buf(buf, start_lnum + i, false)) + 1; + deleted_bytes += (bcount_t)strlen(ml_get_buf(buf, start_lnum + i, false)) + 1; } if (end_lnum > max_lnum) { return deleted_bytes; diff --git a/src/nvim/ops.h b/src/nvim/ops.h index 05893c9940..75ea1853a0 100644 --- a/src/nvim/ops.h +++ b/src/nvim/ops.h @@ -2,14 +2,17 @@ #define NVIM_OPS_H #include <stdbool.h> +#include <stddef.h> #include "nvim/ascii.h" #include "nvim/eval/typval.h" -#include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/extmark.h" #include "nvim/macros.h" -#include "nvim/normal.h" // for MotionType and oparg_T +#include "nvim/normal.h" #include "nvim/os/time.h" +#include "nvim/pos.h" #include "nvim/types.h" typedef int (*Indenter)(void); @@ -23,15 +26,13 @@ typedef int (*Indenter)(void); #define PUT_LINE_FORWARD 32 // put linewise register below Visual sel. #define PUT_BLOCK_INNER 64 // in block mode, do not add trailing spaces -/* - * Registers: - * 0 = register for latest (unnamed) yank - * 1..9 = registers '1' to '9', for deletes - * 10..35 = registers 'a' to 'z' - * 36 = delete register '-' - * 37 = selection register '*' - * 38 = clipboard register '+' - */ +// Registers: +// 0 = register for latest (unnamed) yank +// 1..9 = registers '1' to '9', for deletes +// 10..35 = registers 'a' to 'z' +// 36 = delete register '-' +// 37 = selection register '*' +// 38 = clipboard register '+' #define DELETION_REGISTER 36 #define NUM_SAVED_REGISTERS 37 // The following registers should not be saved in ShaDa file: diff --git a/src/nvim/option.c b/src/nvim/option.c index 0acfc5c261..1e7cd62d22 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -21,37 +21,46 @@ #define IN_OPTION_C #include <assert.h> +#include <ctype.h> #include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> -#include "nvim/arglist.h" +#include "auto/config.h" +#include "nvim/api/private/defs.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" +#include "nvim/cmdexpand.h" #include "nvim/cursor_shape.h" #include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" -#include "nvim/ex_cmds2.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" -#include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" -#include "nvim/getchar.h" -#include "nvim/hardcopy.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" +#include "nvim/insexpand.h" #include "nvim/keycodes.h" +#include "nvim/locale.h" +#include "nvim/log.h" #include "nvim/macros.h" #include "nvim/mapping.h" #include "nvim/mbyte.h" @@ -64,60 +73,49 @@ #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/option_defs.h" #include "nvim/optionstr.h" #include "nvim/os/os.h" -#include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/popupmenu.h" +#include "nvim/pos.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/screen.h" +#include "nvim/search.h" +#include "nvim/sign_defs.h" #include "nvim/spell.h" #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" #include "nvim/strings.h" -#include "nvim/syntax.h" +#include "nvim/tag.h" +#include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" -#include "nvim/ui_compositor.h" #include "nvim/undo.h" #include "nvim/vim.h" #include "nvim/window.h" -#ifdef WIN32 +#ifdef MSWIN # include "nvim/os/pty_conpty_win.h" #endif #include "nvim/api/extmark.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/lua/executor.h" #include "nvim/os/input.h" #include "nvim/os/lang.h" -/* - * The options that are local to a window or buffer have "indir" set to one of - * these values. Special values: - * PV_NONE: global option. - * PV_WIN is added: window-local option - * PV_BUF is added: buffer-local option - * PV_BOTH is added: global option which also has a local value. - */ -#define PV_BOTH 0x1000 -#define PV_WIN 0x2000 -#define PV_BUF 0x4000 -#define PV_MASK 0x0fff -#define OPT_WIN(x) (idopt_T)(PV_WIN + (int)(x)) -#define OPT_BUF(x) (idopt_T)(PV_BUF + (int)(x)) -#define OPT_BOTH(x) (idopt_T)(PV_BOTH + (int)(x)) - -// WV_ and BV_ values get typecasted to this for the "indir" field -typedef enum { - PV_NONE = 0, - PV_MAXVAL = 0xffff, // to avoid warnings for value out of range -} idopt_T; - -/* - * Options local to a window have a value local to a buffer and global to all - * buffers. Indicate this by setting "var" to VAR_WIN. - */ -#define VAR_WIN ((char_u *)-1) +static char e_unknown_option[] + = N_("E518: Unknown option"); +static char e_not_allowed_in_modeline[] + = N_("E520: Not allowed in a modeline"); +static char e_not_allowed_in_modeline_when_modelineexpr_is_off[] + = N_("E992: Not allowed in a modeline when 'modelineexpr' is off"); +static char e_key_code_not_set[] + = N_("E846: Key code not set"); +static char e_number_required_after_equal[] + = N_("E521: Number required after ="); +static char e_preview_window_already_exists[] + = N_("E590: A preview window already exists"); static char *p_term = NULL; static char *p_ttytype = NULL; @@ -134,29 +132,14 @@ static int p_et_nopaste; static long p_sts_nopaste; static long p_tw_nopaste; static long p_wm_nopaste; -static char_u *p_vsts_nopaste; - -typedef struct vimoption { - char *fullname; // full option name - char *shortname; // permissible abbreviation - uint32_t flags; // see below - char_u *var; // global option: pointer to variable; - // window-local option: VAR_WIN; - // buffer-local option: global value - idopt_T indir; // global option: PV_NONE; - // local option: indirect option index - char_u *def_val; // default values for variable (neovim!!) - LastSet last_set; // script in which the option was last set -} vimoption_T; - -/* - * options[] is initialized here. - * The order of the options MUST be alphabetic for ":set all" and findoption(). - * All option names MUST start with a lowercase letter (for findoption()). - * Exception: "t_" options are at the end. - * The options with a NULL variable are 'hidden': a set command for them is - * ignored and they are not printed. - */ +static char *p_vsts_nopaste; + +// options[] is initialized here. +// The order of the options MUST be alphabetic for ":set all" and findoption(). +// All option names MUST start with a lowercase letter (for findoption()). +// Exception: "t_" options are at the end. +// The options with a NULL variable are 'hidden': a set command for them is +// ignored and they are not printed. #ifdef INCLUDE_GENERATED_DECLARATIONS # include "options.generated.h" @@ -164,10 +147,24 @@ typedef struct vimoption { #define OPTION_COUNT ARRAY_SIZE(options) +typedef enum { + OP_NONE = 0, + OP_ADDING, ///< "opt+=arg" + OP_PREPENDING, ///< "opt^=arg" + OP_REMOVING, ///< "opt-=arg" +} set_op_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "option.c.generated.h" #endif +void set_init_tablocal(void) +{ + // susy baka: cmdheight calls itself OPT_GLOBAL but is really tablocal! + int ch_idx = findoption("cmdheight"); + p_ch = (long)options[ch_idx].def_val; +} + /// Initialize the options, first part. /// /// Called only once from main(), just after creating the first buffer. @@ -178,14 +175,10 @@ typedef struct vimoption { /// editor state initialized here. Do logging in set_init_2 or later. void set_init_1(bool clean_arg) { - int opt_idx; - langmap_init(); - /* - * Find default value for 'shell' option. - * Don't use it if it is empty. - */ + // Find default value for 'shell' option. + // Don't use it if it is empty. { const char *shell = os_getenv("SHELL"); if (shell != NULL) { @@ -200,10 +193,8 @@ void set_init_1(bool clean_arg) } } - /* - * Set the default for 'backupskip' to include environment variables for - * temp files. - */ + // Set the default for 'backupskip' to include environment variables for + // temp files. { #ifdef UNIX static char *(names[4]) = { "", "TMPDIR", "TEMP", "TMP" }; @@ -211,7 +202,7 @@ void set_init_1(bool clean_arg) static char *(names[3]) = { "TMPDIR", "TEMP", "TMP" }; #endif garray_T ga; - opt_idx = findoption("backupskip"); + int opt_idx = findoption("backupskip"); ga_init(&ga, 1, 100); for (size_t n = 0; n < ARRAY_SIZE(names); n++) { @@ -225,7 +216,7 @@ void set_init_1(bool clean_arg) p = "/tmp"; # endif mustfree = false; - } else + } else // NOLINT(readability/braces) #endif { p = vim_getenv(names[n]); @@ -237,7 +228,7 @@ void set_init_1(bool clean_arg) xstrlcpy(item, p, len); add_pathsep(item); xstrlcat(item, "*", len); - if (find_dup_item(ga.ga_data, (char_u *)item, options[opt_idx].flags) + if (find_dup_item(ga.ga_data, item, options[opt_idx].flags) == NULL) { ga_grow(&ga, (int)len); if (!GA_EMPTY(&ga)) { @@ -260,19 +251,14 @@ void set_init_1(bool clean_arg) } { - char_u *cdpath; - char_u *buf; - int i; - int j; - // Initialize the 'cdpath' option's default value. - cdpath = (char_u *)vim_getenv("CDPATH"); + char *cdpath = vim_getenv("CDPATH"); if (cdpath != NULL) { - buf = xmalloc(2 * STRLEN(cdpath) + 2); + char *buf = xmalloc(2 * strlen(cdpath) + 2); { buf[0] = ','; // start with ",", current dir first - j = 1; - for (i = 0; cdpath[i] != NUL; i++) { + int j = 1; + for (int i = 0; cdpath[i] != NUL; i++) { if (vim_ispathlistsep(cdpath[i])) { buf[j++] = ','; } else { @@ -283,7 +269,7 @@ void set_init_1(bool clean_arg) } } buf[j] = NUL; - opt_idx = findoption("cdpath"); + int opt_idx = findoption("cdpath"); if (opt_idx >= 0) { options[opt_idx].def_val = buf; options[opt_idx].flags |= P_DEF_ALLOCED; @@ -295,28 +281,6 @@ void set_init_1(bool clean_arg) } } -#if defined(MSWIN) || defined(MAC) - // Set print encoding on platforms that don't default to latin1 - set_string_default("printencoding", "hp-roman8", false); -#endif - - // 'printexpr' must be allocated to be able to evaluate it. - set_string_default("printexpr", -#ifdef UNIX - "system(['lpr'] " - "+ (empty(&printdevice)?[]:['-P', &printdevice]) " - "+ [v:fname_in])" - ". delete(v:fname_in)" - "+ v:shell_error", -#elif defined(MSWIN) - "system(['copy', v:fname_in, " - "empty(&printdevice)?'LPT1':&printdevice])" - ". delete(v:fname_in)", -#else - "", -#endif - false); - char *backupdir = stdpaths_user_state_subpath("backup", 2, true); const size_t backupdir_len = strlen(backupdir); backupdir = xrealloc(backupdir, backupdir_len + 3); @@ -339,10 +303,8 @@ void set_init_1(bool clean_arg) rtp = NULL; // ownership taken } - /* - * Set all the options (except the terminal options) to their default - * value. Also set the global value for local options. - */ + // Set all the options (except the terminal options) to their default + // value. Also set the global value for local options. set_options_default(0); curbuf->b_p_initialized = true; @@ -365,34 +327,32 @@ void set_init_1(bool clean_arg) // didset_options() because it only depends on 'encoding'. init_spell_chartab(); - /* - * Expand environment variables and things like "~" for the defaults. - * If option_expand() returns non-NULL the variable is expanded. This can - * only happen for non-indirect options. - * Also set the default to the expanded value, so ":set" does not list - * them. - * Don't set the P_ALLOCED flag, because we don't want to free the - * default. - */ - for (opt_idx = 0; options[opt_idx].fullname; opt_idx++) { - if (options[opt_idx].flags & P_NO_DEF_EXP) { + // Expand environment variables and things like "~" for the defaults. + // If option_expand() returns non-NULL the variable is expanded. This can + // only happen for non-indirect options. + // Also set the default to the expanded value, so ":set" does not list + // them. + // Don't set the P_ALLOCED flag, because we don't want to free the + // default. + for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) { + vimoption_T *opt = &options[opt_idx]; + if (opt->flags & P_NO_DEF_EXP) { continue; } char *p; - if ((options[opt_idx].flags & P_GETTEXT) - && options[opt_idx].var != NULL) { - p = _(*(char **)options[opt_idx].var); + if ((opt->flags & P_GETTEXT) && opt->var != NULL) { + p = _(*(char **)opt->var); } else { - p = (char *)option_expand(opt_idx, NULL); + p = option_expand(opt_idx, NULL); } if (p != NULL) { p = xstrdup(p); - *(char **)options[opt_idx].var = p; - if (options[opt_idx].flags & P_DEF_ALLOCED) { - xfree(options[opt_idx].def_val); + *(char **)opt->var = p; + if (opt->flags & P_DEF_ALLOCED) { + xfree(opt->def_val); } - options[opt_idx].def_val = (char_u *)p; - options[opt_idx].flags |= P_DEF_ALLOCED; + opt->def_val = p; + opt->flags |= P_DEF_ALLOCED; } } @@ -404,7 +364,7 @@ void set_init_1(bool clean_arg) // NOTE: mlterm's author is being asked to 'set' a variable // instead of an environment variable due to inheritance. if (os_env_exists("MLTERM")) { - set_option_value("tbidi", 1L, NULL, 0); + set_option_value_give_err("tbidi", 1L, NULL, 0); } didset_options2(); @@ -414,17 +374,17 @@ void set_init_1(bool clean_arg) // enc_locale() will try to find the encoding of the current locale. // This will be used when 'default' is used as encoding specifier // in 'fileencodings' - char_u *p = enc_locale(); + char *p = enc_locale(); if (p == NULL) { // use utf-8 as 'default' if locale encoding can't be detected. - p = (char_u *)xmemdupz(S_LEN("utf-8")); + p = xmemdupz(S_LEN("utf-8")); } fenc_default = p; #ifdef HAVE_WORKING_LIBINTL // GNU gettext 0.10.37 supports this feature: set the codeset used for // translated messages independently from the current locale. - (void)bind_textdomain_codeset(PROJECT_NAME, (char *)p_enc); + (void)bind_textdomain_codeset(PROJECT_NAME, p_enc); #endif // Set the default for 'helplang'. @@ -435,32 +395,32 @@ void set_init_1(bool clean_arg) /// This does not take care of side effects! /// /// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL -static void set_option_default(int opt_idx, int opt_flags) +static void set_option_default(const int opt_idx, int opt_flags) { - char_u *varp; // pointer to variable for current option int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; - varp = get_varp_scope(&(options[opt_idx]), both ? OPT_LOCAL : opt_flags); - uint32_t flags = options[opt_idx].flags; + // pointer to variable for current option + vimoption_T *opt = &options[opt_idx]; + char_u *varp = (char_u *)get_varp_scope(opt, both ? OPT_LOCAL : opt_flags); + uint32_t flags = opt->flags; if (varp != NULL) { // skip hidden option, nothing to do for it if (flags & P_STRING) { // Use set_string_option_direct() for local options to handle // freeing and allocating the value. - if (options[opt_idx].indir != PV_NONE) { - set_string_option_direct(NULL, opt_idx, - (char *)options[opt_idx].def_val, opt_flags, 0); + if (opt->indir != PV_NONE) { + set_string_option_direct(NULL, opt_idx, opt->def_val, opt_flags, 0); } else { if ((opt_flags & OPT_FREE) && (flags & P_ALLOCED)) { - free_string_option(*(char_u **)(varp)); + free_string_option(*(char **)(varp)); } - *(char_u **)varp = options[opt_idx].def_val; - options[opt_idx].flags &= ~P_ALLOCED; + *(char **)varp = opt->def_val; + opt->flags &= ~P_ALLOCED; } } else if (flags & P_NUM) { - if (options[opt_idx].indir == PV_SCROLL) { + if (opt->indir == PV_SCROLL) { win_comp_scroll(curwin); } else { - long def_val = (long)options[opt_idx].def_val; + long def_val = (long)opt->def_val; if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) { // 'scrolloff' and 'sidescrolloff' local values have a @@ -471,21 +431,21 @@ static void set_option_default(int opt_idx, int opt_flags) } // May also set global value for local option. if (both) { - *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = + *(long *)get_varp_scope(opt, OPT_GLOBAL) = def_val; } } } else { // P_BOOL - *(int *)varp = (int)(intptr_t)options[opt_idx].def_val; + *(int *)varp = (int)(intptr_t)opt->def_val; #ifdef UNIX // 'modeline' defaults to off for root - if (options[opt_idx].indir == PV_ML && getuid() == ROOT_UID) { + if (opt->indir == PV_ML && getuid() == ROOT_UID) { *(int *)varp = false; } #endif // May also set global value for local option. if (both) { - *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = + *(int *)get_varp_scope(opt, OPT_GLOBAL) = *(int *)varp; } } @@ -528,32 +488,31 @@ static void set_string_default(const char *name, char *val, bool allocated) { int opt_idx = findoption(name); if (opt_idx >= 0) { - if (options[opt_idx].flags & P_DEF_ALLOCED) { - xfree(options[opt_idx].def_val); + vimoption_T *opt = &options[opt_idx]; + if (opt->flags & P_DEF_ALLOCED) { + xfree(opt->def_val); } - options[opt_idx].def_val = allocated - ? (char_u *)val - : (char_u *)xstrdup(val); - options[opt_idx].flags |= P_DEF_ALLOCED; + opt->def_val = allocated ? val : xstrdup(val); + opt->flags |= P_DEF_ALLOCED; } } -// For an option value that contains comma separated items, find "newval" in -// "origval". Return NULL if not found. -static char_u *find_dup_item(char_u *origval, const char_u *newval, uint32_t flags) +/// For an option value that contains comma separated items, find "newval" in +/// "origval". Return NULL if not found. +static char *find_dup_item(char *origval, const char *newval, uint32_t flags) FUNC_ATTR_NONNULL_ARG(2) { - int bs = 0; - if (origval == NULL) { return NULL; } - const size_t newlen = STRLEN(newval); - for (char_u *s = origval; *s != NUL; s++) { + int bs = 0; + + const size_t newlen = strlen(newval); + for (char *s = origval; *s != NUL; s++) { if ((!(flags & P_COMMA) || s == origval || (s[-1] == ',' && !(bs & 1))) - && STRNCMP(s, newval, newlen) == 0 + && strncmp(s, newval, newlen) == 0 && (!(flags & P_COMMA) || s[newlen] == ',' || s[newlen] == NUL)) { return s; } @@ -574,11 +533,9 @@ static char_u *find_dup_item(char_u *origval, const char_u *newval, uint32_t fla /// Used for 'lines' and 'columns'. void set_number_default(char *name, long val) { - int opt_idx; - - opt_idx = findoption(name); + int opt_idx = findoption(name); if (opt_idx >= 0) { - options[opt_idx].def_val = (char_u *)(intptr_t)val; + options[opt_idx].def_val = (char *)(intptr_t)val; } } @@ -590,17 +547,18 @@ void free_all_options(void) if (options[i].indir == PV_NONE) { // global option: free value and default value. if ((options[i].flags & P_ALLOCED) && options[i].var != NULL) { - free_string_option(*(char_u **)options[i].var); + free_string_option(*(char **)options[i].var); } if (options[i].flags & P_DEF_ALLOCED) { free_string_option(options[i].def_val); } } else if (options[i].var != VAR_WIN && (options[i].flags & P_STRING)) { // buffer-local option: free global value - clear_string_option((char_u **)options[i].var); + clear_string_option((char **)options[i].var); } } free_operatorfunc_option(); + free_tagfunc_option(); } #endif @@ -610,25 +568,20 @@ void set_init_2(bool headless) // set in set_init_1 but logging is not allowed there ILOG("startup runtimepath/packpath value: %s", p_rtp); - int idx; - // 'scroll' defaults to half the window height. The stored default is zero, // which results in the actual value computed from the window height. - idx = findoption("scroll"); + int idx = findoption("scroll"); if (idx >= 0 && !(options[idx].flags & P_WAS_SET)) { set_option_default(idx, OPT_LOCAL); } comp_col(); - /* - * 'window' is only for backwards compatibility with Vi. - * Default is Rows - 1. - */ + // 'window' is only for backwards compatibility with Vi. + // Default is Rows - 1. if (!option_was_set("window")) { p_window = Rows - 1; } set_number_default("window", Rows - 1); - (void)parse_printoptions(); // parse 'printoptions' default value } /// Initialize the options, part three: After reading the .vimrc @@ -649,41 +602,41 @@ void set_init_3(void) : !(options[idx_sp].flags & P_WAS_SET); size_t len = 0; - char_u *p = (char_u *)invocation_path_tail(p_sh, &len); - p = vim_strnsave(p, len); + char *p = (char *)invocation_path_tail(p_sh, &len); + p = xstrnsave(p, len); { // // Default for p_sp is "| tee", for p_srr is ">". // For known shells it is changed here to include stderr. // - if (FNAMECMP(p, "csh") == 0 - || FNAMECMP(p, "tcsh") == 0) { + if (path_fnamecmp(p, "csh") == 0 + || path_fnamecmp(p, "tcsh") == 0) { if (do_sp) { - p_sp = (char_u *)"|& tee"; + p_sp = "|& tee"; options[idx_sp].def_val = p_sp; } if (do_srr) { - p_srr = (char_u *)">&"; + p_srr = ">&"; options[idx_srr].def_val = p_srr; } - } else if (FNAMECMP(p, "sh") == 0 - || FNAMECMP(p, "ksh") == 0 - || FNAMECMP(p, "mksh") == 0 - || FNAMECMP(p, "pdksh") == 0 - || FNAMECMP(p, "zsh") == 0 - || FNAMECMP(p, "zsh-beta") == 0 - || FNAMECMP(p, "bash") == 0 - || FNAMECMP(p, "fish") == 0 - || FNAMECMP(p, "ash") == 0 - || FNAMECMP(p, "dash") == 0) { + } else if (path_fnamecmp(p, "sh") == 0 + || path_fnamecmp(p, "ksh") == 0 + || path_fnamecmp(p, "mksh") == 0 + || path_fnamecmp(p, "pdksh") == 0 + || path_fnamecmp(p, "zsh") == 0 + || path_fnamecmp(p, "zsh-beta") == 0 + || path_fnamecmp(p, "bash") == 0 + || path_fnamecmp(p, "fish") == 0 + || path_fnamecmp(p, "ash") == 0 + || path_fnamecmp(p, "dash") == 0) { // Always use POSIX shell style redirection if we reach this if (do_sp) { - p_sp = (char_u *)"2>&1| tee"; + p_sp = "2>&1| tee"; options[idx_sp].def_val = p_sp; } if (do_srr) { - p_srr = (char_u *)">%s 2>&1"; + p_srr = ">%s 2>&1"; options[idx_srr].def_val = p_srr; } } @@ -715,23 +668,25 @@ void set_helplang_default(const char *lang) return; } int idx = findoption("hlg"); - if (idx >= 0 && !(options[idx].flags & P_WAS_SET)) { - if (options[idx].flags & P_ALLOCED) { - free_string_option(p_hlg); - } - p_hlg = (char_u *)xmemdupz(lang, lang_len); - // zh_CN becomes "cn", zh_TW becomes "tw". - if (STRNICMP(p_hlg, "zh_", 3) == 0 && STRLEN(p_hlg) >= 5) { - p_hlg[0] = (char_u)TOLOWER_ASC(p_hlg[3]); - p_hlg[1] = (char_u)TOLOWER_ASC(p_hlg[4]); - } else if (STRLEN(p_hlg) >= 1 && *p_hlg == 'C') { - // any C like setting, such as C.UTF-8, becomes "en" - p_hlg[0] = 'e'; - p_hlg[1] = 'n'; - } - p_hlg[2] = NUL; - options[idx].flags |= P_ALLOCED; + if (idx < 0 || (options[idx].flags & P_WAS_SET)) { + return; } + + if (options[idx].flags & P_ALLOCED) { + free_string_option(p_hlg); + } + p_hlg = xmemdupz(lang, lang_len); + // zh_CN becomes "cn", zh_TW becomes "tw". + if (STRNICMP(p_hlg, "zh_", 3) == 0 && strlen(p_hlg) >= 5) { + p_hlg[0] = (char)TOLOWER_ASC(p_hlg[3]); + p_hlg[1] = (char)TOLOWER_ASC(p_hlg[4]); + } else if (strlen(p_hlg) >= 1 && *p_hlg == 'C') { + // any C like setting, such as C.UTF-8, becomes "en" + p_hlg[0] = 'e'; + p_hlg[1] = 'n'; + } + p_hlg[2] = NUL; + options[idx].flags |= P_ALLOCED; } /// 'title' and 'icon' only default to true if they have not been set or reset @@ -741,21 +696,17 @@ void set_helplang_default(const char *lang) /// machine. void set_title_defaults(void) { - int idx1; - - /* - * If GUI is (going to be) used, we can always set the window title and - * icon name. Saves a bit of time, because the X11 display server does - * not need to be contacted. - */ - idx1 = findoption("title"); + // If GUI is (going to be) used, we can always set the window title and + // icon name. Saves a bit of time, because the X11 display server does + // not need to be contacted. + int idx1 = findoption("title"); if (idx1 >= 0 && !(options[idx1].flags & P_WAS_SET)) { - options[idx1].def_val = (char_u *)(intptr_t)0; + options[idx1].def_val = 0; p_title = 0; } idx1 = findoption("icon"); if (idx1 >= 0 && !(options[idx1].flags & P_WAS_SET)) { - options[idx1].def_val = (char_u *)(intptr_t)0; + options[idx1].def_val = 0; p_icon = 0; } } @@ -775,6 +726,342 @@ void ex_set(exarg_T *eap) (void)do_set(eap->arg, flags); } +/// Part of do_set() for string options. +/// @return FAIL on failure, do not process further options. +static int do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, set_op_T op_arg, + uint32_t flags, char *varp_arg, char *errbuf, size_t errbuflen, + int *value_checked, char **errmsg) +{ + char *arg = *argp; + set_op_T op = op_arg; + char *varp = varp_arg; + char *save_arg = NULL; + char *s = NULL; + char_u *origval_l = NULL; + char_u *origval_g = NULL; + char whichwrap[80]; + + // When using ":set opt=val" for a global option + // with a local value the local value will be + // reset, use the global value here. + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 + && ((int)options[opt_idx].indir & PV_BOTH)) { + varp = (char *)options[opt_idx].var; + } + + // The old value is kept until we are sure that the new value is valid. + char *oldval = *(char **)varp; + + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + origval_l = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); + origval_g = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); + + // A global-local string option might have an empty option as value to + // indicate that the global value should be used. + if (((int)options[opt_idx].indir & PV_BOTH) && origval_l == (char_u *)empty_option) { + origval_l = origval_g; + } + } + + char *origval; + // When setting the local value of a global option, the old value may be + // the global value. + if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags & OPT_LOCAL)) { + origval = *(char **)get_varp(&options[opt_idx]); + } else { + origval = oldval; + } + + char *newval; + if (nextchar == '&') { // set to default val + newval = options[opt_idx].def_val; + // expand environment variables and ~ since the default value was + // already expanded, only required when an environment variable was set + // later + if (newval == NULL) { + newval = empty_option; + } else if (!(options[opt_idx].flags & P_NO_DEF_EXP)) { + s = option_expand(opt_idx, newval); + if (s == NULL) { + s = newval; + } + newval = xstrdup(s); + } else { + newval = xstrdup(newval); + } + } else if (nextchar == '<') { // set to global val + newval = xstrdup(*(char **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL)); + } else { + arg++; // jump to after the '=' or ':' + + // Set 'keywordprg' to ":help" if an empty + // value was passed to :set by the user. + if (varp == (char *)&p_kp && (*arg == NUL || *arg == ' ')) { + save_arg = arg; + arg = ":help"; + } else if (varp == (char *)&p_bs && ascii_isdigit(**(char_u **)varp)) { + // Convert 'backspace' number to string, for + // adding, prepending and removing string. + int i = getdigits_int((char **)varp, true, 0); + switch (i) { + case 0: + *(char **)varp = empty_option; + break; + case 1: + *(char_u **)varp = (char_u *)xstrdup("indent,eol"); + break; + case 2: + *(char_u **)varp = (char_u *)xstrdup("indent,eol,start"); + break; + case 3: + *(char_u **)varp = (char_u *)xstrdup("indent,eol,nostop"); + break; + } + xfree(oldval); + if (origval == oldval) { + origval = *(char **)varp; + } + if (origval_l == (char_u *)oldval) { + origval_l = *(char_u **)varp; + } + if (origval_g == (char_u *)oldval) { + origval_g = *(char_u **)varp; + } + oldval = *(char **)varp; + } else if (varp == (char *)&p_ww && ascii_isdigit(*arg)) { + // Convert 'whichwrap' number to string, for backwards compatibility + // with Vim 3.0. + *whichwrap = NUL; + int i = getdigits_int(&arg, true, 0); + if (i & 1) { + xstrlcat(whichwrap, "b,", sizeof(whichwrap)); + } + if (i & 2) { + xstrlcat(whichwrap, "s,", sizeof(whichwrap)); + } + if (i & 4) { + xstrlcat(whichwrap, "h,l,", sizeof(whichwrap)); + } + if (i & 8) { + xstrlcat(whichwrap, "<,>,", sizeof(whichwrap)); + } + if (i & 16) { + xstrlcat(whichwrap, "[,],", sizeof(whichwrap)); + } + if (*whichwrap != NUL) { // remove trailing , + whichwrap[strlen(whichwrap) - 1] = NUL; + } + save_arg = arg; + arg = whichwrap; + } else if (*arg == '>' && (varp == (char *)&p_dir || varp == (char *)&p_bdir)) { + // Remove '>' before 'dir' and 'bdir', for backwards compatibility with + // version 3.0 + arg++; + } + + // Copy the new string into allocated memory. + // Can't use set_string_option_direct(), because we need to remove the + // backslashes. + + // get a bit too much + size_t newlen = strlen(arg) + 1; + if (op != OP_NONE) { + newlen += strlen(origval) + 1; + } + newval = xmalloc(newlen); + s = newval; + + // Copy the string, skip over escaped chars. + // For MS-Windows backslashes before normal file name characters + // are not removed, and keep backslash at start, for "\\machine\path", + // but do remove it for "\\\\machine\\path". + // The reverse is found in ExpandOldSetting(). + while (*arg != NUL && !ascii_iswhite(*arg)) { + if (*arg == '\\' && arg[1] != NUL +#ifdef BACKSLASH_IN_FILENAME + && !((flags & P_EXPAND) + && vim_isfilec((uint8_t)arg[1]) + && !ascii_iswhite(arg[1]) + && (arg[1] != '\\' + || (s == newval && arg[2] != '\\'))) +#endif + ) { + arg++; // remove backslash + } + int i = utfc_ptr2len(arg); + if (i > 1) { + // copy multibyte char + memmove(s, arg, (size_t)i); + arg += i; + s += i; + } else { + *s++ = *arg++; + } + } + *s = NUL; + + // Expand environment variables and ~. + // Don't do it when adding without inserting a comma. + if (op == OP_NONE || (flags & P_COMMA)) { + s = option_expand(opt_idx, newval); + if (s != NULL) { + xfree(newval); + newlen = (unsigned)strlen(s) + 1; + if (op != OP_NONE) { + newlen += (unsigned)strlen(origval) + 1; + } + newval = xmalloc(newlen); + STRCPY(newval, s); + } + } + + // locate newval[] in origval[] when removing it + // and when adding to avoid duplicates + int len = 0; + if (op == OP_REMOVING || (flags & P_NODUP)) { + len = (int)strlen(newval); + s = find_dup_item(origval, newval, flags); + + // do not add if already there + if ((op == OP_ADDING || op == OP_PREPENDING) && s != NULL) { + op = OP_NONE; + STRCPY(newval, origval); + } + + // if no duplicate, move pointer to end of original value + if (s == NULL) { + s = origval + (int)strlen(origval); + } + } + + // concatenate the two strings; add a ',' if needed + if (op == OP_ADDING || op == OP_PREPENDING) { + int comma = ((flags & P_COMMA) && *origval != NUL && *newval != NUL); + if (op == OP_ADDING) { + len = (int)strlen(origval); + // Strip a trailing comma, would get 2. + if (comma && len > 1 + && (flags & P_ONECOMMA) == P_ONECOMMA + && origval[len - 1] == ',' + && origval[len - 2] != '\\') { + len--; + } + memmove(newval + len + comma, newval, strlen(newval) + 1); + memmove(newval, origval, (size_t)len); + } else { + len = (int)strlen(newval); + STRMOVE(newval + len + comma, origval); + } + if (comma) { + newval[len] = ','; + } + } + + // Remove newval[] from origval[]. (Note: "len" has been set above and + // is used here). + if (op == OP_REMOVING) { + STRCPY(newval, origval); + if (*s) { + // may need to remove a comma + if (flags & P_COMMA) { + if (s == origval) { + // include comma after string + if (s[len] == ',') { + len++; + } + } else { + // include comma before string + s--; + len++; + } + } + STRMOVE(newval + (s - origval), s + len); + } + } + + if (flags & P_FLAGLIST) { + // Remove flags that appear twice. + for (s = newval; *s;) { + // if options have P_FLAGLIST and P_ONECOMMA such as + // 'whichwrap' + if (flags & P_ONECOMMA) { + if (*s != ',' && *(s + 1) == ',' + && vim_strchr(s + 2, (uint8_t)(*s)) != NULL) { + // Remove the duplicated value and the next comma. + STRMOVE(s, s + 2); + continue; + } + } else { + if ((!(flags & P_COMMA) || *s != ',') + && vim_strchr(s + 1, (uint8_t)(*s)) != NULL) { + STRMOVE(s, s + 1); + continue; + } + } + s++; + } + } + + if (save_arg != NULL) { + arg = save_arg; // arg was temporarily changed, restore it + } + } + + // Set the new value. + *(char_u **)(varp) = (char_u *)newval; + + // origval may be freed by did_set_string_option(), make a copy. + char *saved_origval = (origval != NULL) ? xstrdup(origval) : NULL; + char *saved_origval_l = (origval_l != NULL) ? xstrdup((char *)origval_l) : NULL; + char *saved_origval_g = (origval_g != NULL) ? xstrdup((char *)origval_g) : NULL; + + // newval (and varp) may become invalid if the buffer is closed by + // autocommands. + char *saved_newval = (newval != NULL) ? xstrdup(newval) : NULL; + + { + uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); + const int secure_saved = secure; + + // When an option is set in the sandbox, from a modeline or in secure + // mode, then deal with side effects in secure mode. Also when the + // value was set with the P_INSECURE flag and is not completely + // replaced. + if ((opt_flags & OPT_MODELINE) + || sandbox != 0 + || (op != OP_NONE && (*p & P_INSECURE))) { + secure = 1; + } + + // Handle side effects, and set the global value for ":set" on local + // options. Note: when setting 'syntax' or 'filetype' autocommands may + // be triggered that can cause havoc. + *errmsg = did_set_string_option(opt_idx, (char **)varp, oldval, + errbuf, errbuflen, + opt_flags, value_checked); + + secure = secure_saved; + } + + if (*errmsg == NULL) { + if (!starting) { + trigger_optionset_string(opt_idx, opt_flags, saved_origval, saved_origval_l, + saved_origval_g, saved_newval); + } + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + STRING_OBJ(cstr_as_string(saved_newval))); + } + } + xfree(saved_origval); + xfree(saved_origval_l); + xfree(saved_origval_g); + xfree(saved_newval); + + *argp = arg; + return *errmsg == NULL ? OK : FAIL; +} + /// Parse 'arg' for option settings. /// /// 'arg' may be IObuff, but only when no errors can be present and option @@ -792,23 +1079,7 @@ void ex_set(exarg_T *eap) /// @return FAIL if an error is detected, OK otherwise int do_set(char *arg, int opt_flags) { - int opt_idx; - char *errmsg; - char errbuf[80]; - char_u *startarg; - int prefix; // 1: nothing, 0: "no", 2: "inv" in front of name - char_u nextchar; // next non-white char after option name - int afterchar; // character just after option name - int len; - int i; - varnumber_T value; - int key; - uint32_t flags; // flags for current option - char *varp = NULL; // pointer to variable for current option int did_show = false; // already showed one value - int adding; // "opt+=arg" - int prepending; // "opt^=arg" - int removing; // "opt-=arg" if (*arg == NUL) { showoptions(0, opt_flags); @@ -816,16 +1087,16 @@ int do_set(char *arg, int opt_flags) goto theend; } + char errbuf[80]; + while (*arg != NUL) { // loop to process all options - errmsg = NULL; - startarg = (char_u *)arg; // remember for error message + char *errmsg = NULL; + char *startarg = arg; // remember for error message - if (STRNCMP(arg, "all", 3) == 0 && !isalpha(arg[3]) + if (strncmp(arg, "all", 3) == 0 && !ASCII_ISALPHA(arg[3]) && !(opt_flags & OPT_MODELINE)) { - /* - * ":set all" show all options. - * ":set all&" set all options to their default value. - */ + // ":set all" show all options. + // ":set all&" set all options to their default value. arg += 3; if (*arg == '&') { arg++; @@ -834,23 +1105,25 @@ int do_set(char *arg, int opt_flags) didset_options(); didset_options2(); ui_refresh_options(); - redraw_all_later(CLEAR); + redraw_all_later(UPD_CLEAR); } else { showoptions(1, opt_flags); did_show = true; } } else { - prefix = 1; - if (STRNCMP(arg, "no", 2) == 0) { + int prefix = 1; // 1: nothing, 0: "no", 2: "inv" in front of name + if (strncmp(arg, "no", 2) == 0) { prefix = 0; arg += 2; - } else if (STRNCMP(arg, "inv", 3) == 0) { + } else if (strncmp(arg, "inv", 3) == 0) { prefix = 2; arg += 3; } // find end of name - key = 0; + int key = 0; + int len; + int opt_idx; if (*arg == '<') { opt_idx = -1; // look out for <t_>;> @@ -871,7 +1144,7 @@ int do_set(char *arg, int opt_flags) } len++; if (opt_idx == -1) { - key = find_key_option((char_u *)arg + 1, true); + key = find_key_option(arg + 1, true); } } else { len = 0; @@ -885,54 +1158,55 @@ int do_set(char *arg, int opt_flags) } opt_idx = findoption_len((const char *)arg, (size_t)len); if (opt_idx == -1) { - key = find_key_option((char_u *)arg, false); + key = find_key_option(arg, false); } } // remember character after option name - afterchar = (uint8_t)arg[len]; + int afterchar = (uint8_t)arg[len]; // skip white space, allow ":set ai ?" while (ascii_iswhite(arg[len])) { len++; } - adding = false; - prepending = false; - removing = false; + set_op_T op = OP_NONE; if (arg[len] != NUL && arg[len + 1] == '=') { if (arg[len] == '+') { - adding = true; // "+=" + op = OP_ADDING; // "+=" len++; } else if (arg[len] == '^') { - prepending = true; // "^=" + op = OP_PREPENDING; // "^=" len++; } else if (arg[len] == '-') { - removing = true; // "-=" + op = OP_REMOVING; // "-=" len++; } } - nextchar = (uint8_t)arg[len]; + char_u nextchar = (uint8_t)arg[len]; // next non-white char after option name if (opt_idx == -1 && key == 0) { // found a mismatch: skip - errmsg = N_("E518: Unknown option"); + errmsg = e_unknown_option; goto skip; } + uint32_t flags; // flags for current option + char *varp = NULL; // pointer to variable for current option + if (opt_idx >= 0) { if (options[opt_idx].var == NULL) { // hidden option: skip // Only give an error message when requesting the value of // a hidden option, ignore setting it. - if (vim_strchr("=:!&<", nextchar) == NULL + if (vim_strchr("=:!&<", (uint8_t)nextchar) == NULL && (!(options[opt_idx].flags & P_BOOL) || nextchar == '?')) { - errmsg = _(e_unsupportedoption); + errmsg = e_unsupportedoption; } goto skip; } flags = options[opt_idx].flags; - varp = (char *)get_varp_scope(&(options[opt_idx]), opt_flags); + varp = get_varp_scope(&(options[opt_idx]), opt_flags); } else { flags = P_STRING; } @@ -953,11 +1227,11 @@ int do_set(char *arg, int opt_flags) // Disallow changing some options from modelines. if (opt_flags & OPT_MODELINE) { if (flags & (P_SECURE | P_NO_ML)) { - errmsg = N_("E520: Not allowed in a modeline"); + errmsg = e_not_allowed_in_modeline; goto skip; } if ((flags & P_MLE) && !p_mle) { - errmsg = N_("E992: Not allowed in a modeline when 'modelineexpr' is off"); + errmsg = e_not_allowed_in_modeline_when_modelineexpr_is_off; goto skip; } // In diff mode some options are overruled. This avoids that @@ -977,7 +1251,7 @@ int do_set(char *arg, int opt_flags) goto skip; } - if (vim_strchr("?=:!&<", nextchar) != NULL) { + if (vim_strchr("?=:!&<", (uint8_t)nextchar) != NULL) { arg += len; if (nextchar == '&' && arg[1] == 'v' && arg[2] == 'i') { if (arg[3] == 'm') { // "opt&vim": set to Vim default @@ -986,7 +1260,7 @@ int do_set(char *arg, int opt_flags) arg += 2; } } - if (vim_strchr("?!&<", nextchar) != NULL + if (vim_strchr("?!&<", (uint8_t)nextchar) != NULL && arg[1] != NUL && !ascii_iswhite(arg[1])) { errmsg = e_trailing; goto skip; @@ -999,11 +1273,9 @@ int do_set(char *arg, int opt_flags) // if (nextchar == '?' || (prefix == 1 - && vim_strchr("=:&<", nextchar) == NULL + && vim_strchr("=:&<", (uint8_t)nextchar) == NULL && !(flags & P_BOOL))) { - /* - * print value - */ + // print value if (did_show) { msg_putchar('\n'); // cursor below last one } else { @@ -1025,7 +1297,7 @@ int do_set(char *arg, int opt_flags) } } } else { - errmsg = N_("E846: Key code not set"); + errmsg = e_key_code_not_set; goto skip; } if (nextchar != '?' @@ -1033,8 +1305,8 @@ int do_set(char *arg, int opt_flags) errmsg = e_trailing; } } else { - int value_is_replaced = !prepending && !adding && !removing; int value_checked = false; + varnumber_T value; if (flags & P_BOOL) { // boolean if (nextchar == '=' || nextchar == ':') { @@ -1042,11 +1314,9 @@ int do_set(char *arg, int opt_flags) goto skip; } - /* - * ":set opt!": invert - * ":set opt&": reset to default value - * ":set opt<": reset to global value - */ + // ":set opt!": invert + // ":set opt&": reset to default value + // ":set opt<": reset to global value if (nextchar == '!') { value = *(int *)(varp) ^ 1; } else if (nextchar == '&') { @@ -1061,10 +1331,8 @@ int do_set(char *arg, int opt_flags) OPT_GLOBAL); } } else { - /* - * ":set invopt": invert - * ":set opt" or ":set noopt": set or reset - */ + // ":set invopt": invert + // ":set opt" or ":set noopt": set or reset if (nextchar != NUL && !ascii_iswhite(afterchar)) { errmsg = e_trailing; goto skip; @@ -1078,7 +1346,7 @@ int do_set(char *arg, int opt_flags) errmsg = set_bool_option(opt_idx, (char_u *)varp, (int)value, opt_flags); } else { // Numeric or string. - if (vim_strchr("=:&<", nextchar) == NULL + if (vim_strchr("=:&<", (uint8_t)nextchar) == NULL || prefix != 1) { errmsg = e_invarg; goto skip; @@ -1109,390 +1377,44 @@ int do_set(char *arg, int opt_flags) || *arg == '^' || (*arg != NUL && (!arg[1] || ascii_iswhite(arg[1])) && !ascii_isdigit(*arg)))) { - value = string_to_key((char_u *)arg); + value = string_to_key(arg); if (value == 0 && (long *)varp != &p_wcm) { errmsg = e_invarg; goto skip; } } else if (*arg == '-' || ascii_isdigit(*arg)) { + int i; // Allow negative, octal and hex numbers. - vim_str2nr((char_u *)arg, NULL, &i, STR2NR_ALL, &value, NULL, 0, true); + vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0, true); if (i == 0 || (arg[i] != NUL && !ascii_iswhite(arg[i]))) { - errmsg = N_("E521: Number required after ="); + errmsg = e_number_required_after_equal; goto skip; } } else { - errmsg = N_("E521: Number required after ="); + errmsg = e_number_required_after_equal; goto skip; } - if (adding) { + if (op == OP_ADDING) { value = *(long *)varp + value; } - if (prepending) { + if (op == OP_PREPENDING) { value = *(long *)varp * value; } - if (removing) { + if (op == OP_REMOVING) { value = *(long *)varp - value; } errmsg = set_num_option(opt_idx, (char_u *)varp, (long)value, errbuf, sizeof(errbuf), opt_flags); } else if (opt_idx >= 0) { // String. - char_u *save_arg = NULL; - char_u *s = NULL; - char_u *oldval = NULL; // previous value if *varp - char_u *newval; - char_u *origval = NULL; - char_u *origval_l = NULL; - char_u *origval_g = NULL; - char *saved_origval = NULL; - char *saved_origval_l = NULL; - char *saved_origval_g = NULL; - char *saved_newval = NULL; - unsigned newlen; - int comma; - - // When using ":set opt=val" for a global option - // with a local value the local value will be - // reset, use the global value here. - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 - && ((int)options[opt_idx].indir & PV_BOTH)) { - varp = (char *)options[opt_idx].var; - } - - // The old value is kept until we are sure that the - // new value is valid. - oldval = *(char_u **)varp; - - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - origval_l = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); - origval_g = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); - - // A global-local string option might have an empty - // option as value to indicate that the global - // value should be used. - if (((int)options[opt_idx].indir & PV_BOTH) && origval_l == empty_option) { - origval_l = origval_g; - } - } - - // When setting the local value of a global - // option, the old value may be the global value. - if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags & OPT_LOCAL)) { - origval = *(char_u **)get_varp(&options[opt_idx]); - } else { - origval = oldval; - } - - if (nextchar == '&') { // set to default val - newval = options[opt_idx].def_val; - // expand environment variables and ~ since the - // default value was already expanded, only - // required when an environment variable was set - // later - if (newval == NULL) { - newval = empty_option; - } else if (!(options[opt_idx].flags & P_NO_DEF_EXP)) { - s = option_expand(opt_idx, newval); - if (s == NULL) { - s = newval; - } - newval = vim_strsave(s); - } else { - newval = (char_u *)xstrdup((char *)newval); - } - } else if (nextchar == '<') { // set to global val - newval = vim_strsave(*(char_u **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL)); - } else { - arg++; // jump to after the '=' or ':' - - // Set 'keywordprg' to ":help" if an empty - // value was passed to :set by the user. - // Misuse errbuf[] for the resulting string. - if (varp == (char *)&p_kp && (*arg == NUL || *arg == ' ')) { - STRCPY(errbuf, ":help"); - save_arg = (char_u *)arg; - arg = errbuf; - } else if (varp == (char *)&p_bs && ascii_isdigit(**(char_u **)varp)) { - // Convert 'backspace' number to string, for - // adding, prepending and removing string. - i = getdigits_int((char **)varp, true, 0); - switch (i) { - case 0: - *(char_u **)varp = empty_option; - break; - case 1: - *(char_u **)varp = vim_strsave((char_u *)"indent,eol"); - break; - case 2: - *(char_u **)varp = vim_strsave((char_u *)"indent,eol,start"); - break; - case 3: - *(char_u **)varp = vim_strsave((char_u *)"indent,eol,nostop"); - break; - } - xfree(oldval); - if (origval == oldval) { - origval = *(char_u **)varp; - } - if (origval_l == oldval) { - origval_l = *(char_u **)varp; - } - if (origval_g == oldval) { - origval_g = *(char_u **)varp; - } - oldval = *(char_u **)varp; - } else if (varp == (char *)&p_ww && ascii_isdigit(*arg)) { - // Convert 'whichwrap' number to string, for - // backwards compatibility with Vim 3.0. - // Misuse errbuf[] for the resulting string. - *errbuf = NUL; - i = getdigits_int(&arg, true, 0); - if (i & 1) { - STRLCAT(errbuf, "b,", sizeof(errbuf)); - } - if (i & 2) { - STRLCAT(errbuf, "s,", sizeof(errbuf)); - } - if (i & 4) { - STRLCAT(errbuf, "h,l,", sizeof(errbuf)); - } - if (i & 8) { - STRLCAT(errbuf, "<,>,", sizeof(errbuf)); - } - if (i & 16) { - STRLCAT(errbuf, "[,],", sizeof(errbuf)); - } - save_arg = (char_u *)arg; - arg = errbuf; - } else if (*arg == '>' - && (varp == (char *)&p_dir - || varp == (char *)&p_bdir)) { - // Remove '>' before 'dir' and 'bdir', for - // backwards compatibility with version 3.0 - arg++; - } - - /* - * Copy the new string into allocated memory. - * Can't use set_string_option_direct(), because - * we need to remove the backslashes. - */ - // get a bit too much - newlen = (unsigned)STRLEN(arg) + 1; - if (adding || prepending || removing) { - newlen += (unsigned)STRLEN(origval) + 1; - } - newval = xmalloc(newlen); - s = newval; - - /* - * Copy the string, skip over escaped chars. - * For WIN32 backslashes before normal - * file name characters are not removed, and keep - * backslash at start, for "\\machine\path", but - * do remove it for "\\\\machine\\path". - * The reverse is found in ExpandOldSetting(). - */ - while (*arg && !ascii_iswhite(*arg)) { - if (*arg == '\\' && arg[1] != NUL -#ifdef BACKSLASH_IN_FILENAME - && !((flags & P_EXPAND) - && vim_isfilec(arg[1]) - && !ascii_iswhite(arg[1]) - && (arg[1] != '\\' - || (s == newval - && arg[2] != '\\'))) -#endif - ) { - arg++; // remove backslash - } - i = utfc_ptr2len(arg); - if (i > 1) { - // copy multibyte char - memmove(s, arg, (size_t)i); - arg += i; - s += i; - } else { - *s++ = (uint8_t)(*arg++); - } - } - *s = NUL; - - /* - * Expand environment variables and ~. - * Don't do it when adding without inserting a - * comma. - */ - if (!(adding || prepending || removing) - || (flags & P_COMMA)) { - s = option_expand(opt_idx, newval); - if (s != NULL) { - xfree(newval); - newlen = (unsigned)STRLEN(s) + 1; - if (adding || prepending || removing) { - newlen += (unsigned)STRLEN(origval) + 1; - } - newval = xmalloc(newlen); - STRCPY(newval, s); - } - } - - // locate newval[] in origval[] when removing it - // and when adding to avoid duplicates - i = 0; // init for GCC - if (removing || (flags & P_NODUP)) { - i = (int)STRLEN(newval); - s = find_dup_item(origval, newval, flags); - - // do not add if already there - if ((adding || prepending) && s != NULL) { - prepending = false; - adding = false; - STRCPY(newval, origval); - } - - // if no duplicate, move pointer to end of - // original value - if (s == NULL) { - s = origval + (int)STRLEN(origval); - } - } - - // concatenate the two strings; add a ',' if - // needed - if (adding || prepending) { - comma = ((flags & P_COMMA) && *origval != NUL - && *newval != NUL); - if (adding) { - i = (int)STRLEN(origval); - // Strip a trailing comma, would get 2. - if (comma && i > 1 - && (flags & P_ONECOMMA) == P_ONECOMMA - && origval[i - 1] == ',' - && origval[i - 2] != '\\') { - i--; - } - memmove(newval + i + comma, newval, - STRLEN(newval) + 1); - memmove(newval, origval, (size_t)i); - } else { - i = (int)STRLEN(newval); - STRMOVE(newval + i + comma, origval); - } - if (comma) { - newval[i] = ','; - } - } - - // Remove newval[] from origval[]. (Note: "i" has - // been set above and is used here). - if (removing) { - STRCPY(newval, origval); - if (*s) { - // may need to remove a comma - if (flags & P_COMMA) { - if (s == origval) { - // include comma after string - if (s[i] == ',') { - i++; - } - } else { - // include comma before string - s--; - i++; - } - } - STRMOVE(newval + (s - origval), s + i); - } - } - - if (flags & P_FLAGLIST) { - // Remove flags that appear twice. - for (s = newval; *s;) { - // if options have P_FLAGLIST and P_ONECOMMA such as - // 'whichwrap' - if (flags & P_ONECOMMA) { - if (*s != ',' && *(s + 1) == ',' - && vim_strchr((char *)s + 2, *s) != NULL) { - // Remove the duplicated value and the next comma. - STRMOVE(s, s + 2); - continue; - } - } else { - if ((!(flags & P_COMMA) || *s != ',') - && vim_strchr((char *)s + 1, *s) != NULL) { - STRMOVE(s, s + 1); - continue; - } - } - s++; - } - } - - if (save_arg != NULL) { // number for 'whichwrap' - arg = (char *)save_arg; - } - } - - // Set the new value. - *(char_u **)(varp) = newval; - - // origval may be freed by - // did_set_string_option(), make a copy. - saved_origval = (origval != NULL) ? xstrdup((char *)origval) : 0; - saved_origval_l = (origval_l != NULL) ? xstrdup((char *)origval_l) : 0; - saved_origval_g = (origval_g != NULL) ? xstrdup((char *)origval_g) : 0; - - // newval (and varp) may become invalid if the - // buffer is closed by autocommands. - saved_newval = (newval != NULL) ? xstrdup((char *)newval) : 0; - - { - uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags); - const int secure_saved = secure; - - // When an option is set in the sandbox, from a - // modeline or in secure mode, then deal with side - // effects in secure mode. Also when the value was - // set with the P_INSECURE flag and is not - // completely replaced. - if ((opt_flags & OPT_MODELINE) - || sandbox != 0 - || (!value_is_replaced && (*p & P_INSECURE))) { - secure = 1; - } - - // Handle side effects, and set the global value - // for ":set" on local options. Note: when setting - // 'syntax' or 'filetype' autocommands may be - // triggered that can cause havoc. - errmsg = did_set_string_option(opt_idx, (char_u **)varp, oldval, - errbuf, sizeof(errbuf), - opt_flags, &value_checked); - - secure = secure_saved; - } - - if (errmsg == NULL) { - if (!starting) { - trigger_optionsset_string(opt_idx, opt_flags, saved_origval, saved_origval_l, - saved_origval_g, saved_newval); - } - if (options[opt_idx].flags & P_UI_OPTION) { - ui_call_option_set(cstr_as_string(options[opt_idx].fullname), - STRING_OBJ(cstr_as_string(saved_newval))); + if (do_set_string(opt_idx, opt_flags, &arg, nextchar, + op, flags, varp, errbuf, sizeof(errbuf), + &value_checked, &errmsg) == FAIL) { + if (errmsg != NULL) { + goto skip; } - } - xfree(saved_origval); - xfree(saved_origval_l); - xfree(saved_origval_g); - xfree(saved_newval); - - // If error detected, print the error message. - if (errmsg != NULL) { - goto skip; + break; } } else { // key code option(FIXME(tarruda): Show a warning or something @@ -1501,18 +1423,16 @@ int do_set(char *arg, int opt_flags) } if (opt_idx >= 0) { - did_set_option(opt_idx, opt_flags, value_is_replaced, value_checked); + did_set_option(opt_idx, opt_flags, op == OP_NONE, value_checked); } } skip: - /* - * Advance to next argument. - * - skip until a blank found, taking care of backslashes - * - skip blanks - * - skip one "=val" argument (for hidden options ":set gfn =xx") - */ - for (i = 0; i < 2; i++) { + // Advance to next argument. + // - skip until a blank found, taking care of backslashes + // - skip blanks + // - skip one "=val" argument (for hidden options ":set gfn =xx") + for (int i = 0; i < 2; i++) { while (*arg != NUL && !ascii_iswhite(*arg)) { if (*arg++ == '\\' && *arg != NUL) { arg++; @@ -1526,20 +1446,20 @@ skip: } if (errmsg != NULL) { - STRLCPY(IObuff, _(errmsg), IOSIZE); - i = (int)STRLEN(IObuff) + 2; - if (i + ((char_u *)arg - startarg) < IOSIZE) { + xstrlcpy(IObuff, _(errmsg), IOSIZE); + int i = (int)strlen(IObuff) + 2; + if (i + (arg - startarg) < IOSIZE) { // append the argument with the error STRCAT(IObuff, ": "); - assert((char_u *)arg >= startarg); - memmove(IObuff + i, startarg, (size_t)((char_u *)arg - startarg)); - IObuff[i + ((char_u *)arg - startarg)] = NUL; + assert(arg >= startarg); + memmove(IObuff + i, startarg, (size_t)(arg - startarg)); + IObuff[i + (arg - startarg)] = NUL; } // make sure all characters are printable - trans_characters((char *)IObuff, IOSIZE); + trans_characters(IObuff, IOSIZE); - no_wait_return++; // wait_return done later - emsg((char *)IObuff); // show error highlighted + no_wait_return++; // wait_return() done later + emsg(IObuff); // show error highlighted no_wait_return--; return FAIL; @@ -1552,11 +1472,11 @@ theend: if (silent_mode && did_show) { // After displaying option values in silent mode. silent_mode = false; - info_message = true; // use mch_msg(), not mch_errmsg() + info_message = true; // use os_msg(), not os_errmsg() msg_putchar('\n'); ui_flush(); silent_mode = true; - info_message = false; // use mch_msg(), not mch_errmsg() + info_message = false; // use os_msg(), not os_errmsg() } return OK; @@ -1587,33 +1507,15 @@ void did_set_option(int opt_idx, int opt_flags, int new_value, int value_checked /// Convert a key name or string into a key value. /// Used for 'wildchar' and 'cedit' options. -static int string_to_key(char_u *arg) +int string_to_key(char *arg) { if (*arg == '<') { return find_key_option(arg + 1, true); } if (*arg == '^') { - return CTRL_CHR(arg[1]); - } - return *arg; -} - -/// Check value of 'cedit' and set cedit_key. -/// Returns NULL if value is OK, error message otherwise. -char *check_cedit(void) -{ - int n; - - if (*p_cedit == NUL) { - cedit_key = -1; - } else { - n = string_to_key(p_cedit); - if (vim_isprintc(n)) { - return e_invarg; - } - cedit_key = n; + return CTRL_CHR((uint8_t)arg[1]); } - return NULL; + return (uint8_t)(*arg); } // When changing 'title', 'titlestring', 'icon' or 'iconstring', call @@ -1632,10 +1534,8 @@ void did_set_title(void) /// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL void set_options_bin(int oldval, int newval, int opt_flags) { - /* - * The option values that are changed when 'bin' changes are - * copied when 'bin is set and restored when 'bin' is reset. - */ + // The option values that are changed when 'bin' changes are + // copied when 'bin is set and restored when 'bin' is reset. if (newval) { if (!oldval) { // switched on if (!(opt_flags & OPT_GLOBAL)) { @@ -1688,11 +1588,9 @@ void set_options_bin(int oldval, int newval, int opt_flags) /// number, return -1. int get_shada_parameter(int type) { - char_u *p; - - p = find_shada_parameter(type); + char *p = find_shada_parameter(type); if (p != NULL && ascii_isdigit(*p)) { - return atoi((char *)p); + return atoi(p); } return -1; } @@ -1700,18 +1598,16 @@ int get_shada_parameter(int type) /// Find the parameter represented by the given character (eg ''', ':', '"', or /// '/') in the 'shada' option and return a pointer to the string after it. /// Return NULL if the parameter is not specified in the string. -char_u *find_shada_parameter(int type) +char *find_shada_parameter(int type) { - char_u *p; - - for (p = p_shada; *p; p++) { + for (char *p = p_shada; *p; p++) { if (*p == type) { return p + 1; } if (*p == 'n') { // 'n' is always the last one break; } - p = (char_u *)vim_strchr((char *)p, ','); // skip until next ',' + p = vim_strchr(p, ','); // skip until next ',' if (p == NULL) { // hit the end without finding parameter break; } @@ -1723,7 +1619,7 @@ char_u *find_shada_parameter(int type) /// These string options cannot be indirect! /// If "val" is NULL expand the current value of the option. /// Return pointer to NameBuff, or NULL when not expanded. -static char_u *option_expand(int opt_idx, char_u *val) +static char *option_expand(int opt_idx, char *val) { // if option doesn't need expansion nothing to do if (!(options[opt_idx].flags & P_EXPAND) || options[opt_idx].var == NULL) { @@ -1731,26 +1627,24 @@ static char_u *option_expand(int opt_idx, char_u *val) } if (val == NULL) { - val = *(char_u **)options[opt_idx].var; + val = *(char **)options[opt_idx].var; } // If val is longer than MAXPATHL no meaningful expansion can be done, // expand_env() would truncate the string. - if (val == NULL || STRLEN(val) > MAXPATHL) { + if (val == NULL || strlen(val) > MAXPATHL) { return NULL; } - /* - * Expanding this with NameBuff, expand_env() must not be passed IObuff. - * Escape spaces when expanding 'tags', they are used to separate file - * names. - * For 'spellsuggest' expand after "file:". - */ + // Expanding this with NameBuff, expand_env() must not be passed IObuff. + // Escape spaces when expanding 'tags', they are used to separate file + // names. + // For 'spellsuggest' expand after "file:". expand_env_esc(val, NameBuff, MAXPATHL, - (char_u **)options[opt_idx].var == &p_tags, false, - (char_u **)options[opt_idx].var == &p_sps ? (char_u *)"file:" : + (char **)options[opt_idx].var == &p_tags, false, + (char_u **)options[opt_idx].var == (char_u **)&p_sps ? "file:" : NULL); - if (STRCMP(NameBuff, val) == 0) { // they are the same + if (strcmp(NameBuff, val) == 0) { // they are the same return NULL; } @@ -1800,11 +1694,9 @@ static void didset_options2(void) /// Check for string options that are NULL (normally only termcap options). void check_options(void) { - int opt_idx; - - for (opt_idx = 0; options[opt_idx].fullname != NULL; opt_idx++) { + for (int opt_idx = 0; options[opt_idx].fullname != NULL; opt_idx++) { if ((options[opt_idx].flags & P_STRING) && options[opt_idx].var != NULL) { - check_string_option((char_u **)get_varp(&(options[opt_idx]))); + check_string_option((char **)get_varp(&(options[opt_idx]))); } } } @@ -1863,12 +1755,12 @@ void redraw_titles(void) /// Return true if "val" is a valid name: only consists of alphanumeric ASCII /// characters or characters in "allowed". -bool valid_name(const char_u *val, const char *allowed) +bool valid_name(const char *val, const char *allowed) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - for (const char_u *s = val; *s != NUL; s++) { + for (const char *s = val; *s != NUL; s++) { if (!ASCII_ISALNUM(*s) - && vim_strchr(allowed, *s) == NULL) { + && vim_strchr(allowed, (uint8_t)(*s)) == NULL) { return false; } } @@ -1935,15 +1827,16 @@ void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; int indir = (int)options[opt_idx].indir; nlua_set_sctx(&script_ctx); - const LastSet last_set = { - .script_ctx = { - script_ctx.sc_sid, - script_ctx.sc_seq, - script_ctx.sc_lnum + SOURCING_LNUM - }, - current_channel_id + LastSet last_set = { + .script_ctx = script_ctx, + .channel_id = current_channel_id, }; + // Modeline already has the line number set. + if (!(opt_flags & OPT_MODELINE)) { + last_set.script_ctx.sc_lnum += SOURCING_LNUM; + } + // Remember where the option was set. For local options need to do that // in the buffer or window structure. if (both || (opt_flags & OPT_GLOBAL) || (indir & (PV_BUF|PV_WIN)) == 0) { @@ -1958,6 +1851,46 @@ void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) } } +/// Apply the OptionSet autocommand. +static void apply_optionset_autocmd(int opt_idx, long opt_flags, long oldval, long oldval_g, + long newval, const char *errmsg) +{ + // Don't do this while starting up, failure or recursively. + if (starting || errmsg != NULL || *get_vim_var_str(VV_OPTION_TYPE) != NUL) { + return; + } + + char buf_old[12], buf_old_global[12], buf_new[12], buf_type[12]; + + vim_snprintf(buf_old, sizeof(buf_old), "%ld", oldval); + vim_snprintf(buf_old_global, sizeof(buf_old_global), "%ld", oldval_g); + vim_snprintf(buf_new, sizeof(buf_new), "%ld", newval); + vim_snprintf(buf_type, sizeof(buf_type), "%s", + (opt_flags & OPT_LOCAL) ? "local" : "global"); + set_vim_var_string(VV_OPTION_NEW, buf_new, -1); + set_vim_var_string(VV_OPTION_OLD, buf_old, -1); + set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); + if (opt_flags & OPT_LOCAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + } + if (opt_flags & OPT_GLOBAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old, -1); + } + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + set_vim_var_string(VV_OPTION_COMMAND, "set", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old_global, -1); + } + if (opt_flags & OPT_MODELINE) { + set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + } + apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL); + reset_v_option_vars(); +} + /// Set the value of a boolean option, taking care of side effects /// /// @param[in] opt_idx Option index in options[] table. @@ -1971,6 +1904,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va { int old_value = *(int *)varp; int old_global_value = 0; + char *errmsg = NULL; // Disallow changing some options from secure mode if ((secure || sandbox != 0) @@ -2025,7 +1959,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va || (opt_flags & OPT_GLOBAL) || opt_flags == 0) && !bufIsChanged(bp) && bp->b_ml.ml_mfp != NULL) { u_compute_hash(bp, hash); - u_read_undo(NULL, hash, (char_u *)bp->b_fname); + u_read_undo(NULL, hash, bp->b_fname); } } } @@ -2044,14 +1978,12 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } else if ((int *)varp == &curbuf->b_p_ma) { // when 'modifiable' is changed, redraw the window title redraw_titles(); - } else if ((int *)varp == &curbuf->b_p_eol) { - // when 'endofline' is changed, redraw the window title - redraw_titles(); - } else if ((int *)varp == &curbuf->b_p_fixeol) { - // when 'fixeol' is changed, redraw the window title - redraw_titles(); - } else if ((int *)varp == &curbuf->b_p_bomb) { - // when 'bomb' is changed, redraw the window title and tab page text + } else if ((int *)varp == &curbuf->b_p_eof + || (int *)varp == &curbuf->b_p_eol + || (int *)varp == &curbuf->b_p_fixeol + || (int *)varp == &curbuf->b_p_bomb) { + // redraw the window title and tab page text when 'endoffile', 'endofline', + // 'fixeol' or 'bomb' is changed redraw_titles(); } else if ((int *)varp == &curbuf->b_p_bin) { // when 'bin' is set also set some other options @@ -2075,7 +2007,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va paste_option_changed(); } else if ((int *)varp == &p_ic && p_hls) { // when 'ignorecase' is set or reset and 'hlsearch' is set, redraw - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } else if ((int *)varp == &p_hls) { // when 'hlsearch' is set or reset: reset no_hlsearch set_no_hlsearch(false); @@ -2092,7 +2024,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va FOR_ALL_WINDOWS_IN_TAB(win, curtab) { if (win->w_p_pvw && win != curwin) { curwin->w_p_pvw = false; - return N_("E590: A preview window already exists"); + return e_preview_window_already_exists; } } } @@ -2111,10 +2043,9 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } redraw_titles(); modified_was_set = value; - } #ifdef BACKSLASH_IN_FILENAME - else if ((int *)varp == &p_ssl) { + } else if ((int *)varp == &p_ssl) { if (p_ssl) { psepc = '/'; psepcN = '\\'; @@ -2129,9 +2060,8 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va buflist_slash_adjust(); alist_slash_adjust(); scriptnames_slash_adjust(); - } #endif - else if ((int *)varp == &curwin->w_p_wrap) { + } else if ((int *)varp == &curwin->w_p_wrap) { // If 'wrap' is set, set w_leftcol to zero. if (curwin->w_p_wrap) { curwin->w_leftcol = 0; @@ -2151,18 +2081,16 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } } else if ((int *)varp == &curwin->w_p_spell) { // 'spell' if (curwin->w_p_spell) { - char *errmsg = did_set_spelllang(curwin); - if (errmsg != NULL) { - emsg(_(errmsg)); - } + errmsg = did_set_spelllang(curwin); } + } else if (((int *)varp == &curwin->w_p_nu || (int *)varp == &curwin->w_p_rnu) + && *curwin->w_p_stc != NUL) { // '(relative)number' + 'statuscolumn' + curwin->w_nrwidth_line_count = 0; } if ((int *)varp == &curwin->w_p_arab) { if (curwin->w_p_arab) { - /* - * 'arabic' is set, handle various sub-settings. - */ + // 'arabic' is set, handle various sub-settings. if (!p_tbidi) { // set rightleft mode if (!curwin->w_p_rl) { @@ -2173,13 +2101,13 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va // Enable Arabic shaping (major part of what Arabic requires) if (!p_arshape) { p_arshape = true; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } } // Arabic requires a utf-8 encoding, inform the user if it's not // set. - if (STRCMP(p_enc, "utf-8") != 0) { + if (strcmp(p_enc, "utf-8") != 0) { static char *w_arabic = N_("W17: Arabic requires UTF-8, do ':set encoding=utf-8'"); msg_source(HL_ATTR(HLF_W)); @@ -2191,11 +2119,9 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va p_deco = true; // Force-set the necessary keymap for arabic. - set_option_value("keymap", 0L, "arabic", OPT_LOCAL); + errmsg = set_option_value("keymap", 0L, "arabic", OPT_LOCAL); } else { - /* - * 'arabic' is reset, handle various sub-settings. - */ + // 'arabic' is reset, handle various sub-settings. if (!p_tbidi) { // reset rightleft mode if (curwin->w_p_rl) { @@ -2216,48 +2142,16 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } } - /* - * End of handling side effects for bool options. - */ + // End of handling side effects for bool options. // after handling side effects, call autocommand options[opt_idx].flags |= P_WAS_SET; - // Don't do this while starting up or recursively. - if (!starting && *get_vim_var_str(VV_OPTION_TYPE) == NUL) { - char buf_old[2]; - char buf_old_global[2]; - char buf_new[2]; - char buf_type[7]; - vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%d", old_value ? true : false); - vim_snprintf(buf_old_global, ARRAY_SIZE(buf_old_global), "%d", old_global_value ? true : false); - vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%d", value ? true : false); - vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", - (opt_flags & OPT_LOCAL) ? "local" : "global"); - set_vim_var_string(VV_OPTION_NEW, buf_new, -1); - set_vim_var_string(VV_OPTION_OLD, buf_old, -1); - set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - if (opt_flags & OPT_LOCAL) { - set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); - } - if (opt_flags & OPT_GLOBAL) { - set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); - set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old, -1); - } - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - set_vim_var_string(VV_OPTION_COMMAND, "set", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); - set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old_global, -1); - } - if (opt_flags & OPT_MODELINE) { - set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); - } - apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL); - reset_v_option_vars(); - } + apply_optionset_autocmd(opt_idx, opt_flags, + (long)(old_value ? true : false), + (long)(old_global_value ? true : false), + (long)(value ? true : false), NULL); if (options[opt_idx].flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(options[opt_idx].fullname), @@ -2271,7 +2165,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } check_redraw(options[opt_idx].flags); - return NULL; + return errmsg; } /// Set the value of a number option, taking care of side effects @@ -2354,6 +2248,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, int minval = 0; if (value < minval) { errmsg = e_positive; + } else { + p_ch_was_zero = value == 0; } } else if (pp == &p_tm) { if (value < 0) { @@ -2412,7 +2308,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } else if (pp == &curwin->w_p_nuw || pp == &curwin->w_allbuf_opt.wo_nuw) { if (value < 1) { errmsg = e_positive; - } else if (value > 20) { + } else if (value > MAX_NUMBERWIDTH) { errmsg = e_invarg; } } else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) { @@ -2546,7 +2442,9 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, // if p_ch changed value, change the command line height // Only compute the new window layout when startup has been // completed. Otherwise the frame sizes may be wrong. - if (p_ch != old_value && full_screen) { + if ((p_ch != old_value + || tabline_height() + global_stl_height() + topframe->fr_height != Rows - p_ch) + && full_screen) { command_height(); } } else if (pp == &p_uc) { @@ -2622,9 +2520,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, Rows = (int)p_lines; Columns = (int)p_columns; check_screensize(); - if (cmdline_row > Rows - p_ch && Rows > p_ch) { - assert(p_ch >= 0 && Rows - p_ch <= INT_MAX); - cmdline_row = (int)(Rows - p_ch); + int new_row = (int)(Rows - MAX(p_ch, 1)); + if (cmdline_row > new_row && Rows > p_ch) { + assert(p_ch >= 0 && new_row <= INT_MAX); + cmdline_row = new_row; } } if (p_window >= Rows || !option_was_set("window")) { @@ -2664,41 +2563,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, options[opt_idx].flags |= P_WAS_SET; - // Don't do this while starting up, failure or recursively. - if (!starting && errmsg == NULL && *get_vim_var_str(VV_OPTION_TYPE) == NUL) { - char buf_old[NUMBUFLEN]; - char buf_old_global[NUMBUFLEN]; - char buf_new[NUMBUFLEN]; - char buf_type[7]; - - vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%ld", old_value); - vim_snprintf(buf_old_global, ARRAY_SIZE(buf_old_global), "%ld", old_global_value); - vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%ld", value); - vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", - (opt_flags & OPT_LOCAL) ? "local" : "global"); - set_vim_var_string(VV_OPTION_NEW, buf_new, -1); - set_vim_var_string(VV_OPTION_OLD, buf_old, -1); - set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - if (opt_flags & OPT_LOCAL) { - set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); - } - if (opt_flags & OPT_GLOBAL) { - set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); - set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old, -1); - } - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - set_vim_var_string(VV_OPTION_COMMAND, "set", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); - set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old_global, -1); - } - if (opt_flags & OPT_MODELINE) { - set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); - } - apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL); - reset_v_option_vars(); - } + apply_optionset_autocmd(opt_idx, opt_flags, old_value, old_global_value, + value, errmsg); if (errmsg == NULL && options[opt_idx].flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(options[opt_idx].fullname), @@ -2716,32 +2582,38 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } /// Called after an option changed: check if something needs to be redrawn. -void check_redraw(uint32_t flags) +void check_redraw_for(buf_T *buf, win_T *win, uint32_t flags) { - // Careful: P_RCLR and P_RALL are a combination of other P_ flags - bool doclear = (flags & P_RCLR) == P_RCLR; - bool all = ((flags & P_RALL) == P_RALL || doclear); + // Careful: P_RALL is a combination of other P_ flags + bool all = (flags & P_RALL) == P_RALL; if ((flags & P_RSTAT) || all) { // mark all status lines and window bars dirty status_redraw_all(); } + if ((flags & P_RTABL) || all) { // mark tablines dirty + redraw_tabline = true; + } + if ((flags & P_RBUF) || (flags & P_RWIN) || all) { - changed_window_setting(); + changed_window_setting_win(win); } if (flags & P_RBUF) { - redraw_curbuf_later(NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); } if (flags & P_RWINONLY) { - redraw_later(curwin, NOT_VALID); + redraw_later(win, UPD_NOT_VALID); } - if (doclear) { - redraw_all_later(CLEAR); - } else if (all) { - redraw_all_later(NOT_VALID); + if (all) { + redraw_all_later(UPD_NOT_VALID); } } +void check_redraw(uint32_t flags) +{ + check_redraw_for(curbuf, curwin, flags); +} + /// Find index for named option /// /// @param[in] arg Option to find index for. @@ -2751,15 +2623,14 @@ void check_redraw(uint32_t flags) int findoption_len(const char *const arg, const size_t len) { const char *s; - const char *p; static int quick_tab[27] = { 0, 0 }; // quick access table // For first call: Initialize the quick-access table. // It contains the index for the first option that starts with a certain // letter. There are 26 letters, plus the first "t_" option. if (quick_tab[1] == 0) { - p = options[0].fullname; - for (short int i = 1; (s = options[i].fullname) != NULL; i++) { + const char *p = options[0].fullname; + for (uint16_t i = 1; (s = options[i].fullname) != NULL; i++) { if (s[0] != p[0]) { if (s[0] == 't' && s[1] == '_') { quick_tab[26] = i; @@ -2804,11 +2675,11 @@ int findoption_len(const char *const arg, const size_t len) opt_idx = -1; } else { // Nvim: handle option aliases. - if (STRNCMP(options[opt_idx].fullname, "viminfo", 7) == 0) { - if (STRLEN(options[opt_idx].fullname) == 7) { + if (strncmp(options[opt_idx].fullname, "viminfo", 7) == 0) { + if (strlen(options[opt_idx].fullname) == 7) { return findoption_len("shada", 5); } - assert(STRCMP(options[opt_idx].fullname, "viminfofile") == 0); + assert(strcmp(options[opt_idx].fullname, "viminfofile") == 0); return findoption_len("shadafile", 9); } } @@ -2888,7 +2759,7 @@ bool set_tty_option(const char *name, char *value) void set_tty_background(const char *value) { - if (option_was_set("bg") || strequal((char *)p_bg, value)) { + if (option_was_set("bg") || strequal(p_bg, value)) { // background is already set... ignore return; } @@ -2898,7 +2769,7 @@ void set_tty_background(const char *value) ? "autocmd VimEnter * ++once ++nested set bg=light" : "autocmd VimEnter * ++once ++nested set bg=dark"); } else { - set_option_value("bg", 0L, value, 0); + set_option_value_give_err("bg", 0L, value, 0); reset_option_was_set("bg"); } } @@ -2917,6 +2788,7 @@ int findoption(const char *const arg) /// Gets the value for an option. /// /// @param stringval NULL when only checking existence +/// @param flagsp set to the option flags (P_xxxx) (if not NULL) /// /// @returns: /// Number option: gov_number, *numval gets value. @@ -2926,7 +2798,8 @@ int findoption(const char *const arg) /// Hidden Toggle option: gov_hidden_bool. /// Hidden String option: gov_hidden_string. /// Unknown option: gov_unknown. -getoption_T get_option_value(const char *name, long *numval, char **stringval, int opt_flags) +getoption_T get_option_value(const char *name, long *numval, char **stringval, uint32_t *flagsp, + int scope) { if (get_tty_option(name, stringval)) { return gov_string; @@ -2937,14 +2810,19 @@ getoption_T get_option_value(const char *name, long *numval, char **stringval, i return gov_unknown; } - char_u *varp = get_varp_scope(&(options[opt_idx]), opt_flags); + char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), scope); + + if (flagsp != NULL) { + // Return the P_xxxx option flags. + *flagsp = options[opt_idx].flags; + } if (options[opt_idx].flags & P_STRING) { if (varp == NULL) { // hidden option return gov_hidden_string; } if (stringval != NULL) { - if ((char_u **)varp == &p_pt) { // 'pastetoggle' + if ((char **)varp == &p_pt) { // 'pastetoggle' *stringval = str2special_save(*(char **)(varp), false, false); } else { *stringval = xstrdup(*(char **)(varp)); @@ -2992,7 +2870,6 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o return SOPT_STRING | SOPT_GLOBAL; } - char_u *varp = NULL; int rv = 0; int opt_idx = findoption(name); if (opt_idx < 0) { @@ -3028,15 +2905,13 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o if (p->indir & PV_WIN) { if (opt_type == SREQ_BUF) { return 0; // Requested buffer-local, not window-local option - } else { - rv |= SOPT_WIN; } + rv |= SOPT_WIN; } else if (p->indir & PV_BUF) { if (opt_type == SREQ_WIN) { return 0; // Requested window-local, not buffer-local option - } else { - rv |= SOPT_BUF; } + rv |= SOPT_BUF; } } @@ -3044,12 +2919,13 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o return rv; } + char_u *varp = NULL; + if (opt_type == SREQ_GLOBAL) { if (p->var == VAR_WIN) { return 0; - } else { - varp = p->var; } + varp = p->var; } else { if (opt_type == SREQ_BUF) { // Special case: 'modified' is b_changed, but we also want to @@ -3063,7 +2939,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o // only getting a pointer, no need to use aucmd_prepbuf() curbuf = (buf_T *)from; curwin->w_buffer = curbuf; - varp = get_varp_scope(p, OPT_LOCAL); + varp = (char_u *)get_varp_scope(p, OPT_LOCAL); curbuf = save_curbuf; curwin->w_buffer = curbuf; } @@ -3071,7 +2947,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o win_T *save_curwin = curwin; curwin = (win_T *)from; curbuf = curwin->w_buffer; - varp = get_varp_scope(p, OPT_LOCAL); + varp = (char_u *)get_varp_scope(p, OPT_LOCAL); curwin = save_curwin; curbuf = curwin->w_buffer; } @@ -3083,7 +2959,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o if (varp != NULL) { if (p->flags & P_STRING) { - *stringval = xstrdup(*(char **)(varp)); + *stringval = *(char **)(varp); } else if (p->flags & P_NUM) { *numval = *(long *)varp; } else { @@ -3094,47 +2970,10 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o return rv; } -/// Return the flags for the option at 'opt_idx'. -uint32_t get_option_flags(int opt_idx) -{ - return options[opt_idx].flags; -} - -/// Set a flag for the option at 'opt_idx'. -void set_option_flag(int opt_idx, uint32_t flag) +// Return information for option at 'opt_idx' +vimoption_T *get_option(int opt_idx) { - options[opt_idx].flags |= flag; -} - -/// Clear a flag for the option at 'opt_idx'. -void clear_option_flag(int opt_idx, uint32_t flag) -{ - options[opt_idx].flags &= ~flag; -} - -/// Returns true if the option at 'opt_idx' is a global option -bool is_global_option(int opt_idx) -{ - return options[opt_idx].indir == PV_NONE; -} - -/// Returns true if the option at 'opt_idx' is a global option which also has a -/// local value. -int is_global_local_option(int opt_idx) -{ - return options[opt_idx].indir & PV_BOTH; -} - -/// Returns true if the option at 'opt_idx' is a window-local option -bool is_window_local_option(int opt_idx) -{ - return options[opt_idx].var == VAR_WIN; -} - -/// Returns true if the option at 'opt_idx' is a hidden option -bool is_hidden_option(int opt_idx) -{ - return options[opt_idx].var == NULL; + return &options[opt_idx]; } /// Set the value of an option @@ -3147,7 +2986,7 @@ bool is_hidden_option(int opt_idx) /// is cleared (the exact semantics of this depend /// on the option). /// -/// @return NULL on success, error message on error. +/// @return NULL on success, an untranslated error message on error. char *set_option_value(const char *const name, const long number, const char *const string, const int opt_flags) FUNC_ATTR_NONNULL_ARG(1) @@ -3156,65 +2995,83 @@ char *set_option_value(const char *const name, const long number, const char *co return NULL; // Fail silently; many old vimrcs set t_xx options. } - int opt_idx; - char_u *varp; - - opt_idx = findoption(name); + int opt_idx = findoption(name); if (opt_idx < 0) { semsg(_("E355: Unknown option: %s"), name); - } else { - uint32_t flags = options[opt_idx].flags; - // Disallow changing some options in the sandbox - if (sandbox > 0 && (flags & P_SECURE)) { - emsg(_(e_sandbox)); - return NULL; + return NULL; + } + + uint32_t flags = options[opt_idx].flags; + // Disallow changing some options in the sandbox + if (sandbox > 0 && (flags & P_SECURE)) { + emsg(_(e_sandbox)); + return NULL; + } + + if (flags & P_STRING) { + const char *s = string; + if (s == NULL || opt_flags & OPT_CLEAR) { + s = ""; } - if (flags & P_STRING) { - const char *s = string; - if (s == NULL || opt_flags & OPT_CLEAR) { - s = ""; - } - return set_string_option(opt_idx, s, opt_flags); - } - - varp = get_varp_scope(&(options[opt_idx]), opt_flags); - if (varp != NULL) { // hidden option is not changed - if (number == 0 && string != NULL) { - int idx; - - // Either we are given a string or we are setting option - // to zero. - for (idx = 0; string[idx] == '0'; idx++) {} - if (string[idx] != NUL || idx == 0) { - // There's another character after zeros or the string - // is empty. In both cases, we are trying to set a - // num option using a string. - semsg(_("E521: Number required: &%s = '%s'"), - name, string); - return NULL; // do nothing as we hit an error - } - } - long numval = number; - if (opt_flags & OPT_CLEAR) { - if ((int *)varp == &curbuf->b_p_ar) { - numval = -1; - } else if ((long *)varp == &curbuf->b_p_ul) { - numval = NO_LOCAL_UNDOLEVEL; - } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) { - numval = -1; - } else { - char *s = NULL; - (void)get_option_value(name, &numval, &s, OPT_GLOBAL); - } - } - if (flags & P_NUM) { - return set_num_option(opt_idx, varp, numval, NULL, 0, opt_flags); - } else { - return set_bool_option(opt_idx, varp, (int)numval, opt_flags); - } + return set_string_option(opt_idx, s, opt_flags); + } + + char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), opt_flags); + if (varp == NULL) { + // hidden option is not changed + return NULL; + } + + if (number == 0 && string != NULL) { + int idx; + + // Either we are given a string or we are setting option + // to zero. + for (idx = 0; string[idx] == '0'; idx++) {} + if (string[idx] != NUL || idx == 0) { + // There's another character after zeros or the string + // is empty. In both cases, we are trying to set a + // num option using a string. + semsg(_("E521: Number required: &%s = '%s'"), + name, string); + return NULL; // do nothing as we hit an error + } + } + long numval = number; + if (opt_flags & OPT_CLEAR) { + if ((int *)varp == &curbuf->b_p_ar) { + numval = -1; + } else if ((long *)varp == &curbuf->b_p_ul) { + numval = NO_LOCAL_UNDOLEVEL; + } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) { + numval = -1; + } else { + char *s = NULL; + (void)get_option_value(name, &numval, &s, NULL, OPT_GLOBAL); } } - return NULL; + if (flags & P_NUM) { + return set_num_option(opt_idx, varp, numval, NULL, 0, opt_flags); + } + return set_bool_option(opt_idx, varp, (int)numval, opt_flags); +} + +/// Call set_option_value() and when an error is returned report it. +/// +/// @param opt_flags OPT_LOCAL or 0 (both) +void set_option_value_give_err(const char *name, long number, const char *string, int opt_flags) +{ + char *errmsg = set_option_value(name, number, string, opt_flags); + + if (errmsg != NULL) { + emsg(_(errmsg)); + } +} + +bool is_option_allocated(const char *name) +{ + int idx = findoption(name); + return idx >= 0 && (options[idx].flags & P_ALLOCED); } /// Return true if "name" is a string option. @@ -3228,19 +3085,18 @@ bool is_string_option(const char *name) // Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number. // When "has_lt" is true there is a '<' before "*arg_arg". // Returns 0 when the key is not recognized. -int find_key_option_len(const char_u *arg_arg, size_t len, bool has_lt) +int find_key_option_len(const char *arg_arg, size_t len, bool has_lt) { int key = 0; - int modifiers; - const char_u *arg = arg_arg; + const char *arg = arg_arg; // Don't use get_special_key_code() for t_xx, we don't want it to call // add_termcap_entry(). if (len >= 4 && arg[0] == 't' && arg[1] == '_') { - key = TERMCAP2KEY(arg[2], arg[3]); + key = TERMCAP2KEY((uint8_t)arg[2], (uint8_t)arg[3]); } else if (has_lt) { arg--; // put arg at the '<' - modifiers = 0; + int modifiers = 0; key = find_special_key(&arg, len + 1, &modifiers, FSK_KEYCODE | FSK_KEEP_X_KEY | FSK_SIMPLIFY, NULL); if (modifiers) { // can't handle modifiers here @@ -3250,9 +3106,9 @@ int find_key_option_len(const char_u *arg_arg, size_t len, bool has_lt) return key; } -static int find_key_option(const char_u *arg, bool has_lt) +static int find_key_option(const char *arg, bool has_lt) { - return find_key_option_len(arg, STRLEN(arg), has_lt); + return find_key_option_len(arg, strlen(arg), has_lt); } /// if 'all' == 0: show changed options @@ -3261,16 +3117,6 @@ static int find_key_option(const char_u *arg, bool has_lt) /// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL static void showoptions(int all, int opt_flags) { - vimoption_T *p; - int col; - char_u *varp; - int item_count; - int run; - int row, rows; - int cols; - int i; - int len; - #define INC 20 #define GAP 3 @@ -3289,32 +3135,33 @@ static void showoptions(int all, int opt_flags) // 1. display the short items // 2. display the long items (only strings and numbers) // When "opt_flags" has OPT_ONECOLUMN do everything in run 2. - for (run = 1; run <= 2 && !got_int; run++) { + for (int run = 1; run <= 2 && !got_int; run++) { // collect the items in items[] - item_count = 0; - for (p = &options[0]; p->fullname != NULL; p++) { + int item_count = 0; + for (vimoption_T *p = &options[0]; p->fullname != NULL; p++) { // apply :filter /pat/ - if (message_filtered((char_u *)p->fullname)) { + if (message_filtered(p->fullname)) { continue; } - varp = NULL; + char_u *varp = NULL; if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) != 0) { if (p->indir != PV_NONE) { - varp = get_varp_scope(p, opt_flags); + varp = (char_u *)get_varp_scope(p, opt_flags); } } else { varp = get_varp(p); } if (varp != NULL && (all == 1 || (all == 0 && !optval_default(p, varp)))) { + int len; if (opt_flags & OPT_ONECOLUMN) { len = Columns; } else if (p->flags & P_BOOL) { len = 1; // a toggle option fits always } else { option_value2string(p, opt_flags); - len = (int)STRLEN(p->fullname) + vim_strsize((char *)NameBuff) + 1; + len = (int)strlen(p->fullname) + vim_strsize(NameBuff) + 1; } if ((len <= INC - GAP && run == 1) || (len > INC - GAP && run == 2)) { @@ -3323,15 +3170,15 @@ static void showoptions(int all, int opt_flags) } } - /* - * display the items - */ + int rows; + + // display the items if (run == 1) { assert(Columns <= INT_MAX - GAP && Columns + GAP >= INT_MIN + 3 && (Columns + GAP - 3) / INC >= INT_MIN && (Columns + GAP - 3) / INC <= INT_MAX); - cols = (Columns + GAP - 3) / INC; + int cols = (Columns + GAP - 3) / INC; if (cols == 0) { cols = 1; } @@ -3339,18 +3186,17 @@ static void showoptions(int all, int opt_flags) } else { // run == 2 rows = item_count; } - for (row = 0; row < rows && !got_int; row++) { + for (int row = 0; row < rows && !got_int; row++) { msg_putchar('\n'); // go to next line if (got_int) { // 'q' typed in more break; } - col = 0; - for (i = row; i < item_count; i += rows) { + int col = 0; + for (int i = row; i < item_count; i += rows) { msg_col = col; // make columns showoneopt(items[i], opt_flags); col += INC; } - ui_flush(); os_breakcheck(); } } @@ -3358,7 +3204,7 @@ static void showoptions(int all, int opt_flags) } /// Return true if option "p" has its default value. -static int optval_default(vimoption_T *p, char_u *varp) +static int optval_default(vimoption_T *p, const char_u *varp) { if (varp == NULL) { return true; // hidden option is always at default @@ -3370,7 +3216,7 @@ static int optval_default(vimoption_T *p, char_u *varp) return *(int *)varp == (int)(intptr_t)p->def_val; } // P_STRING - return STRCMP(*(char_u **)varp, p->def_val) == 0; + return strcmp(*(char **)varp, p->def_val) == 0; } /// Send update to UIs with values of UI relevant options @@ -3405,13 +3251,12 @@ void ui_refresh_options(void) /// @param opt_flags OPT_LOCAL or OPT_GLOBAL static void showoneopt(vimoption_T *p, int opt_flags) { - char_u *varp; int save_silent = silent_mode; silent_mode = false; - info_message = true; // use mch_msg(), not mch_errmsg() + info_message = true; // use os_msg(), not os_errmsg() - varp = get_varp_scope(p, opt_flags); + char_u *varp = (char_u *)get_varp_scope(p, opt_flags); // for 'modified' we also need to check if 'ff' or 'fenc' changed. if ((p->flags & P_BOOL) && ((int *)varp == &curbuf->b_changed @@ -3427,7 +3272,7 @@ static void showoneopt(vimoption_T *p, int opt_flags) msg_putchar('='); // put value string in NameBuff option_value2string(p, opt_flags); - msg_outtrans((char *)NameBuff); + msg_outtrans(NameBuff); } silent_mode = save_silent; @@ -3456,25 +3301,15 @@ static void showoneopt(vimoption_T *p, int opt_flags) /// Return FAIL on error, OK otherwise. int makeset(FILE *fd, int opt_flags, int local_only) { - vimoption_T *p; - char_u *varp; // currently used value - char_u *varp_fresh; // local value - char_u *varp_local = NULL; // fresh value - char *cmd; - int round; - int pri; - - /* - * Some options are never written: - * - Options that don't have a default (terminal name, columns, lines). - * - Terminal options. - * - Hidden options. - * - * Do the loop over "options[]" twice: once for options with the - * P_PRI_MKRC flag and once without. - */ - for (pri = 1; pri >= 0; pri--) { - for (p = &options[0]; p->fullname; p++) { + // Some options are never written: + // - Options that don't have a default (terminal name, columns, lines). + // - Terminal options. + // - Hidden options. + // + // Do the loop over "options[]" twice: once for options with the + // P_PRI_MKRC flag and once without. + for (int pri = 1; pri >= 0; pri--) { + for (vimoption_T *p = &options[0]; p->fullname; p++) { if (!(p->flags & P_NO_MKRC) && ((pri == 1) == ((p->flags & P_PRI_MKRC) != 0))) { // skip global option when only doing locals @@ -3488,13 +3323,13 @@ int makeset(FILE *fd, int opt_flags, int local_only) continue; } - varp = get_varp_scope(p, opt_flags); + char *varp = get_varp_scope(p, opt_flags); // currently used value // Hidden options are never written. if (!varp) { continue; } // Global values are only written when not at the default value. - if ((opt_flags & OPT_GLOBAL) && optval_default(p, varp)) { + if ((opt_flags & OPT_GLOBAL) && optval_default(p, (char_u *)varp)) { continue; } @@ -3503,7 +3338,8 @@ int makeset(FILE *fd, int opt_flags, int local_only) continue; } - round = 2; + int round = 2; + char_u *varp_local = NULL; // fresh value if (p->indir != PV_NONE) { if (p->var == VAR_WIN) { // skip window-local option when only doing globals @@ -3513,11 +3349,11 @@ int makeset(FILE *fd, int opt_flags, int local_only) // When fresh value of window-local option is not at the // default, need to write it too. if (!(opt_flags & OPT_GLOBAL) && !local_only) { - varp_fresh = get_varp_scope(p, OPT_GLOBAL); + char_u *varp_fresh = (char_u *)get_varp_scope(p, OPT_GLOBAL); // local value if (!optval_default(p, varp_fresh)) { round = 1; - varp_local = varp; - varp = varp_fresh; + varp_local = (char_u *)varp; + varp = (char *)varp_fresh; } } } @@ -3525,7 +3361,8 @@ int makeset(FILE *fd, int opt_flags, int local_only) // Round 1: fresh value for window-local options. // Round 2: other values - for (; round <= 2; varp = varp_local, round++) { + for (; round <= 2; varp = (char *)varp_local, round++) { + char *cmd; if (round == 1 || (opt_flags & OPT_GLOBAL)) { cmd = "set"; } else { @@ -3553,8 +3390,7 @@ int makeset(FILE *fd, int opt_flags, int local_only) } do_endif = true; } - if (put_setstring(fd, cmd, p->fullname, (char_u **)varp, - p->flags) == FAIL) { + if (put_setstring(fd, cmd, p->fullname, (char **)varp, p->flags) == FAIL) { return FAIL; } if (do_endif) { @@ -3575,65 +3411,58 @@ int makeset(FILE *fd, int opt_flags, int local_only) int makefoldset(FILE *fd) { if (put_setstring(fd, "setlocal", "fdm", &curwin->w_p_fdm, 0) == FAIL - || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, 0) - == FAIL - || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, 0) - == FAIL - || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, 0) - == FAIL + || put_setstring(fd, "setlocal", "fde", &curwin->w_p_fde, 0) == FAIL + || put_setstring(fd, "setlocal", "fmr", &curwin->w_p_fmr, 0) == FAIL + || put_setstring(fd, "setlocal", "fdi", &curwin->w_p_fdi, 0) == FAIL || put_setnum(fd, "setlocal", "fdl", &curwin->w_p_fdl) == FAIL || put_setnum(fd, "setlocal", "fml", &curwin->w_p_fml) == FAIL || put_setnum(fd, "setlocal", "fdn", &curwin->w_p_fdn) == FAIL - || put_setbool(fd, "setlocal", "fen", - curwin->w_p_fen) == FAIL) { + || put_setbool(fd, "setlocal", "fen", curwin->w_p_fen) == FAIL) { return FAIL; } return OK; } -static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint64_t flags) +static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_t flags) { - char_u *s; - char_u *buf = NULL; - char_u *part = NULL; - char *p; - if (fprintf(fd, "%s %s=", cmd, name) < 0) { return FAIL; } + + char *buf = NULL; + char_u *part = NULL; + if (*valuep != NULL) { // Output 'pastetoggle' as key names. For other // options some characters have to be escaped with // CTRL-V or backslash if (valuep == &p_pt) { - s = *valuep; + char_u *s = (char_u *)(*valuep); while (*s != NUL) { - if (put_escstr(fd, (char_u *)str2special((const char **)&s, false, - false), 2) - == FAIL) { + if (put_escstr(fd, (char *)str2special((const char **)&s, false, false), 2) == FAIL) { return FAIL; } } } else if ((flags & P_EXPAND) != 0) { - size_t size = (size_t)STRLEN(*valuep) + 1; + size_t size = (size_t)strlen(*valuep) + 1; // replace home directory in the whole option value into "buf" buf = xmalloc(size); - home_replace(NULL, (char *)(*valuep), (char *)buf, size, false); + home_replace(NULL, *valuep, buf, size, false); // If the option value is longer than MAXPATHL, we need to append // each comma separated part of the option separately, so that it // can be expanded when read back. if (size >= MAXPATHL && (flags & P_COMMA) != 0 - && vim_strchr((char *)(*valuep), ',') != NULL) { + && vim_strchr(*valuep, ',') != NULL) { part = xmalloc(size); // write line break to clear the option, e.g. ':set rtp=' if (put_eol(fd) == FAIL) { goto fail; } - p = (char *)buf; + char *p = buf; while (*p != NUL) { // for each comma separated option part, append value to // the option, :set rtp+=value @@ -3641,7 +3470,7 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char_u **valuep, uint6 goto fail; } (void)copy_option_part(&p, (char *)part, size, ","); - if (put_escstr(fd, part, 2) == FAIL || put_eol(fd) == FAIL) { + if (put_escstr(fd, (char *)part, 2) == FAIL || put_eol(fd) == FAIL) { goto fail; } } @@ -3670,11 +3499,10 @@ fail: static int put_setnum(FILE *fd, char *cmd, char *name, long *valuep) { - long wc; - if (fprintf(fd, "%s %s=", cmd, name) < 0) { return FAIL; } + long wc; if (wc_use_keyname((char_u *)valuep, &wc)) { // print 'wildchar' and 'wildcharm' as a key name if (fputs((char *)get_special_key_name((int)wc, 0), fd) < 0) { @@ -3714,8 +3542,7 @@ void unset_global_local_option(char *name, void *from) } p = &(options[opt_idx]); - switch ((int)p->indir) - { + switch ((int)p->indir) { // global option with local value: use local value if it's been set case PV_EP: clear_string_option(&buf->b_p_ep); @@ -3780,7 +3607,7 @@ void unset_global_local_option(char *name, void *from) clear_string_option(&((win_T *)from)->w_p_stl); break; case PV_WBR: - clear_string_option((char_u **)&((win_T *)from)->w_p_wbr); + clear_string_option(&((win_T *)from)->w_p_wbr); break; case PV_UL: buf->b_p_ul = NO_LOCAL_UNDOLEVEL; @@ -3794,102 +3621,104 @@ void unset_global_local_option(char *name, void *from) case PV_LCS: clear_string_option(&((win_T *)from)->w_p_lcs); set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs, true); - redraw_later((win_T *)from, NOT_VALID); + redraw_later((win_T *)from, UPD_NOT_VALID); break; case PV_FCS: clear_string_option(&((win_T *)from)->w_p_fcs); set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true); - redraw_later((win_T *)from, NOT_VALID); + redraw_later((win_T *)from, UPD_NOT_VALID); break; case PV_VE: clear_string_option(&((win_T *)from)->w_p_ve); ((win_T *)from)->w_ve_flags = 0; break; + case PV_STC: + clear_string_option(&((win_T *)from)->w_p_stc); + break; } } -/// Get pointer to option variable, depending on local or global scope. -static char_u *get_varp_scope(vimoption_T *p, int opt_flags) +char *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win) { - if ((opt_flags & OPT_GLOBAL) && p->indir != PV_NONE) { + if ((scope & OPT_GLOBAL) && p->indir != PV_NONE) { if (p->var == VAR_WIN) { - return (char_u *)GLOBAL_WO(get_varp(p)); + return GLOBAL_WO(get_varp_from(p, buf, win)); } - return p->var; + return (char *)p->var; } - if ((opt_flags & OPT_LOCAL) && ((int)p->indir & PV_BOTH)) { + if ((scope & OPT_LOCAL) && ((int)p->indir & PV_BOTH)) { switch ((int)p->indir) { case PV_FP: - return (char_u *)&(curbuf->b_p_fp); + return (char *)&(buf->b_p_fp); case PV_EFM: - return (char_u *)&(curbuf->b_p_efm); + return (char *)&(buf->b_p_efm); case PV_GP: - return (char_u *)&(curbuf->b_p_gp); + return (char *)&(buf->b_p_gp); case PV_MP: - return (char_u *)&(curbuf->b_p_mp); + return (char *)&(buf->b_p_mp); case PV_EP: - return (char_u *)&(curbuf->b_p_ep); + return (char *)&(buf->b_p_ep); case PV_KP: - return (char_u *)&(curbuf->b_p_kp); + return (char *)&(buf->b_p_kp); case PV_PATH: - return (char_u *)&(curbuf->b_p_path); + return (char *)&(buf->b_p_path); case PV_AR: - return (char_u *)&(curbuf->b_p_ar); + return (char *)&(buf->b_p_ar); case PV_TAGS: - return (char_u *)&(curbuf->b_p_tags); + return (char *)&(buf->b_p_tags); case PV_TC: - return (char_u *)&(curbuf->b_p_tc); + return (char *)&(buf->b_p_tc); case PV_SISO: - return (char_u *)&(curwin->w_p_siso); + return (char *)&(win->w_p_siso); case PV_SO: - return (char_u *)&(curwin->w_p_so); + return (char *)&(win->w_p_so); case PV_DEF: - return (char_u *)&(curbuf->b_p_def); + return (char *)&(buf->b_p_def); case PV_INC: - return (char_u *)&(curbuf->b_p_inc); + return (char *)&(buf->b_p_inc); case PV_DICT: - return (char_u *)&(curbuf->b_p_dict); + return (char *)&(buf->b_p_dict); case PV_TSR: - return (char_u *)&(curbuf->b_p_tsr); + return (char *)&(buf->b_p_tsr); case PV_TSRFU: - return (char_u *)&(curbuf->b_p_tsrfu); + return (char *)&(buf->b_p_tsrfu); case PV_TFU: - return (char_u *)&(curbuf->b_p_tfu); + return (char *)&(buf->b_p_tfu); case PV_SBR: - return (char_u *)&(curwin->w_p_sbr); + return (char *)&(win->w_p_sbr); case PV_STL: - return (char_u *)&(curwin->w_p_stl); + return (char *)&(win->w_p_stl); case PV_WBR: - return (char_u *)&(curwin->w_p_wbr); + return (char *)&(win->w_p_wbr); case PV_UL: - return (char_u *)&(curbuf->b_p_ul); + return (char *)&(buf->b_p_ul); case PV_LW: - return (char_u *)&(curbuf->b_p_lw); + return (char *)&(buf->b_p_lw); case PV_BKC: - return (char_u *)&(curbuf->b_p_bkc); + return (char *)&(buf->b_p_bkc); case PV_MENC: - return (char_u *)&(curbuf->b_p_menc); + return (char *)&(buf->b_p_menc); case PV_FCS: - return (char_u *)&(curwin->w_p_fcs); + return (char *)&(win->w_p_fcs); case PV_LCS: - return (char_u *)&(curwin->w_p_lcs); + return (char *)&(win->w_p_lcs); case PV_VE: - return (char_u *)&(curwin->w_p_ve); + return (char *)&(win->w_p_ve); } return NULL; // "cannot happen" } - return get_varp(p); + return (char *)get_varp_from(p, buf, win); } -/// Get pointer to option variable at 'opt_idx', depending on local or global -/// scope. -char_u *get_option_varp_scope(int opt_idx, int opt_flags) +/// Get pointer to option variable, depending on local or global scope. +/// +/// @param scope can be OPT_LOCAL, OPT_GLOBAL or a combination. +char *get_varp_scope(vimoption_T *p, int scope) { - return get_varp_scope(&(options[opt_idx]), opt_flags); + return get_varp_scope_from(p, scope, curbuf, curwin); } -/// Get pointer to option variable. -static char_u *get_varp(vimoption_T *p) +static char_u *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) { // hidden option, always return NULL if (p->var == NULL) { @@ -3902,317 +3731,317 @@ static char_u *get_varp(vimoption_T *p) // global option with local value: use local value if it's been set case PV_EP: - return *curbuf->b_p_ep != NUL - ? (char_u *)&curbuf->b_p_ep : p->var; + return *buf->b_p_ep != NUL + ? (char_u *)&buf->b_p_ep : p->var; case PV_KP: - return *curbuf->b_p_kp != NUL - ? (char_u *)&curbuf->b_p_kp : p->var; + return *buf->b_p_kp != NUL + ? (char_u *)&buf->b_p_kp : p->var; case PV_PATH: - return *curbuf->b_p_path != NUL - ? (char_u *)&(curbuf->b_p_path) : p->var; + return *buf->b_p_path != NUL + ? (char_u *)&(buf->b_p_path) : p->var; case PV_AR: - return curbuf->b_p_ar >= 0 - ? (char_u *)&(curbuf->b_p_ar) : p->var; + return buf->b_p_ar >= 0 + ? (char_u *)&(buf->b_p_ar) : p->var; case PV_TAGS: - return *curbuf->b_p_tags != NUL - ? (char_u *)&(curbuf->b_p_tags) : p->var; + return *buf->b_p_tags != NUL + ? (char_u *)&(buf->b_p_tags) : p->var; case PV_TC: - return *curbuf->b_p_tc != NUL - ? (char_u *)&(curbuf->b_p_tc) : p->var; + return *buf->b_p_tc != NUL + ? (char_u *)&(buf->b_p_tc) : p->var; case PV_SISO: - return curwin->w_p_siso >= 0 - ? (char_u *)&(curwin->w_p_siso) : p->var; + return win->w_p_siso >= 0 + ? (char_u *)&(win->w_p_siso) : p->var; case PV_SO: - return curwin->w_p_so >= 0 - ? (char_u *)&(curwin->w_p_so) : p->var; + return win->w_p_so >= 0 + ? (char_u *)&(win->w_p_so) : p->var; case PV_BKC: - return *curbuf->b_p_bkc != NUL - ? (char_u *)&(curbuf->b_p_bkc) : p->var; + return *buf->b_p_bkc != NUL + ? (char_u *)&(buf->b_p_bkc) : p->var; case PV_DEF: - return *curbuf->b_p_def != NUL - ? (char_u *)&(curbuf->b_p_def) : p->var; + return *buf->b_p_def != NUL + ? (char_u *)&(buf->b_p_def) : p->var; case PV_INC: - return *curbuf->b_p_inc != NUL - ? (char_u *)&(curbuf->b_p_inc) : p->var; + return *buf->b_p_inc != NUL + ? (char_u *)&(buf->b_p_inc) : p->var; case PV_DICT: - return *curbuf->b_p_dict != NUL - ? (char_u *)&(curbuf->b_p_dict) : p->var; + return *buf->b_p_dict != NUL + ? (char_u *)&(buf->b_p_dict) : p->var; case PV_TSR: - return *curbuf->b_p_tsr != NUL - ? (char_u *)&(curbuf->b_p_tsr) : p->var; + return *buf->b_p_tsr != NUL + ? (char_u *)&(buf->b_p_tsr) : p->var; case PV_TSRFU: - return *curbuf->b_p_tsrfu != NUL - ? (char_u *)&(curbuf->b_p_tsrfu) : p->var; + return *buf->b_p_tsrfu != NUL + ? (char_u *)&(buf->b_p_tsrfu) : p->var; case PV_FP: - return *curbuf->b_p_fp != NUL - ? (char_u *)&(curbuf->b_p_fp) : p->var; + return *buf->b_p_fp != NUL + ? (char_u *)&(buf->b_p_fp) : p->var; case PV_EFM: - return *curbuf->b_p_efm != NUL - ? (char_u *)&(curbuf->b_p_efm) : p->var; + return *buf->b_p_efm != NUL + ? (char_u *)&(buf->b_p_efm) : p->var; case PV_GP: - return *curbuf->b_p_gp != NUL - ? (char_u *)&(curbuf->b_p_gp) : p->var; + return *buf->b_p_gp != NUL + ? (char_u *)&(buf->b_p_gp) : p->var; case PV_MP: - return *curbuf->b_p_mp != NUL - ? (char_u *)&(curbuf->b_p_mp) : p->var; + return *buf->b_p_mp != NUL + ? (char_u *)&(buf->b_p_mp) : p->var; case PV_SBR: - return *curwin->w_p_sbr != NUL - ? (char_u *)&(curwin->w_p_sbr) : p->var; + return *win->w_p_sbr != NUL + ? (char_u *)&(win->w_p_sbr) : p->var; case PV_STL: - return *curwin->w_p_stl != NUL - ? (char_u *)&(curwin->w_p_stl) : p->var; + return *win->w_p_stl != NUL + ? (char_u *)&(win->w_p_stl) : p->var; case PV_WBR: - return *curwin->w_p_wbr != NUL - ? (char_u *)&(curwin->w_p_wbr) : p->var; + return *win->w_p_wbr != NUL + ? (char_u *)&(win->w_p_wbr) : p->var; case PV_UL: - return curbuf->b_p_ul != NO_LOCAL_UNDOLEVEL - ? (char_u *)&(curbuf->b_p_ul) : p->var; + return buf->b_p_ul != NO_LOCAL_UNDOLEVEL + ? (char_u *)&(buf->b_p_ul) : p->var; case PV_LW: - return *curbuf->b_p_lw != NUL - ? (char_u *)&(curbuf->b_p_lw) : p->var; + return *buf->b_p_lw != NUL + ? (char_u *)&(buf->b_p_lw) : p->var; case PV_MENC: - return *curbuf->b_p_menc != NUL - ? (char_u *)&(curbuf->b_p_menc) : p->var; + return *buf->b_p_menc != NUL + ? (char_u *)&(buf->b_p_menc) : p->var; case PV_FCS: - return *curwin->w_p_fcs != NUL - ? (char_u *)&(curwin->w_p_fcs) : p->var; + return *win->w_p_fcs != NUL + ? (char_u *)&(win->w_p_fcs) : p->var; case PV_LCS: - return *curwin->w_p_lcs != NUL - ? (char_u *)&(curwin->w_p_lcs) : p->var; + return *win->w_p_lcs != NUL + ? (char_u *)&(win->w_p_lcs) : p->var; case PV_VE: - return *curwin->w_p_ve != NUL - ? (char_u *)&curwin->w_p_ve : p->var; + return *win->w_p_ve != NUL + ? (char_u *)&win->w_p_ve : p->var; case PV_ARAB: - return (char_u *)&(curwin->w_p_arab); + return (char_u *)&(win->w_p_arab); case PV_LIST: - return (char_u *)&(curwin->w_p_list); + return (char_u *)&(win->w_p_list); case PV_SPELL: - return (char_u *)&(curwin->w_p_spell); + return (char_u *)&(win->w_p_spell); case PV_CUC: - return (char_u *)&(curwin->w_p_cuc); + return (char_u *)&(win->w_p_cuc); case PV_CUL: - return (char_u *)&(curwin->w_p_cul); + return (char_u *)&(win->w_p_cul); case PV_CULOPT: - return (char_u *)&(curwin->w_p_culopt); + return (char_u *)&(win->w_p_culopt); case PV_CC: - return (char_u *)&(curwin->w_p_cc); + return (char_u *)&(win->w_p_cc); case PV_DIFF: - return (char_u *)&(curwin->w_p_diff); + return (char_u *)&(win->w_p_diff); case PV_FDC: - return (char_u *)&(curwin->w_p_fdc); + return (char_u *)&(win->w_p_fdc); case PV_FEN: - return (char_u *)&(curwin->w_p_fen); + return (char_u *)&(win->w_p_fen); case PV_FDI: - return (char_u *)&(curwin->w_p_fdi); + return (char_u *)&(win->w_p_fdi); case PV_FDL: - return (char_u *)&(curwin->w_p_fdl); + return (char_u *)&(win->w_p_fdl); case PV_FDM: - return (char_u *)&(curwin->w_p_fdm); + return (char_u *)&(win->w_p_fdm); case PV_FML: - return (char_u *)&(curwin->w_p_fml); + return (char_u *)&(win->w_p_fml); case PV_FDN: - return (char_u *)&(curwin->w_p_fdn); + return (char_u *)&(win->w_p_fdn); case PV_FDE: - return (char_u *)&(curwin->w_p_fde); + return (char_u *)&(win->w_p_fde); case PV_FDT: - return (char_u *)&(curwin->w_p_fdt); + return (char_u *)&(win->w_p_fdt); case PV_FMR: - return (char_u *)&(curwin->w_p_fmr); + return (char_u *)&(win->w_p_fmr); case PV_NU: - return (char_u *)&(curwin->w_p_nu); + return (char_u *)&(win->w_p_nu); case PV_RNU: - return (char_u *)&(curwin->w_p_rnu); + return (char_u *)&(win->w_p_rnu); case PV_NUW: - return (char_u *)&(curwin->w_p_nuw); + return (char_u *)&(win->w_p_nuw); case PV_WFH: - return (char_u *)&(curwin->w_p_wfh); + return (char_u *)&(win->w_p_wfh); case PV_WFW: - return (char_u *)&(curwin->w_p_wfw); + return (char_u *)&(win->w_p_wfw); case PV_PVW: - return (char_u *)&(curwin->w_p_pvw); + return (char_u *)&(win->w_p_pvw); case PV_RL: - return (char_u *)&(curwin->w_p_rl); + return (char_u *)&(win->w_p_rl); case PV_RLC: - return (char_u *)&(curwin->w_p_rlc); + return (char_u *)&(win->w_p_rlc); case PV_SCROLL: - return (char_u *)&(curwin->w_p_scr); + return (char_u *)&(win->w_p_scr); case PV_WRAP: - return (char_u *)&(curwin->w_p_wrap); + return (char_u *)&(win->w_p_wrap); case PV_LBR: - return (char_u *)&(curwin->w_p_lbr); + return (char_u *)&(win->w_p_lbr); case PV_BRI: - return (char_u *)&(curwin->w_p_bri); + return (char_u *)&(win->w_p_bri); case PV_BRIOPT: - return (char_u *)&(curwin->w_p_briopt); + return (char_u *)&(win->w_p_briopt); case PV_SCBIND: - return (char_u *)&(curwin->w_p_scb); + return (char_u *)&(win->w_p_scb); case PV_CRBIND: - return (char_u *)&(curwin->w_p_crb); + return (char_u *)&(win->w_p_crb); case PV_COCU: - return (char_u *)&(curwin->w_p_cocu); + return (char_u *)&(win->w_p_cocu); case PV_COLE: - return (char_u *)&(curwin->w_p_cole); + return (char_u *)&(win->w_p_cole); case PV_AI: - return (char_u *)&(curbuf->b_p_ai); + return (char_u *)&(buf->b_p_ai); case PV_BIN: - return (char_u *)&(curbuf->b_p_bin); + return (char_u *)&(buf->b_p_bin); case PV_BOMB: - return (char_u *)&(curbuf->b_p_bomb); + return (char_u *)&(buf->b_p_bomb); case PV_BH: - return (char_u *)&(curbuf->b_p_bh); + return (char_u *)&(buf->b_p_bh); case PV_BT: - return (char_u *)&(curbuf->b_p_bt); + return (char_u *)&(buf->b_p_bt); case PV_BL: - return (char_u *)&(curbuf->b_p_bl); + return (char_u *)&(buf->b_p_bl); case PV_CHANNEL: - return (char_u *)&(curbuf->b_p_channel); + return (char_u *)&(buf->b_p_channel); case PV_CI: - return (char_u *)&(curbuf->b_p_ci); + return (char_u *)&(buf->b_p_ci); case PV_CIN: - return (char_u *)&(curbuf->b_p_cin); + return (char_u *)&(buf->b_p_cin); case PV_CINK: - return (char_u *)&(curbuf->b_p_cink); + return (char_u *)&(buf->b_p_cink); case PV_CINO: - return (char_u *)&(curbuf->b_p_cino); + return (char_u *)&(buf->b_p_cino); case PV_CINSD: - return (char_u *)&(curbuf->b_p_cinsd); + return (char_u *)&(buf->b_p_cinsd); case PV_CINW: - return (char_u *)&(curbuf->b_p_cinw); + return (char_u *)&(buf->b_p_cinw); case PV_COM: - return (char_u *)&(curbuf->b_p_com); + return (char_u *)&(buf->b_p_com); case PV_CMS: - return (char_u *)&(curbuf->b_p_cms); + return (char_u *)&(buf->b_p_cms); case PV_CPT: - return (char_u *)&(curbuf->b_p_cpt); + return (char_u *)&(buf->b_p_cpt); #ifdef BACKSLASH_IN_FILENAME case PV_CSL: - return (char_u *)&(curbuf->b_p_csl); + return (char_u *)&(buf->b_p_csl); #endif case PV_CFU: - return (char_u *)&(curbuf->b_p_cfu); + return (char_u *)&(buf->b_p_cfu); case PV_OFU: - return (char_u *)&(curbuf->b_p_ofu); + return (char_u *)&(buf->b_p_ofu); + case PV_EOF: + return (char_u *)&(buf->b_p_eof); case PV_EOL: - return (char_u *)&(curbuf->b_p_eol); + return (char_u *)&(buf->b_p_eol); case PV_FIXEOL: - return (char_u *)&(curbuf->b_p_fixeol); + return (char_u *)&(buf->b_p_fixeol); case PV_ET: - return (char_u *)&(curbuf->b_p_et); + return (char_u *)&(buf->b_p_et); case PV_FENC: - return (char_u *)&(curbuf->b_p_fenc); + return (char_u *)&(buf->b_p_fenc); case PV_FF: - return (char_u *)&(curbuf->b_p_ff); + return (char_u *)&(buf->b_p_ff); case PV_FT: - return (char_u *)&(curbuf->b_p_ft); + return (char_u *)&(buf->b_p_ft); case PV_FO: - return (char_u *)&(curbuf->b_p_fo); + return (char_u *)&(buf->b_p_fo); case PV_FLP: - return (char_u *)&(curbuf->b_p_flp); + return (char_u *)&(buf->b_p_flp); case PV_IMI: - return (char_u *)&(curbuf->b_p_iminsert); + return (char_u *)&(buf->b_p_iminsert); case PV_IMS: - return (char_u *)&(curbuf->b_p_imsearch); + return (char_u *)&(buf->b_p_imsearch); case PV_INF: - return (char_u *)&(curbuf->b_p_inf); + return (char_u *)&(buf->b_p_inf); case PV_ISK: - return (char_u *)&(curbuf->b_p_isk); + return (char_u *)&(buf->b_p_isk); case PV_INEX: - return (char_u *)&(curbuf->b_p_inex); + return (char_u *)&(buf->b_p_inex); case PV_INDE: - return (char_u *)&(curbuf->b_p_inde); + return (char_u *)&(buf->b_p_inde); case PV_INDK: - return (char_u *)&(curbuf->b_p_indk); + return (char_u *)&(buf->b_p_indk); case PV_FEX: - return (char_u *)&(curbuf->b_p_fex); + return (char_u *)&(buf->b_p_fex); case PV_LISP: - return (char_u *)&(curbuf->b_p_lisp); + return (char_u *)&(buf->b_p_lisp); + case PV_LOP: + return (char_u *)&(buf->b_p_lop); case PV_ML: - return (char_u *)&(curbuf->b_p_ml); + return (char_u *)&(buf->b_p_ml); case PV_MPS: - return (char_u *)&(curbuf->b_p_mps); + return (char_u *)&(buf->b_p_mps); case PV_MA: - return (char_u *)&(curbuf->b_p_ma); + return (char_u *)&(buf->b_p_ma); case PV_MOD: - return (char_u *)&(curbuf->b_changed); + return (char_u *)&(buf->b_changed); case PV_NF: - return (char_u *)&(curbuf->b_p_nf); + return (char_u *)&(buf->b_p_nf); case PV_PI: - return (char_u *)&(curbuf->b_p_pi); + return (char_u *)&(buf->b_p_pi); case PV_QE: - return (char_u *)&(curbuf->b_p_qe); + return (char_u *)&(buf->b_p_qe); case PV_RO: - return (char_u *)&(curbuf->b_p_ro); + return (char_u *)&(buf->b_p_ro); case PV_SCBK: - return (char_u *)&(curbuf->b_p_scbk); + return (char_u *)&(buf->b_p_scbk); case PV_SI: - return (char_u *)&(curbuf->b_p_si); + return (char_u *)&(buf->b_p_si); case PV_STS: - return (char_u *)&(curbuf->b_p_sts); + return (char_u *)&(buf->b_p_sts); case PV_SUA: - return (char_u *)&(curbuf->b_p_sua); + return (char_u *)&(buf->b_p_sua); case PV_SWF: - return (char_u *)&(curbuf->b_p_swf); + return (char_u *)&(buf->b_p_swf); case PV_SMC: - return (char_u *)&(curbuf->b_p_smc); + return (char_u *)&(buf->b_p_smc); case PV_SYN: - return (char_u *)&(curbuf->b_p_syn); + return (char_u *)&(buf->b_p_syn); case PV_SPC: - return (char_u *)&(curwin->w_s->b_p_spc); + return (char_u *)&(win->w_s->b_p_spc); case PV_SPF: - return (char_u *)&(curwin->w_s->b_p_spf); + return (char_u *)&(win->w_s->b_p_spf); case PV_SPL: - return (char_u *)&(curwin->w_s->b_p_spl); + return (char_u *)&(win->w_s->b_p_spl); case PV_SPO: - return (char_u *)&(curwin->w_s->b_p_spo); + return (char_u *)&(win->w_s->b_p_spo); case PV_SW: - return (char_u *)&(curbuf->b_p_sw); + return (char_u *)&(buf->b_p_sw); case PV_TFU: - return (char_u *)&(curbuf->b_p_tfu); + return (char_u *)&(buf->b_p_tfu); case PV_UMF: - return (char_u *)&(curbuf->b_p_umf); + return (char_u *)&(buf->b_p_umf); case PV_TS: - return (char_u *)&(curbuf->b_p_ts); + return (char_u *)&(buf->b_p_ts); case PV_TW: - return (char_u *)&(curbuf->b_p_tw); + return (char_u *)&(buf->b_p_tw); case PV_UDF: - return (char_u *)&(curbuf->b_p_udf); + return (char_u *)&(buf->b_p_udf); case PV_WM: - return (char_u *)&(curbuf->b_p_wm); + return (char_u *)&(buf->b_p_wm); case PV_VSTS: - return (char_u *)&(curbuf->b_p_vsts); + return (char_u *)&(buf->b_p_vsts); case PV_VTS: - return (char_u *)&(curbuf->b_p_vts); + return (char_u *)&(buf->b_p_vts); case PV_KMAP: - return (char_u *)&(curbuf->b_p_keymap); + return (char_u *)&(buf->b_p_keymap); case PV_SCL: - return (char_u *)&(curwin->w_p_scl); + return (char_u *)&(win->w_p_scl); case PV_WINHL: - return (char_u *)&(curwin->w_p_winhl); + return (char_u *)&(win->w_p_winhl); case PV_WINBL: - return (char_u *)&(curwin->w_p_winbl); + return (char_u *)&(win->w_p_winbl); + case PV_STC: + return (char_u *)&(win->w_p_stc); default: iemsg(_("E356: get_varp ERROR")); } // always return a valid pointer to avoid a crash! - return (char_u *)&(curbuf->b_p_wm); -} - -/// Return a pointer to the variable for option at 'opt_idx' -char_u *get_option_var(int opt_idx) -{ - return options[opt_idx].var; + return (char_u *)&(buf->b_p_wm); } -/// Return the full name of the option at 'opt_idx' -char *get_option_fullname(int opt_idx) +/// Get pointer to option variable. +static inline char_u *get_varp(vimoption_T *p) { - return options[opt_idx].fullname; + return get_varp_from(p, curbuf, curwin); } /// Get the value of 'equalprg', either the buffer-local one or the global one. -char_u *get_equalprg(void) +char *get_equalprg(void) { if (*curbuf->b_p_ep == NUL) { return p_ep; @@ -4229,6 +4058,14 @@ void win_copy_options(win_T *wp_from, win_T *wp_to) didset_window_options(wp_to, true); } +static char *copy_option_val(const char *val) +{ + if (val == empty_option) { + return empty_option; // no need to allocate memory + } + return xstrdup(val); +} + /// Copy the options from one winopt_T to another. /// Doesn't free the old option values in "to", use clear_winopt() for that. /// The 'scroll' option is not copied, because it depends on the window height. @@ -4237,21 +4074,23 @@ void copy_winopt(winopt_T *from, winopt_T *to) { to->wo_arab = from->wo_arab; to->wo_list = from->wo_list; + to->wo_lcs = copy_option_val(from->wo_lcs); + to->wo_fcs = copy_option_val(from->wo_fcs); to->wo_nu = from->wo_nu; to->wo_rnu = from->wo_rnu; - to->wo_ve = vim_strsave(from->wo_ve); + to->wo_ve = copy_option_val(from->wo_ve); to->wo_ve_flags = from->wo_ve_flags; to->wo_nuw = from->wo_nuw; to->wo_rl = from->wo_rl; - to->wo_rlc = vim_strsave(from->wo_rlc); - to->wo_sbr = vim_strsave(from->wo_sbr); - to->wo_stl = vim_strsave(from->wo_stl); - to->wo_wbr = xstrdup(from->wo_wbr); + to->wo_rlc = copy_option_val(from->wo_rlc); + to->wo_sbr = copy_option_val(from->wo_sbr); + to->wo_stl = copy_option_val(from->wo_stl); + to->wo_wbr = copy_option_val(from->wo_wbr); to->wo_wrap = from->wo_wrap; to->wo_wrap_save = from->wo_wrap_save; to->wo_lbr = from->wo_lbr; to->wo_bri = from->wo_bri; - to->wo_briopt = vim_strsave(from->wo_briopt); + to->wo_briopt = copy_option_val(from->wo_briopt); to->wo_scb = from->wo_scb; to->wo_scb_save = from->wo_scb_save; to->wo_crb = from->wo_crb; @@ -4259,33 +4098,30 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_spell = from->wo_spell; to->wo_cuc = from->wo_cuc; to->wo_cul = from->wo_cul; - to->wo_culopt = vim_strsave(from->wo_culopt); - to->wo_cc = vim_strsave(from->wo_cc); + to->wo_culopt = copy_option_val(from->wo_culopt); + to->wo_cc = copy_option_val(from->wo_cc); to->wo_diff = from->wo_diff; to->wo_diff_saved = from->wo_diff_saved; - to->wo_cocu = vim_strsave(from->wo_cocu); + to->wo_cocu = copy_option_val(from->wo_cocu); to->wo_cole = from->wo_cole; - to->wo_fdc = vim_strsave(from->wo_fdc); - to->wo_fdc_save = from->wo_diff_saved - ? vim_strsave(from->wo_fdc_save) : empty_option; + to->wo_fdc = copy_option_val(from->wo_fdc); + to->wo_fdc_save = from->wo_diff_saved ? xstrdup(from->wo_fdc_save) : empty_option; to->wo_fen = from->wo_fen; to->wo_fen_save = from->wo_fen_save; - to->wo_fdi = vim_strsave(from->wo_fdi); + to->wo_fdi = copy_option_val(from->wo_fdi); to->wo_fml = from->wo_fml; to->wo_fdl = from->wo_fdl; to->wo_fdl_save = from->wo_fdl_save; - to->wo_fdm = vim_strsave(from->wo_fdm); - to->wo_fdm_save = from->wo_diff_saved - ? vim_strsave(from->wo_fdm_save) : empty_option; + to->wo_fdm = copy_option_val(from->wo_fdm); + to->wo_fdm_save = from->wo_diff_saved ? xstrdup(from->wo_fdm_save) : empty_option; to->wo_fdn = from->wo_fdn; - to->wo_fde = vim_strsave(from->wo_fde); - to->wo_fdt = vim_strsave(from->wo_fdt); - to->wo_fmr = vim_strsave(from->wo_fmr); - to->wo_scl = vim_strsave(from->wo_scl); - to->wo_winhl = vim_strsave(from->wo_winhl); - to->wo_fcs = vim_strsave(from->wo_fcs); - to->wo_lcs = vim_strsave(from->wo_lcs); + to->wo_fde = copy_option_val(from->wo_fde); + to->wo_fdt = copy_option_val(from->wo_fdt); + to->wo_fmr = copy_option_val(from->wo_fmr); + to->wo_scl = copy_option_val(from->wo_scl); + to->wo_winhl = copy_option_val(from->wo_winhl); to->wo_winbl = from->wo_winbl; + to->wo_stc = copy_option_val(from->wo_stc); // Copy the script context so that we know were the value was last set. memmove(to->wo_script_ctx, from->wo_script_ctx, sizeof(to->wo_script_ctx)); @@ -4319,10 +4155,11 @@ static void check_winopt(winopt_T *wop) check_string_option(&wop->wo_cocu); check_string_option(&wop->wo_briopt); check_string_option(&wop->wo_winhl); - check_string_option(&wop->wo_fcs); check_string_option(&wop->wo_lcs); + check_string_option(&wop->wo_fcs); check_string_option(&wop->wo_ve); - check_string_option((char_u **)&wop->wo_wbr); + check_string_option(&wop->wo_wbr); + check_string_option(&wop->wo_stc); } /// Free the allocated memory inside a winopt_T. @@ -4345,10 +4182,11 @@ void clear_winopt(winopt_T *wop) clear_string_option(&wop->wo_cocu); clear_string_option(&wop->wo_briopt); clear_string_option(&wop->wo_winhl); - clear_string_option(&wop->wo_fcs); clear_string_option(&wop->wo_lcs); + clear_string_option(&wop->wo_fcs); clear_string_option(&wop->wo_ve); - clear_string_option((char_u **)&wop->wo_wbr); + clear_string_option(&wop->wo_wbr); + clear_string_option(&wop->wo_stc); } void didset_window_options(win_T *wp, bool valid_cursor) @@ -4394,14 +4232,11 @@ static void init_buf_opt_idx(void) void buf_copy_options(buf_T *buf, int flags) { int should_copy = true; - char_u *save_p_isk = NULL; // init for GCC - int dont_do_help; + char *save_p_isk = NULL; // init for GCC int did_isk = false; - /* - * Skip this when the option defaults have not been set yet. Happens when - * main() allocates the first buffer. - */ + // Skip this when the option defaults have not been set yet. Happens when + // main() allocates the first buffer. if (p_cpo != NULL) { // // Always copy when entering and 'cpo' contains 'S'. @@ -4428,7 +4263,7 @@ void buf_copy_options(buf_T *buf, int flags) // Don't copy the options specific to a help buffer when // BCO_NOHELP is given or the options were initialized already // (jumping back to a help file with CTRL-T or CTRL-O) - dont_do_help = ((flags & BCO_NOHELP) && buf->b_help) || buf->b_p_initialized; + bool dont_do_help = ((flags & BCO_NOHELP) && buf->b_help) || buf->b_p_initialized; if (dont_do_help) { // don't free b_p_isk save_p_isk = buf->b_p_isk; buf->b_p_isk = NULL; @@ -4438,19 +4273,19 @@ void buf_copy_options(buf_T *buf, int flags) if (!buf->b_p_initialized) { free_buf_options(buf, true); buf->b_p_ro = false; // don't copy readonly - buf->b_p_fenc = vim_strsave(p_fenc); + buf->b_p_fenc = xstrdup(p_fenc); switch (*p_ffs) { case 'm': - buf->b_p_ff = vim_strsave((char_u *)FF_MAC); + buf->b_p_ff = xstrdup(FF_MAC); break; case 'd': - buf->b_p_ff = vim_strsave((char_u *)FF_DOS); + buf->b_p_ff = xstrdup(FF_DOS); break; case 'u': - buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + buf->b_p_ff = xstrdup(FF_UNIX); break; default: - buf->b_p_ff = vim_strsave(p_ff); + buf->b_p_ff = xstrdup(p_ff); break; } buf->b_p_bh = empty_option; @@ -4495,44 +4330,45 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_swf = p_swf; COPY_OPT_SCTX(buf, BV_SWF); } - buf->b_p_cpt = vim_strsave(p_cpt); + buf->b_p_cpt = xstrdup(p_cpt); COPY_OPT_SCTX(buf, BV_CPT); #ifdef BACKSLASH_IN_FILENAME - buf->b_p_csl = vim_strsave(p_csl); + buf->b_p_csl = xstrdup(p_csl); COPY_OPT_SCTX(buf, BV_CSL); #endif - buf->b_p_cfu = vim_strsave(p_cfu); + buf->b_p_cfu = xstrdup(p_cfu); COPY_OPT_SCTX(buf, BV_CFU); - buf->b_p_ofu = vim_strsave(p_ofu); + set_buflocal_cfu_callback(buf); + buf->b_p_ofu = xstrdup(p_ofu); COPY_OPT_SCTX(buf, BV_OFU); - buf->b_p_tfu = vim_strsave(p_tfu); + set_buflocal_ofu_callback(buf); + buf->b_p_tfu = xstrdup(p_tfu); COPY_OPT_SCTX(buf, BV_TFU); - buf->b_p_umf = vim_strsave(p_umf); + buf->b_p_umf = xstrdup(p_umf); COPY_OPT_SCTX(buf, BV_UMF); + set_buflocal_tfu_callback(buf); buf->b_p_sts = p_sts; COPY_OPT_SCTX(buf, BV_STS); buf->b_p_sts_nopaste = p_sts_nopaste; - buf->b_p_vsts = vim_strsave(p_vsts); + buf->b_p_vsts = xstrdup(p_vsts); COPY_OPT_SCTX(buf, BV_VSTS); if (p_vsts && p_vsts != empty_option) { (void)tabstop_set(p_vsts, &buf->b_p_vsts_array); } else { buf->b_p_vsts_array = NULL; } - buf->b_p_vsts_nopaste = p_vsts_nopaste - ? vim_strsave(p_vsts_nopaste) - : NULL; - buf->b_p_com = vim_strsave(p_com); + buf->b_p_vsts_nopaste = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : NULL; + buf->b_p_com = xstrdup(p_com); COPY_OPT_SCTX(buf, BV_COM); - buf->b_p_cms = vim_strsave(p_cms); + buf->b_p_cms = xstrdup(p_cms); COPY_OPT_SCTX(buf, BV_CMS); - buf->b_p_fo = vim_strsave(p_fo); + buf->b_p_fo = xstrdup(p_fo); COPY_OPT_SCTX(buf, BV_FO); - buf->b_p_flp = vim_strsave(p_flp); + buf->b_p_flp = xstrdup(p_flp); COPY_OPT_SCTX(buf, BV_FLP); - buf->b_p_nf = vim_strsave(p_nf); + buf->b_p_nf = xstrdup(p_nf); COPY_OPT_SCTX(buf, BV_NF); - buf->b_p_mps = vim_strsave(p_mps); + buf->b_p_mps = xstrdup(p_mps); COPY_OPT_SCTX(buf, BV_MPS); buf->b_p_si = p_si; COPY_OPT_SCTX(buf, BV_SI); @@ -4542,18 +4378,20 @@ void buf_copy_options(buf_T *buf, int flags) COPY_OPT_SCTX(buf, BV_CI); buf->b_p_cin = p_cin; COPY_OPT_SCTX(buf, BV_CIN); - buf->b_p_cink = vim_strsave(p_cink); + buf->b_p_cink = xstrdup(p_cink); COPY_OPT_SCTX(buf, BV_CINK); - buf->b_p_cino = vim_strsave(p_cino); + buf->b_p_cino = xstrdup(p_cino); COPY_OPT_SCTX(buf, BV_CINO); - buf->b_p_cinsd = vim_strsave(p_cinsd); + buf->b_p_cinsd = xstrdup(p_cinsd); COPY_OPT_SCTX(buf, BV_CINSD); + buf->b_p_lop = xstrdup(p_lop); + COPY_OPT_SCTX(buf, BV_LOP); // Don't copy 'filetype', it must be detected buf->b_p_ft = empty_option; buf->b_p_pi = p_pi; COPY_OPT_SCTX(buf, BV_PI); - buf->b_p_cinw = vim_strsave(p_cinw); + buf->b_p_cinw = xstrdup(p_cinw); COPY_OPT_SCTX(buf, BV_CINW); buf->b_p_lisp = p_lisp; COPY_OPT_SCTX(buf, BV_LISP); @@ -4562,25 +4400,25 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_smc = p_smc; COPY_OPT_SCTX(buf, BV_SMC); buf->b_s.b_syn_isk = empty_option; - buf->b_s.b_p_spc = vim_strsave(p_spc); + buf->b_s.b_p_spc = xstrdup(p_spc); COPY_OPT_SCTX(buf, BV_SPC); (void)compile_cap_prog(&buf->b_s); - buf->b_s.b_p_spf = vim_strsave(p_spf); + buf->b_s.b_p_spf = xstrdup(p_spf); COPY_OPT_SCTX(buf, BV_SPF); - buf->b_s.b_p_spl = vim_strsave(p_spl); + buf->b_s.b_p_spl = xstrdup(p_spl); COPY_OPT_SCTX(buf, BV_SPL); - buf->b_s.b_p_spo = vim_strsave(p_spo); + buf->b_s.b_p_spo = xstrdup(p_spo); COPY_OPT_SCTX(buf, BV_SPO); - buf->b_p_inde = vim_strsave(p_inde); + buf->b_p_inde = xstrdup(p_inde); COPY_OPT_SCTX(buf, BV_INDE); - buf->b_p_indk = vim_strsave(p_indk); + buf->b_p_indk = xstrdup(p_indk); COPY_OPT_SCTX(buf, BV_INDK); buf->b_p_fp = empty_option; - buf->b_p_fex = vim_strsave(p_fex); + buf->b_p_fex = xstrdup(p_fex); COPY_OPT_SCTX(buf, BV_FEX); - buf->b_p_sua = vim_strsave(p_sua); + buf->b_p_sua = xstrdup(p_sua); COPY_OPT_SCTX(buf, BV_SUA); - buf->b_p_keymap = vim_strsave(p_keymap); + buf->b_p_keymap = xstrdup(p_keymap); COPY_OPT_SCTX(buf, BV_KMAP); buf->b_kmap_state |= KEYMAP_INIT; // This isn't really an option, but copying the langmap and IME @@ -4607,24 +4445,22 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_tc_flags = 0; buf->b_p_def = empty_option; buf->b_p_inc = empty_option; - buf->b_p_inex = vim_strsave(p_inex); + buf->b_p_inex = xstrdup(p_inex); COPY_OPT_SCTX(buf, BV_INEX); buf->b_p_dict = empty_option; buf->b_p_tsr = empty_option; buf->b_p_tsrfu = empty_option; - buf->b_p_qe = vim_strsave(p_qe); + buf->b_p_qe = xstrdup(p_qe); COPY_OPT_SCTX(buf, BV_QE); buf->b_p_udf = p_udf; COPY_OPT_SCTX(buf, BV_UDF); buf->b_p_lw = empty_option; buf->b_p_menc = empty_option; - /* - * Don't copy the options set by ex_help(), use the saved values, - * when going from a help buffer to a non-help buffer. - * Don't touch these at all when BCO_NOHELP is used and going from - * or to a help buffer. - */ + // Don't copy the options set by ex_help(), use the saved values, + // when going from a help buffer to a non-help buffer. + // Don't touch these at all when BCO_NOHELP is used and going from + // or to a help buffer. if (dont_do_help) { buf->b_p_isk = save_p_isk; if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { @@ -4633,12 +4469,12 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_vts_array = NULL; } } else { - buf->b_p_isk = vim_strsave(p_isk); + buf->b_p_isk = xstrdup(p_isk); COPY_OPT_SCTX(buf, BV_ISK); did_isk = true; buf->b_p_ts = p_ts; COPY_OPT_SCTX(buf, BV_TS); - buf->b_p_vts = vim_strsave(p_vts); + buf->b_p_vts = xstrdup(p_vts); COPY_OPT_SCTX(buf, BV_VTS); if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { (void)tabstop_set(p_vts, &buf->b_p_vts_array); @@ -4654,10 +4490,8 @@ void buf_copy_options(buf_T *buf, int flags) } } - /* - * When the options should be copied (ignoring BCO_ALWAYS), set the - * flag that indicates that the options have been initialized. - */ + // When the options should be copied (ignoring BCO_ALWAYS), set the + // flag that indicates that the options have been initialized. if (should_copy) { buf->b_p_initialized = true; } @@ -4683,15 +4517,15 @@ void reset_modifiable(void) } /// Set the global value for 'iminsert' to the local value. -void set_iminsert_global(void) +void set_iminsert_global(buf_T *buf) { - p_iminsert = curbuf->b_p_iminsert; + p_iminsert = buf->b_p_iminsert; } /// Set the global value for 'imsearch' to the local value. -void set_imsearch_global(void) +void set_imsearch_global(buf_T *buf) { - p_imsearch = curbuf->b_p_imsearch; + p_imsearch = buf->b_p_imsearch; } static int expand_option_idx = -1; @@ -4699,30 +4533,22 @@ static char_u expand_option_name[5] = { 't', '_', NUL, NUL, NUL }; static int expand_option_flags = 0; /// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL -void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags) +void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags) { - char_u nextchar; - uint32_t flags = 0; // init for GCC - int opt_idx = 0; // init for GCC - char_u *p; - char_u *s; - int is_term_option = false; - int key; - expand_option_flags = opt_flags; xp->xp_context = EXPAND_SETTINGS; if (*arg == NUL) { - xp->xp_pattern = (char *)arg; + xp->xp_pattern = arg; return; } - p = arg + STRLEN(arg) - 1; + char *p = arg + strlen(arg) - 1; if (*p == ' ' && *(p - 1) != '\\') { - xp->xp_pattern = (char *)p + 1; + xp->xp_pattern = p + 1; return; } while (p > arg) { - s = p; + char *s = p; // count number of backslashes before ' ' or ',' if (*p == ' ' || *p == ',') { while (s > arg && *(s - 1) == '\\') { @@ -4736,23 +4562,29 @@ void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags) } p--; } - if (STRNCMP(p, "no", 2) == 0) { + if (strncmp(p, "no", 2) == 0) { xp->xp_context = EXPAND_BOOL_SETTINGS; p += 2; } - if (STRNCMP(p, "inv", 3) == 0) { + if (strncmp(p, "inv", 3) == 0) { xp->xp_context = EXPAND_BOOL_SETTINGS; p += 3; } - xp->xp_pattern = (char *)p; + xp->xp_pattern = p; arg = p; + + char nextchar; + uint32_t flags = 0; + int opt_idx = 0; + int is_term_option = false; + if (*arg == '<') { while (*p != '>') { if (*p++ == NUL) { // expand terminal option name return; } } - key = get_special_key_code(arg + 1); + int key = get_special_key_code((char_u *)arg + 1); if (key == 0) { // unknown name xp->xp_context = EXPAND_NOTHING; return; @@ -4772,8 +4604,8 @@ void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags) } nextchar = *++p; is_term_option = true; - expand_option_name[2] = p[-2]; - expand_option_name[3] = p[-1]; + expand_option_name[2] = (char_u)p[-2]; + expand_option_name[3] = (char_u)p[-1]; } else { // Allow * wildcard. while (ASCII_ISALNUM(*p) || *p == '_' || *p == '*') { @@ -4812,7 +4644,7 @@ void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags) } else { expand_option_idx = opt_idx; } - xp->xp_pattern = (char *)p + 1; + xp->xp_pattern = p + 1; return; } xp->xp_context = EXPAND_NOTHING; @@ -4820,30 +4652,29 @@ void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags) return; } - xp->xp_pattern = (char *)p + 1; + xp->xp_pattern = p + 1; if (flags & P_EXPAND) { - p = options[opt_idx].var; - if (p == (char_u *)&p_bdir - || p == (char_u *)&p_dir - || p == (char_u *)&p_path - || p == (char_u *)&p_pp - || p == (char_u *)&p_rtp - || p == (char_u *)&p_cdpath - || p == (char_u *)&p_vdir) { + p = (char *)options[opt_idx].var; + if (p == (char *)&p_bdir + || p == (char *)&p_dir + || p == (char *)&p_path + || p == (char *)&p_pp + || p == (char *)&p_rtp + || p == (char *)&p_cdpath + || p == (char *)&p_vdir) { xp->xp_context = EXPAND_DIRECTORIES; - if (p == (char_u *)&p_path - || p == (char_u *)&p_cdpath) { + if (p == (char *)&p_path || p == (char *)&p_cdpath) { xp->xp_backslash = XP_BS_THREE; } else { xp->xp_backslash = XP_BS_ONE; } - } else if (p == (char_u *)&p_ft) { + } else if (p == (char *)&p_ft) { xp->xp_context = EXPAND_FILETYPE; } else { xp->xp_context = EXPAND_FILES; // for 'tags' need three backslashes for a space - if (p == (char_u *)&p_tags) { + if (p == (char *)&p_tags) { xp->xp_backslash = XP_BS_THREE; } else { xp->xp_backslash = XP_BS_ONE; @@ -4853,57 +4684,100 @@ void set_context_in_set_cmd(expand_T *xp, char_u *arg, int opt_flags) // For an option that is a list of file names, find the start of the // last file name. - for (p = arg + STRLEN(arg) - 1; p > (char_u *)xp->xp_pattern; p--) { + for (p = arg + strlen(arg) - 1; p > xp->xp_pattern; p--) { // count number of backslashes before ' ' or ',' if (*p == ' ' || *p == ',') { - s = p; - while (s > (char_u *)xp->xp_pattern && *(s - 1) == '\\') { + char *s = p; + while (s > xp->xp_pattern && *(s - 1) == '\\') { s--; } if ((*p == ' ' && (xp->xp_backslash == XP_BS_THREE && (p - s) < 3)) || (*p == ',' && (flags & P_COMMA) && ((p - s) & 1) == 0)) { - xp->xp_pattern = (char *)p + 1; + xp->xp_pattern = p + 1; break; } } // for 'spellsuggest' start at "file:" if (options[opt_idx].var == (char_u *)&p_sps - && STRNCMP(p, "file:", 5) == 0) { - xp->xp_pattern = (char *)p + 5; + && strncmp(p, "file:", 5) == 0) { + xp->xp_pattern = p + 5; break; } } } -int ExpandSettings(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***file) +/// Returns true if "str" either matches "regmatch" or fuzzy matches "pat". +/// +/// If "test_only" is true and "fuzzy" is false and if "str" matches the regular +/// expression "regmatch", then returns true. Otherwise returns false. +/// +/// If "test_only" is false and "fuzzy" is false and if "str" matches the +/// regular expression "regmatch", then stores the match in matches[idx] and +/// returns true. +/// +/// If "test_only" is true and "fuzzy" is true and if "str" fuzzy matches +/// "fuzzystr", then returns true. Otherwise returns false. +/// +/// If "test_only" is false and "fuzzy" is true and if "str" fuzzy matches +/// "fuzzystr", then stores the match details in fuzmatch[idx] and returns true. +static bool match_str(char *const str, regmatch_T *const regmatch, char **const matches, + const int idx, const bool test_only, const bool fuzzy, + const char *const fuzzystr, fuzmatch_str_T *const fuzmatch) +{ + if (!fuzzy) { + if (vim_regexec(regmatch, str, (colnr_T)0)) { + if (!test_only) { + matches[idx] = xstrdup(str); + } + return true; + } + } else { + const int score = fuzzy_match_str(str, fuzzystr); + if (score != 0) { + if (!test_only) { + fuzmatch[idx].idx = idx; + fuzmatch[idx].str = xstrdup(str); + fuzmatch[idx].score = score; + } + return true; + } + } + return false; +} + +int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char *fuzzystr, int *numMatches, + char ***matches, const bool can_fuzzy) { int num_normal = 0; // Nr of matching non-term-code settings - int match; int count = 0; - char_u *str; - int loop; static char *(names[]) = { "all" }; int ic = regmatch->rm_ic; // remember the ignore-case flag + fuzmatch_str_T *fuzmatch = NULL; + const bool fuzzy = can_fuzzy && cmdline_fuzzy_complete(fuzzystr); + // do this loop twice: // loop == 0: count the number of matching options // loop == 1: copy the matching options into allocated memory - for (loop = 0; loop <= 1; loop++) { + for (int loop = 0; loop <= 1; loop++) { + int match; regmatch->rm_ic = ic; if (xp->xp_context != EXPAND_BOOL_SETTINGS) { for (match = 0; match < (int)ARRAY_SIZE(names); match++) { - if (vim_regexec(regmatch, names[match], (colnr_T)0)) { + if (match_str(names[match], regmatch, *matches, + count, (loop == 0), fuzzy, fuzzystr, fuzmatch)) { if (loop == 0) { num_normal++; } else { - (*file)[count++] = xstrdup(names[match]); + count++; } } } } - for (size_t opt_idx = 0; (str = (char_u *)options[opt_idx].fullname) != NULL; + char *str; + for (size_t opt_idx = 0; (str = options[opt_idx].fullname) != NULL; opt_idx++) { if (options[opt_idx].var == NULL) { continue; @@ -4912,46 +4786,56 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, int *num_file, char ***fi && !(options[opt_idx].flags & P_BOOL)) { continue; } - match = false; - if (vim_regexec(regmatch, (char *)str, (colnr_T)0) - || (options[opt_idx].shortname != NULL - && vim_regexec(regmatch, - options[opt_idx].shortname, - (colnr_T)0))) { - match = true; - } - if (match) { + if (match_str(str, regmatch, *matches, count, (loop == 0), + fuzzy, fuzzystr, fuzmatch)) { if (loop == 0) { num_normal++; } else { - (*file)[count++] = (char *)vim_strsave(str); + count++; + } + } else if (!fuzzy && options[opt_idx].shortname != NULL + && vim_regexec(regmatch, options[opt_idx].shortname, (colnr_T)0)) { + // Compare against the abbreviated option name (for regular + // expression match). Fuzzy matching (previous if) already + // matches against both the expanded and abbreviated names. + if (loop == 0) { + num_normal++; + } else { + (*matches)[count++] = xstrdup(str); } } } if (loop == 0) { if (num_normal > 0) { - *num_file = num_normal; + *numMatches = num_normal; } else { return OK; } - *file = xmalloc((size_t)(*num_file) * sizeof(char_u *)); + if (!fuzzy) { + *matches = xmalloc((size_t)(*numMatches) * sizeof(char *)); + } else { + fuzmatch = xmalloc((size_t)(*numMatches) * sizeof(fuzmatch_str_T)); + } } } + + if (fuzzy) { + fuzzymatches_to_strmatches(fuzmatch, matches, count, false); + } + return OK; } void ExpandOldSetting(int *num_file, char ***file) { - char_u *var = NULL; + char *var = NULL; *num_file = 0; *file = xmalloc(sizeof(char_u *)); - /* - * For a terminal key code expand_option_idx is < 0. - */ + // For a terminal key code expand_option_idx is < 0. if (expand_option_idx < 0) { expand_option_idx = findoption((const char *)expand_option_name); } @@ -4961,12 +4845,12 @@ void ExpandOldSetting(int *num_file, char ***file) option_value2string(&options[expand_option_idx], expand_option_flags); var = NameBuff; } else { - var = (char_u *)""; + var = ""; } // A backslash is required before some characters. This is the reverse of // what happens in do_set(). - char_u *buf = vim_strsave_escaped(var, escape_chars); + char *buf = vim_strsave_escaped(var, escape_chars); #ifdef BACKSLASH_IN_FILENAME // For MS-Windows et al. we don't double backslashes at the start and @@ -4975,14 +4859,14 @@ void ExpandOldSetting(int *num_file, char ***file) if (var[0] == '\\' && var[1] == '\\' && expand_option_idx >= 0 && (options[expand_option_idx].flags & P_EXPAND) - && vim_isfilec(var[2]) + && vim_isfilec((uint8_t)var[2]) && (var[2] != '\\' || (var == buf && var[4] != '\\'))) { STRMOVE(var, var + 1); } } #endif - *file[0] = (char *)buf; + *file[0] = buf; *num_file = 1; } @@ -4990,36 +4874,34 @@ void ExpandOldSetting(int *num_file, char ***file) /// NameBuff[]. Must not be called with a hidden option! /// /// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL -static void option_value2string(vimoption_T *opp, int opt_flags) +static void option_value2string(vimoption_T *opp, int scope) { - char_u *varp; - - varp = get_varp_scope(opp, opt_flags); + char *varp = get_varp_scope(opp, scope); if (opp->flags & P_NUM) { long wc = 0; - if (wc_use_keyname(varp, &wc)) { - STRLCPY(NameBuff, get_special_key_name((int)wc, 0), sizeof(NameBuff)); + if (wc_use_keyname((char_u *)varp, &wc)) { + xstrlcpy(NameBuff, (char *)get_special_key_name((int)wc, 0), sizeof(NameBuff)); } else if (wc != 0) { - STRLCPY(NameBuff, transchar((int)wc), sizeof(NameBuff)); + xstrlcpy(NameBuff, transchar((int)wc), sizeof(NameBuff)); } else { - snprintf((char *)NameBuff, + snprintf(NameBuff, sizeof(NameBuff), "%" PRId64, - (int64_t)*(long *)varp); + (int64_t)(*(long *)varp)); } } else { // P_STRING - varp = *(char_u **)(varp); + varp = *(char **)(varp); if (varp == NULL) { // Just in case. NameBuff[0] = NUL; } else if (opp->flags & P_EXPAND) { - home_replace(NULL, (char *)varp, (char *)NameBuff, MAXPATHL, false); + home_replace(NULL, varp, (char *)NameBuff, MAXPATHL, false); // Translate 'pastetoggle' into special key names. - } else if ((char_u **)opp->var == &p_pt) { - str2specialbuf((const char *)p_pt, (char *)NameBuff, MAXPATHL); + } else if ((char **)opp->var == &p_pt) { + str2specialbuf((const char *)p_pt, NameBuff, MAXPATHL); } else { - STRLCPY(NameBuff, varp, MAXPATHL); + xstrlcpy(NameBuff, varp, MAXPATHL); } } } @@ -5027,7 +4909,7 @@ static void option_value2string(vimoption_T *opp, int opt_flags) /// Return true if "varp" points to 'wildchar' or 'wildcharm' and it can be /// printed as a keyname. /// "*wcp" is set to the value of the option if it's 'wildchar' or 'wildcharm'. -static int wc_use_keyname(char_u *varp, long *wcp) +static int wc_use_keyname(const char_u *varp, long *wcp) { if (((long *)varp == &p_wc) || ((long *)varp == &p_wcm)) { *wcp = *(long *)varp; @@ -5038,25 +4920,14 @@ static int wc_use_keyname(char_u *varp, long *wcp) return false; } -/// Return true if format option 'x' is in effect. -/// Take care of no formatting when 'paste' is set. -bool has_format_option(int x) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (p_paste) { - return false; - } - return vim_strchr((char *)curbuf->b_p_fo, x) != NULL; -} - /// @returns true if "x" is present in 'shortmess' option, or /// 'shortmess' contains 'a' and "x" is present in SHM_ALL_ABBREVIATIONS. bool shortmess(int x) { return (p_shm != NULL - && (vim_strchr((char *)p_shm, x) != NULL - || (vim_strchr((char *)p_shm, 'a') != NULL - && vim_strchr((char *)SHM_ALL_ABBREVIATIONS, x) != NULL))); + && (vim_strchr(p_shm, x) != NULL + || (vim_strchr(p_shm, 'a') != NULL + && vim_strchr(SHM_ALL_ABBREVIATIONS, x) != NULL))); } /// paste_option_changed() - Called after p_paste was set or reset. @@ -5070,10 +4941,8 @@ static void paste_option_changed(void) static int save_hkmap = 0; if (p_paste) { - /* - * Paste switched from off to on. - * Save the current values, so they can be restored later. - */ + // Paste switched from off to on. + // Save the current values, so they can be restored later. if (!old_p_paste) { // save options for each buffer FOR_ALL_BUFFERS(buf) { @@ -5086,7 +4955,7 @@ static void paste_option_changed(void) xfree(buf->b_p_vsts_nopaste); } buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_option - ? vim_strsave(buf->b_p_vsts) + ? xstrdup(buf->b_p_vsts) : NULL; } @@ -5105,9 +4974,7 @@ static void paste_option_changed(void) if (p_vsts_nopaste) { xfree(p_vsts_nopaste); } - p_vsts_nopaste = p_vsts && p_vsts != empty_option - ? vim_strsave(p_vsts) - : NULL; + p_vsts_nopaste = p_vsts && p_vsts != empty_option ? xstrdup(p_vsts) : NULL; } // Always set the option values, also when 'paste' is set when it is @@ -5157,9 +5024,7 @@ static void paste_option_changed(void) if (buf->b_p_vsts) { free_string_option(buf->b_p_vsts); } - buf->b_p_vsts = buf->b_p_vsts_nopaste - ? vim_strsave(buf->b_p_vsts_nopaste) - : empty_option; + buf->b_p_vsts = buf->b_p_vsts_nopaste ? xstrdup(buf->b_p_vsts_nopaste) : empty_option; xfree(buf->b_p_vsts_array); if (buf->b_p_vsts && buf->b_p_vsts != empty_option) { (void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array); @@ -5186,7 +5051,7 @@ static void paste_option_changed(void) if (p_vsts) { free_string_option(p_vsts); } - p_vsts = p_vsts_nopaste ? vim_strsave(p_vsts_nopaste) : empty_option; + p_vsts = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : empty_option; } old_p_paste = p_paste; @@ -5237,33 +5102,31 @@ bool option_was_set(const char *name) void reset_option_was_set(const char *name) { const int idx = findoption(name); - - if (idx >= 0) { - options[idx].flags &= ~P_WAS_SET; + if (idx < 0) { + return; } + + options[idx].flags &= ~P_WAS_SET; } /// fill_breakat_flags() -- called when 'breakat' changes value. void fill_breakat_flags(void) { - char_u *p; - int i; - - for (i = 0; i < 256; i++) { + for (int i = 0; i < 256; i++) { breakat_flags[i] = false; } if (p_breakat != NULL) { - for (p = p_breakat; *p; p++) { + for (char_u *p = (char_u *)p_breakat; *p; p++) { breakat_flags[*p] = true; } } } /// fill_culopt_flags() -- called when 'culopt' changes value -int fill_culopt_flags(char_u *val, win_T *wp) +int fill_culopt_flags(char *val, win_T *wp) { - char_u *p; + char *p; char_u culopt_flags_new = 0; if (val == NULL) { @@ -5272,16 +5135,16 @@ int fill_culopt_flags(char_u *val, win_T *wp) p = val; } while (*p != NUL) { - if (STRNCMP(p, "line", 4) == 0) { + if (strncmp(p, "line", 4) == 0) { p += 4; culopt_flags_new |= CULOPT_LINE; - } else if (STRNCMP(p, "both", 4) == 0) { + } else if (strncmp(p, "both", 4) == 0) { p += 4; culopt_flags_new |= CULOPT_LINE | CULOPT_NBR; - } else if (STRNCMP(p, "number", 6) == 0) { + } else if (strncmp(p, "number", 6) == 0) { p += 6; culopt_flags_new |= CULOPT_NBR; - } else if (STRNCMP(p, "screenline", 10) == 0) { + } else if (strncmp(p, "screenline", 10) == 0) { p += 10; culopt_flags_new |= CULOPT_SCRLINE; } @@ -5303,10 +5166,24 @@ int fill_culopt_flags(char_u *val, win_T *wp) return OK; } +/// Get the value of 'magic' taking "magic_overruled" into account. +bool magic_isset(void) +{ + switch (magic_overruled) { + case OPTION_MAGIC_ON: + return true; + case OPTION_MAGIC_OFF: + return false; + case OPTION_MAGIC_NOT_SET: + break; + } + return p_magic; +} + /// Set the callback function value for an option that accepts a function name, /// lambda, et al. (e.g. 'operatorfunc', 'tagfunc', etc.) /// @return OK if the option is successfully set to a function, otherwise FAIL -int option_set_callback_func(char_u *optval, Callback *optcb) +int option_set_callback_func(char *optval, Callback *optcb) { if (optval == NULL || *optval == NUL) { callback_free(optcb); @@ -5315,10 +5192,10 @@ int option_set_callback_func(char_u *optval, Callback *optcb) typval_T *tv; if (*optval == '{' - || (STRNCMP(optval, "function(", 9) == 0) - || (STRNCMP(optval, "funcref(", 8) == 0)) { + || (strncmp(optval, "function(", 9) == 0) + || (strncmp(optval, "funcref(", 8) == 0)) { // Lambda expression or a funcref - tv = eval_expr((char *)optval); + tv = eval_expr(optval); if (tv == NULL) { return FAIL; } @@ -5326,11 +5203,11 @@ int option_set_callback_func(char_u *optval, Callback *optcb) // treat everything else as a function name string tv = xcalloc(1, sizeof(*tv)); tv->v_type = VAR_STRING; - tv->vval.v_string = (char *)vim_strsave(optval); + tv->vval.v_string = xstrdup(optval); } Callback cb; - if (!callback_from_typval(&cb, tv)) { + if (!callback_from_typval(&cb, tv) || cb.type == kCallbackNone) { tv_free(tv); return FAIL; } @@ -5341,59 +5218,6 @@ int option_set_callback_func(char_u *optval, Callback *optcb) return OK; } -/// Read the 'wildmode' option, fill wim_flags[]. -int check_opt_wim(void) -{ - char_u new_wim_flags[4]; - char_u *p; - int i; - int idx = 0; - - for (i = 0; i < 4; i++) { - new_wim_flags[i] = 0; - } - - for (p = p_wim; *p; p++) { - for (i = 0; ASCII_ISALPHA(p[i]); i++) {} - if (p[i] != NUL && p[i] != ',' && p[i] != ':') { - return FAIL; - } - if (i == 7 && STRNCMP(p, "longest", 7) == 0) { - new_wim_flags[idx] |= WIM_LONGEST; - } else if (i == 4 && STRNCMP(p, "full", 4) == 0) { - new_wim_flags[idx] |= WIM_FULL; - } else if (i == 4 && STRNCMP(p, "list", 4) == 0) { - new_wim_flags[idx] |= WIM_LIST; - } else if (i == 8 && STRNCMP(p, "lastused", 8) == 0) { - new_wim_flags[idx] |= WIM_BUFLASTUSED; - } else { - return FAIL; - } - p += i; - if (*p == NUL) { - break; - } - if (*p == ',') { - if (idx == 3) { - return FAIL; - } - idx++; - } - } - - // fill remaining entries with last flag - while (idx < 3) { - new_wim_flags[idx + 1] = new_wim_flags[idx]; - idx++; - } - - // only when there are no errors, wim_flags[] is changed - for (i = 0; i < 4; i++) { - wim_flags[i] = new_wim_flags[i]; - } - return OK; -} - /// Check if backspacing over something is allowed. /// @param what BS_INDENT, BS_EOL, BS_START, or BS_NOSTOP bool can_bs(int what) @@ -5411,99 +5235,7 @@ bool can_bs(int what) case '0': return false; } - return vim_strchr((char *)p_bs, what) != NULL; -} - -/// Save the current values of 'fileformat' and 'fileencoding', so that we know -/// the file must be considered changed when the value is different. -void save_file_ff(buf_T *buf) -{ - buf->b_start_ffc = *buf->b_p_ff; - buf->b_start_eol = buf->b_p_eol; - buf->b_start_bomb = buf->b_p_bomb; - - // Only use free/alloc when necessary, they take time. - if (buf->b_start_fenc == NULL - || STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0) { - xfree(buf->b_start_fenc); - buf->b_start_fenc = (char *)vim_strsave(buf->b_p_fenc); - } -} - -/// Return true if 'fileformat' and/or 'fileencoding' has a different value -/// from when editing started (save_file_ff() called). -/// Also when 'endofline' was changed and 'binary' is set, or when 'bomb' was -/// changed and 'binary' is not set. -/// Also when 'endofline' was changed and 'fixeol' is not set. -/// When "ignore_empty" is true don't consider a new, empty buffer to be -/// changed. -bool file_ff_differs(buf_T *buf, bool ignore_empty) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - // In a buffer that was never loaded the options are not valid. - if (buf->b_flags & BF_NEVERLOADED) { - return false; - } - if (ignore_empty - && (buf->b_flags & BF_NEW) - && buf->b_ml.ml_line_count == 1 - && *ml_get_buf(buf, (linenr_T)1, false) == NUL) { - return false; - } - if (buf->b_start_ffc != *buf->b_p_ff) { - return true; - } - if ((buf->b_p_bin || !buf->b_p_fixeol) && buf->b_start_eol != buf->b_p_eol) { - return true; - } - if (!buf->b_p_bin && buf->b_start_bomb != buf->b_p_bomb) { - return true; - } - if (buf->b_start_fenc == NULL) { - return *buf->b_p_fenc != NUL; - } - return STRCMP(buf->b_start_fenc, buf->b_p_fenc) != 0; -} - -/// This is called when 'breakindentopt' is changed and when a window is -/// initialized -bool briopt_check(win_T *wp) -{ - int bri_shift = 0; - int bri_min = 20; - bool bri_sbr = false; - int bri_list = 0; - - char *p = (char *)wp->w_p_briopt; - while (*p != NUL) { - if (STRNCMP(p, "shift:", 6) == 0 - && ((p[6] == '-' && ascii_isdigit(p[7])) || ascii_isdigit(p[6]))) { - p += 6; - bri_shift = getdigits_int(&p, true, 0); - } else if (STRNCMP(p, "min:", 4) == 0 && ascii_isdigit(p[4])) { - p += 4; - bri_min = getdigits_int(&p, true, 0); - } else if (STRNCMP(p, "sbr", 3) == 0) { - p += 3; - bri_sbr = true; - } else if (STRNCMP(p, "list:", 5) == 0) { - p += 5; - bri_list = (int)getdigits(&p, false, 0); - } - if (*p != ',' && *p != NUL) { - return false; - } - if (*p == ',') { - p++; - } - } - - wp->w_briopt_shift = bri_shift; - wp->w_briopt_min = bri_min; - wp->w_briopt_sbr = bri_sbr; - wp->w_briopt_list = bri_list; - - return true; + return vim_strchr(p_bs, what) != NULL; } /// Get the local or global value of 'backupcopy'. @@ -5514,6 +5246,17 @@ unsigned int get_bkc_value(buf_T *buf) return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags; } +/// Get the local or global value of 'formatlistpat'. +/// +/// @param buf The buffer. +char *get_flp_value(buf_T *buf) +{ + if (buf->b_p_flp == NULL || *buf->b_p_flp == NUL) { + return p_flp; + } + return buf->b_p_flp; +} + /// Get the local or global value of the 'virtualedit' flags. unsigned int get_ve_flags(void) { @@ -5524,13 +5267,13 @@ unsigned int get_ve_flags(void) /// /// @param win If not NULL, the window to get the local option from; global /// otherwise. -char_u *get_showbreak_value(win_T *const win) +char *get_showbreak_value(win_T *const win) FUNC_ATTR_WARN_UNUSED_RESULT { if (win->w_p_sbr == NULL || *win->w_p_sbr == NUL) { return p_sbr; } - if (STRCMP(win->w_p_sbr, "NONE") == 0) { + if (strcmp(win->w_p_sbr, "NONE") == 0) { return empty_option; } return win->w_p_sbr; @@ -5540,7 +5283,7 @@ char_u *get_showbreak_value(win_T *const win) int get_fileformat(const buf_T *buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int c = *buf->b_p_ff; + int c = (unsigned char)(*buf->b_p_ff); if (buf->b_p_bin || c == 'u') { return EOL_UNIX; @@ -5567,7 +5310,7 @@ int get_fileformat_force(const buf_T *buf, const exarg_T *eap) ? (eap->force_bin == FORCE_BIN) : buf->b_p_bin) { return EOL_UNIX; } - c = *buf->b_p_ff; + c = (unsigned char)(*buf->b_p_ff); } if (c == 'u') { return EOL_UNIX; @@ -5624,7 +5367,7 @@ void set_fileformat(int eol_style, int opt_flags) } /// Skip to next part of an option argument: skip space and comma -char_u *skip_to_option_part(const char_u *p) +char *skip_to_option_part(const char *p) { if (*p == ',') { p++; @@ -5632,7 +5375,7 @@ char_u *skip_to_option_part(const char_u *p) while (*p == ' ') { p++; } - return (char_u *)p; + return (char *)p; } /// Isolate one part of a string option separated by `sep_chars`. @@ -5652,9 +5395,9 @@ size_t copy_option_part(char **option, char *buf, size_t maxlen, char *sep_chars if (*p == '.') { buf[len++] = *p++; } - while (*p != NUL && vim_strchr(sep_chars, *p) == NULL) { + while (*p != NUL && vim_strchr(sep_chars, (uint8_t)(*p)) == NULL) { // Skip backslash before a separator character and space. - if (p[0] == '\\' && vim_strchr(sep_chars, p[1]) != NULL) { + if (p[0] == '\\' && vim_strchr(sep_chars, (uint8_t)p[1]) != NULL) { p++; } if (len < maxlen - 1) { @@ -5667,7 +5410,7 @@ size_t copy_option_part(char **option, char *buf, size_t maxlen, char *sep_chars if (*p != NUL && *p != ',') { // skip non-standard separator p++; } - p = (char *)skip_to_option_part((char_u *)p); // p points to next file name + p = skip_to_option_part(p); // p points to next file name *option = p; return len; @@ -5676,13 +5419,13 @@ size_t copy_option_part(char **option, char *buf, size_t maxlen, char *sep_chars /// Return true when 'shell' has "csh" in the tail. int csh_like_shell(void) { - return strstr(path_tail((char *)p_sh), "csh") != NULL; + return strstr(path_tail(p_sh), "csh") != NULL; } /// Return true when 'shell' has "fish" in the tail. bool fish_like_shell(void) { - return strstr(path_tail((char *)p_sh), "fish") != NULL; + return strstr(path_tail(p_sh), "fish") != NULL; } /// Return the number of requested sign columns, based on current @@ -5840,7 +5583,7 @@ static Dictionary vimoption2dict(vimoption_T *opt) const char *type; Object def; // TODO(bfredl): do you even nocp? - char_u *def_val = opt->def_val; + char_u *def_val = (char_u *)opt->def_val; if (opt->flags & P_STRING) { type = "string"; def = CSTR_TO_OBJ(def_val ? (char *)def_val : ""); diff --git a/src/nvim/option.h b/src/nvim/option.h index c65d2ee182..636257bfe8 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -1,7 +1,7 @@ #ifndef NVIM_OPTION_H #define NVIM_OPTION_H -#include "nvim/ex_cmds_defs.h" // for exarg_T +#include "nvim/ex_cmds_defs.h" /// Returned by get_option_value(). typedef enum { @@ -19,6 +19,8 @@ typedef enum { #define BCO_ALWAYS 2 // always copy the options #define BCO_NOHELP 4 // don't touch the help related options +#define MAX_NUMBERWIDTH 20 // used for 'numberwidth' and 'statuscolumn' + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "option.h.generated.h" #endif diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 7211d466a8..16b46dca7a 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -1,8 +1,8 @@ #ifndef NVIM_OPTION_DEFS_H #define NVIM_OPTION_DEFS_H -#include "eval/typval.h" // For scid_T -#include "nvim/macros.h" // For EXTERN +#include "nvim/eval/typval.h" +#include "nvim/macros.h" #include "nvim/types.h" // option_defs.h: definition of global variables for settable options @@ -24,6 +24,8 @@ #define P_NO_MKRC 0x200U ///< don't include in :mkvimrc output // when option changed, what to display: +#define P_UI_OPTION 0x400U ///< send option to remote UI +#define P_RTABL 0x800U ///< redraw tabline #define P_RSTAT 0x1000U ///< redraw status lines #define P_RWIN 0x2000U ///< redraw current window and recompute text #define P_RBUF 0x4000U ///< redraw current buffer and recompute text @@ -49,9 +51,9 @@ #define P_NDNAME 0x8000000U ///< only normal dir name chars allowed #define P_RWINONLY 0x10000000U ///< only redraw current window #define P_MLE 0x20000000U ///< under control of 'modelineexpr' +#define P_FUNC 0x40000000U ///< accept a function reference or a lambda -#define P_NO_DEF_EXP 0x40000000U ///< Do not expand default value. -#define P_UI_OPTION 0x80000000U ///< send option to remote ui +#define P_NO_DEF_EXP 0x80000000U ///< Do not expand default value. /// Flags for option-setting functions /// @@ -97,7 +99,7 @@ typedef enum { // Default values for 'errorformat'. // The "%f|%l| %m" one is used for when the contents of the quickfix window is // written to a file. -#ifdef WIN32 +#ifdef MSWIN # define DFLT_EFM \ "%f(%l) \\=: %t%*\\D%n: %m,%*[^\"]\"%f\"%*\\D%l: %m,%f(%l) \\=: %m,%*[^ ] %f %l: %m,%f:%l:%c:%m,%f(%l):%m,%f:%l:%m,%f|%l| %m" #else @@ -239,7 +241,7 @@ enum { SHM_MOD = 'm', ///< Modified. SHM_FILE = 'f', ///< (file 1 of 2) SHM_LAST = 'i', ///< Last line incomplete. - SHM_TEXT = 'x', ///< Tx instead of textmode. + SHM_TEXT = 'x', ///< tx instead of textmode. SHM_LINES = 'l', ///< "L" instead of "lines". SHM_NEW = 'n', ///< "[New]" instead of "[New file]". SHM_WRI = 'w', ///< "[w]" instead of "written". @@ -253,15 +255,15 @@ enum { SHM_ATTENTION = 'A', ///< No ATTENTION messages. SHM_INTRO = 'I', ///< Intro messages. SHM_COMPLETIONMENU = 'c', ///< Completion menu messages. + SHM_COMPLETIONSCAN = 'C', ///< Completion scanning messages. SHM_RECORDING = 'q', ///< Short recording message. SHM_FILEINFO = 'F', ///< No file info messages. - SHM_SEARCHCOUNT = 'S', ///< Search sats: '[1/10]' + SHM_SEARCHCOUNT = 'S', ///< Search stats: '[1/10]' }; /// Represented by 'a' flag. -#define SHM_ALL_ABBREVIATIONS ((char_u[]) { \ +#define SHM_ALL_ABBREVIATIONS ((char[]) { \ SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, \ - 0, \ - }) + 0 }) // characters for p_go: #define GO_ASEL 'a' // autoselect @@ -333,6 +335,9 @@ enum { STL_ALTPERCENT = 'P', ///< Percentage as TOP BOT ALL or NN%. STL_ARGLISTSTAT = 'a', ///< Argument list status as (x of y). STL_PAGENUM = 'N', ///< Page number (when printing). + STL_SHOWCMD = 'S', ///< 'showcmd' buffer + STL_FOLDCOL = 'C', ///< Fold column for 'statuscolumn' + STL_SIGNCOL = 's', ///< Sign column for 'statuscolumn' STL_VIM_EXPR = '{', ///< Start of expression to substitute. STL_SEPARATE = '=', ///< Separation between alignment sections. STL_TRUNCMARK = '<', ///< Truncation mark if line is too long. @@ -350,10 +355,10 @@ enum { STL_HELPFLAG, STL_HELPFLAG_ALT, STL_FILETYPE, STL_FILETYPE_ALT, \ STL_PREVIEWFLAG, STL_PREVIEWFLAG_ALT, STL_MODIFIED, STL_MODIFIED_ALT, \ STL_QUICKFIX, STL_PERCENTAGE, STL_ALTPERCENT, STL_ARGLISTSTAT, STL_PAGENUM, \ - STL_VIM_EXPR, STL_SEPARATE, STL_TRUNCMARK, STL_USER_HL, STL_HIGHLIGHT, \ - STL_TABPAGENR, STL_TABCLOSENR, STL_CLICK_FUNC, \ - 0, \ - }) + STL_SHOWCMD, STL_FOLDCOL, STL_SIGNCOL, STL_VIM_EXPR, STL_SEPARATE, \ + STL_TRUNCMARK, STL_USER_HL, STL_HIGHLIGHT, STL_TABPAGENR, STL_TABCLOSENR, \ + STL_CLICK_FUNC, STL_TABPAGENR, STL_TABCLOSENR, STL_CLICK_FUNC, \ + 0, }) // flags used for parsed 'wildmode' #define WIM_FULL 0x01 @@ -379,12 +384,10 @@ enum { #define LISPWORD_VALUE \ "defun,define,defmacro,set!,lambda,if,case,let,flet,let*,letrec,do,do*,define-syntax,let-syntax,letrec-syntax,destructuring-bind,defpackage,defparameter,defstruct,deftype,defvar,do-all-symbols,do-external-symbols,do-symbols,dolist,dotimes,ecase,etypecase,eval-when,labels,macrolet,multiple-value-bind,multiple-value-call,multiple-value-prog1,multiple-value-setq,prog1,progv,typecase,unless,unwind-protect,when,with-input-from-string,with-open-file,with-open-stream,with-output-to-string,with-package-iterator,define-condition,handler-bind,handler-case,restart-bind,restart-case,with-simple-restart,store-value,use-value,muffle-warning,abort,continue,with-slots,with-slots*,with-accessors,with-accessors*,defclass,defmethod,print-unreadable-object" -/* - * The following are actual variables for the options - */ +// The following are actual variables for the options EXTERN long p_aleph; // 'aleph' -EXTERN char_u *p_ambw; ///< 'ambiwidth' +EXTERN char *p_ambw; ///< 'ambiwidth' EXTERN int p_acd; ///< 'autochdir' EXTERN int p_ai; ///< 'autoindent' EXTERN int p_bin; ///< 'binary' @@ -392,29 +395,30 @@ EXTERN int p_bomb; ///< 'bomb' EXTERN int p_bl; ///< 'buflisted' EXTERN int p_cin; ///< 'cindent' EXTERN long p_channel; ///< 'channel' -EXTERN char_u *p_cink; ///< 'cinkeys' -EXTERN char_u *p_cinsd; ///< 'cinscopedecls' -EXTERN char_u *p_cinw; ///< 'cinwords' -EXTERN char_u *p_cfu; ///< 'completefunc' -EXTERN char_u *p_ofu; ///< 'omnifunc' -EXTERN char_u *p_tsrfu; ///< 'thesaurusfunc' +EXTERN char *p_cink; ///< 'cinkeys' +EXTERN char *p_cinsd; ///< 'cinscopedecls' +EXTERN char *p_cinw; ///< 'cinwords' +EXTERN char *p_umf; ///< 'usermarkfunc' +EXTERN char *p_cfu; ///< 'completefunc' +EXTERN char *p_ofu; ///< 'omnifunc' +EXTERN char *p_tsrfu; ///< 'thesaurusfunc' EXTERN int p_ci; ///< 'copyindent' EXTERN int p_ar; // 'autoread' EXTERN int p_aw; // 'autowrite' EXTERN int p_awa; // 'autowriteall' -EXTERN char_u *p_bs; // 'backspace' -EXTERN char_u *p_bg; // 'background' +EXTERN char *p_bs; // 'backspace' +EXTERN char *p_bg; // 'background' EXTERN int p_bk; // 'backup' -EXTERN char_u *p_bkc; // 'backupcopy' +EXTERN char *p_bkc; // 'backupcopy' EXTERN unsigned int bkc_flags; ///< flags from 'backupcopy' #define BKC_YES 0x001 #define BKC_AUTO 0x002 #define BKC_NO 0x004 #define BKC_BREAKSYMLINK 0x008 #define BKC_BREAKHARDLINK 0x010 -EXTERN char_u *p_bdir; // 'backupdir' -EXTERN char_u *p_bex; // 'backupext' -EXTERN char_u *p_bo; // 'belloff' +EXTERN char *p_bdir; // 'backupdir' +EXTERN char *p_bex; // 'backupext' +EXTERN char *p_bo; // 'belloff' EXTERN char breakat_flags[256]; // which characters are in 'breakat' EXTERN unsigned bo_flags; @@ -439,88 +443,80 @@ EXTERN unsigned bo_flags; #define BO_SPELL 0x20000 #define BO_WILD 0x40000 -EXTERN char_u *p_bsk; // 'backupskip' -EXTERN char_u *p_breakat; // 'breakat' -EXTERN char_u *p_bh; ///< 'bufhidden' -EXTERN char_u *p_bt; ///< 'buftype' -EXTERN char_u *p_cmp; // 'casemap' +EXTERN char *p_bsk; // 'backupskip' +EXTERN char *p_breakat; // 'breakat' +EXTERN char *p_bh; ///< 'bufhidden' +EXTERN char *p_bt; ///< 'buftype' +EXTERN char *p_cmp; // 'casemap' EXTERN unsigned cmp_flags; #define CMP_INTERNAL 0x001 #define CMP_KEEPASCII 0x002 -EXTERN char_u *p_enc; // 'encoding' -EXTERN int p_deco; // 'delcombine' -EXTERN char_u *p_ccv; // 'charconvert' -EXTERN char_u *p_cino; ///< 'cinoptions' -EXTERN char_u *p_cedit; // 'cedit' -EXTERN char_u *p_cb; // 'clipboard' +EXTERN char *p_enc; // 'encoding' +EXTERN int p_deco; // 'delcombine' +EXTERN char *p_ccv; // 'charconvert' +EXTERN char *p_cino; ///< 'cinoptions' +EXTERN char *p_cedit; // 'cedit' +EXTERN char *p_cb; // 'clipboard' EXTERN unsigned cb_flags; #define CB_UNNAMED 0x001 #define CB_UNNAMEDPLUS 0x002 #define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS) EXTERN long p_cwh; // 'cmdwinheight' EXTERN long p_ch; // 'cmdheight' -EXTERN char_u *p_cms; ///< 'commentstring' -EXTERN char_u *p_cpt; ///< 'complete' +EXTERN char *p_cms; ///< 'commentstring' +EXTERN char *p_cpt; ///< 'complete' EXTERN long p_columns; // 'columns' EXTERN int p_confirm; // 'confirm' -EXTERN char_u *p_cot; // 'completeopt' +EXTERN char *p_cot; // 'completeopt' #ifdef BACKSLASH_IN_FILENAME -EXTERN char_u *p_csl; // 'completeslash' +EXTERN char *p_csl; // 'completeslash' #endif EXTERN long p_pb; // 'pumblend' EXTERN long p_ph; // 'pumheight' EXTERN long p_pw; // 'pumwidth' -EXTERN char_u *p_com; ///< 'comments' +EXTERN char *p_com; ///< 'comments' EXTERN char *p_cpo; // 'cpoptions' -EXTERN char_u *p_csprg; // 'cscopeprg' -EXTERN int p_csre; // 'cscoperelative' -EXTERN char_u *p_csqf; // 'cscopequickfix' -#define CSQF_CMDS "sgdctefia" -#define CSQF_FLAGS "+-0" -EXTERN int p_cst; // 'cscopetag' -EXTERN long p_csto; // 'cscopetagorder' -EXTERN long p_cspc; // 'cscopepathcomp' -EXTERN int p_csverbose; // 'cscopeverbose' -EXTERN char_u *p_debug; // 'debug' -EXTERN char_u *p_def; // 'define' -EXTERN char_u *p_inc; -EXTERN char_u *p_dip; // 'diffopt' -EXTERN char_u *p_dex; // 'diffexpr' -EXTERN char_u *p_dict; // 'dictionary' +EXTERN char *p_debug; // 'debug' +EXTERN char *p_def; // 'define' +EXTERN char *p_inc; +EXTERN char *p_dip; // 'diffopt' +EXTERN char *p_dex; // 'diffexpr' +EXTERN char *p_dict; // 'dictionary' EXTERN int p_dg; // 'digraph' -EXTERN char_u *p_dir; // 'directory' -EXTERN char_u *p_dy; // 'display' +EXTERN char *p_dir; // 'directory' +EXTERN char *p_dy; // 'display' EXTERN unsigned dy_flags; #define DY_LASTLINE 0x001 #define DY_TRUNCATE 0x002 #define DY_UHEX 0x004 -// code should use msg_use_msgsep() to check if msgsep is active +// legacy flag, not used #define DY_MSGSEP 0x008 EXTERN int p_ed; // 'edcompatible' -EXTERN char_u *p_ead; // 'eadirection' +EXTERN char *p_ead; // 'eadirection' EXTERN int p_emoji; // 'emoji' EXTERN int p_ea; // 'equalalways' -EXTERN char_u *p_ep; // 'equalprg' +EXTERN char *p_ep; // 'equalprg' EXTERN int p_eb; // 'errorbells' -EXTERN char_u *p_ef; // 'errorfile' -EXTERN char *p_efm; // 'errorformat' -EXTERN char *p_gefm; // 'grepformat' -EXTERN char_u *p_gp; // 'grepprg' +EXTERN char *p_ef; // 'errorfile' +EXTERN char *p_efm; // 'errorformat' +EXTERN char *p_gefm; // 'grepformat' +EXTERN char *p_gp; // 'grepprg' +EXTERN int p_eof; ///< 'endoffile' EXTERN int p_eol; ///< 'endofline' -EXTERN char_u *p_ei; // 'eventignore' +EXTERN char *p_ei; // 'eventignore' EXTERN int p_et; ///< 'expandtab' EXTERN int p_exrc; // 'exrc' -EXTERN char_u *p_fenc; ///< 'fileencoding' -EXTERN char_u *p_fencs; // 'fileencodings' -EXTERN char_u *p_ff; ///< 'fileformat' -EXTERN char *p_ffs; // 'fileformats' +EXTERN char *p_fenc; ///< 'fileencoding' +EXTERN char *p_fencs; // 'fileencodings' +EXTERN char *p_ff; ///< 'fileformat' +EXTERN char *p_ffs; // 'fileformats' EXTERN int p_fic; // 'fileignorecase' -EXTERN char_u *p_ft; ///< 'filetype' -EXTERN char_u *p_fcs; ///< 'fillchar' +EXTERN char *p_ft; ///< 'filetype' +EXTERN char *p_fcs; ///< 'fillchar' EXTERN int p_fixeol; ///< 'fixendofline' -EXTERN char_u *p_fcl; // 'foldclose' +EXTERN char *p_fcl; // 'foldclose' EXTERN long p_fdls; // 'foldlevelstart' -EXTERN char_u *p_fdo; // 'foldopen' +EXTERN char *p_fdo; // 'foldopen' EXTERN unsigned fdo_flags; #define FDO_ALL 0x001 #define FDO_BLOCK 0x002 @@ -533,137 +529,129 @@ EXTERN unsigned fdo_flags; #define FDO_INSERT 0x100 #define FDO_UNDO 0x200 #define FDO_JUMP 0x400 -EXTERN char_u *p_fex; ///< 'formatexpr' -EXTERN char_u *p_flp; ///< 'formatlistpat' -EXTERN char_u *p_fo; ///< 'formatoptions' -EXTERN char_u *p_fp; // 'formatprg' +EXTERN char *p_fex; ///< 'formatexpr' +EXTERN char *p_flp; ///< 'formatlistpat' +EXTERN char *p_fo; ///< 'formatoptions' +EXTERN char *p_fp; // 'formatprg' EXTERN int p_fs; // 'fsync' EXTERN int p_gd; // 'gdefault' -EXTERN char_u *p_pdev; // 'printdevice' -EXTERN char_u *p_penc; // 'printencoding' -EXTERN char_u *p_pexpr; // 'printexpr' -EXTERN char_u *p_pmfn; // 'printmbfont' -EXTERN char_u *p_pmcs; // 'printmbcharset' -EXTERN char_u *p_pfn; // 'printfont' -EXTERN char_u *p_popt; // 'printoptions' -EXTERN char_u *p_header; // 'printheader' -EXTERN char_u *p_guicursor; // 'guicursor' -EXTERN char_u *p_guifont; // 'guifont' -EXTERN char_u *p_guifontwide; // 'guifontwide' -EXTERN char_u *p_hf; // 'helpfile' +EXTERN char *p_guicursor; // 'guicursor' +EXTERN char *p_guifont; // 'guifont' +EXTERN char *p_guifontwide; // 'guifontwide' +EXTERN char *p_hf; // 'helpfile' EXTERN long p_hh; // 'helpheight' -EXTERN char_u *p_hlg; // 'helplang' +EXTERN char *p_hlg; // 'helplang' EXTERN int p_hid; // 'hidden' -EXTERN char_u *p_hl; // 'highlight' +EXTERN char *p_hl; // 'highlight' EXTERN int p_hls; // 'hlsearch' EXTERN long p_hi; // 'history' EXTERN int p_hkmap; // 'hkmap' EXTERN int p_hkmapp; // 'hkmapp' EXTERN int p_arshape; // 'arabicshape' EXTERN int p_icon; // 'icon' -EXTERN char_u *p_iconstring; // 'iconstring' +EXTERN char *p_iconstring; // 'iconstring' EXTERN int p_ic; // 'ignorecase' EXTERN long p_iminsert; ///< 'iminsert' EXTERN long p_imsearch; ///< 'imsearch' EXTERN int p_inf; ///< 'infercase' -EXTERN char_u *p_inex; ///< 'includeexpr' +EXTERN char *p_inex; ///< 'includeexpr' EXTERN int p_is; // 'incsearch' -EXTERN char_u *p_inde; ///< 'indentexpr' -EXTERN char_u *p_indk; ///< 'indentkeys' -EXTERN char_u *p_icm; // 'inccommand' -EXTERN char_u *p_isf; // 'isfname' -EXTERN char_u *p_isi; // 'isident' -EXTERN char_u *p_isk; ///< 'iskeyword' -EXTERN char_u *p_isp; // 'isprint' +EXTERN char *p_inde; ///< 'indentexpr' +EXTERN char *p_indk; ///< 'indentkeys' +EXTERN char *p_icm; // 'inccommand' +EXTERN char *p_isf; // 'isfname' +EXTERN char *p_isi; // 'isident' +EXTERN char *p_isk; ///< 'iskeyword' +EXTERN char *p_isp; // 'isprint' EXTERN int p_js; // 'joinspaces' -EXTERN char_u *p_jop; // 'jumpooptions' +EXTERN char *p_jop; // 'jumpooptions' EXTERN unsigned jop_flags; #define JOP_STACK 0x01 #define JOP_VIEW 0x02 -EXTERN char_u *p_keymap; ///< 'keymap' -EXTERN char_u *p_kp; // 'keywordprg' -EXTERN char_u *p_km; // 'keymodel' -EXTERN char_u *p_langmap; // 'langmap' +EXTERN char *p_keymap; ///< 'keymap' +EXTERN char *p_kp; // 'keywordprg' +EXTERN char *p_km; // 'keymodel' +EXTERN char *p_langmap; // 'langmap' EXTERN int p_lnr; // 'langnoremap' EXTERN int p_lrm; // 'langremap' -EXTERN char_u *p_lm; // 'langmenu' -EXTERN long p_lines; // 'lines' -EXTERN long p_linespace; // 'linespace' +EXTERN char *p_lm; // 'langmenu' +EXTERN long p_lines; // 'lines' +EXTERN long p_linespace; // 'linespace' EXTERN int p_lisp; ///< 'lisp' -EXTERN char_u *p_lispwords; // 'lispwords' +EXTERN char *p_lop; ///< 'lispoptions' +EXTERN char *p_lispwords; // 'lispwords' EXTERN long p_ls; // 'laststatus' EXTERN long p_stal; // 'showtabline' -EXTERN char_u *p_lcs; // 'listchars' +EXTERN char *p_lcs; // 'listchars' EXTERN int p_lz; // 'lazyredraw' EXTERN int p_lpl; // 'loadplugins' EXTERN int p_magic; // 'magic' -EXTERN char_u *p_menc; // 'makeencoding' -EXTERN char *p_mef; // 'makeef' -EXTERN char_u *p_mp; // 'makeprg' -EXTERN char_u *p_mps; ///< 'matchpairs' -EXTERN char_u *p_cc; // 'colorcolumn' -EXTERN int p_cc_cols[256]; // array for 'colorcolumn' columns +EXTERN char *p_menc; // 'makeencoding' +EXTERN char *p_mef; // 'makeef' +EXTERN char *p_mp; // 'makeprg' +EXTERN char *p_mps; ///< 'matchpairs' EXTERN long p_mat; // 'matchtime' EXTERN long p_mco; // 'maxcombine' EXTERN long p_mfd; // 'maxfuncdepth' EXTERN long p_mmd; // 'maxmapdepth' EXTERN long p_mmp; // 'maxmempattern' EXTERN long p_mis; // 'menuitems' -EXTERN char_u *p_msm; // 'mkspellmem' +EXTERN char *p_msm; // 'mkspellmem' EXTERN int p_ml; ///< 'modeline' -EXTERN int p_mle; // 'modelineexpr' +EXTERN int p_mle; // 'modelineexpr' EXTERN long p_mls; // 'modelines' EXTERN int p_ma; ///< 'modifiable' EXTERN int p_mod; ///< 'modified' -EXTERN char_u *p_mouse; // 'mouse' -EXTERN char_u *p_mousem; // 'mousemodel' -EXTERN int p_mousef; // 'mousefocus' -EXTERN char_u *p_mousescroll; // 'mousescroll' +EXTERN char *p_mouse; // 'mouse' +EXTERN char *p_mousem; // 'mousemodel' +EXTERN int p_mousemev; ///< 'mousemoveevent' +EXTERN int p_mousef; // 'mousefocus' +EXTERN char *p_mousescroll; // 'mousescroll' EXTERN long p_mousescroll_vert INIT(= MOUSESCROLL_VERT_DFLT); EXTERN long p_mousescroll_hor INIT(= MOUSESCROLL_HOR_DFLT); EXTERN long p_mouset; // 'mousetime' EXTERN int p_more; // 'more' -EXTERN char_u *p_nf; ///< 'nrformats' -EXTERN char_u *p_opfunc; // 'operatorfunc' -EXTERN char_u *p_para; // 'paragraphs' +EXTERN char *p_nf; ///< 'nrformats' +EXTERN char *p_opfunc; // 'operatorfunc' +EXTERN char *p_para; // 'paragraphs' EXTERN int p_paste; // 'paste' -EXTERN char_u *p_pt; // 'pastetoggle' -EXTERN char_u *p_pex; // 'patchexpr' -EXTERN char_u *p_pm; // 'patchmode' -EXTERN char_u *p_path; // 'path' -EXTERN char_u *p_cdpath; // 'cdpath' +EXTERN char *p_pt; // 'pastetoggle' +EXTERN char *p_pex; // 'patchexpr' +EXTERN char *p_pm; // 'patchmode' +EXTERN char *p_path; // 'path' +EXTERN char *p_cdpath; // 'cdpath' EXTERN int p_pi; ///< 'preserveindent' EXTERN long p_pyx; // 'pyxversion' -EXTERN char_u *p_qe; ///< 'quoteescape' +EXTERN char *p_qe; ///< 'quoteescape' EXTERN int p_ro; ///< 'readonly' -EXTERN char_u *p_rdb; // 'redrawdebug' +EXTERN char *p_rdb; // 'redrawdebug' EXTERN unsigned rdb_flags; #define RDB_COMPOSITOR 0x001 #define RDB_NOTHROTTLE 0x002 #define RDB_INVALID 0x004 #define RDB_NODELTA 0x008 -EXTERN long p_rdt; // 'redrawtime' -EXTERN long p_re; // 'regexpengine' -EXTERN long p_report; // 'report' -EXTERN long p_pvh; // 'previewheight' -EXTERN int p_ari; // 'allowrevins' -EXTERN int p_ri; // 'revins' -EXTERN int p_ru; // 'ruler' -EXTERN char_u *p_ruf; // 'rulerformat' -EXTERN char_u *p_pp; // 'packpath' -EXTERN char_u *p_qftf; // 'quickfixtextfunc' -EXTERN char_u *p_rtp; // 'runtimepath' -EXTERN long p_scbk; // 'scrollback' -EXTERN long p_sj; // 'scrolljump' -EXTERN long p_so; // 'scrolloff' -EXTERN char *p_sbo; // 'scrollopt' -EXTERN char_u *p_sections; // 'sections' -EXTERN int p_secure; // 'secure' -EXTERN char_u *p_sel; // 'selection' -EXTERN char_u *p_slm; // 'selectmode' -EXTERN char_u *p_ssop; // 'sessionoptions' +EXTERN long p_rdt; // 'redrawtime' +EXTERN long p_re; // 'regexpengine' +EXTERN long p_report; // 'report' +EXTERN long p_pvh; // 'previewheight' +EXTERN int p_ari; // 'allowrevins' +EXTERN int p_ri; // 'revins' +EXTERN int p_ru; // 'ruler' +EXTERN char *p_ruf; // 'rulerformat' +EXTERN char *p_pp; // 'packpath' +EXTERN char *p_qftf; // 'quickfixtextfunc' +EXTERN char *p_rtp; // 'runtimepath' +EXTERN long p_scbk; // 'scrollback' +EXTERN long p_sj; // 'scrolljump' +EXTERN long p_so; // 'scrolloff' +EXTERN char *p_sbo; // 'scrollopt' +EXTERN char *p_sections; // 'sections' +EXTERN int p_secure; // 'secure' +EXTERN char *p_sel; // 'selection' +EXTERN char *p_slm; // 'selectmode' +EXTERN char *p_ssop; // 'sessionoptions' EXTERN unsigned ssop_flags; #define SSOP_BUFFERS 0x001 @@ -685,24 +673,25 @@ EXTERN unsigned ssop_flags; #define SSOP_TERMINAL 0x10000 #define SSOP_SKIP_RTP 0x20000 -EXTERN char_u *p_sh; // 'shell' -EXTERN char_u *p_shcf; // 'shellcmdflag' -EXTERN char_u *p_sp; // 'shellpipe' -EXTERN char_u *p_shq; // 'shellquote' -EXTERN char_u *p_sxq; // 'shellxquote' -EXTERN char_u *p_sxe; // 'shellxescape' -EXTERN char_u *p_srr; // 'shellredir' +EXTERN char *p_sh; // 'shell' +EXTERN char *p_shcf; // 'shellcmdflag' +EXTERN char *p_sp; // 'shellpipe' +EXTERN char *p_shq; // 'shellquote' +EXTERN char *p_sxq; // 'shellxquote' +EXTERN char *p_sxe; // 'shellxescape' +EXTERN char *p_srr; // 'shellredir' EXTERN int p_stmp; // 'shelltemp' #ifdef BACKSLASH_IN_FILENAME EXTERN int p_ssl; // 'shellslash' #endif -EXTERN char_u *p_stl; // 'statusline' -EXTERN char *p_wbr; // 'winbar' +EXTERN char *p_stl; // 'statusline' +EXTERN char *p_wbr; // 'winbar' EXTERN int p_sr; // 'shiftround' EXTERN long p_sw; ///< 'shiftwidth' -EXTERN char_u *p_shm; // 'shortmess' -EXTERN char_u *p_sbr; // 'showbreak' +EXTERN char *p_shm; // 'shortmess' +EXTERN char *p_sbr; // 'showbreak' EXTERN int p_sc; // 'showcmd' +EXTERN char *p_sloc; // 'showcmdloc' EXTERN int p_sft; // 'showfulltag' EXTERN int p_sm; // 'showmatch' EXTERN int p_smd; // 'showmode' @@ -713,12 +702,12 @@ EXTERN int p_si; ///< 'smartindent' EXTERN int p_sta; // 'smarttab' EXTERN long p_sts; ///< 'softtabstop' EXTERN int p_sb; // 'splitbelow' -EXTERN char_u *p_sua; ///< 'suffixesadd' +EXTERN char *p_sua; ///< 'suffixesadd' EXTERN int p_swf; ///< 'swapfile' EXTERN long p_smc; ///< 'synmaxcol' EXTERN long p_tpm; // 'tabpagemax' -EXTERN char_u *p_tal; // 'tabline' -EXTERN char_u *p_tpf; // 'termpastefilter' +EXTERN char *p_tal; // 'tabline' +EXTERN char *p_tpf; // 'termpastefilter' EXTERN unsigned int tpf_flags; ///< flags from 'termpastefilter' #define TPF_BS 0x001 #define TPF_HT 0x002 @@ -727,16 +716,18 @@ EXTERN unsigned int tpf_flags; ///< flags from 'termpastefilter' #define TPF_DEL 0x010 #define TPF_C0 0x020 #define TPF_C1 0x040 -EXTERN char_u *p_tfu; ///< 'tagfunc' -EXTERN char_u *p_spc; ///< 'spellcapcheck' -EXTERN char_u *p_spf; ///< 'spellfile' -EXTERN char_u *p_spl; ///< 'spelllang' -EXTERN char_u *p_spo; // 'spelloptions' -EXTERN char_u *p_sps; // 'spellsuggest' +EXTERN char *p_tfu; ///< 'tagfunc' +EXTERN char *p_spc; ///< 'spellcapcheck' +EXTERN char *p_spf; ///< 'spellfile' +EXTERN char *p_spk; ///< 'splitkeep' +EXTERN char *p_spl; ///< 'spelllang' +EXTERN char *p_spo; // 'spelloptions' +EXTERN unsigned int spo_flags; +EXTERN char *p_sps; // 'spellsuggest' EXTERN int p_spr; // 'splitright' EXTERN int p_sol; // 'startofline' -EXTERN char_u *p_su; // 'suffixes' -EXTERN char_u *p_swb; // 'switchbuf' +EXTERN char *p_su; // 'suffixes' +EXTERN char *p_swb; // 'switchbuf' EXTERN unsigned swb_flags; // Keep in sync with p_swb_values in optionstr.c #define SWB_USEOPEN 0x001 @@ -745,10 +736,10 @@ EXTERN unsigned swb_flags; #define SWB_NEWTAB 0x008 #define SWB_VSPLIT 0x010 #define SWB_USELAST 0x020 -EXTERN char_u *p_syn; ///< 'syntax' +EXTERN char *p_syn; ///< 'syntax' EXTERN long p_ts; ///< 'tabstop' EXTERN int p_tbs; ///< 'tagbsearch' -EXTERN char_u *p_tc; ///< 'tagcase' +EXTERN char *p_tc; ///< 'tagcase' EXTERN unsigned tc_flags; ///< flags from 'tagcase' #define TC_FOLLOWIC 0x01 #define TC_IGNORE 0x02 @@ -757,7 +748,7 @@ EXTERN unsigned tc_flags; ///< flags from 'tagcase' #define TC_SMART 0x10 EXTERN long p_tl; ///< 'taglength' EXTERN int p_tr; ///< 'tagrelative' -EXTERN char_u *p_tags; ///< 'tags' +EXTERN char *p_tags; ///< 'tags' EXTERN int p_tgst; ///< 'tagstack' EXTERN int p_tbidi; ///< 'termbidi' EXTERN long p_tw; ///< 'textwidth' @@ -766,53 +757,54 @@ EXTERN int p_timeout; ///< 'timeout' EXTERN long p_tm; ///< 'timeoutlen' EXTERN int p_title; ///< 'title' EXTERN long p_titlelen; ///< 'titlelen' -EXTERN char_u *p_titleold; ///< 'titleold' -EXTERN char_u *p_titlestring; ///< 'titlestring' -EXTERN char_u *p_tsr; ///< 'thesaurus' +EXTERN char *p_titleold; ///< 'titleold' +EXTERN char *p_titlestring; ///< 'titlestring' +EXTERN char *p_tsr; ///< 'thesaurus' EXTERN int p_tgc; ///< 'termguicolors' EXTERN int p_ttimeout; ///< 'ttimeout' EXTERN long p_ttm; ///< 'ttimeoutlen' -EXTERN char_u *p_udir; ///< 'undodir' +EXTERN char *p_udir; ///< 'undodir' EXTERN int p_udf; ///< 'undofile' EXTERN long p_ul; ///< 'undolevels' EXTERN long p_ur; ///< 'undoreload' EXTERN long p_uc; ///< 'updatecount' EXTERN long p_ut; ///< 'updatetime' -EXTERN char_u *p_shada; ///< 'shada' +EXTERN char *p_shada; ///< 'shada' EXTERN char *p_shadafile; ///< 'shadafile' -EXTERN char_u *p_vsts; ///< 'varsofttabstop' -EXTERN char_u *p_vts; ///< 'vartabstop' -EXTERN char_u *p_vdir; ///< 'viewdir' -EXTERN char_u *p_vop; ///< 'viewoptions' +EXTERN char *p_vsts; ///< 'varsofttabstop' +EXTERN char *p_vts; ///< 'vartabstop' +EXTERN char *p_vdir; ///< 'viewdir' +EXTERN char *p_vop; ///< 'viewoptions' EXTERN unsigned vop_flags; ///< uses SSOP_ flags EXTERN int p_vb; ///< 'visualbell' -EXTERN char_u *p_ve; ///< 'virtualedit' +EXTERN char *p_ve; ///< 'virtualedit' EXTERN unsigned ve_flags; -#define VE_BLOCK 5U // includes "all" -#define VE_INSERT 6U // includes "all" +#define VE_BLOCK 5U // includes "all" +#define VE_INSERT 6U // includes "all" #define VE_ALL 4U #define VE_ONEMORE 8U -#define VE_NONE 16U // "none" -#define VE_NONEU 32U // "NONE" +#define VE_NONE 16U // "none" +#define VE_NONEU 32U // "NONE" EXTERN long p_verbose; // 'verbose' #ifdef IN_OPTION_C -char_u *p_vfile = (char_u *)""; // used before options are initialized +char *p_vfile = ""; // used before options are initialized #else -extern char_u *p_vfile; // 'verbosefile' +extern char *p_vfile; // 'verbosefile' #endif EXTERN int p_warn; // 'warn' -EXTERN char_u *p_wop; // 'wildoptions' +EXTERN char *p_wop; // 'wildoptions' EXTERN unsigned wop_flags; #define WOP_TAGFILE 0x01 #define WOP_PUM 0x02 +#define WOP_FUZZY 0x04 EXTERN long p_window; // 'window' -EXTERN char_u *p_wak; // 'winaltkeys' -EXTERN char_u *p_wig; // 'wildignore' -EXTERN char_u *p_ww; // 'whichwrap' +EXTERN char *p_wak; // 'winaltkeys' +EXTERN char *p_wig; // 'wildignore' +EXTERN char *p_ww; // 'whichwrap' EXTERN long p_wc; // 'wildchar' EXTERN long p_wcm; // 'wildcharm' EXTERN int p_wic; // 'wildignorecase' -EXTERN char_u *p_wim; // 'wildmode' +EXTERN char *p_wim; // 'wildmode' EXTERN int p_wmnu; // 'wildmenu' EXTERN long p_wh; // 'winheight' EXTERN long p_wmh; // 'winminheight' @@ -863,6 +855,7 @@ enum { BV_CFU, BV_DEF, BV_INC, + BV_EOF, BV_EOL, BV_FIXEOL, BV_EP, @@ -885,6 +878,7 @@ enum { BV_KMAP, BV_KP, BV_LISP, + BV_LOP, BV_LW, BV_MENC, BV_MA, @@ -925,11 +919,9 @@ enum { BV_COUNT, // must be the last one }; -/* - * "indir" values for window-local options. - * These need to be defined globally, so that the WV_COUNT can be used in the - * window structure. - */ +// "indir" values for window-local options. +// These need to be defined globally, so that the WV_COUNT can be used in the +// window structure. enum { WV_LIST = 0, WV_ARAB, @@ -967,14 +959,15 @@ enum { WV_CULOPT, WV_CC, WV_SBR, + WV_STC, WV_STL, WV_WFH, WV_WFW, WV_WRAP, WV_SCL, WV_WINHL, - WV_FCS, WV_LCS, + WV_FCS, WV_WINBL, WV_WBR, WV_COUNT, // must be the last one @@ -993,4 +986,41 @@ typedef struct { uint64_t channel_id; /// Only used when script_id is SID_API_CLIENT. } LastSet; -#endif // NVIM_OPTION_DEFS_H +// WV_ and BV_ values get typecasted to this for the "indir" field +typedef enum { + PV_NONE = 0, + PV_MAXVAL = 0xffff, // to avoid warnings for value out of range +} idopt_T; + +typedef struct vimoption { + char *fullname; // full option name + char *shortname; // permissible abbreviation + uint32_t flags; // see below + char_u *var; // global option: pointer to variable; + // window-local option: VAR_WIN; + // buffer-local option: global value + idopt_T indir; // global option: PV_NONE; + // local option: indirect option index + char *def_val; // default values for variable (neovim!!) + LastSet last_set; // script in which the option was last set +} vimoption_T; + +// The options that are local to a window or buffer have "indir" set to one of +// these values. Special values: +// PV_NONE: global option. +// PV_WIN is added: window-local option +// PV_BUF is added: buffer-local option +// PV_BOTH is added: global option which also has a local value. +#define PV_BOTH 0x1000 +#define PV_WIN 0x2000 +#define PV_BUF 0x4000 +#define PV_MASK 0x0fff +#define OPT_WIN(x) (idopt_T)(PV_WIN + (int)(x)) +#define OPT_BUF(x) (idopt_T)(PV_BUF + (int)(x)) +#define OPT_BOTH(x) (idopt_T)(PV_BOTH + (int)(x)) + +// Options local to a window have a value local to a buffer and global to all +// buffers. Indicate this by setting "var" to VAR_WIN. +#define VAR_WIN ((char_u *)-1) + +#endif // NVIM_OPTION_DEFS_H diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 5e4c73a5db..bfbfdd938e 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -10,6 +10,7 @@ -- secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil, -- pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil, -- modelineexpr=nil, +-- func=nil, -- expand=nil, nodefault=nil, no_mkrc=nil, -- alloced=nil, -- save_pv_indir=nil, @@ -19,8 +20,8 @@ -- types: bool, number, string -- lists: (nil), comma, onecomma, flags, flagscomma -- scopes: global, buffer, window --- redraw options: statuslines, current_window, curent_window_only, --- current_buffer, all_windows, everything, curswant +-- redraw options: statuslines, tabline, current_window, current_window_only, +-- current_buffer, all_windows, curswant -- defaults: {condition=#if condition, if_true=default, if_false=default} -- #if condition: -- string: #ifdef string @@ -128,7 +129,6 @@ return { full_name='background', abbreviation='bg', short_desc=N_("\"dark\" or \"light\", used for highlight colors"), type='string', scope={'global'}, - redraw={'all_windows'}, varname='p_bg', defaults={if_true="dark"} }, @@ -395,7 +395,6 @@ return { short_desc=N_("number of columns in the display"), type='number', scope={'global'}, no_mkrc=true, - redraw={'everything'}, varname='p_columns', defaults={if_true=macros('DFLT_COLS')} }, @@ -422,7 +421,6 @@ return { full_name='compatible', abbreviation='cp', short_desc=N_("No description"), type='bool', scope={'global'}, - redraw={'all_windows'}, varname='p_force_off', -- pri_mkrc isn't needed here, optval_default() -- always returns TRUE for 'compatible' @@ -458,6 +456,7 @@ return { type='string', scope={'buffer'}, secure=true, alloced=true, + func=true, varname='p_cfu', defaults={if_true=""} }, @@ -499,58 +498,6 @@ return { defaults={if_true=macros('CPO_VIM')} }, { - full_name='cscopepathcomp', abbreviation='cspc', - short_desc=N_("how many components of the path to show"), - type='number', scope={'global'}, - varname='p_cspc', - defaults={if_true=0} - }, - { - full_name='cscopeprg', abbreviation='csprg', - short_desc=N_("command to execute cscope"), - type='string', scope={'global'}, - secure=true, - expand=true, - varname='p_csprg', - defaults={if_true="cscope"} - }, - { - full_name='cscopequickfix', abbreviation='csqf', - short_desc=N_("use quickfix window for cscope results"), - type='string', list='onecomma', scope={'global'}, - deny_duplicates=true, - varname='p_csqf', - defaults={if_true=""} - }, - { - full_name='cscoperelative', abbreviation='csre', - short_desc=N_("Use cscope.out path basename as prefix"), - type='bool', scope={'global'}, - varname='p_csre', - defaults={if_true=0} - }, - { - full_name='cscopetag', abbreviation='cst', - short_desc=N_("use cscope for tag commands"), - type='bool', scope={'global'}, - varname='p_cst', - defaults={if_true=0} - }, - { - full_name='cscopetagorder', abbreviation='csto', - short_desc=N_("determines \":cstag\" search order"), - type='number', scope={'global'}, - varname='p_csto', - defaults={if_true=0} - }, - { - full_name='cscopeverbose', abbreviation='csverb', - short_desc=N_("give messages when adding a cscope database"), - type='bool', scope={'global'}, - varname='p_csverbose', - defaults={if_true=1} - }, - { full_name='cursorbind', abbreviation='crb', short_desc=N_("move cursor in window as it moves in other windows"), type='bool', scope={'window'}, @@ -663,7 +610,7 @@ return { deny_duplicates=true, redraw={'all_windows'}, varname='p_dy', - defaults={if_true="lastline,msgsep"} + defaults={if_true="lastline"} }, { full_name='eadirection', abbreviation='ead', @@ -696,6 +643,15 @@ return { defaults={if_true=macros('ENC_DFLT')} }, { + full_name='endoffile', abbreviation='eof', + short_desc=N_("write CTRL-Z for last line in file"), + type='bool', scope={'buffer'}, + no_mkrc=true, + redraw={'statuslines'}, + varname='p_eof', + defaults={if_true=false} + }, + { full_name='endofline', abbreviation='eol', short_desc=N_("write <EOL> for last line in file"), type='bool', scope={'buffer'}, @@ -708,7 +664,6 @@ return { full_name='equalalways', abbreviation='ea', short_desc=N_("windows are automatically made the same size"), type='bool', scope={'global'}, - redraw={'all_windows'}, varname='p_ea', defaults={if_true=true} }, @@ -1014,7 +969,7 @@ return { expand=true, varname='p_gp', defaults={ - condition='WIN32', + condition='MSWIN', -- Add an extra file name so that grep will always -- insert a file name in the match line. */ if_true="findstr /n $* nul", @@ -1051,7 +1006,6 @@ return { full_name='guioptions', abbreviation='go', short_desc=N_("GUI: Which components and options are used"), type='string', list='flags', scope={'global'}, - redraw={'all_windows'}, enable_if=false, }, { @@ -1195,7 +1149,6 @@ return { full_name='inccommand', abbreviation='icm', short_desc=N_("Live preview of substitution"), type='string', scope={'global'}, - redraw={'all_windows'}, varname='p_icm', defaults={if_true="nosplit"} }, @@ -1276,7 +1229,7 @@ return { deny_duplicates=true, varname='p_isi', defaults={ - condition='WIN32', + condition='MSWIN', if_true="@,48-57,_,128-167,224-235", if_false="@,48-57,_,192-255" } @@ -1403,7 +1356,6 @@ return { short_desc=N_("of lines in the display"), type='number', scope={'global'}, no_mkrc=true, - redraw={'everything'}, varname='p_lines', defaults={if_true=macros('DFLT_ROWS')} }, @@ -1423,6 +1375,14 @@ return { defaults={if_true=false} }, { + full_name='lispoptions', abbreviation='lop', + short_desc=N_("options for lisp indenting"), + type='string', list='onecomma', scope={'buffer'}, + deny_duplicates=true, + varname='p_lop', pv_name='p_lop', + defaults={if_true=''} + }, + { full_name='lispwords', abbreviation='lw', short_desc=N_("words that change how lisp indenting works"), type='string', list='onecomma', scope={'global', 'buffer'}, @@ -1622,6 +1582,14 @@ return { defaults={if_true="popup_setpos"} }, { + full_name='mousemoveevent', abbreviation='mousemev', + short_desc=N_("deliver mouse move events to input queue"), + type='bool', scope={'global'}, + redraw={'ui_option'}, + varname='p_mousemev', + defaults={if_true=false} + }, + { full_name='mousescroll', short_desc=N_("amount to scroll by when scrolling with a mouse"), type='string', list='comma', scope={'global'}, @@ -1672,6 +1640,7 @@ return { type='string', scope={'buffer'}, secure=true, alloced=true, + func=true, varname='p_ofu', defaults={if_true=""} }, @@ -1687,6 +1656,7 @@ return { short_desc=N_("function to be called for |g@| operator"), type='string', scope={'global'}, secure=true, + func=true, varname='p_opfunc', defaults={if_true=""} }, @@ -1770,65 +1740,6 @@ return { defaults={if_true=false} }, { - full_name='printdevice', abbreviation='pdev', - short_desc=N_("name of the printer to be used for :hardcopy"), - type='string', scope={'global'}, - secure=true, - varname='p_pdev', - defaults={if_true=""} - }, - { - full_name='printencoding', abbreviation='penc', - short_desc=N_("encoding to be used for printing"), - type='string', scope={'global'}, - varname='p_penc', - defaults={if_true=""} - }, - { - full_name='printexpr', abbreviation='pexpr', - short_desc=N_("expression used to print PostScript for :hardcopy"), - type='string', scope={'global'}, - secure=true, - varname='p_pexpr', - defaults={if_true=""} - }, - { - full_name='printfont', abbreviation='pfn', - short_desc=N_("name of the font to be used for :hardcopy"), - type='string', scope={'global'}, - varname='p_pfn', - defaults={if_true="courier"} - }, - { - full_name='printheader', abbreviation='pheader', - short_desc=N_("format of the header used for :hardcopy"), - type='string', scope={'global'}, - varname='p_header', - defaults={if_true="%<%f%h%m%=Page %N"} - }, - { - full_name='printmbcharset', abbreviation='pmbcs', - short_desc=N_("CJK character set to be used for :hardcopy"), - type='string', scope={'global'}, - varname='p_pmcs', - defaults={if_true=""} - }, - { - full_name='printmbfont', abbreviation='pmbfn', - short_desc=N_("font names to be used for CJK output of :hardcopy"), - type='string', scope={'global'}, - varname='p_pmfn', - defaults={if_true=""} - }, - { - full_name='printoptions', abbreviation='popt', - short_desc=N_("controls the format of :hardcopy output"), - type='string', list='onecomma', scope={'global'}, - deny_duplicates=true, - varname='p_popt', - defaults={if_true=""} - }, - { full_name='prompt', short_desc=N_("enable prompt in Ex mode"), type='bool', scope={'global'}, @@ -1869,6 +1780,8 @@ return { full_name='quickfixtextfunc', abbreviation='qftf', short_desc=N_("customize the quickfix window"), type='string', scope={'global'}, + secure=true, + func=true, varname='p_qftf', defaults={if_true=""} }, @@ -2015,7 +1928,6 @@ return { full_name='scrolloff', abbreviation='so', short_desc=N_("minimum nr. of lines above and below cursor"), type='number', scope={'global', 'window'}, - redraw={'all_windows'}, varname='p_so', defaults={if_true=0} }, @@ -2036,7 +1948,7 @@ return { }, { full_name='secure', - short_desc=N_("mode for reading .vimrc in current dir"), + short_desc=N_("No description"), type='bool', scope={'global'}, secure=true, varname='p_secure', @@ -2092,7 +2004,7 @@ return { expand=true, varname='p_sh', defaults={ - condition='WIN32', + condition='MSWIN', if_true="cmd.exe", if_false="sh" } @@ -2104,7 +2016,7 @@ return { secure=true, varname='p_shcf', defaults={ - condition='WIN32', + condition='MSWIN', if_true="/s /c", if_false="-c" } @@ -2116,7 +2028,7 @@ return { secure=true, varname='p_sp', defaults={ - condition='WIN32', + condition='MSWIN', if_true=">%s 2>&1", if_false="| tee", } @@ -2136,7 +2048,7 @@ return { secure=true, varname='p_srr', defaults={ - condition='WIN32', + condition='MSWIN', if_true=">%s 2>&1", if_false=">" } @@ -2163,7 +2075,7 @@ return { secure=true, varname='p_sxq', defaults={ - condition='WIN32', + condition='MSWIN', if_true="\"", if_false="", } @@ -2213,6 +2125,13 @@ return { defaults={if_true=true} }, { + full_name='showcmdloc', abbreviation='sloc', + short_desc=N_("change location of partial command"), + type='string', scope={'global'}, + varname='p_sloc', + defaults={if_true="last"} + }, + { full_name='showfulltag', abbreviation='sft', short_desc=N_("show full tag pattern when completing tag"), type='bool', scope={'global'}, @@ -2252,7 +2171,6 @@ return { full_name='sidescrolloff', abbreviation='siso', short_desc=N_("min. nr. of columns to left and right of cursor"), type='number', scope={'global', 'window'}, - redraw={'all_windows'}, varname='p_siso', defaults={if_true=0} }, @@ -2347,6 +2265,7 @@ return { secure=true, expand=true, varname='p_spo', + redraw={'current_buffer'}, defaults={if_true=""} }, { @@ -2357,6 +2276,13 @@ return { defaults={if_true=false} }, { + full_name='splitkeep', abbreviation='spk', + short_desc=N_("determines scroll behavior for split windows"), + type='string', scope={'global'}, + varname='p_spk', + defaults={if_true='cursor'} + }, + { full_name='splitright', abbreviation='spr', short_desc=N_("new window is put right of the current one"), type='bool', scope={'global'}, @@ -2372,6 +2298,15 @@ return { defaults={if_true=false} }, { + full_name='statuscolumn', abbreviation='stc', + short_desc=N_("custom format for the status column"), + type='string', scope={'window'}, + redraw={'current_window'}, + secure=true, + alloced=true, + defaults={if_true=""} + }, + { full_name='statusline', abbreviation='stl', short_desc=N_("custom format for the status line"), type='string', scope={'global', 'window'}, @@ -2436,6 +2371,8 @@ return { full_name='tagfunc', abbreviation='tfu', short_desc=N_("function used to perform tag searches"), type='string', scope={'buffer'}, + secure=true, + func=true, varname='p_tfu', defaults={if_true=""} }, @@ -2444,7 +2381,7 @@ return { short_desc=N_("custom format for the console tab pages line"), type='string', scope={'global'}, modelineexpr=true, - redraw={'all_windows'}, + redraw={'tabline'}, varname='p_tal', defaults={if_true=""} }, @@ -2566,6 +2503,7 @@ return { type='string', scope={'global', 'buffer'}, secure=true, alloced=true, + func=true, varname='p_tsrfu', defaults={if_true=""} }, @@ -2716,7 +2654,7 @@ return { full_name='verbose', abbreviation='vbs', short_desc=N_("give informative messages"), type='number', scope={'global'}, - varname='p_verbose', + varname='p_verbose', redraw={'ui_option'}, defaults={if_true=0} }, { @@ -2866,7 +2804,8 @@ return { { full_name='winhighlight', abbreviation='winhl', short_desc=N_("Setup window-local highlights"); - type='string', scope={'window'}, + type='string', list='onecomma', scope={'window'}, + deny_duplicates=true, alloced=true, redraw={'current_window'}, defaults={if_true=""} diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index c50225dfdf..a5a708600f 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -2,14 +2,14 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <inttypes.h> #include <stdbool.h> -#include <stdlib.h> +#include <stdint.h> #include <string.h> #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/cursor_shape.h" @@ -17,26 +17,40 @@ #include "nvim/digraph.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/userfunc.h" #include "nvim/eval/vars.h" -#include "nvim/hardcopy.h" +#include "nvim/ex_getln.h" +#include "nvim/fold.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/insexpand.h" #include "nvim/keycodes.h" +#include "nvim/macros.h" #include "nvim/mapping.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/option_defs.h" #include "nvim/optionstr.h" +#include "nvim/os/os.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" #include "nvim/runtime.h" +#include "nvim/screen.h" #include "nvim/spell.h" #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" #include "nvim/strings.h" +#include "nvim/tag.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -45,8 +59,14 @@ # include "optionstr.c.generated.h" #endif -static char e_unclosed_expression_sequence[] = N_("E540: Unclosed expression sequence"); -static char e_unbalanced_groups[] = N_("E542: unbalanced groups"); +static char e_unclosed_expression_sequence[] + = N_("E540: Unclosed expression sequence"); +static char e_unbalanced_groups[] + = N_("E542: unbalanced groups"); +static char e_backupext_and_patchmode_are_equal[] + = N_("E589: 'backupext' and 'patchmode' are equal"); +static char e_showbreak_contains_unprintable_or_wide_character[] + = N_("E595: 'showbreak' contains unprintable or wide character"); static char *(p_ambw_values[]) = { "single", "double", NULL }; static char *(p_bg_values[]) = { "light", "dark", NULL }; @@ -68,9 +88,10 @@ static char *(p_ssop_values[]) = { "buffers", "winpos", "resize", "winsize", "lo // Keep in sync with SWB_ flags in option_defs.h static char *(p_swb_values[]) = { "useopen", "usetab", "split", "newtab", "vsplit", "uselast", NULL }; +static char *(p_spk_values[]) = { "cursor", "screen", "topline", NULL }; static char *(p_tc_values[]) = { "followic", "ignore", "match", "followscs", "smart", NULL }; static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL }; -static char *(p_wop_values[]) = { "tagfile", "pum", NULL }; +static char *(p_wop_values[]) = { "tagfile", "pum", "fuzzy", NULL }; static char *(p_wak_values[]) = { "yes", "menu", "no", NULL }; static char *(p_mousem_values[]) = { "extend", "popup", "popup_setpos", "mac", NULL }; static char *(p_sel_values[]) = { "inclusive", "exclusive", "old", NULL }; @@ -100,16 +121,19 @@ static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", "auto:3", "auto:4" "auto:6", "auto:7", "auto:8", "auto:9", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL }; static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL }; +static char *(p_spo_values[]) = { "camel", "noplainbuffer", NULL }; static char *(p_icm_values[]) = { "nosplit", "split", NULL }; static char *(p_jop_values[]) = { "stack", "view", NULL }; static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL }; static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", NULL }; +static char *(p_sloc_values[]) = { "last", "statusline", "tabline", NULL }; /// All possible flags for 'shm'. -static char_u SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, - SHM_WRI, SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL, - SHM_OVER, SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO, - SHM_COMPLETIONMENU, SHM_RECORDING, SHM_FILEINFO, SHM_SEARCHCOUNT, 0, }; +static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, + SHM_WRI, SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL, + SHM_OVER, SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO, + SHM_COMPLETIONMENU, SHM_COMPLETIONSCAN, SHM_RECORDING, SHM_FILEINFO, + SHM_SEARCHCOUNT, 0, }; /// After setting various option values: recompute variables that depend on /// option values. @@ -139,40 +163,41 @@ void didset_string_options(void) /// "oldval_l" the old local value (only non-NULL if global and local value are set) /// "oldval_g" the old global value (only non-NULL if global and local value are set) /// "newval" the new value -void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l, - char *oldval_g, char *newval) +void trigger_optionset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l, + char *oldval_g, char *newval) { // Don't do this recursively. - if (oldval != NULL - && newval != NULL - && *get_vim_var_str(VV_OPTION_TYPE) == NUL) { - char buf_type[7]; + if (oldval == NULL || newval == NULL + || *get_vim_var_str(VV_OPTION_TYPE) != NUL) { + return; + } - vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", - (opt_flags & OPT_LOCAL) ? "local" : "global"); - set_vim_var_string(VV_OPTION_OLD, oldval, -1); - set_vim_var_string(VV_OPTION_NEW, newval, -1); - set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - if (opt_flags & OPT_LOCAL) { - set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); - } - if (opt_flags & OPT_GLOBAL) { - set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); - set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval, -1); - } - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - set_vim_var_string(VV_OPTION_COMMAND, "set", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, oldval_l, -1); - set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval_g, -1); - } - if (opt_flags & OPT_MODELINE) { - set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); - set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); - } - apply_autocmds(EVENT_OPTIONSET, get_option_fullname(opt_idx), NULL, false, NULL); - reset_v_option_vars(); + char buf_type[7]; + + vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", + (opt_flags & OPT_LOCAL) ? "local" : "global"); + set_vim_var_string(VV_OPTION_OLD, oldval, -1); + set_vim_var_string(VV_OPTION_NEW, newval, -1); + set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); + if (opt_flags & OPT_LOCAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); + } + if (opt_flags & OPT_GLOBAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval, -1); + } + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + set_vim_var_string(VV_OPTION_COMMAND, "set", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, oldval_l, -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval_g, -1); + } + if (opt_flags & OPT_MODELINE) { + set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); } + apply_autocmds(EVENT_OPTIONSET, get_option(opt_idx)->fullname, NULL, false, NULL); + reset_v_option_vars(); } static char *illegal_char(char *errbuf, size_t errbuflen, int c) @@ -181,7 +206,7 @@ static char *illegal_char(char *errbuf, size_t errbuflen, int c) return ""; } vim_snprintf(errbuf, errbuflen, _("E539: Illegal character <%s>"), - (char *)transchar(c)); + transchar(c)); return errbuf; } @@ -218,6 +243,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_cink); check_string_option(&buf->b_p_cino); parse_cino(buf); + check_string_option(&buf->b_p_lop); check_string_option(&buf->b_p_ft); check_string_option(&buf->b_p_cinw); check_string_option(&buf->b_p_cinsd); @@ -245,17 +271,17 @@ void check_buf_options(buf_T *buf) /// Free the string allocated for an option. /// Checks for the string being empty_option. This may happen if we're out of -/// memory, vim_strsave() returned NULL, which was replaced by empty_option by +/// memory, xstrdup() returned NULL, which was replaced by empty_option by /// check_options(). /// Does NOT check for P_ALLOCED flag! -void free_string_option(char_u *p) +void free_string_option(char *p) { if (p != empty_option) { xfree(p); } } -void clear_string_option(char_u **pp) +void clear_string_option(char **pp) { if (*pp != empty_option) { xfree(*pp); @@ -263,7 +289,7 @@ void clear_string_option(char_u **pp) *pp = empty_option; } -void check_string_option(char_u **pp) +void check_string_option(char **pp) { if (*pp == NULL) { *pp = empty_option; @@ -272,20 +298,20 @@ void check_string_option(char_u **pp) /// Set global value for string option when it's a local option. /// -/// @param opt_idx option index +/// @param opt option /// @param varp pointer to option variable -static void set_string_option_global(int opt_idx, char_u **varp) +static void set_string_option_global(vimoption_T *opt, char **varp) { - char_u **p, *s; + char **p; // the global value is always allocated - if (is_window_local_option(opt_idx)) { - p = (char_u **)GLOBAL_WO(varp); + if (opt->var == VAR_WIN) { + p = (char **)GLOBAL_WO(varp); } else { - p = (char_u **)get_option_var(opt_idx); + p = (char **)opt->var; } - if (!is_global_option(opt_idx) && p != varp) { - s = vim_strsave(*varp); + if (opt->indir != PV_NONE && p != varp) { + char *s = xstrdup(*varp); free_string_option(*p); *p = s; } @@ -316,32 +342,34 @@ void set_string_option_direct(const char *name, int opt_idx, const char *val, in } } - if (is_hidden_option(idx)) { // can't set hidden option + vimoption_T *opt = get_option(idx); + + if (opt->var == NULL) { // can't set hidden option return; } - assert((void *)get_option_var(idx) != (void *)&p_shada); + assert((void *)opt->var != (void *)&p_shada); s = xstrdup(val); { - varp = (char **)get_option_varp_scope(idx, both ? OPT_LOCAL : opt_flags); - if ((opt_flags & OPT_FREE) && (get_option_flags(idx) & P_ALLOCED)) { - free_string_option((char_u *)(*varp)); + varp = (char **)get_varp_scope(opt, both ? OPT_LOCAL : opt_flags); + if ((opt_flags & OPT_FREE) && (opt->flags & P_ALLOCED)) { + free_string_option(*varp); } *varp = s; // For buffer/window local option may also set the global value. if (both) { - set_string_option_global(idx, (char_u **)varp); + set_string_option_global(opt, varp); } - set_option_flag(idx, P_ALLOCED); + opt->flags |= P_ALLOCED; // When setting both values of a global option with a local value, // make the local value empty, so that the global value is used. - if (is_global_local_option(idx) && both) { - free_string_option((char_u *)(*varp)); - *varp = (char *)empty_option; + if ((opt->indir & PV_BOTH) && both) { + free_string_option(*varp); + *varp = empty_option; } if (set_sid != SID_NONE) { sctx_T script_ctx; @@ -381,28 +409,28 @@ void set_string_option_direct_in_win(win_T *wp, const char *name, int opt_idx, c /// @param[in] opt_flags Option flags: expected to contain #OPT_LOCAL and/or /// #OPT_GLOBAL. /// -/// @return NULL on success, error message on error. +/// @return NULL on success, an untranslated error message on error. char *set_string_option(const int opt_idx, const char *const value, const int opt_flags) FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_WARN_UNUSED_RESULT { - if (is_hidden_option(opt_idx)) { // don't set hidden option + vimoption_T *opt = get_option(opt_idx); + + if (opt->var == NULL) { // don't set hidden option return NULL; } char *const s = xstrdup(value); char **const varp - = (char **)get_option_varp_scope(opt_idx, - (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 - ? (is_global_local_option(opt_idx) - ? OPT_GLOBAL : OPT_LOCAL) - : opt_flags); + = (char **)get_varp_scope(opt, ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 + ? ((opt->indir & PV_BOTH) ? OPT_GLOBAL : OPT_LOCAL) + : opt_flags)); char *const oldval = *varp; char *oldval_l = NULL; char *oldval_g = NULL; if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { - oldval_l = *(char **)get_option_varp_scope(opt_idx, OPT_LOCAL); - oldval_g = *(char **)get_option_varp_scope(opt_idx, OPT_GLOBAL); + oldval_l = *(char **)get_varp_scope(opt, OPT_LOCAL); + oldval_g = *(char **)get_varp_scope(opt, OPT_GLOBAL); } *varp = s; @@ -413,21 +441,21 @@ char *set_string_option(const int opt_idx, const char *const value, const int op char *const saved_newval = xstrdup(s); int value_checked = false; - char *const r = did_set_string_option(opt_idx, (char_u **)varp, (char_u *)oldval, - NULL, 0, - opt_flags, &value_checked); - if (r == NULL) { + char *const errmsg = did_set_string_option(opt_idx, varp, oldval, + NULL, 0, + opt_flags, &value_checked); + if (errmsg == NULL) { did_set_option(opt_idx, opt_flags, true, value_checked); } // call autocommand after handling side effects - if (r == NULL) { + if (errmsg == NULL) { if (!starting) { - trigger_optionsset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g, - saved_newval); + trigger_optionset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g, + saved_newval); } - if (get_option_flags(opt_idx) & P_UI_OPTION) { - ui_call_option_set(cstr_as_string(get_option_fullname(opt_idx)), + if (opt->flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(opt->fullname), STRING_OBJ(cstr_as_string(saved_newval))); } } @@ -436,12 +464,12 @@ char *set_string_option(const int opt_idx, const char *const value, const int op xfree(saved_oldval_g); xfree(saved_newval); - return r; + return errmsg; } /// Return true if "val" is a valid 'filetype' name. /// Also used for 'syntax' and 'keymap'. -static bool valid_filetype(const char_u *val) +static bool valid_filetype(const char *val) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return valid_name(val, ".-_"); @@ -456,7 +484,7 @@ static char *check_mousescroll(char *string) for (;;) { char *end = vim_strchr(string, ','); - size_t length = end ? (size_t)(end - string) : STRLEN(string); + size_t length = end ? (size_t)(end - string) : strlen(string); // Both "ver:" and "hor:" are 4 bytes long. // They should be followed by at least one digit. @@ -513,7 +541,7 @@ static char *check_mousescroll(char *string) /// Handle setting 'signcolumn' for value 'val' /// /// @return OK when the value is valid, FAIL otherwise -static int check_signcolumn(char_u *val) +static int check_signcolumn(char *val) { if (*val == NUL) { return FAIL; @@ -524,8 +552,8 @@ static int check_signcolumn(char_u *val) } // check for 'auto:<NUMBER>-<NUMBER>' - if (STRLEN(val) == 8 - && !STRNCMP(val, "auto:", 5) + if (strlen(val) == 8 + && !strncmp(val, "auto:", 5) && ascii_isdigit(val[5]) && val[6] == '-' && ascii_isdigit(val[7])) { @@ -586,7 +614,7 @@ char *check_stl_option(char *s) groupdepth++; continue; } - if (vim_strchr(STL_ALL, *s) == NULL) { + if (vim_strchr(STL_ALL, (uint8_t)(*s)) == NULL) { return illegal_char(errbuf, sizeof(errbuf), *s); } if (*s == '{') { @@ -612,894 +640,1220 @@ char *check_stl_option(char *s) static int shada_idx = -1; -/// Handle string options that need some action to perform when changed. -/// The new value must be allocated. -/// -/// @param opt_idx index in options[] table -/// @param varp pointer to the option variable -/// @param oldval previous value of the option -/// @param errbuf buffer for errors, or NULL -/// @param errbuflen length of errors buffer -/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL -/// @param value_checked value was checked to be safe, no need to set P_INSECURE -/// -/// @return NULL for success, or an untranslated error message for an error -char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, char *errbuf, - size_t errbuflen, int opt_flags, int *value_checked) +static bool check_illegal_path_names(char *val, uint32_t flags) { - char *errmsg = NULL; - char *s, *p; - int did_chartab = false; - char_u **gvarp; - bool free_oldval = (get_option_flags(opt_idx) & P_ALLOCED); - bool value_changed = false; + // Disallow a path separator (slash and/or backslash), wildcards and + // characters that are often illegal in a file name. Be more permissive + // if "secure" is off. + return (((flags & P_NFNAME) + && strpbrk(val, (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL) + || ((flags & P_NDNAME) + && strpbrk(val, "*?[|;&<>\r\n") != NULL)); +} - // Get the global option to compare with, otherwise we would have to check - // two values for all local options. - gvarp = (char_u **)get_option_varp_scope(opt_idx, OPT_GLOBAL); +static void did_set_backupcopy(buf_T *buf, char *oldval, int opt_flags, char **errmsg) +{ + char *bkc = p_bkc; + unsigned int *flags = &bkc_flags; - // Disallow changing some options from secure mode - if ((secure || sandbox != 0) - && (get_option_flags(opt_idx) & P_SECURE)) { - errmsg = e_secure; - } else if (((get_option_flags(opt_idx) & P_NFNAME) - && strpbrk((char *)(*varp), - (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL) - || ((get_option_flags(opt_idx) & P_NDNAME) - && strpbrk((char *)(*varp), "*?[|;&<>\r\n") != NULL)) { - // Check for a "normal" directory or file name in some options. Disallow a - // path separator (slash and/or backslash), wildcards and characters that - // are often illegal in a file name. Be more permissive if "secure" is off. - errmsg = e_invarg; - } else if (gvarp == &p_bkc) { // 'backupcopy' - char_u *bkc = p_bkc; - unsigned int *flags = &bkc_flags; + if (opt_flags & OPT_LOCAL) { + bkc = buf->b_p_bkc; + flags = &buf->b_bkc_flags; + } - if (opt_flags & OPT_LOCAL) { - bkc = curbuf->b_p_bkc; - flags = &curbuf->b_bkc_flags; + if ((opt_flags & OPT_LOCAL) && *bkc == NUL) { + // make the local value empty: use the global value + *flags = 0; + } else { + if (opt_strings_flags(bkc, p_bkc_values, flags, true) != OK) { + *errmsg = e_invarg; } - if ((opt_flags & OPT_LOCAL) && *bkc == NUL) { - // make the local value empty: use the global value - *flags = 0; - } else { - if (opt_strings_flags(bkc, p_bkc_values, flags, true) != OK) { - errmsg = e_invarg; - } - - if (((*flags & BKC_AUTO) != 0) - + ((*flags & BKC_YES) != 0) - + ((*flags & BKC_NO) != 0) != 1) { - // Must have exactly one of "auto", "yes" and "no". - (void)opt_strings_flags(oldval, p_bkc_values, flags, true); - errmsg = e_invarg; - } - } - } else if (varp == &p_bex || varp == &p_pm) { // 'backupext' and 'patchmode' - if (STRCMP(*p_bex == '.' ? p_bex + 1 : p_bex, - *p_pm == '.' ? p_pm + 1 : p_pm) == 0) { - errmsg = N_("E589: 'backupext' and 'patchmode' are equal"); + if (((*flags & BKC_AUTO) != 0) + + ((*flags & BKC_YES) != 0) + + ((*flags & BKC_NO) != 0) != 1) { + // Must have exactly one of "auto", "yes" and "no". + (void)opt_strings_flags(oldval, p_bkc_values, flags, true); + *errmsg = e_invarg; } - } else if (varp == &curwin->w_p_briopt) { // 'breakindentopt' - if (briopt_check(curwin) == FAIL) { - errmsg = e_invarg; + } +} + +static void did_set_backupext_or_patchmode(char **errmsg) +{ + if (strcmp(*p_bex == '.' ? p_bex + 1 : p_bex, + *p_pm == '.' ? p_pm + 1 : p_pm) == 0) { + *errmsg = e_backupext_and_patchmode_are_equal; + } +} + +static void did_set_breakindentopt(win_T *win, char **errmsg) +{ + if (briopt_check(win) == FAIL) { + *errmsg = e_invarg; + } + // list setting requires a redraw + if (win == curwin && win->w_briopt_list) { + redraw_all_later(UPD_NOT_VALID); + } +} + +static void did_set_isopt(buf_T *buf, bool *did_chartab, char **errmsg) +{ + // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[] + // If the new option is invalid, use old value. 'lisp' option: refill + // g_chartab[] for '-' char + if (buf_init_chartab(buf, true) == FAIL) { + *did_chartab = true; // need to restore it below + *errmsg = e_invarg; // error in value + } +} + +static void did_set_helpfile(void) +{ + // May compute new values for $VIM and $VIMRUNTIME + if (didset_vim) { + vim_unsetenv_ext("VIM"); + } + if (didset_vimruntime) { + vim_unsetenv_ext("VIMRUNTIME"); + } +} + +static void did_set_cursorlineopt(win_T *win, char **varp, char **errmsg) +{ + if (**varp == NUL || fill_culopt_flags(*varp, win) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_helplang(char **errmsg) +{ + // Check for "", "ab", "ab,cd", etc. + for (char *s = p_hlg; *s != NUL; s += 3) { + if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) { + *errmsg = e_invarg; + break; } - } else if (varp == &p_isi - || varp == &(curbuf->b_p_isk) - || varp == &p_isp - || varp == &p_isf) { - // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[] - // If the new option is invalid, use old value. 'lisp' option: refill - // g_chartab[] for '-' char - if (init_chartab() == FAIL) { - did_chartab = true; // need to restore it below - errmsg = e_invarg; // error in value + if (s[2] == NUL) { + break; } - } else if (varp == &p_hf) { // 'helpfile' - // May compute new values for $VIM and $VIMRUNTIME - if (didset_vim) { - vim_unsetenv_ext("VIM"); + } +} + +static void did_set_highlight(char **varp, char **errmsg) +{ + if (strcmp(*varp, HIGHLIGHT_INIT) != 0) { + *errmsg = e_unsupportedoption; + } +} + +static void did_set_opt_flags(char *val, char **values, unsigned *flagp, bool list, char **errmsg) +{ + if (opt_strings_flags(val, values, flagp, list) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_opt_strings(char *val, char **values, bool list, char **errmsg) +{ + did_set_opt_flags(val, values, NULL, list, errmsg); +} + +static void did_set_sessionoptions(char *oldval, char **errmsg) +{ + if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) { + *errmsg = e_invarg; + } + if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) { + // Don't allow both "sesdir" and "curdir". + (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true); + *errmsg = e_invarg; + } +} + +static void did_set_ambiwidth(char **errmsg) +{ + if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) { + *errmsg = e_invarg; + } else { + *errmsg = check_chars_options(); + } +} + +static void did_set_background(char **errmsg) +{ + if (check_opt_strings(p_bg, p_bg_values, false) != OK) { + *errmsg = e_invarg; + return; + } + + int dark = (*p_bg == 'd'); + + init_highlight(false, false); + + if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) { + // The color scheme must have set 'background' back to another + // value, that's not what we want here. Disable the color + // scheme and set the colors again. + do_unlet(S_LEN("g:colors_name"), true); + free_string_option(p_bg); + p_bg = xstrdup((dark ? "dark" : "light")); + check_string_option(&p_bg); + init_highlight(false, false); + } +} + +static void did_set_wildmode(char **errmsg) +{ + if (check_opt_wim() == FAIL) { + *errmsg = e_invarg; + } +} + +static void did_set_winaltkeys(char **errmsg) +{ + if (*p_wak == NUL || check_opt_strings(p_wak, p_wak_values, false) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_eventignore(char **errmsg) +{ + if (check_ei() == FAIL) { + *errmsg = e_invarg; + } +} + +// 'encoding', 'fileencoding' and 'makeencoding' +static void did_set_encoding(buf_T *buf, char **varp, char **gvarp, int opt_flags, char **errmsg) +{ + if (gvarp == &p_fenc) { + if (!MODIFIABLE(buf) && opt_flags != OPT_GLOBAL) { + *errmsg = e_modifiable; + return; } - if (didset_vimruntime) { - vim_unsetenv_ext("VIMRUNTIME"); + + if (vim_strchr(*varp, ',') != NULL) { + // No comma allowed in 'fileencoding'; catches confusing it + // with 'fileencodings'. + *errmsg = e_invarg; + return; } - } else if (varp == &p_rtp || varp == &p_pp) { // 'runtimepath' 'packpath' - runtime_search_path_invalidate(); - } else if (varp == &curwin->w_p_culopt - || gvarp == &curwin->w_allbuf_opt.wo_culopt) { // 'cursorlineopt' - if (**varp == NUL || fill_culopt_flags(*varp, curwin) != OK) { - errmsg = e_invarg; + + // May show a "+" in the title now. + redraw_titles(); + // Add 'fileencoding' to the swap file. + ml_setflags(buf); + } + + // canonize the value, so that strcmp() can be used on it + char *p = enc_canonize(*varp); + xfree(*varp); + *varp = p; + if (varp == &p_enc) { + // only encoding=utf-8 allowed + if (strcmp(p_enc, "utf-8") != 0) { + *errmsg = e_unsupportedoption; + return; } - } else if (varp == &curwin->w_p_cc) { // 'colorcolumn' - errmsg = check_colorcolumn(curwin); - } else if (varp == &p_hlg) { // 'helplang' - // Check for "", "ab", "ab,cd", etc. - for (s = (char *)p_hlg; *s != NUL; s += 3) { - if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) { - errmsg = e_invarg; - break; + spell_reload(); + } +} + +static void did_set_keymap(buf_T *buf, char **varp, int opt_flags, int *value_checked, + char **errmsg) +{ + if (!valid_filetype(*varp)) { + *errmsg = e_invarg; + return; + } + + int secure_save = secure; + + // Reset the secure flag, since the value of 'keymap' has + // been checked to be safe. + secure = 0; + + // load or unload key mapping tables + *errmsg = keymap_init(); + + secure = secure_save; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; + + if (*errmsg == NULL) { + if (*buf->b_p_keymap != NUL) { + // Installed a new keymap, switch on using it. + buf->b_p_iminsert = B_IMODE_LMAP; + if (buf->b_p_imsearch != B_IMODE_USE_INSERT) { + buf->b_p_imsearch = B_IMODE_LMAP; } - if (s[2] == NUL) { - break; + } else { + // Cleared the keymap, may reset 'iminsert' and 'imsearch'. + if (buf->b_p_iminsert == B_IMODE_LMAP) { + buf->b_p_iminsert = B_IMODE_NONE; + } + if (buf->b_p_imsearch == B_IMODE_LMAP) { + buf->b_p_imsearch = B_IMODE_USE_INSERT; } } - } else if (varp == &p_hl) { // 'highlight' - if (STRCMP(*varp, HIGHLIGHT_INIT) != 0) { - errmsg = e_unsupportedoption; + if ((opt_flags & OPT_LOCAL) == 0) { + set_iminsert_global(buf); + set_imsearch_global(buf); } - } else if (varp == &p_jop) { // 'jumpoptions' - if (opt_strings_flags(p_jop, p_jop_values, &jop_flags, true) != OK) { - errmsg = e_invarg; + status_redraw_buf(buf); + } +} + +static void did_set_fileformat(buf_T *buf, char **varp, const char *oldval, int opt_flags, + char **errmsg) +{ + if (!MODIFIABLE(buf) && !(opt_flags & OPT_GLOBAL)) { + *errmsg = e_modifiable; + } else if (check_opt_strings(*varp, p_ff_values, false) != OK) { + *errmsg = e_invarg; + } else { + redraw_titles(); + // update flag in swap file + ml_setflags(buf); + // Redraw needed when switching to/from "mac": a CR in the text + // will be displayed differently. + if (get_fileformat(buf) == EOL_MAC || *oldval == 'm') { + redraw_buf_later(buf, UPD_NOT_VALID); } - } else if (gvarp == &p_nf) { // 'nrformats' - if (check_opt_strings(*varp, p_nf_values, true) != OK) { - errmsg = e_invarg; + } +} + +static void did_set_matchpairs(char **varp, char **errmsg) +{ + for (char *p = *varp; *p != NUL; p++) { + int x2 = -1; + int x3 = -1; + + p += utfc_ptr2len(p); + if (*p != NUL) { + x2 = (unsigned char)(*p++); } - } else if (varp == &p_ssop) { // 'sessionoptions' - if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) { - errmsg = e_invarg; + if (*p != NUL) { + x3 = utf_ptr2char(p); + p += utfc_ptr2len(p); } - if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) { - // Don't allow both "sesdir" and "curdir". - (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true); - errmsg = e_invarg; + if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) { + *errmsg = e_invarg; + break; } - } else if (varp == &p_vop) { // 'viewoptions' - if (opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true) != OK) { - errmsg = e_invarg; + if (*p == NUL) { + break; } - } else if (varp == &p_rdb) { // 'redrawdebug' - if (opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true) != OK) { - errmsg = e_invarg; + } +} + +static void did_set_comments(char **varp, char *errbuf, size_t errbuflen, char **errmsg) +{ + for (char *s = *varp; *s;) { + while (*s && *s != ':') { + if (vim_strchr(COM_ALL, (uint8_t)(*s)) == NULL + && !ascii_isdigit(*s) && *s != '-') { + *errmsg = illegal_char(errbuf, errbuflen, *s); + break; + } + s++; } - } else if (varp == (char_u **)&p_sbo) { // 'scrollopt' - if (check_opt_strings((char_u *)p_sbo, p_scbopt_values, true) != OK) { - errmsg = e_invarg; + if (*s++ == NUL) { + *errmsg = N_("E524: Missing colon"); + } else if (*s == ',' || *s == NUL) { + *errmsg = N_("E525: Zero length string"); } - } else if (varp == &p_ambw || (int *)varp == &p_emoji) { // 'ambiwidth' - if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) { - errmsg = e_invarg; - } else { - errmsg = check_chars_options(); + if (*errmsg != NULL) { + break; } - } else if (varp == &p_bg) { // 'background' - if (check_opt_strings(p_bg, p_bg_values, false) == OK) { - int dark = (*p_bg == 'd'); - - init_highlight(false, false); - - if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) { - // The color scheme must have set 'background' back to another - // value, that's not what we want here. Disable the color - // scheme and set the colors again. - do_unlet(S_LEN("g:colors_name"), true); - free_string_option(p_bg); - p_bg = vim_strsave((char_u *)(dark ? "dark" : "light")); - check_string_option(&p_bg); - init_highlight(false, false); + while (*s && *s != ',') { + if (*s == '\\' && s[1] != NUL) { + s++; } - } else { - errmsg = e_invarg; - } - } else if (varp == &p_wim) { // 'wildmode' - if (check_opt_wim() == FAIL) { - errmsg = e_invarg; + s++; } - } else if (varp == &p_wop) { // 'wildoptions' - if (opt_strings_flags(p_wop, p_wop_values, &wop_flags, true) != OK) { - errmsg = e_invarg; + s = skip_to_option_part(s); + } +} + +static void did_set_global_listfillchars(win_T *win, char **varp, int opt_flags, char **errmsg) +{ + char **local_ptr = varp == &p_lcs ? &win->w_p_lcs : &win->w_p_fcs; + // only apply the global value to "win" when it does not have a local value + *errmsg = set_chars_option(win, varp, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL)); + if (*errmsg == NULL) { + // If the current window is set to use the global + // 'listchars'/'fillchars' value, clear the window-local value. + if (!(opt_flags & OPT_GLOBAL)) { + clear_string_option(local_ptr); + } + FOR_ALL_TAB_WINDOWS(tp, wp) { + // If the current window has a local value need to apply it + // again, it was changed when setting the global value. + // If no error was returned above, we don't expect an error + // here, so ignore the return value. + local_ptr = varp == &p_lcs ? &wp->w_p_lcs : &wp->w_p_fcs; + if (**local_ptr == NUL) { + (void)set_chars_option(wp, local_ptr, true); + } } - } else if (varp == &p_wak) { // 'winaltkeys' - if (*p_wak == NUL - || check_opt_strings(p_wak, p_wak_values, false) != OK) { - errmsg = e_invarg; + redraw_all_later(UPD_NOT_VALID); + } +} + +static void did_set_verbosefile(char **errmsg) +{ + verbose_stop(); + if (*p_vfile != NUL && verbose_open() == FAIL) { + *errmsg = e_invarg; + } +} + +static void did_set_shada(vimoption_T **opt, int *opt_idx, bool *free_oldval, char *errbuf, + size_t errbuflen, char **errmsg) +{ + // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo + // option. + *opt_idx = (((*opt)->fullname[0] == 'v') + ? (shada_idx == -1 ? ((shada_idx = findoption("shada"))) : shada_idx) + : *opt_idx); + *opt = get_option(*opt_idx); + // Update free_oldval now that we have the opt_idx for 'shada', otherwise + // there would be a disconnect between the check for P_ALLOCED at the start + // of the function and the set of P_ALLOCED at the end of the function. + *free_oldval = ((*opt)->flags & P_ALLOCED); + for (char *s = p_shada; *s;) { + // Check it's a valid character + if (vim_strchr("!\"%'/:<@cfhnrs", (uint8_t)(*s)) == NULL) { + *errmsg = illegal_char(errbuf, errbuflen, *s); + break; } - } else if (varp == &p_ei) { // 'eventignore' - if (check_ei() == FAIL) { - errmsg = e_invarg; + if (*s == 'n') { // name is always last one + break; + } else if (*s == 'r') { // skip until next ',' + while (*++s && *s != ',') {} + } else if (*s == '%') { + // optional number + while (ascii_isdigit(*++s)) {} + } else if (*s == '!' || *s == 'h' || *s == 'c') { + s++; // no extra chars + } else { // must have a number + while (ascii_isdigit(*++s)) {} + + if (!ascii_isdigit(*(s - 1))) { + if (errbuf != NULL) { + vim_snprintf(errbuf, errbuflen, + _("E526: Missing number after <%s>"), + transchar_byte((uint8_t)(*(s - 1)))); + *errmsg = errbuf; + } else { + *errmsg = ""; + } + break; + } } - } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) { - // 'encoding', 'fileencoding' and 'makeencoding' - if (gvarp == &p_fenc) { - if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL) { - errmsg = e_modifiable; - } else if (vim_strchr((char *)(*varp), ',') != NULL) { - // No comma allowed in 'fileencoding'; catches confusing it - // with 'fileencodings'. - errmsg = e_invarg; + if (*s == ',') { + s++; + } else if (*s) { + if (errbuf != NULL) { + *errmsg = N_("E527: Missing comma"); } else { - // May show a "+" in the title now. - redraw_titles(); - // Add 'fileencoding' to the swap file. - ml_setflags(curbuf); + *errmsg = ""; } + break; } + } + if (*p_shada && *errmsg == NULL && get_shada_parameter('\'') < 0) { + *errmsg = N_("E528: Must specify a ' value"); + } +} - if (errmsg == NULL) { - // canonize the value, so that STRCMP() can be used on it - p = (char *)enc_canonize(*varp); - xfree(*varp); - *varp = (char_u *)p; - if (varp == &p_enc) { - // only encoding=utf-8 allowed - if (STRCMP(p_enc, "utf-8") != 0) { - errmsg = e_unsupportedoption; - } else { - spell_reload(); - } - } +static void did_set_showbreak(char **varp, char **errmsg) +{ + for (char *s = *varp; *s;) { + if (ptr2cells(s) != 1) { + *errmsg = e_showbreak_contains_unprintable_or_wide_character; } - } else if (varp == &p_penc) { - // Canonize printencoding if VIM standard one - p = (char *)enc_canonize(p_penc); - xfree(p_penc); - p_penc = (char_u *)p; - } else if (varp == &curbuf->b_p_keymap) { - if (!valid_filetype(*varp)) { - errmsg = e_invarg; - } else { - int secure_save = secure; + MB_PTR_ADV(s); + } +} + +static void did_set_titleiconstring(char **varp) +{ + // 'titlestring' and 'iconstring' + int flagval = (varp == &p_titlestring) ? STL_IN_TITLE : STL_IN_ICON; + + // NULL => statusline syntax + if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) { + stl_syntax |= flagval; + } else { + stl_syntax &= ~flagval; + } + did_set_title(); +} + +static void did_set_selection(char **errmsg) +{ + if (*p_sel == NUL || check_opt_strings(p_sel, p_sel_values, false) != OK) { + *errmsg = e_invarg; + } +} - // Reset the secure flag, since the value of 'keymap' has - // been checked to be safe. - secure = 0; +static void did_set_keymodel(char **errmsg) +{ + if (check_opt_strings(p_km, p_km_values, true) != OK) { + *errmsg = e_invarg; + return; + } + km_stopsel = (vim_strchr(p_km, 'o') != NULL); + km_startsel = (vim_strchr(p_km, 'a') != NULL); +} + +static void did_set_display(char **errmsg) +{ + if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) { + *errmsg = e_invarg; + return; + } + (void)init_chartab(); + msg_grid_validate(); +} - // load or unload key mapping tables - errmsg = keymap_init(); +static void did_set_spellfile(char **varp, char **errmsg) +{ + // When there is a window for this buffer in which 'spell' + // is set load the wordlists. - secure = secure_save; + if ((!valid_spellfile(*varp))) { + *errmsg = e_invarg; + } else { + *errmsg = did_set_spell_option(true); + } +} - // Since we check the value, there is no need to set P_INSECURE, - // even when the value comes from a modeline. - *value_checked = true; +static void did_set_spell(char **varp, char **errmsg) +{ + // When there is a window for this buffer in which 'spell' + // is set load the wordlists. + if (!valid_spelllang(*varp)) { + *errmsg = e_invarg; + } else { + *errmsg = did_set_spell_option(false); + } +} + +static void did_set_spellcapcheck(win_T *win, char **errmsg) +{ + // When 'spellcapcheck' is set compile the regexp program. + *errmsg = compile_cap_prog(win->w_s); +} + +static void did_set_spelloptions(win_T *win, char **errmsg) +{ + if (opt_strings_flags(win->w_s->b_p_spo, p_spo_values, &(win->w_s->b_p_spo_flags), + true) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_spellsuggest(char **errmsg) +{ + if (spell_check_sps() != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_mkspellmem(char **errmsg) +{ + if (spell_check_msm() != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_buftype(buf_T *buf, win_T *win, char **errmsg) +{ + // When 'buftype' is set, check for valid value. + if ((buf->terminal && buf->b_p_bt[0] != 't') + || (!buf->terminal && buf->b_p_bt[0] == 't') + || check_opt_strings(buf->b_p_bt, p_buftype_values, false) != OK) { + *errmsg = e_invarg; + } else { + if (win->w_status_height || global_stl_height()) { + win->w_redr_status = true; + redraw_later(win, UPD_VALID); } + buf->b_help = (buf->b_p_bt[0] == 'h'); + redraw_titles(); + } +} - if (errmsg == NULL) { - if (*curbuf->b_p_keymap != NUL) { - // Installed a new keymap, switch on using it. - curbuf->b_p_iminsert = B_IMODE_LMAP; - if (curbuf->b_p_imsearch != B_IMODE_USE_INSERT) { - curbuf->b_p_imsearch = B_IMODE_LMAP; - } - } else { - // Cleared the keymap, may reset 'iminsert' and 'imsearch'. - if (curbuf->b_p_iminsert == B_IMODE_LMAP) { - curbuf->b_p_iminsert = B_IMODE_NONE; - } - if (curbuf->b_p_imsearch == B_IMODE_LMAP) { - curbuf->b_p_imsearch = B_IMODE_USE_INSERT; - } - } - if ((opt_flags & OPT_LOCAL) == 0) { - set_iminsert_global(); - set_imsearch_global(); - } - status_redraw_curbuf(); +// 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn' +static void did_set_statusline(win_T *win, char **varp, char **gvarp, char **errmsg) +{ + if (varp == &p_ruf) { // reset ru_wid first + ru_wid = 0; + } else if (varp == &win->w_p_stc) { + win->w_nrwidth_line_count = 0; + } + char *s = *varp; + if (varp == &p_ruf && *s == '%') { + // set ru_wid if 'ruf' starts with "%99(" + if (*++s == '-') { // ignore a '-' + s++; } - } else if (gvarp == &p_ff) { // 'fileformat' - if (!MODIFIABLE(curbuf) && !(opt_flags & OPT_GLOBAL)) { - errmsg = e_modifiable; - } else if (check_opt_strings(*varp, p_ff_values, false) != OK) { - errmsg = e_invarg; + int wid = getdigits_int(&s, true, 0); + if (wid && *s == '(' && (*errmsg = check_stl_option(p_ruf)) == NULL) { + ru_wid = wid; } else { - redraw_titles(); - // update flag in swap file - ml_setflags(curbuf); - // Redraw needed when switching to/from "mac": a CR in the text - // will be displayed differently. - if (get_fileformat(curbuf) == EOL_MAC || *oldval == 'm') { - redraw_curbuf_later(NOT_VALID); - } - } - } else if (varp == (char_u **)&p_ffs) { // 'fileformats' - if (check_opt_strings((char_u *)p_ffs, p_ff_values, true) != OK) { - errmsg = e_invarg; + *errmsg = check_stl_option(p_ruf); } - } else if (gvarp == &p_mps) { // 'matchpairs' - for (p = (char *)(*varp); *p != NUL; p++) { - int x2 = -1; - int x3 = -1; + } else if (varp == &p_ruf || s[0] != '%' || s[1] != '!') { + // check 'statusline', 'winbar', 'tabline' or 'statuscolumn' + // only if it doesn't start with "%!" + *errmsg = check_stl_option(s); + } + if (varp == &p_ruf && *errmsg == NULL) { + comp_col(); + } + // add / remove window bars for 'winbar' + if (gvarp == &p_wbr) { + set_winbar(true); + } +} - p += utfc_ptr2len(p); - if (*p != NUL) { - x2 = (unsigned char)(*p++); - } - if (*p != NUL) { - x3 = utf_ptr2char(p); - p += utfc_ptr2len(p); - } - if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) { - errmsg = e_invarg; - break; - } - if (*p == NUL) { - break; - } +static void did_set_complete(char **varp, char *errbuf, size_t errbuflen, char **errmsg) +{ + // check if it is a valid value for 'complete' -- Acevedo + for (char *s = *varp; *s;) { + while (*s == ',' || *s == ' ') { + s++; } - } else if (gvarp == &p_com) { // 'comments' - for (s = (char *)(*varp); *s;) { - while (*s && *s != ':') { - if (vim_strchr(COM_ALL, *s) == NULL - && !ascii_isdigit(*s) && *s != '-') { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - s++; - } - if (*s++ == NUL) { - errmsg = N_("E524: Missing colon"); - } else if (*s == ',' || *s == NUL) { - errmsg = N_("E525: Zero length string"); - } - if (errmsg != NULL) { - break; - } - while (*s && *s != ',') { - if (*s == '\\' && s[1] != NUL) { - s++; - } - s++; - } - s = (char *)skip_to_option_part((char_u *)s); - } - } else if (varp == &p_lcs) { // global 'listchars' - errmsg = set_chars_option(curwin, varp, false); - if (errmsg == NULL) { - // The current window is set to use the global 'listchars' value. - // So clear the window-local value. - if (!(opt_flags & OPT_GLOBAL)) { - clear_string_option(&curwin->w_p_lcs); - } - FOR_ALL_TAB_WINDOWS(tp, wp) { - // If no error was returned above, we don't expect an error - // here, so ignore the return value. - (void)set_chars_option(wp, &wp->w_p_lcs, true); - } - redraw_all_later(NOT_VALID); - } - } else if (varp == &curwin->w_p_lcs) { // local 'listchars' - errmsg = set_chars_option(curwin, varp, true); - } else if (varp == &p_fcs) { // global 'fillchars' - errmsg = set_chars_option(curwin, varp, false); - if (errmsg == NULL) { - // The current window is set to use the global 'fillchars' value. - // So clear the window-local value. - if (!(opt_flags & OPT_GLOBAL)) { - clear_string_option(&curwin->w_p_fcs); - } - FOR_ALL_TAB_WINDOWS(tp, wp) { - // If no error was returned above, we don't expect an error - // here, so ignore the return value. - (void)set_chars_option(wp, &wp->w_p_fcs, true); - } - redraw_all_later(NOT_VALID); + if (!*s) { + break; } - } else if (varp == &curwin->w_p_fcs) { // local 'fillchars' - errmsg = set_chars_option(curwin, varp, true); - } else if (varp == &p_cedit) { // 'cedit' - errmsg = check_cedit(); - } else if (varp == &p_vfile) { // 'verbosefile' - verbose_stop(); - if (*p_vfile != NUL && verbose_open() == FAIL) { - errmsg = e_invarg; + if (vim_strchr(".wbuksid]tU", (uint8_t)(*s)) == NULL) { + *errmsg = illegal_char(errbuf, errbuflen, *s); + break; } - } else if (varp == &p_shada) { // 'shada' - // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo - // option. - opt_idx = ((get_option_fullname(opt_idx)[0] == 'v') - ? (shada_idx == -1 ? ((shada_idx = findoption("shada"))) : shada_idx) - : opt_idx); - // Update free_oldval now that we have the opt_idx for 'shada', otherwise - // there would be a disconnect between the check for P_ALLOCED at the start - // of the function and the set of P_ALLOCED at the end of the function. - free_oldval = (get_option_flags(opt_idx) & P_ALLOCED); - for (s = (char *)p_shada; *s;) { - // Check it's a valid character - if (vim_strchr("!\"%'/:<@cfhnrs", *s) == NULL) { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - if (*s == 'n') { // name is always last one - break; - } else if (*s == 'r') { // skip until next ',' - while (*++s && *s != ',') {} - } else if (*s == '%') { - // optional number - while (ascii_isdigit(*++s)) {} - } else if (*s == '!' || *s == 'h' || *s == 'c') { - s++; // no extra chars - } else { // must have a number - while (ascii_isdigit(*++s)) {} - - if (!ascii_isdigit(*(s - 1))) { - if (errbuf != NULL) { - vim_snprintf(errbuf, errbuflen, - _("E526: Missing number after <%s>"), - transchar_byte(*(s - 1))); - errmsg = errbuf; - } else { - errmsg = ""; + if (*++s != NUL && *s != ',' && *s != ' ') { + if (s[-1] == 'k' || s[-1] == 's') { + // skip optional filename after 'k' and 's' + while (*s && *s != ',' && *s != ' ') { + if (*s == '\\' && s[1] != NUL) { + s++; } - break; + s++; } - } - if (*s == ',') { - s++; - } else if (*s) { + } else { if (errbuf != NULL) { - errmsg = N_("E527: Missing comma"); + vim_snprintf(errbuf, errbuflen, + _("E535: Illegal character after <%c>"), + *--s); + *errmsg = errbuf; } else { - errmsg = ""; + *errmsg = ""; } break; } } - if (*p_shada && errmsg == NULL && get_shada_parameter('\'') < 0) { - errmsg = N_("E528: Must specify a ' value"); + } +} + +static void did_set_completeopt(char **errmsg) +{ + if (check_opt_strings(p_cot, p_cot_values, true) != OK) { + *errmsg = e_invarg; + } else { + completeopt_was_set(); + } +} + +static void did_set_signcolumn(win_T *win, char **varp, const char *oldval, char **errmsg) +{ + if (check_signcolumn(*varp) != OK) { + *errmsg = e_invarg; + } + // When changing the 'signcolumn' to or from 'number', recompute the + // width of the number column if 'number' or 'relativenumber' is set. + if (((*oldval == 'n' && *(oldval + 1) == 'u') + || (*win->w_p_scl == 'n' && *(win->w_p_scl + 1) == 'u')) + && (win->w_p_nu || win->w_p_rnu)) { + win->w_nrwidth_line_count = 0; + } +} + +static void did_set_foldcolumn(char **varp, char **errmsg) +{ + if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_pastetoggle(void) +{ + // 'pastetoggle': translate key codes like in a mapping + if (*p_pt) { + char *p = NULL; + (void)replace_termcodes(p_pt, + strlen(p_pt), + &p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL, + CPO_TO_CPO_FLAGS); + if (p != NULL) { + free_string_option(p_pt); + p_pt = p; + } + } +} + +static void did_set_backspace(char **errmsg) +{ + if (ascii_isdigit(*p_bs)) { + if (*p_bs > '3' || p_bs[1] != NUL) { + *errmsg = e_invarg; } - } else if (gvarp == &p_sbr) { // 'showbreak' - for (s = (char *)(*varp); *s;) { - if (ptr2cells(s) != 1) { - errmsg = N_("E595: 'showbreak' contains unprintable or wide character"); - } - MB_PTR_ADV(s); + } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_tagcase(buf_T *buf, int opt_flags, char **errmsg) +{ + unsigned int *flags; + char *p; + + if (opt_flags & OPT_LOCAL) { + p = buf->b_p_tc; + flags = &buf->b_tc_flags; + } else { + p = p_tc; + flags = &tc_flags; + } + + if ((opt_flags & OPT_LOCAL) && *p == NUL) { + // make the local value empty: use the global value + *flags = 0; + } else if (*p == NUL + || opt_strings_flags(p, p_tc_values, flags, false) != OK) { + *errmsg = e_invarg; + } +} + +static void did_set_diffopt(char **errmsg) +{ + if (diffopt_changed() == FAIL) { + *errmsg = e_invarg; + } +} + +static void did_set_foldmethod(win_T *win, char **varp, char **errmsg) +{ + if (check_opt_strings(*varp, p_fdm_values, false) != OK + || *win->w_p_fdm == NUL) { + *errmsg = e_invarg; + } else { + foldUpdateAll(win); + if (foldmethodIsDiff(win)) { + newFoldLevel(); + } + } +} + +static void did_set_foldmarker(win_T *win, char **varp, char **errmsg) +{ + char *p = vim_strchr(*varp, ','); + if (p == NULL) { + *errmsg = N_("E536: comma required"); + } else if (p == *varp || p[1] == NUL) { + *errmsg = e_invarg; + } else if (foldmethodIsMarker(win)) { + foldUpdateAll(win); + } +} + +static void did_set_commentstring(char **varp, char **errmsg) +{ + if (**varp != NUL && strstr(*varp, "%s") == NULL) { + *errmsg = N_("E537: 'commentstring' must be empty or contain %s"); + } +} + +static void did_set_foldignore(win_T *win) +{ + if (foldmethodIsIndent(win)) { + foldUpdateAll(win); + } +} + +static void did_set_virtualedit(win_T *win, int opt_flags, char *oldval, char **errmsg) +{ + char *ve = p_ve; + unsigned int *flags = &ve_flags; + + if (opt_flags & OPT_LOCAL) { + ve = win->w_p_ve; + flags = &win->w_ve_flags; + } + + if ((opt_flags & OPT_LOCAL) && *ve == NUL) { + // make the local value empty: use the global value + *flags = 0; + } else { + if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) { + *errmsg = e_invarg; + } else if (strcmp(p_ve, oldval) != 0) { + // Recompute cursor position in case the new 've' setting + // changes something. + validate_virtcol_win(win); + coladvance(win->w_virtcol); + } + } +} + +static void did_set_lispoptions(char **varp, char **errmsg) +{ + if (**varp != NUL && strcmp(*varp, "expr:0") != 0 && strcmp(*varp, "expr:1") != 0) { + *errmsg = e_invarg; + } +} + +static void did_set_filetype_or_syntax(char **varp, char *oldval, int *value_checked, + bool *value_changed, char **errmsg) +{ + if (!valid_filetype(*varp)) { + *errmsg = e_invarg; + return; + } + + *value_changed = strcmp(oldval, *varp) != 0; + + // Since we check the value, there is no need to set P_INSECURE, + // even when the value comes from a modeline. + *value_checked = true; +} + +static void did_set_winhl(win_T *win, char **errmsg) +{ + if (!parse_winhl_opt(win)) { + *errmsg = e_invarg; + } +} + +static void did_set_varsoftabstop(buf_T *buf, char **varp, char **errmsg) +{ + if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { + XFREE_CLEAR(buf->b_p_vsts_array); + return; + } + + for (char *cp = *varp; *cp; cp++) { + if (ascii_isdigit(*cp)) { + continue; + } + if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { + continue; + } + *errmsg = e_invarg; + return; + } + + long *oldarray = buf->b_p_vsts_array; + if (tabstop_set(*varp, &(buf->b_p_vsts_array))) { + xfree(oldarray); + } else { + *errmsg = e_invarg; + } +} + +static void did_set_vartabstop(buf_T *buf, win_T *win, char **varp, char **errmsg) +{ + if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { + XFREE_CLEAR(buf->b_p_vts_array); + return; + } + + for (char *cp = *varp; *cp; cp++) { + if (ascii_isdigit(*cp)) { + continue; + } + if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { + continue; + } + *errmsg = e_invarg; + return; + } + + long *oldarray = buf->b_p_vts_array; + if (tabstop_set(*varp, &(buf->b_p_vts_array))) { + xfree(oldarray); + if (foldmethodIsIndent(win)) { + foldUpdateAll(win); + } + } else { + *errmsg = e_invarg; + } +} + +static void did_set_optexpr(win_T *win, char **p_opt, char **varp, char **gvarp) +{ + char *name = get_scriptlocal_funcname(*p_opt); + if (name != NULL) { + free_string_option(*p_opt); + *p_opt = name; + } +} + +// handle option that is a list of flags. +static void did_set_option_listflag(char **varp, char *flags, char *errbuf, size_t errbuflen, + char **errmsg) +{ + for (char *s = *varp; *s; s++) { + if (vim_strchr(flags, *s) == NULL) { + *errmsg = illegal_char(errbuf, errbuflen, *s); + break; + } + } +} + +// When 'syntax' is set, load the syntax of that name +static void do_syntax_autocmd(buf_T *buf, bool value_changed) +{ + static int syn_recursive = 0; + + syn_recursive++; + // Only pass true for "force" when the value changed or not used + // recursively, to avoid endless recurrence. + apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname, + value_changed || syn_recursive == 1, buf); + buf->b_flags |= BF_SYN_SET; + syn_recursive--; +} + +static void do_filetype_autocmd(buf_T *buf, char **varp, int opt_flags, bool value_changed) +{ + // 'filetype' is set, trigger the FileType autocommand + // Skip this when called from a modeline and the filetype was + // already set to this value. + if (!(opt_flags & OPT_MODELINE) || value_changed) { + static int ft_recursive = 0; + int secure_save = secure; + + // Reset the secure flag, since the value of 'filetype' has + // been checked to be safe. + secure = 0; + + ft_recursive++; + did_filetype = true; + // Only pass true for "force" when the value changed or not + // used recursively, to avoid endless recurrence. + apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, + value_changed || ft_recursive == 1, buf); + ft_recursive--; + // Just in case the old "buf" is now invalid + if (varp != &(buf->b_p_ft)) { + varp = NULL; + } + secure = secure_save; + } +} + +static void did_set_spelllang_source(win_T *win) +{ + char fname[200]; + char *q = win->w_s->b_p_spl; + + // Skip the first name if it is "cjk". + if (strncmp(q, "cjk,", 4) == 0) { + q += 4; + } + + // Source the spell/LANG.vim in 'runtimepath'. + // They could set 'spellcapcheck' depending on the language. + // Use the first name in 'spelllang' up to '_region' or + // '.encoding'. + char *p; + for (p = q; *p != NUL; p++) { + if (!ASCII_ISALNUM(*p) && *p != '-') { + break; } + } + if (p > q) { + vim_snprintf(fname, sizeof(fname), "spell/%.*s.vim", (int)(p - q), q); + source_runtime(fname, DIP_ALL); + } +} + +/// Handle string options that need some action to perform when changed. +/// The new value must be allocated. +/// +/// @param opt_idx index in options[] table +/// @param varp pointer to the option variable +/// @param oldval previous value of the option +/// @param errbuf buffer for errors, or NULL +/// @param errbuflen length of errors buffer +/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL +/// @param value_checked value was checked to be safe, no need to set P_INSECURE +/// +/// @return NULL for success, or an untranslated error message for an error +static char *did_set_string_option_for(buf_T *buf, win_T *win, int opt_idx, char **varp, + char *oldval, char *errbuf, size_t errbuflen, int opt_flags, + int *value_checked) +{ + char *errmsg = NULL; + bool did_chartab = false; + vimoption_T *opt = get_option(opt_idx); + bool free_oldval = (opt->flags & P_ALLOCED); + bool value_changed = false; + + // Get the global option to compare with, otherwise we would have to check + // two values for all local options. + char **gvarp = (char **)get_varp_scope(opt, OPT_GLOBAL); + + // Disallow changing some options from secure mode + if ((secure || sandbox != 0) + && (opt->flags & P_SECURE)) { + errmsg = e_secure; + } else if (check_illegal_path_names(*varp, opt->flags)) { + // Check for a "normal" directory or file name in some options. + errmsg = e_invarg; + } else if (gvarp == &p_bkc) { // 'backupcopy' + did_set_backupcopy(buf, oldval, opt_flags, &errmsg); + } else if (varp == &p_bex || varp == &p_pm) { // 'backupext' and 'patchmode' + did_set_backupext_or_patchmode(&errmsg); + } else if (varp == &win->w_p_briopt) { // 'breakindentopt' + did_set_breakindentopt(win, &errmsg); + } else if (varp == &p_isi + || varp == &buf->b_p_isk + || varp == &p_isp + || varp == &p_isf) { + // 'isident', 'iskeyword', 'isprint or 'isfname' option + did_set_isopt(buf, &did_chartab, &errmsg); + } else if (varp == &p_hf) { // 'helpfile' + did_set_helpfile(); + } else if (varp == &p_rtp || varp == &p_pp) { // 'runtimepath' 'packpath' + runtime_search_path_invalidate(); + } else if (varp == &win->w_p_culopt + || gvarp == &win->w_allbuf_opt.wo_culopt) { // 'cursorlineopt' + did_set_cursorlineopt(win, varp, &errmsg); + } else if (varp == &win->w_p_cc) { // 'colorcolumn' + errmsg = check_colorcolumn(win); + } else if (varp == &p_hlg) { // 'helplang' + did_set_helplang(&errmsg); + } else if (varp == &p_hl) { // 'highlight' + did_set_highlight(varp, &errmsg); + } else if (varp == &p_jop) { // 'jumpoptions' + did_set_opt_flags(p_jop, p_jop_values, &jop_flags, true, &errmsg); + } else if (gvarp == &p_nf) { // 'nrformats' + did_set_opt_strings(*varp, p_nf_values, true, &errmsg); + } else if (varp == &p_ssop) { // 'sessionoptions' + did_set_sessionoptions(oldval, &errmsg); + } else if (varp == &p_vop) { // 'viewoptions' + did_set_opt_flags(p_vop, p_ssop_values, &vop_flags, true, &errmsg); + } else if (varp == &p_rdb) { // 'redrawdebug' + did_set_opt_flags(p_rdb, p_rdb_values, &rdb_flags, true, &errmsg); + } else if (varp == &p_sbo) { // 'scrollopt' + did_set_opt_strings(p_sbo, p_scbopt_values, true, &errmsg); + } else if (varp == &p_ambw || (int *)varp == &p_emoji) { // 'ambiwidth' + did_set_ambiwidth(&errmsg); + } else if (varp == &p_bg) { // 'background' + did_set_background(&errmsg); + } else if (varp == &p_wim) { // 'wildmode' + did_set_wildmode(&errmsg); + } else if (varp == &p_wop) { // 'wildoptions' + did_set_opt_flags(p_wop, p_wop_values, &wop_flags, true, &errmsg); + } else if (varp == &p_wak) { // 'winaltkeys' + did_set_winaltkeys(&errmsg); + } else if (varp == &p_ei) { // 'eventignore' + did_set_eventignore(&errmsg); + } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) { + // 'encoding', 'fileencoding' and 'makeencoding' + did_set_encoding(buf, varp, gvarp, opt_flags, &errmsg); + } else if (varp == &buf->b_p_keymap) { + did_set_keymap(buf, varp, opt_flags, value_checked, &errmsg); + } else if (gvarp == &p_ff) { // 'fileformat' + did_set_fileformat(buf, varp, oldval, opt_flags, &errmsg); + } else if (varp == &p_ffs) { // 'fileformats' + did_set_opt_strings(p_ffs, p_ff_values, true, &errmsg); + } else if (gvarp == &p_mps) { // 'matchpairs' + did_set_matchpairs(varp, &errmsg); + } else if (gvarp == &p_com) { // 'comments' + did_set_comments(varp, errbuf, errbuflen, &errmsg); + } else if (varp == &p_lcs || varp == &p_fcs) { // global 'listchars' or 'fillchars' + did_set_global_listfillchars(win, varp, opt_flags, &errmsg); + } else if (varp == &win->w_p_lcs) { // local 'listchars' + errmsg = set_chars_option(win, varp, true); + } else if (varp == &win->w_p_fcs) { // local 'fillchars' + errmsg = set_chars_option(win, varp, true); + } else if (varp == &p_cedit) { // 'cedit' + errmsg = check_cedit(); + } else if (varp == &p_vfile) { // 'verbosefile' + did_set_verbosefile(&errmsg); + } else if (varp == &p_shada) { // 'shada' + did_set_shada(&opt, &opt_idx, &free_oldval, errbuf, errbuflen, &errmsg); + } else if (gvarp == &p_sbr) { // 'showbreak' + did_set_showbreak(varp, &errmsg); } else if (varp == &p_guicursor) { // 'guicursor' errmsg = parse_shape_opt(SHAPE_CURSOR); - } else if (varp == &p_popt) { - errmsg = parse_printoptions(); - } else if (varp == &p_pmfn) { - errmsg = parse_printmbfont(); } else if (varp == &p_langmap) { // 'langmap' langmap_set(); } else if (varp == &p_breakat) { // 'breakat' fill_breakat_flags(); } else if (varp == &p_titlestring || varp == &p_iconstring) { // 'titlestring' and 'iconstring' - int flagval = (varp == &p_titlestring) ? STL_IN_TITLE : STL_IN_ICON; - - // NULL => statusline syntax - if (vim_strchr((char *)(*varp), '%') && check_stl_option((char *)(*varp)) == NULL) { - stl_syntax |= flagval; - } else { - stl_syntax &= ~flagval; - } - did_set_title(); + did_set_titleiconstring(varp); } else if (varp == &p_sel) { // 'selection' - if (*p_sel == NUL - || check_opt_strings(p_sel, p_sel_values, false) != OK) { - errmsg = e_invarg; - } + did_set_selection(&errmsg); } else if (varp == &p_slm) { // 'selectmode' - if (check_opt_strings(p_slm, p_slm_values, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_strings(p_slm, p_slm_values, true, &errmsg); } else if (varp == &p_km) { // 'keymodel' - if (check_opt_strings(p_km, p_km_values, true) != OK) { - errmsg = e_invarg; - } else { - km_stopsel = (vim_strchr((char *)p_km, 'o') != NULL); - km_startsel = (vim_strchr((char *)p_km, 'a') != NULL); - } + did_set_keymodel(&errmsg); } else if (varp == &p_mousem) { // 'mousemodel' - if (check_opt_strings(p_mousem, p_mousem_values, false) != OK) { - errmsg = e_invarg; - } + did_set_opt_strings(p_mousem, p_mousem_values, false, &errmsg); } else if (varp == &p_mousescroll) { // 'mousescroll' - errmsg = check_mousescroll((char *)p_mousescroll); + errmsg = check_mousescroll(p_mousescroll); } else if (varp == &p_swb) { // 'switchbuf' - if (opt_strings_flags(p_swb, p_swb_values, &swb_flags, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_swb, p_swb_values, &swb_flags, true, &errmsg); + } else if (varp == &p_spk) { // 'splitkeep' + did_set_opt_strings(p_spk, p_spk_values, false, &errmsg); } else if (varp == &p_debug) { // 'debug' - if (check_opt_strings(p_debug, p_debug_values, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_strings(p_debug, p_debug_values, true, &errmsg); } else if (varp == &p_dy) { // 'display' - if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) { - errmsg = e_invarg; - } else { - (void)init_chartab(); - msg_grid_validate(); - } + did_set_display(&errmsg); } else if (varp == &p_ead) { // 'eadirection' - if (check_opt_strings(p_ead, p_ead_values, false) != OK) { - errmsg = e_invarg; - } + did_set_opt_strings(p_ead, p_ead_values, false, &errmsg); } else if (varp == &p_cb) { // 'clipboard' - if (opt_strings_flags(p_cb, p_cb_values, &cb_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &(curwin->w_s->b_p_spl) // 'spell' - || varp == &(curwin->w_s->b_p_spf)) { - // When 'spelllang' or 'spellfile' is set and there is a window for this - // buffer in which 'spell' is set load the wordlists. - const bool is_spellfile = varp == &(curwin->w_s->b_p_spf); - - if ((is_spellfile && !valid_spellfile(*varp)) - || (!is_spellfile && !valid_spelllang(*varp))) { - errmsg = e_invarg; - } else { - errmsg = did_set_spell_option(is_spellfile); - } - } else if (varp == &(curwin->w_s->b_p_spc)) { - // When 'spellcapcheck' is set compile the regexp program. - errmsg = compile_cap_prog(curwin->w_s); - } else if (varp == &(curwin->w_s->b_p_spo)) { // 'spelloptions' - if (**varp != NUL && STRCMP("camel", *varp) != 0) { - errmsg = e_invarg; - } + did_set_opt_flags(p_cb, p_cb_values, &cb_flags, true, &errmsg); + } else if (varp == &win->w_s->b_p_spf) { + did_set_spellfile(varp, &errmsg); + } else if (varp == &win->w_s->b_p_spl) { // 'spell' + did_set_spell(varp, &errmsg); + } else if (varp == &win->w_s->b_p_spc) { + did_set_spellcapcheck(win, &errmsg); + } else if (varp == &win->w_s->b_p_spo) { // 'spelloptions' + did_set_spelloptions(win, &errmsg); } else if (varp == &p_sps) { // 'spellsuggest' - if (spell_check_sps() != OK) { - errmsg = e_invarg; - } + did_set_spellsuggest(&errmsg); } else if (varp == &p_msm) { // 'mkspellmem' - if (spell_check_msm() != OK) { - errmsg = e_invarg; - } + did_set_mkspellmem(&errmsg); } else if (gvarp == &p_bh) { - // When 'bufhidden' is set, check for valid value. - if (check_opt_strings(curbuf->b_p_bh, p_bufhidden_values, false) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &p_bt) { - // When 'buftype' is set, check for valid value. - if ((curbuf->terminal && curbuf->b_p_bt[0] != 't') - || (!curbuf->terminal && curbuf->b_p_bt[0] == 't') - || check_opt_strings(curbuf->b_p_bt, p_buftype_values, false) != OK) { - errmsg = e_invarg; - } else { - if (curwin->w_status_height || global_stl_height()) { - curwin->w_redr_status = true; - redraw_later(curwin, VALID); - } - curbuf->b_help = (curbuf->b_p_bt[0] == 'h'); - redraw_titles(); - } - } else if (gvarp == &p_stl || gvarp == (char_u **)&p_wbr || varp == &p_tal || varp == &p_ruf) { - // 'statusline', 'winbar', 'tabline' or 'rulerformat' - int wid; - - if (varp == &p_ruf) { // reset ru_wid first - ru_wid = 0; - } - s = (char *)(*varp); - if (varp == &p_ruf && *s == '%') { - // set ru_wid if 'ruf' starts with "%99(" - if (*++s == '-') { // ignore a '-' - s++; - } - wid = getdigits_int(&s, true, 0); - if (wid && *s == '(' && (errmsg = check_stl_option((char *)p_ruf)) == NULL) { - ru_wid = wid; - } else { - errmsg = check_stl_option((char *)p_ruf); - } - } else if (varp == &p_ruf || s[0] != '%' || s[1] != '!') { - // check 'statusline', 'winbar' or 'tabline' only if it doesn't start with "%!" - errmsg = check_stl_option(s); - } - if (varp == &p_ruf && errmsg == NULL) { - comp_col(); - } - // add / remove window bars for 'winbar' - if (gvarp == (char_u **)&p_wbr) { - set_winbar(true); - } - } else if (gvarp == &p_cpt) { - // check if it is a valid value for 'complete' -- Acevedo - for (s = (char *)(*varp); *s;) { - while (*s == ',' || *s == ' ') { - s++; - } - if (!*s) { - break; - } - if (vim_strchr(".wbuksid]tU", *s) == NULL) { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - if (*++s != NUL && *s != ',' && *s != ' ') { - if (s[-1] == 'k' || s[-1] == 's') { - // skip optional filename after 'k' and 's' - while (*s && *s != ',' && *s != ' ') { - if (*s == '\\' && s[1] != NUL) { - s++; - } - s++; - } - } else { - if (errbuf != NULL) { - vim_snprintf(errbuf, errbuflen, - _("E535: Illegal character after <%c>"), - *--s); - errmsg = errbuf; - } else { - errmsg = ""; - } - break; - } - } - } + did_set_opt_strings(buf->b_p_bh, p_bufhidden_values, false, &errmsg); + } else if (gvarp == &p_bt) { // 'buftype' + did_set_buftype(buf, win, &errmsg); + } else if (gvarp == &p_stl || gvarp == &p_wbr || varp == &p_tal + || varp == &p_ruf || varp == &win->w_p_stc) { + // 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn' + did_set_statusline(win, varp, gvarp, &errmsg); + } else if (gvarp == &p_cpt) { // 'complete' + did_set_complete(varp, errbuf, errbuflen, &errmsg); } else if (varp == &p_cot) { // 'completeopt' - if (check_opt_strings(p_cot, p_cot_values, true) != OK) { - errmsg = e_invarg; - } else { - completeopt_was_set(); - } + did_set_completeopt(&errmsg); #ifdef BACKSLASH_IN_FILENAME } else if (gvarp == &p_csl) { // 'completeslash' if (check_opt_strings(p_csl, p_csl_values, false) != OK - || check_opt_strings(curbuf->b_p_csl, p_csl_values, false) != OK) { + || check_opt_strings(buf->b_p_csl, p_csl_values, false) != OK) { errmsg = e_invarg; } #endif - } else if (varp == &curwin->w_p_scl) { // 'signcolumn' - if (check_signcolumn(*varp) != OK) { - errmsg = e_invarg; - } - // When changing the 'signcolumn' to or from 'number', recompute the - // width of the number column if 'number' or 'relativenumber' is set. - if (((*oldval == 'n' && *(oldval + 1) == 'u') - || (*curwin->w_p_scl == 'n' && *(curwin->w_p_scl + 1) == 'u')) - && (curwin->w_p_nu || curwin->w_p_rnu)) { - curwin->w_nrwidth_line_count = 0; - } - } else if (varp == &curwin->w_p_fdc || varp == &curwin->w_allbuf_opt.wo_fdc) { + } else if (varp == &win->w_p_scl) { // 'signcolumn' + did_set_signcolumn(win, varp, oldval, &errmsg); + } else if (varp == &p_sloc) { // 'showcmdloc' + did_set_opt_strings(*varp, p_sloc_values, false, &errmsg); + } else if (varp == &win->w_p_fdc + || varp == &win->w_allbuf_opt.wo_fdc) { // 'foldcolumn' - if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) { - errmsg = e_invarg; - } - } else if (varp == &p_pt) { - // 'pastetoggle': translate key codes like in a mapping - if (*p_pt) { - p = NULL; - (void)replace_termcodes((char *)p_pt, - STRLEN(p_pt), - &p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL, - CPO_TO_CPO_FLAGS); - if (p != NULL) { - free_string_option(p_pt); - p_pt = (char_u *)p; - } - } + did_set_foldcolumn(varp, &errmsg); + } else if (varp == &p_pt) { // 'pastetoggle' + did_set_pastetoggle(); } else if (varp == &p_bs) { // 'backspace' - if (ascii_isdigit(*p_bs)) { - if (*p_bs > '3' || p_bs[1] != NUL) { - errmsg = e_invarg; - } - } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) { - errmsg = e_invarg; - } + did_set_backspace(&errmsg); } else if (varp == &p_bo) { - if (opt_strings_flags(p_bo, p_bo_values, &bo_flags, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_bo, p_bo_values, &bo_flags, true, &errmsg); } else if (gvarp == &p_tc) { // 'tagcase' - unsigned int *flags; - - if (opt_flags & OPT_LOCAL) { - p = (char *)curbuf->b_p_tc; - flags = &curbuf->b_tc_flags; - } else { - p = (char *)p_tc; - flags = &tc_flags; - } - - if ((opt_flags & OPT_LOCAL) && *p == NUL) { - // make the local value empty: use the global value - *flags = 0; - } else if (*p == NUL - || opt_strings_flags((char_u *)p, p_tc_values, flags, false) != OK) { - errmsg = e_invarg; - } + did_set_tagcase(buf, opt_flags, &errmsg); } else if (varp == &p_cmp) { // 'casemap' - if (opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_cmp, p_cmp_values, &cmp_flags, true, &errmsg); } else if (varp == &p_dip) { // 'diffopt' - if (diffopt_changed() == FAIL) { - errmsg = e_invarg; - } - } else if (gvarp == &curwin->w_allbuf_opt.wo_fdm) { // 'foldmethod' - if (check_opt_strings(*varp, p_fdm_values, false) != OK - || *curwin->w_p_fdm == NUL) { - errmsg = e_invarg; - } else { - foldUpdateAll(curwin); - if (foldmethodIsDiff(curwin)) { - newFoldLevel(); - } - } - } else if (varp == &curwin->w_p_fde) { // 'foldexpr' - if (foldmethodIsExpr(curwin)) { - foldUpdateAll(curwin); - } - } else if (gvarp == &curwin->w_allbuf_opt.wo_fmr) { // 'foldmarker' - p = vim_strchr((char *)(*varp), ','); - if (p == NULL) { - errmsg = N_("E536: comma required"); - } else if ((char_u *)p == *varp || p[1] == NUL) { - errmsg = e_invarg; - } else if (foldmethodIsMarker(curwin)) { - foldUpdateAll(curwin); - } + did_set_diffopt(&errmsg); + } else if (gvarp == &win->w_allbuf_opt.wo_fdm) { // 'foldmethod' + did_set_foldmethod(win, varp, &errmsg); + } else if (gvarp == &win->w_allbuf_opt.wo_fmr) { // 'foldmarker' + did_set_foldmarker(win, varp, &errmsg); } else if (gvarp == &p_cms) { // 'commentstring' - if (**varp != NUL && strstr((char *)(*varp), "%s") == NULL) { - errmsg = N_("E537: 'commentstring' must be empty or contain %s"); - } + did_set_commentstring(varp, &errmsg); } else if (varp == &p_fdo) { // 'foldopen' - if (opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true) != OK) { - errmsg = e_invarg; - } + did_set_opt_flags(p_fdo, p_fdo_values, &fdo_flags, true, &errmsg); } else if (varp == &p_fcl) { // 'foldclose' - if (check_opt_strings(p_fcl, p_fcl_values, true) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &curwin->w_allbuf_opt.wo_fdi) { // 'foldignore' - if (foldmethodIsIndent(curwin)) { - foldUpdateAll(curwin); - } + did_set_opt_strings(*varp, p_fcl_values, true, &errmsg); + } else if (gvarp == &win->w_allbuf_opt.wo_fdi) { // 'foldignore' + did_set_foldignore(win); } else if (gvarp == &p_ve) { // 'virtualedit' - char_u *ve = p_ve; - unsigned int *flags = &ve_flags; - - if (opt_flags & OPT_LOCAL) { - ve = curwin->w_p_ve; - flags = &curwin->w_ve_flags; - } - - if ((opt_flags & OPT_LOCAL) && *ve == NUL) { - // make the local value empty: use the global value - *flags = 0; - } else { - if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) { - errmsg = e_invarg; - } else if (STRCMP(p_ve, oldval) != 0) { - // Recompute cursor position in case the new 've' setting - // changes something. - validate_virtcol(); - coladvance(curwin->w_virtcol); - } - } - } else if (varp == &p_csqf) { - if (p_csqf != NULL) { - p = (char *)p_csqf; - while (*p != NUL) { - if (vim_strchr(CSQF_CMDS, *p) == NULL - || p[1] == NUL - || vim_strchr(CSQF_FLAGS, p[1]) == NULL - || (p[2] != NUL && p[2] != ',')) { - errmsg = e_invarg; - break; - } else if (p[2] == NUL) { - break; - } else { - p += 3; - } - } - } + did_set_virtualedit(win, opt_flags, oldval, &errmsg); } else if (gvarp == &p_cino) { // 'cinoptions' // TODO(vim): recognize errors - parse_cino(curbuf); + parse_cino(buf); + } else if (gvarp == &p_lop) { // 'lispoptions' + did_set_lispoptions(varp, &errmsg); } else if (varp == &p_icm) { // 'inccommand' - if (check_opt_strings(p_icm, p_icm_values, false) != OK) { - errmsg = e_invarg; - } - } else if (gvarp == &p_ft) { - if (!valid_filetype(*varp)) { - errmsg = e_invarg; - } else { - value_changed = STRCMP(oldval, *varp) != 0; - - // Since we check the value, there is no need to set P_INSECURE, - // even when the value comes from a modeline. - *value_checked = true; - } - } else if (gvarp == &p_syn) { - if (!valid_filetype(*varp)) { - errmsg = e_invarg; - } else { - value_changed = STRCMP(oldval, *varp) != 0; - - // Since we check the value, there is no need to set P_INSECURE, - // even when the value comes from a modeline. - *value_checked = true; - } - } else if (varp == &curwin->w_p_winhl) { - if (!parse_winhl_opt(curwin)) { - errmsg = e_invarg; - } + did_set_opt_strings(*varp, p_icm_values, false, &errmsg); + } else if (gvarp == &p_ft || gvarp == &p_syn) { + did_set_filetype_or_syntax(varp, oldval, value_checked, &value_changed, &errmsg); + } else if (varp == &win->w_p_winhl) { + did_set_winhl(win, &errmsg); } else if (varp == &p_tpf) { - if (opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true) != OK) { - errmsg = e_invarg; - } - } else if (varp == &(curbuf->b_p_vsts)) { // 'varsofttabstop' - char_u *cp; - - if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { - XFREE_CLEAR(curbuf->b_p_vsts_array); - } else { - for (cp = *varp; *cp; cp++) { - if (ascii_isdigit(*cp)) { - continue; - } - if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { - continue; - } - errmsg = e_invarg; - break; - } - if (errmsg == NULL) { - long *oldarray = curbuf->b_p_vsts_array; - if (tabstop_set(*varp, &(curbuf->b_p_vsts_array))) { - xfree(oldarray); - } else { - errmsg = e_invarg; - } - } - } - } else if (varp == &(curbuf->b_p_vts)) { // 'vartabstop' - char_u *cp; - - if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { - XFREE_CLEAR(curbuf->b_p_vts_array); - } else { - for (cp = *varp; *cp; cp++) { - if (ascii_isdigit(*cp)) { - continue; - } - if (*cp == ',' && cp > *varp && *(cp - 1) != ',') { - continue; - } - errmsg = e_invarg; - break; - } - if (errmsg == NULL) { - long *oldarray = curbuf->b_p_vts_array; - if (tabstop_set(*varp, &(curbuf->b_p_vts_array))) { - xfree(oldarray); - if (foldmethodIsIndent(curwin)) { - foldUpdateAll(curwin); - } - } else { - errmsg = e_invarg; - } - } - } + did_set_opt_flags(p_tpf, p_tpf_values, &tpf_flags, true, &errmsg); + } else if (varp == &buf->b_p_vsts) { // 'varsofttabstop' + did_set_varsoftabstop(buf, varp, &errmsg); + } else if (varp == &buf->b_p_vts) { // 'vartabstop' + did_set_vartabstop(buf, win, varp, &errmsg); + } else if (varp == &p_dex) { // 'diffexpr' + did_set_optexpr(win, &p_dex, varp, gvarp); + } else if (varp == &win->w_p_fde) { // 'foldexpr' + did_set_optexpr(win, &win->w_p_fde, varp, gvarp); + if (foldmethodIsExpr(win)) { + foldUpdateAll(win); + } + } else if (varp == &win->w_p_fdt) { // 'foldtext' + did_set_optexpr(win, &win->w_p_fdt, varp, gvarp); + } else if (varp == &p_pex) { // 'patchexpr' + did_set_optexpr(win, &p_pex, varp, gvarp); + } else if (gvarp == &p_fex) { // 'formatexpr' + did_set_optexpr(win, &buf->b_p_fex, varp, gvarp); + } else if (gvarp == &p_inex) { // 'includeexpr' + did_set_optexpr(win, &buf->b_p_inex, varp, gvarp); + } else if (gvarp == &p_inde) { // 'indentexpr' + did_set_optexpr(win, &buf->b_p_inde, varp, gvarp); + } else if (gvarp == &p_cfu) { // 'completefunc' + set_completefunc_option(&errmsg); + } else if (gvarp == &p_ofu) { // 'omnifunc' + set_omnifunc_option(buf, &errmsg); + } else if (gvarp == &p_tsrfu) { // 'thesaurusfunc' + set_thesaurusfunc_option(&errmsg); } else if (varp == &p_opfunc) { // 'operatorfunc' - if (set_operatorfunc_option() == FAIL) { - errmsg = e_invarg; - } + set_operatorfunc_option(&errmsg); } else if (varp == &p_qftf) { // 'quickfixtextfunc' - if (qf_process_qftf_option() == FAIL) { - errmsg = e_invarg; - } - } else { - // Options that are a list of flags. - p = NULL; - if (varp == &p_ww) { // 'whichwrap' - p = WW_ALL; - } - if (varp == &p_shm) { // 'shortmess' - p = (char *)SHM_ALL; - } else if (varp == (char_u **)&(p_cpo)) { // 'cpoptions' - p = CPO_VI; - } else if (varp == &(curbuf->b_p_fo)) { // 'formatoptions' - p = FO_ALL; - } else if (varp == &curwin->w_p_cocu) { // 'concealcursor' - p = COCU_ALL; - } else if (varp == &p_mouse) { // 'mouse' - p = MOUSE_ALL; - } - if (p != NULL) { - for (s = (char *)(*varp); *s; s++) { - if (vim_strchr(p, *s) == NULL) { - errmsg = illegal_char(errbuf, errbuflen, *s); - break; - } - } + qf_process_qftf_option(&errmsg); + } else if (gvarp == &p_tfu) { // 'tagfunc' + set_tagfunc_option(&errmsg); + } else if (varp == &p_ww) { // 'whichwrap' + did_set_option_listflag(varp, WW_ALL, errbuf, errbuflen, &errmsg); + } else if (varp == &p_shm) { // 'shortmess' + did_set_option_listflag(varp, SHM_ALL, errbuf, errbuflen, &errmsg); + } else if (varp == &p_cpo) { // 'cpoptions' + did_set_option_listflag(varp, CPO_VI, errbuf, errbuflen, &errmsg); + } else if (varp == &buf->b_p_fo) { // 'formatoptions' + did_set_option_listflag(varp, FO_ALL, errbuf, errbuflen, &errmsg); + } else if (varp == &win->w_p_cocu) { // 'concealcursor' + did_set_option_listflag(varp, COCU_ALL, errbuf, errbuflen, &errmsg); + } else if (varp == &p_mouse) { // 'mouse' + did_set_option_listflag(varp, MOUSE_ALL, errbuf, errbuflen, &errmsg); + } else if (gvarp == &p_flp) { + if (win->w_briopt_list) { + // Changing Formatlistpattern when briopt includes the list setting: + // redraw + redraw_all_later(UPD_NOT_VALID); } } @@ -1509,7 +1863,7 @@ char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, char *er *varp = oldval; // When resetting some values, need to act on it. if (did_chartab) { - (void)init_chartab(); + (void)buf_init_chartab(buf, true); } } else { // Remember where the option was set. @@ -1520,81 +1874,27 @@ char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, char *er if (free_oldval) { free_string_option(oldval); } - set_option_flag(opt_idx, P_ALLOCED); + opt->flags |= P_ALLOCED; if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 - && is_global_local_option(opt_idx)) { + && (opt->indir & PV_BOTH)) { // global option with local value set to use global value; free // the local value and make it empty - p = (char *)get_option_varp_scope(opt_idx, OPT_LOCAL); - free_string_option(*(char_u **)p); - *(char_u **)p = empty_option; + char *p = get_varp_scope(opt, OPT_LOCAL); + free_string_option(*(char **)p); + *(char **)p = empty_option; } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) { // May set global value for local option. - set_string_option_global(opt_idx, varp); + set_string_option_global(opt, varp); } // Trigger the autocommand only after setting the flags. - // When 'syntax' is set, load the syntax of that name - if (varp == &(curbuf->b_p_syn)) { - static int syn_recursive = 0; - - syn_recursive++; - // Only pass true for "force" when the value changed or not used - // recursively, to avoid endless recurrence. - apply_autocmds(EVENT_SYNTAX, (char *)curbuf->b_p_syn, curbuf->b_fname, - value_changed || syn_recursive == 1, curbuf); - curbuf->b_flags |= BF_SYN_SET; - syn_recursive--; - } else if (varp == &(curbuf->b_p_ft)) { - // 'filetype' is set, trigger the FileType autocommand - // Skip this when called from a modeline and the filetype was - // already set to this value. - if (!(opt_flags & OPT_MODELINE) || value_changed) { - static int ft_recursive = 0; - int secure_save = secure; - - // Reset the secure flag, since the value of 'filetype' has - // been checked to be safe. - secure = 0; - - ft_recursive++; - did_filetype = true; - // Only pass true for "force" when the value changed or not - // used recursively, to avoid endless recurrence. - apply_autocmds(EVENT_FILETYPE, (char *)curbuf->b_p_ft, curbuf->b_fname, - value_changed || ft_recursive == 1, curbuf); - ft_recursive--; - // Just in case the old "curbuf" is now invalid - if (varp != &(curbuf->b_p_ft)) { - varp = NULL; - } - secure = secure_save; - } - } - if (varp == &(curwin->w_s->b_p_spl)) { - char_u fname[200]; - char_u *q = curwin->w_s->b_p_spl; - - // Skip the first name if it is "cjk". - if (STRNCMP(q, "cjk,", 4) == 0) { - q += 4; - } - - // Source the spell/LANG.vim in 'runtimepath'. - // They could set 'spellcapcheck' depending on the language. - // Use the first name in 'spelllang' up to '_region' or - // '.encoding'. - for (p = (char *)q; *p != NUL; p++) { - if (!ASCII_ISALNUM(*p) && *p != '-') { - break; - } - } - if (p > (char *)q) { - vim_snprintf((char *)fname, sizeof(fname), "spell/%.*s.vim", - (int)(p - (char *)q), q); - source_runtime((char *)fname, DIP_ALL); - } + if (varp == &buf->b_p_syn) { + do_syntax_autocmd(buf, value_changed); + } else if (varp == &buf->b_p_ft) { + do_filetype_autocmd(buf, varp, opt_flags, value_changed); + } else if (varp == &win->w_s->b_p_spl) { + did_set_spelllang_source(win); } } @@ -1602,22 +1902,29 @@ char *did_set_string_option(int opt_idx, char_u **varp, char_u *oldval, char *er setmouse(); // in case 'mouse' changed } - if (curwin->w_curswant != MAXCOL - && (get_option_flags(opt_idx) & (P_CURSWANT | P_RALL)) != 0) { - curwin->w_set_curswant = true; + if (win->w_curswant != MAXCOL + && (opt->flags & (P_CURSWANT | P_RALL)) != 0) { + win->w_set_curswant = true; } - check_redraw(get_option_flags(opt_idx)); + check_redraw_for(buf, win, opt->flags); return errmsg; } +char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf, size_t errbuflen, + int opt_flags, int *value_checked) +{ + return did_set_string_option_for(curbuf, curwin, opt_idx, varp, oldval, errbuf, errbuflen, + opt_flags, value_checked); +} + /// Check an option that can be a range of string values. /// /// @param list when true: accept a list of values /// /// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int check_opt_strings(char_u *val, char **values, int list) +static int check_opt_strings(char *val, char **values, int list) { return opt_strings_flags(val, values, NULL, list); } @@ -1630,7 +1937,7 @@ static int check_opt_strings(char_u *val, char **values, int list) /// @param list when true: accept a list of values /// /// @return OK for correct value, FAIL otherwise. Empty is always OK. -static int opt_strings_flags(char_u *val, char **values, unsigned *flagp, bool list) +static int opt_strings_flags(char *val, char **values, unsigned *flagp, bool list) { unsigned int new_flags = 0; @@ -1640,8 +1947,8 @@ static int opt_strings_flags(char_u *val, char **values, unsigned *flagp, bool l return FAIL; } - size_t len = STRLEN(values[i]); - if (STRNCMP(values[i], val, len) == 0 + size_t len = strlen(values[i]); + if (strncmp(values[i], val, len) == 0 && ((list && val[len] == ',') || val[len] == NUL)) { val += len + (val[len] == ','); assert(i < sizeof(1U) * 8); @@ -1658,7 +1965,7 @@ static int opt_strings_flags(char_u *val, char **values, unsigned *flagp, bool l } /// @return OK if "p" is a valid fileformat name, FAIL otherwise. -int check_ff_value(char_u *p) +int check_ff_value(char *p) { return check_opt_strings(p, p_ff_values, false); } diff --git a/src/nvim/optionstr.h b/src/nvim/optionstr.h index ac8d90e10e..3520cc2061 100644 --- a/src/nvim/optionstr.h +++ b/src/nvim/optionstr.h @@ -1,7 +1,7 @@ #ifndef NVIM_OPTIONSTR_H #define NVIM_OPTIONSTR_H -#include "nvim/buffer_defs.h" // for buf_T, win_T +#include "nvim/buffer_defs.h" #include "nvim/option_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c index 7d095d31e3..519cef7876 100644 --- a/src/nvim/os/dl.c +++ b/src/nvim/os/dl.c @@ -4,13 +4,14 @@ /// Functions for using external native libraries #include <stdbool.h> +#include <stddef.h> #include <stdint.h> #include <uv.h> +#include "nvim/gettext.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/os/dl.h" -#include "nvim/os/os.h" /// possible function prototypes that can be called by os_libcall() /// int -> int diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 795bff66cb..0611de14aa 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -4,23 +4,36 @@ // Environment inspection #include <assert.h> +#include <limits.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <string.h> #include <uv.h> +#include "auto/config.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/eval.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/log.h" #include "nvim/macros.h" #include "nvim/map.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/version.h" #include "nvim/vim.h" -#ifdef WIN32 +#ifdef MSWIN # include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 #endif @@ -128,7 +141,7 @@ int os_setenv(const char *name, const char *value, int overwrite) if (name[0] == '\0') { return -1; } -#ifdef WIN32 +#ifdef MSWIN if (!overwrite && os_getenv(name) != NULL) { return 0; } @@ -143,7 +156,7 @@ int os_setenv(const char *name, const char *value, int overwrite) #endif uv_mutex_lock(&mutex); int r; -#ifdef WIN32 +#ifdef MSWIN // libintl uses getenv() for LC_ALL/LANG/etc., so we must use _putenv_s(). if (striequal(name, "LC_ALL") || striequal(name, "LANGUAGE") || striequal(name, "LANG") || striequal(name, "LC_MESSAGES")) { @@ -186,7 +199,7 @@ int os_unsetenv(const char *name) size_t os_get_fullenv_size(void) { size_t len = 0; -#ifdef _WIN32 +#ifdef MSWIN wchar_t *envstrings = GetEnvironmentStringsW(); wchar_t *p = envstrings; size_t l; @@ -235,7 +248,7 @@ void os_free_fullenv(char **env) /// @param env_size size of `env`, @see os_fullenv_size void os_copy_fullenv(char **env, size_t env_size) { -#ifdef _WIN32 +#ifdef MSWIN wchar_t *envstrings = GetEnvironmentStringsW(); if (!envstrings) { return; @@ -280,7 +293,7 @@ void os_copy_fullenv(char **env, size_t env_size) /// @return [allocated] environment variable's value, or NULL char *os_getenvname_at_index(size_t index) { -#ifdef _WIN32 +#ifdef MSWIN wchar_t *envstrings = GetEnvironmentStringsW(); if (!envstrings) { return NULL; @@ -347,7 +360,7 @@ char *os_getenvname_at_index(size_t index) /// @return the process ID. int64_t os_get_pid(void) { -#ifdef _WIN32 +#ifdef MSWIN return (int64_t)GetCurrentProcessId(); #else return (int64_t)getpid(); @@ -368,7 +381,7 @@ void os_get_hostname(char *hostname, size_t size) } else { xstrlcpy(hostname, vutsname.nodename, size); } -#elif defined(WIN32) +#elif defined(MSWIN) wchar_t host_utf16[MAX_COMPUTERNAME_LENGTH + 1]; DWORD host_wsize = sizeof(host_utf16) / sizeof(host_utf16[0]); if (GetComputerNameW(host_utf16, &host_wsize) == 0) { @@ -418,7 +431,7 @@ void init_homedir(void) const char *var = os_getenv("HOME"); -#ifdef WIN32 +#ifdef MSWIN // Typically, $HOME is not defined on Windows, unless the user has // specifically defined it for Vim's sake. However, on Windows NT // platforms, $HOMEDRIVE and $HOMEPATH are automatically defined for @@ -451,7 +464,7 @@ void init_homedir(void) var = NULL; const char *exp = os_getenv(os_buf); if (exp != NULL && *exp != NUL - && STRLEN(exp) + STRLEN(p) < MAXPATHL) { + && strlen(exp) + strlen(p) < MAXPATHL) { vim_snprintf(os_buf, MAXPATHL, "%s%s", exp, p + 1); var = os_buf; } @@ -475,7 +488,7 @@ void init_homedir(void) if (var != NULL) { // Change to the directory and get the actual path. This resolves // links. Don't do it when we can't return. - if (os_dirname((char_u *)os_buf, MAXPATHL) == OK && os_chdir(os_buf) == 0) { + if (os_dirname(os_buf, MAXPATHL) == OK && os_chdir(os_buf) == 0) { if (!os_chdir(var) && os_dirname(IObuff, IOSIZE) == OK) { var = (char *)IObuff; } @@ -487,7 +500,7 @@ void init_homedir(void) // Fall back to current working directory if home is not found if ((var == NULL || *var == NUL) - && os_dirname((char_u *)os_buf, sizeof(os_buf)) == OK) { + && os_dirname(os_buf, sizeof(os_buf)) == OK) { var = os_buf; } #endif @@ -530,7 +543,7 @@ void free_homedir(void) /// @see {expand_env} char *expand_env_save(char *src) { - return (char *)expand_env_save_opt((char_u *)src, false); + return expand_env_save_opt(src, false); } /// Similar to expand_env_save() but when "one" is `true` handle the string as @@ -538,9 +551,9 @@ char *expand_env_save(char *src) /// @param src String containing environment variables to expand /// @param one Should treat as only one file name /// @see {expand_env} -char_u *expand_env_save_opt(char_u *src, bool one) +char *expand_env_save_opt(char *src, bool one) { - char_u *p = xmalloc(MAXPATHL); + char *p = xmalloc(MAXPATHL); expand_env_esc(src, p, MAXPATHL, false, one, NULL); return p; } @@ -553,7 +566,7 @@ char_u *expand_env_save_opt(char_u *src, bool one) /// @param src Input string e.g. "$HOME/vim.hlp" /// @param dst[out] Where to put the result /// @param dstlen Maximum length of the result -void expand_env(char_u *src, char_u *dst, int dstlen) +void expand_env(char *src, char *dst, int dstlen) { expand_env_esc(src, dst, dstlen, false, false, NULL); } @@ -567,34 +580,34 @@ void expand_env(char_u *src, char_u *dst, int dstlen) /// @param esc Escape spaces in expanded variables /// @param one `srcp` is a single filename /// @param prefix Start again after this (can be NULL) -void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, bool esc, bool one, - char_u *prefix) +void expand_env_esc(char *restrict srcp, char *restrict dst, int dstlen, bool esc, bool one, + char *prefix) FUNC_ATTR_NONNULL_ARG(1, 2) { - char_u *tail; - char_u *var; + char *tail; + char *var; bool copy_char; bool mustfree; // var was allocated, need to free it later bool at_start = true; // at start of a name - int prefix_len = (prefix == NULL) ? 0 : (int)STRLEN(prefix); + int prefix_len = (prefix == NULL) ? 0 : (int)strlen(prefix); - char *src = skipwhite((char *)srcp); + char *src = skipwhite(srcp); dstlen--; // leave one char space for "\," while (*src && dstlen > 0) { // Skip over `=expr`. if (src[0] == '`' && src[1] == '=') { - var = (char_u *)src; + var = src; src += 2; (void)skip_expr(&src); if (*src == '`') { src++; } - size_t len = (size_t)(src - (char *)var); + size_t len = (size_t)(src - var); if (len > (size_t)dstlen) { len = (size_t)dstlen; } - memcpy((char *)dst, (char *)var, len); + memcpy(dst, var, len); dst += len; dstlen -= (int)len; continue; @@ -607,7 +620,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo // The variable name is copied into dst temporarily, because it may // be a string in read-only memory and a NUL needs to be appended. if (*src != '~') { // environment var - tail = (char_u *)src + 1; + tail = src + 1; var = dst; int c = dstlen - 1; @@ -621,7 +634,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo } else // NOLINT #endif { - while (c-- > 0 && *tail != NUL && vim_isIDc(*tail)) { + while (c-- > 0 && *tail != NUL && vim_isIDc((uint8_t)(*tail))) { *var++ = *tail++; } } @@ -636,25 +649,25 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo } #endif *var = NUL; - var = (char_u *)vim_getenv((char *)dst); + var = vim_getenv(dst); mustfree = true; #if defined(UNIX) } #endif } else if (src[1] == NUL // home directory || vim_ispathsep(src[1]) - || vim_strchr(" ,\t\n", src[1]) != NULL) { - var = (char_u *)homedir; - tail = (char_u *)src + 1; + || vim_strchr(" ,\t\n", (uint8_t)src[1]) != NULL) { + var = homedir; + tail = src + 1; } else { // user directory #if defined(UNIX) // Copy ~user to dst[], so we can put a NUL after it. - tail = (char_u *)src; + tail = src; var = dst; int c = dstlen - 1; while (c-- > 0 && *tail - && vim_isfilec(*tail) + && vim_isfilec((uint8_t)(*tail)) && !vim_ispathsep(*tail)) { *var++ = *tail++; } @@ -662,7 +675,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo // Get the user directory. If this fails the shell is used to expand // ~user, which is slower and may fail on old versions of /bin/sh. var = (*dst == NUL) ? NULL - : (char_u *)os_get_userdir((char *)dst + 1); + : os_get_userdir(dst + 1); mustfree = true; if (var == NULL) { expand_T xpc; @@ -676,7 +689,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo #else // cannot expand user's home directory, so don't try var = NULL; - tail = (char_u *)""; // for gcc + tail = ""; // for gcc #endif // UNIX } @@ -684,7 +697,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo // If 'shellslash' is set change backslashes to forward slashes. // Can't use slash_adjust(), p_ssl may be set temporarily. if (p_ssl && var != NULL && vim_strchr(var, '\\') != NULL) { - char_u *p = vim_strsave(var); + char *p = xstrdup(var); if (mustfree) { xfree(var); @@ -697,8 +710,8 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo // If "var" contains white space, escape it with a backslash. // Required for ":e ~/tt" when $HOME includes a space. - if (esc && var != NULL && strpbrk((char *)var, " \t") != NULL) { - char_u *p = vim_strsave_escaped(var, (char_u *)" \t"); + if (esc && var != NULL && strpbrk(var, " \t") != NULL) { + char *p = vim_strsave_escaped(var, " \t"); if (mustfree) { xfree(var); @@ -708,13 +721,13 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo } if (var != NULL && *var != NUL - && (STRLEN(var) + STRLEN(tail) + 1 < (unsigned)dstlen)) { + && (strlen(var) + strlen(tail) + 1 < (unsigned)dstlen)) { STRCPY(dst, var); - dstlen -= (int)STRLEN(var); - int c = (int)STRLEN(var); + dstlen -= (int)strlen(var); + int c = (int)strlen(var); // if var[] ends in a path separator and tail[] starts // with it, skip a character - if (after_pathsep((char *)dst, (char *)dst + c) + if (after_pathsep(dst, dst + c) #if defined(BACKSLASH_IN_FILENAME) && dst[-1] != ':' #endif @@ -722,7 +735,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo tail++; } dst += c; - src = (char *)tail; + src = tail; copy_char = false; } if (mustfree) { @@ -736,18 +749,18 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo // ":edit foo ~ foo". at_start = false; if (src[0] == '\\' && src[1] != NUL) { - *dst++ = (char_u)(*src++); + *dst++ = *src++; dstlen--; } else if ((src[0] == ' ' || src[0] == ',') && !one) { at_start = true; } if (dstlen > 0) { - *dst++ = (char_u)(*src++); + *dst++ = *src++; dstlen--; if (prefix != NULL - && src - prefix_len >= (char *)srcp - && STRNCMP(src - prefix_len, prefix, prefix_len) == 0) { + && src - prefix_len >= srcp + && strncmp(src - prefix_len, prefix, (size_t)prefix_len) == 0) { at_start = true; } } @@ -765,12 +778,12 @@ static char *vim_version_dir(const char *vimdir) return NULL; } char *p = concat_fnames(vimdir, VIM_VERSION_NODOT, true); - if (os_isdir((char_u *)p)) { + if (os_isdir(p)) { return p; } xfree(p); p = concat_fnames(vimdir, RUNTIME_DIRNAME, true); - if (os_isdir((char_u *)p)) { + if (os_isdir(p)) { return p; } xfree(p); @@ -800,11 +813,11 @@ static char *vim_version_dir(const char *vimdir) /// @return The new pend including dirname or just pend static char *remove_tail(char *path, char *pend, char *dirname) { - size_t len = STRLEN(dirname); + size_t len = strlen(dirname); char *new_tail = pend - len - 1; if (new_tail >= path - && FNAMENCMP((char_u *)new_tail, (char_u *)dirname, len) == 0 + && path_fnamencmp(new_tail, dirname, len) == 0 && (new_tail == path || after_pathsep(path, new_tail))) { return new_tail; } @@ -837,10 +850,9 @@ const void *vim_env_iter(const char delim, const char *const val, const void *co if (dirend == NULL) { *len = strlen(varval); return NULL; - } else { - *len = (size_t)(dirend - varval); - return dirend + 1; } + *len = (size_t)(dirend - varval); + return dirend + 1; } /// Iterates $PATH-like delimited list `val` in reverse order. @@ -870,11 +882,10 @@ const void *vim_env_iter_rev(const char delim, const char *const val, const void *len = varlen; *dir = val; return NULL; - } else { - *dir = colon + 1; - *len = (size_t)(varend - colon); - return colon - 1; } + *dir = colon + 1; + *len = (size_t)(varend - colon); + return colon - 1; } /// @param[out] exe_name should be at least MAXPATHL in size @@ -900,7 +911,7 @@ char *vim_getenv(const char *name) // init_path() should have been called before now. assert(get_vim_var_str(VV_PROGPATH)[0] != NUL); -#ifdef WIN32 +#ifdef MSWIN if (strcmp(name, "HOME") == 0) { return xstrdup(homedir); } @@ -937,8 +948,8 @@ char *vim_getenv(const char *name) // - the directory name from 'helpfile' (unless it contains '$') // - the executable name from argv[0] if (vim_path == NULL) { - if (p_hf != NULL && vim_strchr((char *)p_hf, '$') == NULL) { - vim_path = (char *)p_hf; + if (p_hf != NULL && vim_strchr(p_hf, '$') == NULL) { + vim_path = p_hf; } char exe_name[MAXPATHL]; @@ -957,7 +968,7 @@ char *vim_getenv(const char *name) char *vim_path_end = path_tail(vim_path); // remove "doc/" from 'helpfile', if present - if (vim_path == (char *)p_hf) { + if (vim_path == p_hf) { vim_path_end = remove_tail(vim_path, vim_path_end, "doc"); } @@ -976,7 +987,7 @@ char *vim_getenv(const char *name) assert(vim_path_end >= vim_path); vim_path = xstrndup(vim_path, (size_t)(vim_path_end - vim_path)); - if (!os_isdir((char_u *)vim_path)) { + if (!os_isdir(vim_path)) { xfree(vim_path); vim_path = NULL; } @@ -1045,7 +1056,7 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si } if (buf != NULL && buf->b_help) { - const size_t dlen = STRLCPY(dst, path_tail((char *)src), dstlen); + const size_t dlen = xstrlcpy(dst, path_tail((char *)src), dstlen); return MIN(dlen, dstlen - 1); } @@ -1056,7 +1067,7 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si } const char *homedir_env = os_getenv("HOME"); -#ifdef WIN32 +#ifdef MSWIN if (homedir_env == NULL) { homedir_env = os_getenv("USERPROFILE"); } @@ -1098,7 +1109,7 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si size_t len = dirlen; for (;;) { if (len - && FNAMENCMP(src, (char_u *)p, len) == 0 + && path_fnamencmp(src, p, len) == 0 && (vim_ispathsep(src[len]) || (!one && (src[len] == ',' || src[len] == ' ')) || src[len] == NUL)) { @@ -1119,10 +1130,16 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si len = envlen; } + if (dstlen == 0) { + break; // Avoid overflowing below. + } // if (!one) skip to separator: space or comma. while (*src && (one || (*src != ',' && *src != ' ')) && --dstlen > 0) { *dst_p++ = *src++; } + if (dstlen == 0) { + break; // Avoid overflowing below. + } // Skip separator. while ((*src == ' ' || *src == ',') && --dstlen > 0) { *dst_p++ = *src++; @@ -1146,7 +1163,7 @@ char *home_replace_save(buf_T *buf, const char *src) { size_t len = 3; // space for "~/" and trailing NUL if (src != NULL) { // just in case - len += STRLEN(src); + len += strlen(src); } char *dst = xmalloc(len); home_replace(buf, src, dst, len, true); @@ -1156,15 +1173,12 @@ char *home_replace_save(buf_T *buf, const char *src) /// Function given to ExpandGeneric() to obtain an environment variable name. char *get_env_name(expand_T *xp, int idx) { -#define ENVNAMELEN 100 - // this static buffer is needed to avoid a memory leak in ExpandGeneric - static char_u name[ENVNAMELEN]; assert(idx >= 0); char *envname = os_getenvname_at_index((size_t)idx); if (envname) { - STRLCPY(name, envname, ENVNAMELEN); + xstrlcpy(xp->xp_buf, envname, EXPAND_BUF_LEN); xfree(envname); - return (char *)name; + return xp->xp_buf; } return NULL; } @@ -1177,14 +1191,14 @@ char *get_env_name(expand_T *xp, int idx) bool os_setenv_append_path(const char *fname) FUNC_ATTR_NONNULL_ALL { -#ifdef WIN32 +#ifdef MSWIN // 8191 (plus NUL) is considered the practical maximum. # define MAX_ENVPATHLEN 8192 #else // No prescribed maximum on unix. # define MAX_ENVPATHLEN INT_MAX #endif - if (!path_is_absolute((char_u *)fname)) { + if (!path_is_absolute(fname)) { internal_error("os_setenv_append_path()"); return false; } diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index b1710737d0..5af39555c9 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -11,22 +11,21 @@ #include <fcntl.h> #include <stdbool.h> #include <stddef.h> - -#include "auto/config.h" - -#ifdef HAVE_SYS_UIO_H -# include <sys/uio.h> -#endif - +#include <stdint.h> #include <uv.h> +#include "auto/config.h" +#include "nvim/gettext.h" #include "nvim/globals.h" +#include "nvim/log.h" #include "nvim/macros.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/os/fileio.h" #include "nvim/os/os.h" +#include "nvim/os/os_defs.h" #include "nvim/rbuffer.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/fileio.c.generated.h" @@ -71,6 +70,7 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname, const int f FLAG(flags, kFileReadOnly, O_RDONLY, kFalse, wr != kTrue); #ifdef O_NOFOLLOW FLAG(flags, kFileNoSymlink, O_NOFOLLOW, kNone, true); + FLAG(flags, kFileMkDir, O_CREAT|O_WRONLY, kTrue, !(flags & kFileCreateOnly)); #endif #undef FLAG // wr is used for kFileReadOnly flag, but on @@ -78,6 +78,13 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname, const int f // `error: variable ‘wr’ set but not used [-Werror=unused-but-set-variable]` (void)wr; + if (flags & kFileMkDir) { + int mkdir_ret = os_file_mkdir((char *)fname, 0755); + if (mkdir_ret < 0) { + return mkdir_ret; + } + } + const int fd = os_open(fname, os_open_flags, mode); if (fd < 0) { @@ -162,6 +169,30 @@ FileDescriptor *file_open_fd_new(int *const error, const int fd, const int flags return fp; } +/// Opens standard input as a FileDescriptor. +FileDescriptor *file_open_stdin(void) + FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT +{ + int error; + int stdin_dup_fd; + if (stdin_fd > 0) { + stdin_dup_fd = stdin_fd; + } else { + stdin_dup_fd = os_dup(STDIN_FILENO); +#ifdef MSWIN + // Replace the original stdin with the console input handle. + os_replace_stdin_to_conin(); +#endif + } + FileDescriptor *const stdin_dup = file_open_fd_new(&error, stdin_dup_fd, + kFileReadOnly|kFileNonBlocking); + assert(stdin_dup != NULL); + if (error != 0) { + ELOG("failed to open stdin: %s", os_strerror(error)); + } + return stdin_dup; +} + /// Close file and free its buffer /// /// @param[in,out] fp File to close. diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h index 426dc422c2..5e47bbf921 100644 --- a/src/nvim/os/fileio.h +++ b/src/nvim/os/fileio.h @@ -35,9 +35,10 @@ typedef enum { ///< be used with kFileCreateOnly. kFileNonBlocking = 128, ///< Do not restart read() or write() syscall if ///< EAGAIN was encountered. + kFileMkDir = 256, } FileOpenFlags; -static inline bool file_eof(const FileDescriptor *const fp) +static inline bool file_eof(const FileDescriptor *fp) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Check whether end of file was encountered @@ -51,7 +52,7 @@ static inline bool file_eof(const FileDescriptor *const fp) return fp->eof && rbuffer_size(fp->rv) == 0; } -static inline int file_fd(const FileDescriptor *const fp) +static inline int file_fd(const FileDescriptor *fp) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Return the file descriptor associated with the FileDescriptor structure diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 0d62a5f5f9..302faa8140 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -5,11 +5,23 @@ #include <assert.h> #include <errno.h> #include <fcntl.h> -#include <limits.h> #include <stdbool.h> #include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> #include "auto/config.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/log.h" +#include "nvim/macros.h" +#include "nvim/option_defs.h" +#include "nvim/os/fs_defs.h" +#include "nvim/types.h" +#include "nvim/vim.h" #ifdef HAVE_SYS_UIO_H # include <sys/uio.h> @@ -18,16 +30,14 @@ #include <uv.h> #include "nvim/ascii.h" -#include "nvim/assert.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/option.h" #include "nvim/os/os.h" -#include "nvim/os/os_defs.h" #include "nvim/path.h" -#include "nvim/strings.h" -#ifdef WIN32 +struct iovec; + +#ifdef MSWIN # include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8 #endif @@ -95,12 +105,12 @@ int os_chdir(const char *path) /// @param buf Buffer to store the directory name. /// @param len Length of `buf`. /// @return `OK` for success, `FAIL` for failure. -int os_dirname(char_u *buf, size_t len) +int os_dirname(char *buf, size_t len) FUNC_ATTR_NONNULL_ALL { int error_number; - if ((error_number = uv_cwd((char *)buf, &len)) != kLibuvSuccess) { - STRLCPY(buf, uv_strerror(error_number), len); + if ((error_number = uv_cwd(buf, &len)) != kLibuvSuccess) { + xstrlcpy(buf, uv_strerror(error_number), len); return FAIL; } return OK; @@ -121,18 +131,17 @@ bool os_isrealdir(const char *name) fs_loop_unlock(); if (S_ISLNK(request.statbuf.st_mode)) { return false; - } else { - return S_ISDIR(request.statbuf.st_mode); } + return S_ISDIR(request.statbuf.st_mode); } /// Check if the given path exists and is a directory. /// /// @return `true` if `name` is a directory. -bool os_isdir(const char_u *name) +bool os_isdir(const char *name) FUNC_ATTR_NONNULL_ALL { - int32_t mode = os_getperm((const char *)name); + int32_t mode = os_getperm(name); if (mode < 0) { return false; } @@ -151,7 +160,7 @@ bool os_isdir(const char_u *name) int os_nodetype(const char *name) FUNC_ATTR_NONNULL_ALL { -#ifndef WIN32 // Unix +#ifndef MSWIN // Unix uv_stat_t statbuf; if (0 != os_stat(name, &statbuf)) { return NODE_NORMAL; // File doesn't exist. @@ -171,7 +180,7 @@ int os_nodetype(const char *name) // Edge case from Vim os_win32.c: // We can't open a file with a name "\\.\con" or "\\.\prn", trying to read // from it later will cause Vim to hang. Thus return NODE_WRITABLE here. - if (STRNCMP(name, "\\\\.\\", 4) == 0) { + if (strncmp(name, "\\\\.\\", 4) == 0) { return NODE_WRITABLE; } @@ -241,7 +250,7 @@ bool os_can_exe(const char *name, char **abspath, bool use_path) FUNC_ATTR_NONNULL_ARG(1) { if (!use_path || gettail_dir(name) != name) { -#ifdef WIN32 +#ifdef MSWIN if (is_executable_ext(name, abspath)) { #else // Must have path separator, cannot execute files in the current directory. @@ -249,9 +258,8 @@ bool os_can_exe(const char *name, char **abspath, bool use_path) && is_executable(name, abspath)) { #endif return true; - } else { - return false; } + return false; } return is_executable_in_path(name, abspath); @@ -270,7 +278,7 @@ static bool is_executable(const char *name, char **abspath) return false; } -#ifdef WIN32 +#ifdef MSWIN // Windows does not have exec bit; just check if the file exists and is not // a directory. const bool ok = S_ISREG(mode); @@ -287,14 +295,16 @@ static bool is_executable(const char *name, char **abspath) return ok; } -#ifdef WIN32 +#ifdef MSWIN /// Checks if file `name` is executable under any of these conditions: /// - extension is in $PATHEXT and `name` is executable /// - result of any $PATHEXT extension appended to `name` is executable static bool is_executable_ext(const char *name, char **abspath) FUNC_ATTR_NONNULL_ARG(1) { - const bool is_unix_shell = strstr((char *)path_tail(p_sh), "sh") != NULL; + const bool is_unix_shell = strstr(path_tail(p_sh), "powershell") == NULL + && strstr(path_tail(p_sh), "pwsh") == NULL + && strstr(path_tail(p_sh), "sh") != NULL; char *nameext = strrchr(name, '.'); size_t nameext_len = nameext ? strlen(nameext) : 0; xstrlcpy(os_buf, name, sizeof(os_buf)); @@ -320,11 +330,11 @@ static bool is_executable_ext(const char *name, char **abspath) const char *ext_end = ext; size_t ext_len = - copy_option_part(&ext_end, (char_u *)buf_end, + copy_option_part((char **)&ext_end, buf_end, sizeof(os_buf) - (size_t)(buf_end - os_buf), ENV_SEPSTR); if (ext_len != 0) { bool in_pathext = nameext_len == ext_len - && 0 == mb_strnicmp((char_u *)nameext, (char_u *)ext, ext_len); + && 0 == mb_strnicmp(nameext, ext, ext_len); if (((in_pathext || is_unix_shell) && is_executable(name, abspath)) || is_executable(os_buf, abspath)) { @@ -351,7 +361,7 @@ static bool is_executable_in_path(const char *name, char **abspath) return false; } -#ifdef WIN32 +#ifdef MSWIN // Prepend ".;" to $PATH. size_t pathlen = strlen(path_env); char *path = memcpy(xmallocz(pathlen + 2), "." ENV_SEPSTR, 2); @@ -371,10 +381,10 @@ static bool is_executable_in_path(const char *name, char **abspath) char *e = xstrchrnul(p, ENV_SEPCHAR); // Combine the $PATH segment with `name`. - STRLCPY(buf, p, e - p + 1); + xstrlcpy(buf, p, (size_t)(e - p) + 1); append_path(buf, name, buf_len); -#ifdef WIN32 +#ifdef MSWIN if (is_executable_ext(buf, abspath)) { #else if (is_executable(buf, abspath)) { @@ -446,7 +456,7 @@ FILE *os_fopen(const char *path, const char *flags) default: abort(); } -#ifdef WIN32 +#ifdef MSWIN if (flags[1] == 'b') { iflags |= O_BINARY; } @@ -756,9 +766,8 @@ int32_t os_getperm(const char *name) int stat_result = os_stat(name, &statbuf); if (stat_result == kLibuvSuccess) { return (int32_t)statbuf.st_mode; - } else { - return stat_result; } + return stat_result; } /// Set the permission of a file. @@ -772,6 +781,38 @@ int os_setperm(const char *const name, int perm) return (r == kLibuvSuccess ? OK : FAIL); } +#if defined(HAVE_ACL) +# ifdef HAVE_SYS_ACL_H +# include <sys/acl.h> +# endif +# ifdef HAVE_SYS_ACCESS_H +# include <sys/access.h> +# endif + +// Return a pointer to the ACL of file "fname" in allocated memory. +// Return NULL if the ACL is not available for whatever reason. +vim_acl_T os_get_acl(const char *fname) +{ + vim_acl_T ret = NULL; + return ret; +} + +// Set the ACL of file "fname" to "acl" (unless it's NULL). +void os_set_acl(const char *fname, vim_acl_T aclent) +{ + if (aclent == NULL) { + return; + } +} + +void os_free_acl(vim_acl_T aclent) +{ + if (aclent == NULL) { + return; + } +} +#endif + #ifdef UNIX /// Checks if the current user owns a file. /// @@ -821,10 +862,10 @@ int os_fchown(int fd, uv_uid_t owner, uv_gid_t group) /// Check if a path exists. /// /// @return `true` if `path` exists -bool os_path_exists(const char_u *path) +bool os_path_exists(const char *path) { uv_stat_t statbuf; - return os_stat((char *)path, &statbuf) == kLibuvSuccess; + return os_stat(path, &statbuf) == kLibuvSuccess; } /// Sets file access and modification times. @@ -865,7 +906,7 @@ int os_file_is_writable(const char *name) int r; RUN_UV_FS_FUNC(r, uv_fs_access, name, W_OK, NULL); if (r == 0) { - return os_isdir((char_u *)name) ? 2 : 1; + return os_isdir(name) ? 2 : 1; } return 0; } @@ -873,12 +914,11 @@ int os_file_is_writable(const char *name) /// Rename a file or directory. /// /// @return `OK` for success, `FAIL` for failure. -int os_rename(const char_u *path, const char_u *new_path) +int os_rename(const char *path, const char *new_path) FUNC_ATTR_NONNULL_ALL { int r; - RUN_UV_FS_FUNC(r, uv_fs_rename, (const char *)path, (const char *)new_path, - NULL); + RUN_UV_FS_FUNC(r, uv_fs_rename, path, new_path, NULL); return (r == kLibuvSuccess ? OK : FAIL); } @@ -911,11 +951,11 @@ int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_di // We're done when it's "/" or "c:/". const size_t dirlen = strlen(dir); char *const curdir = xmemdupz(dir, dirlen); - char *const past_head = (char *)get_past_head((char_u *)curdir); + char *const past_head = get_past_head(curdir); char *e = curdir + dirlen; const char *const real_end = e; const char past_head_save = *past_head; - while (!os_isdir((char_u *)curdir)) { + while (!os_isdir(curdir)) { e = path_tail_with_sep(curdir); if (e <= past_head) { *past_head = NUL; @@ -946,6 +986,37 @@ int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_di return 0; } +/// Create the parent directory of a file if it does not exist +/// +/// @param[in] fname Full path of the file name whose parent directories +/// we want to create +/// @param[in] mode Permissions for the newly-created directory. +/// +/// @return `0` for success, libuv error code for failure. +int os_file_mkdir(char *fname, int32_t mode) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (!dir_of_file_exists(fname)) { + char *tail = path_tail_with_sep(fname); + char *last_char = tail + strlen(tail) - 1; + if (vim_ispathsep(*last_char)) { + emsg(_(e_noname)); + return -1; + } + char c = *tail; + *tail = NUL; + int r; + char *failed_dir; + if (((r = os_mkdir_recurse(fname, mode, &failed_dir)) < 0)) { + semsg(_(e_mkdir), failed_dir, os_strerror(r)); + xfree(failed_dir); + } + *tail = c; + return r; + } + return 0; +} + /// Create a unique temporary directory. /// /// @param[in] template Template of the path to the directory with XXXXXX @@ -1208,7 +1279,7 @@ char *os_realpath(const char *name, char *buf) return result == kLibuvSuccess ? buf : NULL; } -#ifdef WIN32 +#ifdef MSWIN # include <shlobj.h> /// When "fname" is the name of a shortcut (*.lnk) resolve the file it points @@ -1310,7 +1381,7 @@ bool os_is_reparse_point_include(const char *path) } p = utf16_path; - if (isalpha(p[0]) && p[1] == L':' && IS_PATH_SEP(p[2])) { + if (isalpha((uint8_t)p[0]) && p[1] == L':' && IS_PATH_SEP(p[2])) { p += 3; } else if (IS_PATH_SEP(p[0]) && IS_PATH_SEP(p[1])) { p += 2; diff --git a/src/nvim/os/fs.h b/src/nvim/os/fs.h index c68081da02..75c24b8db2 100644 --- a/src/nvim/os/fs.h +++ b/src/nvim/os/fs.h @@ -1,8 +1,8 @@ #ifndef NVIM_OS_FS_H #define NVIM_OS_FS_H -#include "nvim/os/fs_defs.h" // for uv_* -#include "nvim/types.h" // for char_u +#include "nvim/os/fs_defs.h" +#include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/fs.h.generated.h" diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index bfe6d59dc6..759b3cf83c 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -3,25 +3,33 @@ #include <assert.h> #include <stdbool.h> +#include <stdint.h> +#include <stdio.h> #include <string.h> #include <uv.h> #include "nvim/api/private/defs.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/rstream.h" +#include "nvim/event/stream.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/keycodes.h" +#include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/mbyte.h" -#include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/option_defs.h" #include "nvim/os/input.h" +#include "nvim/os/time.h" #include "nvim/profile.h" -#include "nvim/screen.h" +#include "nvim/rbuffer.h" #include "nvim/state.h" -#include "nvim/ui.h" #include "nvim/vim.h" #define READ_BUFFER_SIZE 0xfff @@ -36,8 +44,9 @@ typedef enum { static Stream read_stream = { .closed = true }; // Input before UI starts. static RBuffer *input_buffer = NULL; static bool input_eof = false; -static int global_fd = -1; static bool blocking = false; +static int cursorhold_time = 0; ///< time waiting for CursorHold event +static int cursorhold_tb_change_cnt = 0; ///< tb_change_cnt when waiting started #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/input.c.generated.h" @@ -48,25 +57,14 @@ void input_init(void) input_buffer = rbuffer_new(INPUT_BUFFER_SIZE + MAX_KEY_CODE_LEN); } -void input_global_fd_init(int fd) -{ - global_fd = fd; -} - -/// Global TTY (or pipe for "-es") input stream, before UI starts. -int input_global_fd(void) -{ - return global_fd; -} - -void input_start(int fd) +void input_start(void) { if (!read_stream.closed) { return; } - input_global_fd_init(fd); - rstream_init_fd(&main_loop, &read_stream, fd, READ_BUFFER_SIZE); + used_stdin = true; + rstream_init_fd(&main_loop, &read_stream, STDIN_FILENO, READ_BUFFER_SIZE); rstream_start(&read_stream, input_read_cb, NULL); } @@ -97,13 +95,25 @@ static void create_cursorhold_event(bool events_enabled) multiqueue_put(main_loop.events, cursorhold_event, 0); } +static void restart_cursorhold_wait(int tb_change_cnt) +{ + cursorhold_time = 0; + cursorhold_tb_change_cnt = tb_change_cnt; +} + /// Low level input function /// /// wait until either the input buffer is non-empty or, if `events` is not NULL /// until `events` is non-empty. int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events) { + // This check is needed so that feeding typeahead from RPC can prevent CursorHold. + if (tb_change_cnt != cursorhold_tb_change_cnt) { + restart_cursorhold_wait(tb_change_cnt); + } + if (maxlen && rbuffer_size(input_buffer)) { + restart_cursorhold_wait(tb_change_cnt); return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); } @@ -118,18 +128,22 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e return 0; } } else { - if ((result = inbuf_poll((int)p_ut, events)) == kInputNone) { + uint64_t wait_start = os_hrtime(); + cursorhold_time = MIN(cursorhold_time, (int)p_ut); + if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kInputNone) { if (read_stream.closed && silent_mode) { // Drained eventloop & initial input; exit silent/batch-mode (-es/-Es). read_error_exit(); } - + restart_cursorhold_wait(tb_change_cnt); if (trigger_cursorhold() && !typebuf_changed(tb_change_cnt)) { create_cursorhold_event(events == main_loop.events); } else { before_blocking(); result = inbuf_poll(-1, events); } + } else { + cursorhold_time += (int)((os_hrtime() - wait_start) / 1000000); } } @@ -141,6 +155,7 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e } if (maxlen && rbuffer_size(input_buffer)) { + restart_cursorhold_wait(tb_change_cnt); // Safe to convert rbuffer_read to int, it will never overflow since we use // relatively small buffers. return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); @@ -239,7 +254,8 @@ size_t input_enqueue(String keys) uint8_t buf[19] = { 0 }; // Do not simplify the keys here. Simplification will be done later. unsigned int new_size - = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, FSK_KEYCODE, true, NULL); + = trans_special((const char **)&ptr, (size_t)(end - ptr), (char *)buf, FSK_KEYCODE, true, + NULL); if (new_size) { new_size = handle_mouse_event(&ptr, buf, new_size); @@ -287,36 +303,35 @@ static uint8_t check_multiclick(int code, int grid, int row, int col) static int orig_mouse_row = 0; static uint64_t orig_mouse_time = 0; // time of previous mouse click - if (code == KE_LEFTRELEASE - || code == KE_RIGHTRELEASE - || code == KE_MIDDLERELEASE - || code == KE_MOUSEDOWN - || code == KE_MOUSEUP - || code == KE_MOUSELEFT - || code == KE_MOUSERIGHT) { + if ((code >= KE_MOUSEDOWN && code <= KE_MOUSERIGHT) || code == KE_MOUSEMOVE) { return 0; } - uint64_t mouse_time = os_hrtime(); // time of current mouse click (ns) - - // compute the time elapsed since the previous mouse click and - // convert p_mouse from ms to ns - uint64_t timediff = mouse_time - orig_mouse_time; - uint64_t mouset = (uint64_t)p_mouset * 1000000; - if (code == orig_mouse_code - && timediff < mouset - && orig_num_clicks != 4 - && orig_mouse_grid == grid - && orig_mouse_col == col - && orig_mouse_row == row) { - orig_num_clicks++; - } else { - orig_num_clicks = 1; + + // For click events the number of clicks is updated. + if (code == KE_LEFTMOUSE || code == KE_RIGHTMOUSE || code == KE_MIDDLEMOUSE) { + uint64_t mouse_time = os_hrtime(); // time of current mouse click (ns) + // compute the time elapsed since the previous mouse click and + // convert p_mouse from ms to ns + uint64_t timediff = mouse_time - orig_mouse_time; + uint64_t mouset = (uint64_t)p_mouset * 1000000; + if (code == orig_mouse_code + && timediff < mouset + && orig_num_clicks != 4 + && orig_mouse_grid == grid + && orig_mouse_col == col + && orig_mouse_row == row) { + orig_num_clicks++; + } else { + orig_num_clicks = 1; + } + orig_mouse_code = code; + orig_mouse_time = mouse_time; } - orig_mouse_code = code; + // For drag and release events the number of clicks is kept. + orig_mouse_grid = grid; orig_mouse_col = col; orig_mouse_row = row; - orig_mouse_time = mouse_time; uint8_t modifiers = 0; if (orig_num_clicks == 2) { @@ -347,7 +362,8 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf, unsigned int bu if (type != KS_EXTRA || !((mouse_code >= KE_LEFTMOUSE && mouse_code <= KE_RIGHTRELEASE) - || (mouse_code >= KE_MOUSEDOWN && mouse_code <= KE_MOUSERIGHT))) { + || (mouse_code >= KE_MOUSEDOWN && mouse_code <= KE_MOUSERIGHT) + || mouse_code == KE_MOUSEMOVE)) { return bufsize; } @@ -450,9 +466,8 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events) if (input_ready(events)) { return kInputAvail; - } else { - return input_eof ? kInputEof : kInputNone; } + return input_eof ? kInputEof : kInputNone; } void input_done(void) diff --git a/src/nvim/os/input.h b/src/nvim/os/input.h index 7026781407..6f25efdc7b 100644 --- a/src/nvim/os/input.h +++ b/src/nvim/os/input.h @@ -7,6 +7,8 @@ #include "nvim/api/private/defs.h" #include "nvim/event/multiqueue.h" +EXTERN bool used_stdin INIT(= false); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/input.h.generated.h" #endif diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c index 28f43ff3af..57c82bba86 100644 --- a/src/nvim/os/lang.c +++ b/src/nvim/os/lang.c @@ -7,16 +7,16 @@ # include <CoreServices/CoreServices.h> # undef Boolean # undef FileInfo -#endif -#include "auto/config.h" +# include "auto/config.h" +# ifdef HAVE_LOCALE_H +# include <locale.h> +# endif +# include "nvim/os/os.h" -#ifdef HAVE_LOCALE_H -# include <locale.h> #endif #include "nvim/os/lang.h" -#include "nvim/os/os.h" void lang_init(void) { diff --git a/src/nvim/os/mem.c b/src/nvim/os/mem.c index eccb3c97e5..0b7e8065ef 100644 --- a/src/nvim/os/mem.c +++ b/src/nvim/os/mem.c @@ -3,6 +3,7 @@ /// Functions for accessing system memory information. +#include <stdint.h> #include <uv.h> #include "nvim/os/os.h" diff --git a/src/nvim/os/nvim.manifest b/src/nvim/os/nvim.manifest new file mode 100644 index 0000000000..8878822a5d --- /dev/null +++ b/src/nvim/os/nvim.manifest @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"> + <assemblyIdentity + processorArchitecture="*" + version="0.9.0.0" + type="win32" + name="Neovim" + /> + <description>Neovim</description> + <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <!-- Windows 10 and Windows 11 --> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> + <!-- Windows 8.1 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> + <!-- Windows 8 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> + </application> + </compatibility> +</assembly> diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h index a4361859ec..a30e16eeba 100644 --- a/src/nvim/os/os_defs.h +++ b/src/nvim/os/os_defs.h @@ -7,7 +7,7 @@ #include <sys/stat.h> #include <sys/types.h> -#ifdef WIN32 +#ifdef MSWIN # include "nvim/os/win_defs.h" #else # include "nvim/os/unix_defs.h" @@ -43,7 +43,7 @@ /// Converts system error code to libuv error code. #define os_translate_sys_error uv_translate_sys_error -#ifdef WIN32 +#ifdef MSWIN # define os_strtok strtok_s #else # define os_strtok strtok_r diff --git a/src/nvim/os/os_win_console.c b/src/nvim/os/os_win_console.c index 20b7f869f1..006e27d28f 100644 --- a/src/nvim/os/os_win_console.c +++ b/src/nvim/os/os_win_console.c @@ -2,6 +2,7 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include "nvim/os/input.h" +#include "nvim/os/os.h" #include "nvim/os/os_win_console.h" #include "nvim/vim.h" @@ -9,7 +10,12 @@ # include "os/os_win_console.c.generated.h" #endif -int os_get_conin_fd(void) +static char origTitle[256] = { 0 }; +static HWND hWnd = NULL; +static HICON hOrigIconSmall = NULL; +static HICON hOrigIcon = NULL; + +int os_open_conin_fd(void) { const HANDLE conin_handle = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, @@ -25,7 +31,7 @@ int os_get_conin_fd(void) void os_replace_stdin_to_conin(void) { close(STDIN_FILENO); - const int conin_fd = os_get_conin_fd(); + const int conin_fd = os_open_conin_fd(); assert(conin_fd == STDIN_FILENO); } @@ -45,3 +51,57 @@ void os_replace_stdout_and_stderr_to_conout(void) const int conerr_fd = _open_osfhandle((intptr_t)conout_handle, 0); assert(conerr_fd == STDERR_FILENO); } + +/// Sets Windows console icon, or pass NULL to restore original icon. +void os_icon_set(HICON hIconSmall, HICON hIcon) +{ + if (hWnd == NULL) { + return; + } + hIconSmall = hIconSmall ? hIconSmall : hOrigIconSmall; + hIcon = hIcon ? hIcon : hOrigIcon; + + if (hIconSmall != NULL) { + SendMessage(hWnd, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIconSmall); + } + if (hIcon != NULL) { + SendMessage(hWnd, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon); + } +} + +/// Sets Nvim logo as Windows console icon. +/// +/// Saves the original icon so it can be restored at exit. +void os_icon_init(void) +{ + if ((hWnd = GetConsoleWindow()) == NULL) { + return; + } + // Save Windows console icon to be restored later. + hOrigIconSmall = (HICON)SendMessage(hWnd, WM_GETICON, (WPARAM)ICON_SMALL, (LPARAM)0); + hOrigIcon = (HICON)SendMessage(hWnd, WM_GETICON, (WPARAM)ICON_BIG, (LPARAM)0); + + const char *vimruntime = os_getenv("VIMRUNTIME"); + if (vimruntime != NULL) { + snprintf(NameBuff, MAXPATHL, "%s" _PATHSEPSTR "neovim.ico", vimruntime); + if (!os_path_exists(NameBuff)) { + WLOG("neovim.ico not found: %s", NameBuff); + } else { + HICON hVimIcon = LoadImage(NULL, NameBuff, IMAGE_ICON, 64, 64, + LR_LOADFROMFILE | LR_LOADMAP3DCOLORS); + os_icon_set(hVimIcon, hVimIcon); + } + } +} + +/// Saves the original Windows console title. +void os_title_save(void) +{ + GetConsoleTitle(origTitle, sizeof(origTitle)); +} + +/// Resets the original Windows console title. +void os_title_reset(void) +{ + SetConsoleTitle(origTitle); +} diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index e70bc71961..f4d95e141b 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -6,10 +6,21 @@ /// psutil is a good reference for cross-platform syscall voodoo: /// https://github.com/giampaolo/psutil/tree/master/psutil/arch -#include <uv.h> // for HANDLE (win32) +#include <assert.h> +#include <signal.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <uv.h> -#ifdef WIN32 -# include <tlhelp32.h> // for CreateToolhelp32Snapshot +#include "nvim/log.h" +#include "nvim/memory.h" +#include "nvim/os/process.h" + +#ifdef MSWIN +# include <tlhelp32.h> + +# include "nvim/api/private/helpers.h" #endif #if defined(__FreeBSD__) // XXX: OpenBSD ? @@ -27,18 +38,11 @@ # include <sys/sysctl.h> #endif -#include "nvim/api/private/helpers.h" -#include "nvim/globals.h" -#include "nvim/log.h" -#include "nvim/os/os.h" -#include "nvim/os/os_defs.h" -#include "nvim/os/process.h" - #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/process.c.generated.h" +# include "os/process.c.generated.h" // IWYU pragma: export #endif -#ifdef WIN32 +#ifdef MSWIN static bool os_proc_tree_kill_rec(HANDLE process, int sig) { if (process == NULL) { @@ -114,7 +118,7 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) *proc_list = NULL; *proc_count = 0; -#ifdef WIN32 +#ifdef MSWIN PROCESSENTRY32 pe; // Snapshot of all processes. @@ -215,7 +219,7 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count) return 0; } -#ifdef WIN32 +#ifdef MSWIN /// Gets various properties of the process identified by `pid`. /// /// @param pid Process to inspect. @@ -260,5 +264,16 @@ Dictionary os_proc_info(int pid) /// Return true if process `pid` is running. bool os_proc_running(int pid) { - return uv_kill(pid, 0) == 0; + int err = uv_kill(pid, 0); + // If there is no error the process must be running. + if (err == 0) { + return true; + } + // If the error is ESRCH then the process is not running. + if (err == UV_ESRCH) { + return false; + } + // If the process is running and owned by another user we get EPERM. With + // other errors the process might be running, assuming it is then. + return true; } diff --git a/src/nvim/os/pty_conpty_win.c b/src/nvim/os/pty_conpty_win.c index f9478d951f..43c89f8865 100644 --- a/src/nvim/os/pty_conpty_win.c +++ b/src/nvim/os/pty_conpty_win.c @@ -67,7 +67,7 @@ conpty_t *os_conpty_init(char **in_name, char **out_name, uint16_t width, uint16 | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE; sa.nLength = sizeof(sa); - snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-in-%d-%d", + snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-in-%lld-%d", os_get_pid(), count); *in_name = xstrdup(buf); if ((in_read = CreateNamedPipeA(*in_name, @@ -81,7 +81,7 @@ conpty_t *os_conpty_init(char **in_name, char **out_name, uint16_t width, uint16 emsg = "create input pipe failed"; goto failed; } - snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-out-%d-%d", + snprintf(buf, sizeof(buf), "\\\\.\\pipe\\nvim-term-out-%lld-%d", os_get_pid(), count); *out_name = xstrdup(buf); if ((out_write = CreateNamedPipeA(*out_name, diff --git a/src/nvim/os/pty_conpty_win.h b/src/nvim/os/pty_conpty_win.h index 15e7c3da0c..0c25a5970e 100644 --- a/src/nvim/os/pty_conpty_win.h +++ b/src/nvim/os/pty_conpty_win.h @@ -1,7 +1,7 @@ #ifndef NVIM_OS_PTY_CONPTY_WIN_H #define NVIM_OS_PTY_CONPTY_WIN_H -#include "nvim/lib/kvec.h" +#include "klib/kvec.h" #include "nvim/os/input.h" #ifndef HPCON diff --git a/src/nvim/os/pty_process.h b/src/nvim/os/pty_process.h index 94923499ca..07d346be22 100644 --- a/src/nvim/os/pty_process.h +++ b/src/nvim/os/pty_process.h @@ -1,7 +1,7 @@ #ifndef NVIM_OS_PTY_PROCESS_H #define NVIM_OS_PTY_PROCESS_H -#ifdef WIN32 +#ifdef MSWIN # include "nvim/os/pty_process_win.h" #else # include "nvim/os/pty_process_unix.h" diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index c5d6af0ff6..2413f0339b 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -2,13 +2,15 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com // Some of the code came from pangoterm and libuv -#include <stdbool.h> + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> -#include <sys/types.h> #include <sys/wait.h> -#include <termios.h> // forkpty is not in POSIX, so headers are platform-specific #if defined(__FreeBSD__) || defined(__DragonFly__) @@ -31,13 +33,16 @@ #include <uv.h> +#include "auto/config.h" +#include "klib/klist.h" +#include "nvim/eval/typval.h" #include "nvim/event/loop.h" #include "nvim/event/process.h" -#include "nvim/event/rstream.h" -#include "nvim/event/wstream.h" -#include "nvim/lib/klist.h" +#include "nvim/event/stream.h" #include "nvim/log.h" #include "nvim/os/os.h" +#include "nvim/os/os_defs.h" +#include "nvim/os/pty_process.h" #include "nvim/os/pty_process_unix.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -155,31 +160,13 @@ static pid_t forkpty(int *amaster, char *name, struct termios *termp, struct win #endif -/// termios saved at startup (for TUI) or initialized by pty_process_spawn(). -static struct termios termios_default; - -/// Saves the termios properties associated with `tty_fd`. -/// -/// @param tty_fd TTY file descriptor, or -1 if not in a terminal. -void pty_process_save_termios(int tty_fd) -{ - if (tty_fd == -1) { - return; - } - int rv = tcgetattr(tty_fd, &termios_default); - if (rv != 0) { - ELOG("tcgetattr failed (tty_fd=%d): %s", tty_fd, strerror(errno)); - } else { - DLOG("tty_fd=%d", tty_fd); - } -} - /// @returns zero on success, or negative error code int pty_process_spawn(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { + // termios initialized at first use + static struct termios termios_default; if (!termios_default.c_cflag) { - // TODO(jkeyes): We could pass NULL to forkpty() instead ... init_termios(&termios_default); } diff --git a/src/nvim/os/pty_process_unix.h b/src/nvim/os/pty_process_unix.h index 765490b92b..0cc68cf3e9 100644 --- a/src/nvim/os/pty_process_unix.h +++ b/src/nvim/os/pty_process_unix.h @@ -1,8 +1,10 @@ #ifndef NVIM_OS_PTY_PROCESS_UNIX_H #define NVIM_OS_PTY_PROCESS_UNIX_H +#include <stdint.h> #include <sys/ioctl.h> +#include "nvim/event/loop.h" #include "nvim/event/process.h" typedef struct pty_process { diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index b793b8f9c6..f1e2c5440f 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -3,30 +3,45 @@ #include <assert.h> #include <stdbool.h> -#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> #include <string.h> #include <uv.h> +#include "auto/config.h" +#include "klib/kvec.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/event/libuv_process.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" +#include "nvim/event/process.h" #include "nvim/event/rstream.h" +#include "nvim/event/stream.h" +#include "nvim/event/wstream.h" #include "nvim/ex_cmds.h" #include "nvim/fileio.h" -#include "nvim/lib/kvec.h" -#include "nvim/log.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/macros.h" #include "nvim/main.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option_defs.h" +#include "nvim/os/fs.h" +#include "nvim/os/os_defs.h" #include "nvim/os/shell.h" #include "nvim/os/signal.h" +#include "nvim/os/time.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/profile.h" -#include "nvim/screen.h" +#include "nvim/rbuffer.h" #include "nvim/strings.h" #include "nvim/tag.h" #include "nvim/types.h" @@ -50,13 +65,13 @@ typedef struct { static void save_patterns(int num_pat, char **pat, int *num_file, char ***file) { - *file = xmalloc((size_t)num_pat * sizeof(char_u *)); + *file = xmalloc((size_t)num_pat * sizeof(char *)); for (int i = 0; i < num_pat; i++) { - char_u *s = vim_strsave((char_u *)pat[i]); + char *s = xstrdup(pat[i]); // Be compatible with expand_filename(): halve the number of // backslashes. backslash_halve(s); - (*file)[i] = (char *)s; + (*file)[i] = s; } *num_file = num_pat; } @@ -64,7 +79,7 @@ static void save_patterns(int num_pat, char **pat, int *num_file, char ***file) static bool have_wildcard(int num, char **file) { for (int i = 0; i < num; i++) { - if (path_has_wildcard((char_u *)file[i])) { + if (path_has_wildcard(file[i])) { return true; } } @@ -74,7 +89,7 @@ static bool have_wildcard(int num, char **file) static bool have_dollars(int num, char **file) { for (int i = 0; i < num; i++) { - if (vim_strchr((char *)file[i], '$') != NULL) { + if (vim_strchr(file[i], '$') != NULL) { return true; } } @@ -105,15 +120,15 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in { int i; size_t len; - char_u *p; + char *p; bool dir; - char_u *extra_shell_arg = NULL; + char *extra_shell_arg = NULL; ShellOpts shellopts = kShellOptExpand | kShellOptSilent; int j; - char_u *tempname; - char_u *command; + char *tempname; + char *command; FILE *fd; - char_u *buffer; + char *buffer; #define STYLE_ECHO 0 // use "echo", the default #define STYLE_GLOB 1 // use "glob", for csh #define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh @@ -129,7 +144,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in bool is_fish_shell = #if defined(UNIX) - STRNCMP(invocation_path_tail(p_sh, NULL), "fish", 4) == 0; + strncmp((char *)invocation_path_tail(p_sh, NULL), "fish", 4) == 0; #else false; #endif @@ -178,27 +193,27 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in // STYLE_ECHO: space separated. // A shell we don't know, stay safe and use "echo". if (num_pat == 1 && *pat[0] == '`' - && (len = STRLEN(pat[0])) > 2 + && (len = strlen(pat[0])) > 2 && *(pat[0] + len - 1) == '`') { shell_style = STYLE_BT; - } else if ((len = STRLEN(p_sh)) >= 3) { - if (STRCMP(p_sh + len - 3, "csh") == 0) { + } else if ((len = strlen(p_sh)) >= 3) { + if (strcmp(p_sh + len - 3, "csh") == 0) { shell_style = STYLE_GLOB; - } else if (STRCMP(p_sh + len - 3, "zsh") == 0) { + } else if (strcmp(p_sh + len - 3, "zsh") == 0) { shell_style = STYLE_PRINT; } } if (shell_style == STYLE_ECHO - && strstr(path_tail((char *)p_sh), "sh") != NULL) { + && strstr(path_tail(p_sh), "sh") != NULL) { shell_style = STYLE_VIMGLOB; } // Compute the length of the command. We need 2 extra bytes: for the // optional '&' and for the NUL. // Worst case: "unset nonomatch; print -N >" plus two is 29 - len = STRLEN(tempname) + 29; + len = strlen(tempname) + 29; if (shell_style == STYLE_VIMGLOB) { - len += STRLEN(sh_vimglob_func); + len += strlen(sh_vimglob_func); } for (i = 0; i < num_pat; i++) { @@ -206,7 +221,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in // "command" below. len++; // add space for (j = 0; pat[i][j] != NUL; j++) { - if (vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { + if (vim_strchr(SHELL_SPECIAL, (uint8_t)pat[i][j]) != NULL) { len++; // may add a backslash } len++; @@ -233,7 +248,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in STRCPY(command, "("); } STRCAT(command, pat[0] + 1); // exclude first backtick - p = command + STRLEN(command) - 1; + p = command + strlen(command) - 1; if (is_fish_shell) { *p-- = ';'; STRCAT(command, " end"); @@ -279,7 +294,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in // characters, except inside ``. bool intick = false; - p = command + STRLEN(command); + p = command + strlen(command); *p++ = ' '; for (j = 0; pat[i][j] != NUL; j++) { if (pat[i][j] == '`') { @@ -289,14 +304,14 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in // backslash inside backticks, before a special character // and before a backtick. if (intick - || vim_strchr(SHELL_SPECIAL, pat[i][j + 1]) != NULL + || vim_strchr(SHELL_SPECIAL, (uint8_t)pat[i][j + 1]) != NULL || pat[i][j + 1] == '`') { *p++ = '\\'; } j++; } else if (!intick && ((flags & EW_KEEPDOLLAR) == 0 || pat[i][j] != '$') - && vim_strchr(SHELL_SPECIAL, pat[i][j]) != NULL) { + && vim_strchr(SHELL_SPECIAL, (uint8_t)pat[i][j]) != NULL) { // Put a backslash before a special character, but not // when inside ``. And not for $var when EW_KEEPDOLLAR is // set. @@ -304,7 +319,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in } // Copy one character. - *p++ = (char_u)pat[i][j]; + *p++ = pat[i][j]; } *p = NUL; } @@ -322,13 +337,13 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in // the argument list, otherwise zsh gives an error message and doesn't // expand any other pattern. if (shell_style == STYLE_PRINT) { - extra_shell_arg = (char_u *)"-G"; // Use zsh NULL_GLOB option + extra_shell_arg = "-G"; // Use zsh NULL_GLOB option // If we use -f then shell variables set in .cshrc won't get expanded. // vi can do it, so we will too, but it is only necessary if there is a "$" // in one of the patterns, otherwise we can still use the fast option. } else if (shell_style == STYLE_GLOB && !have_dollars(num_pat, pat)) { - extra_shell_arg = (char_u *)"-f"; // Use csh fast option + extra_shell_arg = "-f"; // Use csh fast option } // execute the shell command @@ -343,7 +358,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in xfree(command); if (i) { // os_call_shell() failed - os_remove((char *)tempname); + os_remove(tempname); xfree(tempname); // With interactive completion, the error message is not printed. if (!(flags & EW_SILENT)) { @@ -362,7 +377,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in } // read the names from the file into memory - fd = fopen((char *)tempname, READBIN); + fd = fopen(tempname, READBIN); if (fd == NULL) { // Something went wrong, perhaps a file name with a special char. if (!(flags & EW_SILENT)) { @@ -392,9 +407,9 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in buffer = xmalloc(len + 1); // fread() doesn't terminate buffer with NUL; // appropriate termination (not always NUL) is done below. - size_t readlen = fread((char *)buffer, 1, len, fd); + size_t readlen = fread(buffer, 1, len, fd); fclose(fd); - os_remove((char *)tempname); + os_remove(tempname); if (readlen != len) { // unexpected read error semsg(_(e_notread), tempname); @@ -412,7 +427,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in while (*p != ' ' && *p != '\n') { p++; } - p = (char_u *)skipwhite((char *)p); // skip to next entry + p = skipwhite(p); // skip to next entry } // file names are separated with NL } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { @@ -425,7 +440,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in if (*p != NUL) { p++; } - p = (char_u *)skipwhite((char *)p); // skip leading white space + p = skipwhite(p); // skip leading white space } // file names are separated with NUL } else { @@ -439,7 +454,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in if (shell_style == STYLE_PRINT && !did_find_nul) { // If there is a NUL, set did_find_nul, else set check_spaces buffer[len] = NUL; - if (len && (int)STRLEN(buffer) < (int)len) { + if (len && (int)strlen(buffer) < (int)len) { did_find_nul = true; } else { check_spaces = true; @@ -473,12 +488,12 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in goto notfound; } *num_file = i; - *file = xmalloc(sizeof(char_u *) * (size_t)i); + *file = xmalloc(sizeof(char *) * (size_t)i); // Isolate the individual file names. p = buffer; for (i = 0; i < *num_file; i++) { - (*file)[i] = (char *)p; + (*file)[i] = p; // Space or NL separates if (shell_style == STYLE_ECHO || shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) { @@ -490,7 +505,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in *p = NUL; } else { *p++ = NUL; - p = (char_u *)skipwhite((char *)p); // skip to next entry + p = skipwhite(p); // skip to next entry } } else { // NUL separates while (*p && p < buffer + len) { // skip entry @@ -503,12 +518,12 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in // Move the file names to allocated memory. for (j = 0, i = 0; i < *num_file; i++) { // Require the files to exist. Helps when using /bin/sh - if (!(flags & EW_NOTFOUND) && !os_path_exists((char_u *)(*file)[i])) { + if (!(flags & EW_NOTFOUND) && !os_path_exists((*file)[i])) { continue; } // check if this entry should be included - dir = (os_isdir((char_u *)(*file)[i])); + dir = (os_isdir((*file)[i])); if ((dir && !(flags & EW_DIR)) || (!dir && !(flags & EW_FILE))) { continue; } @@ -519,12 +534,12 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in continue; } - p = xmalloc(STRLEN((*file)[i]) + 1 + dir); + p = xmalloc(strlen((*file)[i]) + 1 + dir); STRCPY(p, (*file)[i]); if (dir) { - add_pathsep((char *)p); // add '/' to a directory name + add_pathsep(p); // add '/' to a directory name } - (*file)[j++] = (char *)p; + (*file)[j++] = p; } xfree(buffer); *num_file = j; @@ -638,7 +653,7 @@ char *shell_argv_to_str(char **const argv) /// @param extra_args Extra arguments to the shell, or NULL. /// /// @return shell command exit code -int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) +int os_call_shell(char *cmd, ShellOpts opts, char *extra_args) { DynamicBuffer input = DYNAMIC_BUFFER_INIT; char *output = NULL, **output_ptr = NULL; @@ -667,7 +682,7 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) } size_t nread; - int exitcode = do_os_system(shell_build_argv((char *)cmd, (char *)extra_args), + int exitcode = do_os_system(shell_build_argv(cmd, extra_args), input.data, input.len, output_ptr, &nread, emsg_silent, forward_output); xfree(input.data); @@ -693,7 +708,7 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) /// Invalidates cached tags. /// /// @return shell command exit code -int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) +int call_shell(char *cmd, ShellOpts opts, char *extra_shell_arg) { int retval; proftime_T wait_time; @@ -737,23 +752,23 @@ int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) /// @param ret_len length of the stdout /// /// @return an allocated string, or NULL for error. -char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, size_t *ret_len) +char *get_cmd_output(char *cmd, char *infile, ShellOpts flags, size_t *ret_len) { - char_u *buffer = NULL; + char *buffer = NULL; if (check_secure()) { return NULL; } // get a name for the temp file - char_u *tempname = vim_tempname(); + char *tempname = vim_tempname(); if (tempname == NULL) { emsg(_(e_notmp)); return NULL; } // Add the redirection stuff - char_u *command = (char_u *)make_filter_cmd((char *)cmd, (char *)infile, (char *)tempname); + char *command = make_filter_cmd(cmd, infile, tempname); // Call the shell to execute the command (errors are ignored). // Don't check timestamps here. @@ -764,7 +779,7 @@ char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, size_t *ret xfree(command); // read the names from the file into memory - FILE *fd = os_fopen((char *)tempname, READBIN); + FILE *fd = os_fopen(tempname, READBIN); if (fd == NULL) { semsg(_(e_notopen), tempname); @@ -776,9 +791,9 @@ char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, size_t *ret fseek(fd, 0L, SEEK_SET); buffer = xmalloc(len + 1); - size_t i = fread((char *)buffer, 1, len, fd); + size_t i = fread(buffer, 1, len, fd); fclose(fd); - os_remove((char *)tempname); + os_remove(tempname); if (i != len) { semsg(_(e_notread), tempname); XFREE_CLEAR(buffer); @@ -805,7 +820,7 @@ done: /// char *output = NULL; /// size_t nread = 0; /// char *argv[] = {"ls", "-la", NULL}; -/// int exitcode = os_sytem(argv, NULL, 0, &output, &nread); +/// int exitcode = os_system(argv, NULL, 0, &output, &nread); /// /// @param argv The commandline arguments to be passed to the shell. `argv` /// will be consumed. @@ -911,7 +926,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu out_data_ring(NULL, SIZE_MAX); } if (forward_output) { - // caller should decide if wait_return is invoked + // caller should decide if wait_return() is invoked no_wait_return++; msg_end(); no_wait_return--; @@ -1106,13 +1121,13 @@ static void out_data_append_to_screen(char *output, size_t *count, bool eof) // incomplete UTF-8 sequence that could be composing with the last // complete sequence. // This will be corrected when we switch to vterm based implementation - int i = *p ? utfc_ptr2len_len((char_u *)p, (int)(end - p)) : 1; + int i = *p ? utfc_ptr2len_len(p, (int)(end - p)) : 1; if (!eof && i == 1 && utf8len_tab_zero[*(uint8_t *)p] > (end - p)) { *count = (size_t)(p - output); goto end; } - (void)msg_outtrans_len_attr((char_u *)p, i, 0); + (void)msg_outtrans_len_attr(p, i, 0); p += i; } } @@ -1150,14 +1165,14 @@ static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, /// @param argv The vector that will be filled with copies of the parsed /// words. It can be NULL if the caller only needs to count words. /// @return The number of words parsed. -static size_t tokenize(const char_u *const str, char **const argv) +static size_t tokenize(const char *const str, char **const argv) FUNC_ATTR_NONNULL_ARG(1) { size_t argc = 0; - const char *p = (const char *)str; + const char *p = str; while (*p != NUL) { - const size_t len = word_length((const char_u *)p); + const size_t len = word_length(p); if (argv != NULL) { // Fill the slot @@ -1175,9 +1190,9 @@ static size_t tokenize(const char_u *const str, char **const argv) /// /// @param str A pointer to the first character of the word /// @return The offset from `str` at which the word ends. -static size_t word_length(const char_u *str) +static size_t word_length(const char *str) { - const char_u *p = str; + const char *p = str; bool inquote = false; size_t length = 0; @@ -1208,10 +1223,10 @@ static void read_input(DynamicBuffer *buf) { size_t written = 0, l = 0, len = 0; linenr_T lnum = curbuf->b_op_start.lnum; - char_u *lp = ml_get(lnum); + char *lp = ml_get(lnum); for (;;) { - l = strlen((char *)lp + written); + l = strlen(lp + written); if (l == 0) { len = 0; } else if (lp[written] == NL) { @@ -1220,7 +1235,7 @@ static void read_input(DynamicBuffer *buf) dynamic_buffer_ensure(buf, buf->len + len); buf->data[buf->len++] = NUL; } else { - char_u *s = (char_u *)vim_strchr((char *)lp + written, NL); + char *s = vim_strchr(lp + written, NL); len = s == NULL ? l : (size_t)(s - (lp + written)); dynamic_buffer_ensure(buf, buf->len + len); memcpy(buf->data + buf->len, lp + written, len); @@ -1316,17 +1331,17 @@ static char *shell_xescape_xquote(const char *cmd) } const char *ecmd = cmd; - if (*p_sxe != NUL && STRCMP(p_sxq, "(") == 0) { - ecmd = (char *)vim_strsave_escaped_ext((char_u *)cmd, p_sxe, '^', false); + if (*p_sxe != NUL && strcmp(p_sxq, "(") == 0) { + ecmd = vim_strsave_escaped_ext(cmd, p_sxe, '^', false); } - size_t ncmd_size = strlen(ecmd) + STRLEN(p_sxq) * 2 + 1; + size_t ncmd_size = strlen(ecmd) + strlen(p_sxq) * 2 + 1; char *ncmd = xmalloc(ncmd_size); // When 'shellxquote' is ( append ). // When 'shellxquote' is "( append )". - if (STRCMP(p_sxq, "(") == 0) { + if (strcmp(p_sxq, "(") == 0) { vim_snprintf(ncmd, ncmd_size, "(%s)", ecmd); - } else if (STRCMP(p_sxq, "\"(") == 0) { + } else if (strcmp(p_sxq, "\"(") == 0) { vim_snprintf(ncmd, ncmd_size, "\"(%s)\"", ecmd); } else { vim_snprintf(ncmd, ncmd_size, "%s%s%s", p_sxq, ecmd, p_sxq); diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index e592570966..b8daaabba2 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -3,23 +3,20 @@ #include <assert.h> #include <stdbool.h> -#include <uv.h> -#ifndef WIN32 +#include <stdio.h> +#ifndef MSWIN # include <signal.h> // for sigset_t #endif -#include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/eval.h" -#include "nvim/event/loop.h" #include "nvim/event/signal.h" #include "nvim/globals.h" #include "nvim/log.h" #include "nvim/main.h" #include "nvim/memline.h" -#include "nvim/memory.h" #include "nvim/os/signal.h" -#include "nvim/vim.h" static SignalWatcher spipe, shup, squit, sterm, susr1, swinch; #ifdef SIGPWR @@ -34,7 +31,7 @@ static bool rejecting_deadly; void signal_init(void) { -#ifndef WIN32 +#ifndef MSWIN // Ensure a clean slate by unblocking all signals. For example, if SIGCHLD is // blocked, libuv may hang after spawning a subprocess on Linux. #5230 sigset_t mask; @@ -175,7 +172,7 @@ static void deadly_signal(int signum) ILOG("got signal %d (%s)", signum, signal_name(signum)); - snprintf((char *)IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\r\n", + snprintf(IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\r\n", signal_name(signum)); // Preserve files and exit. diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index 59d315d44c..6b07b6ef70 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -2,6 +2,7 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <stdbool.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/fileio.h" @@ -21,7 +22,7 @@ static const char *xdg_env_vars[] = { [kXDGDataDirs] = "XDG_DATA_DIRS", }; -#ifdef WIN32 +#ifdef MSWIN static const char *const xdg_defaults_env_vars[] = { [kXDGConfigHome] = "LOCALAPPDATA", [kXDGDataHome] = "LOCALAPPDATA", @@ -37,7 +38,7 @@ static const char *const xdg_defaults_env_vars[] = { /// /// Used in case environment variables contain nothing. Need to be expanded. static const char *const xdg_defaults[] = { -#ifdef WIN32 +#ifdef MSWIN [kXDGConfigHome] = "~\\AppData\\Local", [kXDGDataHome] = "~\\AppData\\Local", [kXDGCacheHome] = "~\\AppData\\Local\\Temp", @@ -69,7 +70,7 @@ char *stdpaths_get_xdg_var(const XDGVarType idx) const char *env_val = os_getenv(env); -#ifdef WIN32 +#ifdef MSWIN if (env_val == NULL && xdg_defaults_env_vars[idx] != NULL) { env_val = os_getenv(xdg_defaults_env_vars[idx]); } @@ -87,6 +88,9 @@ char *stdpaths_get_xdg_var(const XDGVarType idx) } else if (idx == kXDGRuntimeDir) { // Special-case: stdpath('run') is defined at startup. ret = vim_gettempdir(); + if (ret == NULL) { + ret = "/tmp/"; + } size_t len = strlen(ret); ret = xstrndup(ret, len >= 2 ? len - 1 : 0); // Trim trailing slash. } @@ -107,7 +111,7 @@ char *get_xdg_home(const XDGVarType idx) { char *dir = stdpaths_get_xdg_var(idx); if (dir) { -#if defined(WIN32) +#if defined(MSWIN) dir = concat_fnames_realloc(dir, ((idx == kXDGDataHome || idx == kXDGStateHome) ? "nvim-data" : "nvim"), @@ -117,7 +121,7 @@ char *get_xdg_home(const XDGVarType idx) #endif #ifdef BACKSLASH_IN_FILENAME - slash_adjust((char_u *)dir); + slash_adjust(dir); #endif } return dir; diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 396bf6986a..873302a27d 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -1,22 +1,34 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <assert.h> +#include <inttypes.h> #include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + #include <uv.h> -#include "nvim/assert.h" +#include "auto/config.h" #include "nvim/event/loop.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/main.h" +#include "nvim/memory.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" +struct tm; + static uv_mutex_t delay_mutex; static uv_cond_t delay_cond; #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/time.c.generated.h" +# include "os/time.c.generated.h" // IWYU pragma: export #endif /// Initializes the time module @@ -67,7 +79,7 @@ void os_delay(uint64_t ms, bool ignoreinput) } LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms, got_int); } else { - os_microdelay(ms * 1000u, ignoreinput); + os_microdelay(ms * 1000U, ignoreinput); } } @@ -80,10 +92,10 @@ void os_delay(uint64_t ms, bool ignoreinput) /// If false, waiting is aborted on any input. void os_microdelay(uint64_t us, bool ignoreinput) { - uint64_t elapsed = 0u; + uint64_t elapsed = 0U; uint64_t base = uv_hrtime(); // Convert microseconds to nanoseconds, or UINT64_MAX on overflow. - const uint64_t ns = (us < UINT64_MAX / 1000u) ? us * 1000u : UINT64_MAX; + const uint64_t ns = (us < UINT64_MAX / 1000U) ? us * 1000U : UINT64_MAX; uv_mutex_lock(&delay_mutex); @@ -92,7 +104,7 @@ void os_microdelay(uint64_t us, bool ignoreinput) // Else we check for input in ~100ms intervals. const uint64_t ns_delta = ignoreinput ? ns - elapsed - : MIN(ns - elapsed, 100000000u); // 100ms + : MIN(ns - elapsed, 100000000U); // 100ms const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta); if (0 != rv && UV_ETIMEDOUT != rv) { @@ -167,18 +179,28 @@ struct tm *os_localtime(struct tm *result) FUNC_ATTR_NONNULL_ALL /// @param result[out] Pointer to a 'char' where the result should be placed /// @param result_len length of result buffer /// @return human-readable string of current local time -char *os_ctime_r(const time_t *restrict clock, char *restrict result, size_t result_len) +char *os_ctime_r(const time_t *restrict clock, char *restrict result, size_t result_len, + bool add_newline) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { struct tm clock_local; struct tm *clock_local_ptr = os_localtime_r(clock, &clock_local); // MSVC returns NULL for an invalid value of seconds. if (clock_local_ptr == NULL) { - xstrlcpy(result, _("(Invalid)"), result_len); + xstrlcpy(result, _("(Invalid)"), result_len - 1); } else { - strftime(result, result_len, _("%a %b %d %H:%M:%S %Y"), clock_local_ptr); + // xgettext:no-c-format + if (strftime(result, result_len - 1, _("%a %b %d %H:%M:%S %Y"), clock_local_ptr) == 0) { + // Quoting "man strftime": + // > If the length of the result string (including the terminating + // > null byte) would exceed max bytes, then strftime() returns 0, + // > and the contents of the array are undefined. + xstrlcpy(result, _("(Invalid)"), result_len - 1); + } + } + if (add_newline) { + xstrlcat(result, "\n", result_len); } - xstrlcat(result, "\n", result_len); return result; } @@ -187,11 +209,11 @@ char *os_ctime_r(const time_t *restrict clock, char *restrict result, size_t res /// @param result[out] Pointer to a 'char' where the result should be placed /// @param result_len length of result buffer /// @return human-readable string of current local time -char *os_ctime(char *result, size_t result_len) +char *os_ctime(char *result, size_t result_len, bool add_newline) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { time_t rawtime = time(NULL); - return os_ctime_r(&rawtime, result, result_len); + return os_ctime_r(&rawtime, result, result_len, add_newline); } /// Portable version of POSIX strptime() diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c index 126b1b0044..b5124bd83a 100644 --- a/src/nvim/os/tty.c +++ b/src/nvim/os/tty.c @@ -5,14 +5,14 @@ // Terminal/console utils // -#include "nvim/os/os.h" +#include "nvim/os/os.h" // IWYU pragma: keep (Windows) #include "nvim/os/tty.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os/tty.c.generated.h" +# include "os/tty.c.generated.h" // IWYU pragma: export #endif -#ifdef WIN32 +#ifdef MSWIN # if !defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING) # define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 # endif diff --git a/src/nvim/os/unix_defs.h b/src/nvim/os/unix_defs.h index 4ed3b51694..8d002fc5e9 100644 --- a/src/nvim/os/unix_defs.h +++ b/src/nvim/os/unix_defs.h @@ -3,6 +3,9 @@ #include <sys/param.h> #include <unistd.h> +#if defined(HAVE_TERMIOS_H) +# include <termios.h> +#endif // POSIX.1-2008 says that NAME_MAX should be in here #include <limits.h> diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c index bd34e917b2..ef2986246b 100644 --- a/src/nvim/os/users.c +++ b/src/nvim/os/users.c @@ -3,6 +3,9 @@ // users.c -- operating system user information +#include <stdbool.h> +#include <stdio.h> +#include <string.h> #include <uv.h> #include "auto/config.h" @@ -10,11 +13,12 @@ #include "nvim/garray.h" #include "nvim/memory.h" #include "nvim/os/os.h" -#include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/vim.h" #ifdef HAVE_PWD_H # include <pwd.h> #endif -#ifdef WIN32 +#ifdef MSWIN # include <lm.h> #endif @@ -56,7 +60,7 @@ int os_get_usernames(garray_T *users) } endpwent(); } -#elif defined(WIN32) +#elif defined(MSWIN) { DWORD nusers = 0, ntotal = 0, i; PUSER_INFO_0 uinfo; @@ -93,7 +97,7 @@ int os_get_usernames(garray_T *users) for (i = 0; i < users->ga_len; i++) { char *local_user = ((char **)users->ga_data)[i]; - if (STRCMP(local_user, user_env) == 0) { + if (strcmp(local_user, user_env) == 0) { break; } } @@ -142,7 +146,7 @@ int os_get_uname(uv_uid_t uid, char *s, size_t len) if ((pw = getpwuid(uid)) != NULL // NOLINT(runtime/threadsafe_fn) && pw->pw_name != NULL && *(pw->pw_name) != NUL) { - STRLCPY(s, pw->pw_name, len); + xstrlcpy(s, pw->pw_name, len); return OK; } #endif @@ -208,17 +212,17 @@ char *get_users(expand_T *xp, int idx) /// @return 0 if name does not match any user name. /// 1 if name partially matches the beginning of a user name. /// 2 is name fully matches a user name. -int match_user(char_u *name) +int match_user(char *name) { - int n = (int)STRLEN(name); + int n = (int)strlen(name); int result = 0; init_users(); for (int i = 0; i < ga_users.ga_len; i++) { - if (STRCMP(((char_u **)ga_users.ga_data)[i], name) == 0) { + if (strcmp(((char **)ga_users.ga_data)[i], name) == 0) { return 2; // full match } - if (STRNCMP(((char_u **)ga_users.ga_data)[i], name, n) == 0) { + if (strncmp(((char **)ga_users.ga_data)[i], name, (size_t)n) == 0) { result = 1; // partial match } } diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index 1ae86d6bbe..4f8a242a51 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -1,7 +1,7 @@ #ifndef NVIM_OS_WIN_DEFS_H #define NVIM_OS_WIN_DEFS_H -#ifndef WIN32 +#ifndef MSWIN # error Header must be included only when compiling for Windows. #endif diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c deleted file mode 100644 index 473bf5072c..0000000000 --- a/src/nvim/os_unix.c +++ /dev/null @@ -1,73 +0,0 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - -#include <assert.h> -#include <errno.h> -#include <inttypes.h> -#include <stdbool.h> -#include <string.h> - -#include "nvim/ascii.h" -#include "nvim/buffer.h" -#include "nvim/charset.h" -#include "nvim/eval.h" -#include "nvim/ex_cmds.h" -#include "nvim/fileio.h" -#include "nvim/garray.h" -#include "nvim/getchar.h" -#include "nvim/main.h" -#include "nvim/mbyte.h" -#include "nvim/memline.h" -#include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/mouse.h" -#include "nvim/msgpack_rpc/helpers.h" -#include "nvim/os/input.h" -#include "nvim/os/os.h" -#include "nvim/os/shell.h" -#include "nvim/os/signal.h" -#include "nvim/os/time.h" -#include "nvim/os_unix.h" -#include "nvim/path.h" -#include "nvim/screen.h" -#include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/types.h" -#include "nvim/ui.h" -#include "nvim/vim.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os_unix.c.generated.h" -#endif - -#if defined(HAVE_ACL) -# ifdef HAVE_SYS_ACL_H -# include <sys/acl.h> -# endif -# ifdef HAVE_SYS_ACCESS_H -# include <sys/access.h> -# endif - -// Return a pointer to the ACL of file "fname" in allocated memory. -// Return NULL if the ACL is not available for whatever reason. -vim_acl_T mch_get_acl(const char_u *fname) -{ - vim_acl_T ret = NULL; - return ret; -} - -// Set the ACL of file "fname" to "acl" (unless it's NULL). -void mch_set_acl(const char_u *fname, vim_acl_T aclent) -{ - if (aclent == NULL) { - return; - } -} - -void mch_free_acl(vim_acl_T aclent) -{ - if (aclent == NULL) { - return; - } -} -#endif diff --git a/src/nvim/os_unix.h b/src/nvim/os_unix.h deleted file mode 100644 index aae05f7fcc..0000000000 --- a/src/nvim/os_unix.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef NVIM_OS_UNIX_H -#define NVIM_OS_UNIX_H - -#include "nvim/os/shell.h" -#include "nvim/types.h" // for vim_acl_T - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "os_unix.h.generated.h" -#endif -#endif // NVIM_OS_UNIX_H diff --git a/src/nvim/path.c b/src/nvim/path.c index 1500254de5..513f366a27 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -2,11 +2,17 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <inttypes.h> +#include <ctype.h> +#include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> #include <stdlib.h> +#include <string.h> +#include "auto/config.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/eval.h" @@ -14,27 +20,29 @@ #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/garray.h" -#include "nvim/memfile.h" -#include "nvim/memline.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/shell.h" -#include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/quickfix.h" +#include "nvim/pos.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/strings.h" -#include "nvim/tag.h" #include "nvim/types.h" #include "nvim/vim.h" #include "nvim/window.h" -#define URL_SLASH 1 // path_is_url() has found "://" -#define URL_BACKSLASH 2 // path_is_url() has found ":\\" +enum { + URL_SLASH = 1, // path_is_url() has found ":/" + URL_BACKSLASH = 2, // path_is_url() has found ":\\" +}; #ifdef gen_expand_wildcards # undef gen_expand_wildcards @@ -62,9 +70,9 @@ FileComparison path_full_compare(char *const s1, char *const s2, const bool chec FileID file_id_1, file_id_2; if (expandenv) { - expand_env((char_u *)s1, (char_u *)exp1, MAXPATHL); + expand_env(s1, exp1, MAXPATHL); } else { - STRLCPY(exp1, s1, MAXPATHL); + xstrlcpy(exp1, s1, MAXPATHL); } bool id_ok_1 = os_fileid(exp1, &file_id_1); bool id_ok_2 = os_fileid(s2, &file_id_2); @@ -73,7 +81,7 @@ FileComparison path_full_compare(char *const s1, char *const s2, const bool chec if (checkname) { vim_FullName(exp1, full1, MAXPATHL, false); vim_FullName(s2, full2, MAXPATHL, false); - if (FNAMECMP(full1, full2) == 0) { + if (path_fnamecmp(full1, full2) == 0) { return kEqualFileNames; } } @@ -104,7 +112,7 @@ char *path_tail(const char *fname) return ""; } - const char *tail = (char *)get_past_head((char_u *)fname); + const char *tail = get_past_head(fname); const char *p = tail; // Find last part of path. while (*p != NUL) { @@ -130,7 +138,7 @@ char *path_tail_with_sep(char *fname) assert(fname != NULL); // Don't remove the '/' from "c:/file". - char *past_head = (char *)get_past_head((char_u *)fname); + char *past_head = get_past_head(fname); char *tail = path_tail(fname); while (tail > past_head && after_pathsep(fname, tail)) { tail--; @@ -147,11 +155,11 @@ char *path_tail_with_sep(char *fname) /// @post if `len` is not null, stores the length of the executable name. /// /// @return The position of the last path separator + 1. -const char_u *invocation_path_tail(const char_u *invocation, size_t *len) +const char *invocation_path_tail(const char *invocation, size_t *len) FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1) { - const char_u *tail = get_past_head((char_u *)invocation); - const char_u *p = tail; + const char *tail = get_past_head(invocation); + const char *p = tail; while (*p != NUL && *p != ' ') { bool was_sep = vim_ispathsep_nocolon(*p); MB_PTR_ADV(p); @@ -190,7 +198,7 @@ const char *path_next_component(const char *fname) /// - 1 otherwise int path_head_length(void) { -#ifdef WIN32 +#ifdef MSWIN return 3; #else return 1; @@ -203,10 +211,10 @@ int path_head_length(void) /// @return /// - True if path begins with a path head /// - False otherwise -bool is_path_head(const char_u *path) +bool is_path_head(const char *path) { -#ifdef WIN32 - return isalpha(path[0]) && path[1] == ':'; +#ifdef MSWIN + return isalpha((uint8_t)path[0]) && path[1] == ':'; #else return vim_ispathsep(*path); #endif @@ -215,11 +223,11 @@ bool is_path_head(const char_u *path) /// Get a pointer to one character past the head of a path name. /// Unix: after "/"; Win: after "c:\" /// If there is no head, path is returned. -char_u *get_past_head(const char_u *path) +char *get_past_head(const char *path) { - const char_u *retval = path; + const char *retval = path; -#ifdef WIN32 +#ifdef MSWIN // May skip "c:" if (is_path_head(path)) { retval = path + 2; @@ -230,13 +238,11 @@ char_u *get_past_head(const char_u *path) retval++; } - return (char_u *)retval; + return (char *)retval; } -/* - * Return TRUE if 'c' is a path separator. - * Note that for MS-Windows this includes the colon. - */ +/// Return true if 'c' is a path separator. +/// Note that for MS-Windows this includes the colon. int vim_ispathsep(int c) { #ifdef UNIX @@ -250,9 +256,7 @@ int vim_ispathsep(int c) #endif } -/* - * Like vim_ispathsep(c), but exclude the colon for MS-Windows. - */ +// Like vim_ispathsep(c), but exclude the colon for MS-Windows. int vim_ispathsep_nocolon(int c) { return vim_ispathsep(c) @@ -262,9 +266,7 @@ int vim_ispathsep_nocolon(int c) ; } -/* - * return TRUE if 'c' is a path list separator. - */ +/// return true if 'c' is a path list separator. int vim_ispathlistsep(int c) { #ifdef UNIX @@ -278,13 +280,13 @@ int vim_ispathlistsep(int c) /// "trim_len" specifies how many characters to keep for each directory. /// Must be 1 or more. /// It's done in-place. -void shorten_dir_len(char_u *str, int trim_len) +void shorten_dir_len(char *str, int trim_len) { - char_u *tail = (char_u *)path_tail((char *)str); - char_u *d = str; + char *tail = path_tail(str); + char *d = str; bool skip = false; int dirchunk_len = 0; - for (char_u *s = str;; s++) { + for (char *s = str;; s++) { if (s >= tail) { // copy the whole tail *d++ = *s; if (*s == NUL) { @@ -304,7 +306,7 @@ void shorten_dir_len(char_u *str, int trim_len) skip = true; } } - int l = utfc_ptr2len((char *)s); + int l = utfc_ptr2len(s); while (--l > 0) { *d++ = *++s; } @@ -314,20 +316,18 @@ void shorten_dir_len(char_u *str, int trim_len) /// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" /// It's done in-place. -void shorten_dir(char_u *str) +void shorten_dir(char *str) { shorten_dir_len(str, 1); } -/* - * Return TRUE if the directory of "fname" exists, FALSE otherwise. - * Also returns TRUE if there is no directory name. - * "fname" must be writable!. - */ -bool dir_of_file_exists(char_u *fname) +/// Return true if the directory of "fname" exists, false otherwise. +/// Also returns true if there is no directory name. +/// "fname" must be writable!. +bool dir_of_file_exists(char *fname) { - char *p = path_tail_with_sep((char *)fname); - if ((char_u *)p == fname) { + char *p = path_tail_with_sep(fname); + if (p == fname) { return true; } char c = *p; @@ -339,6 +339,12 @@ bool dir_of_file_exists(char_u *fname) /// Compare two file names /// +/// On some systems case in a file name does not matter, on others it does. +/// +/// @note Does not account for maximum name lengths and things like "../dir", +/// thus it is not 100% accurate. OS may also use different algorithm for +/// case-insensitive comparison. +/// /// Handles '/' and '\\' correctly and deals with &fileignorecase option. /// /// @param[in] fname1 First file name. @@ -376,21 +382,21 @@ int path_fnamencmp(const char *const fname1, const char *const fname2, size_t le const char *p1 = fname1; const char *p2 = fname2; while (len > 0) { - c1 = utf_ptr2char((const char_u *)p1); - c2 = utf_ptr2char((const char_u *)p2); + c1 = utf_ptr2char(p1); + c2 = utf_ptr2char(p2); if ((c1 == NUL || c2 == NUL || (!((c1 == '/' || c1 == '\\') && (c2 == '\\' || c2 == '/')))) && (p_fic ? (c1 != c2 && CH_FOLD(c1) != CH_FOLD(c2)) : c1 != c2)) { break; } - len -= (size_t)utfc_ptr2len((const char_u *)p1); - p1 += utfc_ptr2len((const char_u *)p1); - p2 += utfc_ptr2len((const char_u *)p2); + len -= (size_t)utfc_ptr2len(p1); + p1 += utfc_ptr2len(p1); + p2 += utfc_ptr2len(p2); } return p_fic ? CH_FOLD(c1) - CH_FOLD(c2) : c1 - c2; #else if (p_fic) { - return mb_strnicmp((const char_u *)fname1, (const char_u *)fname2, len); + return mb_strnicmp(fname1, fname2, len); } return strncmp(fname1, fname2, len); #endif @@ -505,7 +511,7 @@ char *FullName_save(const char *fname, bool force) char *save_abs_path(const char *name) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - if (!path_is_absolute((char_u *)name)) { + if (!path_is_absolute(name)) { return FullName_save(name, true); } return xstrdup(name); @@ -515,7 +521,7 @@ char *save_abs_path(const char *name) /// @param p The path to expand. /// @returns Unix: True if it contains one of "?[{`'$". /// @returns Windows: True if it contains one of "*?$[". -bool path_has_wildcard(const char_u *p) +bool path_has_wildcard(const char *p) FUNC_ATTR_NONNULL_ALL { for (; *p; MB_PTR_ADV(p)) { @@ -530,7 +536,7 @@ bool path_has_wildcard(const char_u *p) // Windows: const char *wildcards = "?*$[`"; #endif - if (vim_strchr(wildcards, *p) != NULL + if (vim_strchr(wildcards, (uint8_t)(*p)) != NULL || (p[0] == '~' && p[1] != NUL)) { return true; } @@ -538,9 +544,7 @@ bool path_has_wildcard(const char_u *p) return false; } -/* - * Unix style wildcard expansion code. - */ +// Unix style wildcard expansion code. static int pstrcmp(const void *a, const void *b) { return pathcmp(*(char **)a, *(char **)b, -1); @@ -550,7 +554,7 @@ static int pstrcmp(const void *a, const void *b) /// @param p The path to expand. /// @returns Unix: True if it contains one of *?[{. /// @returns Windows: True if it contains one of *?[. -bool path_has_exp_wildcard(const char_u *p) +bool path_has_exp_wildcard(const char *p) FUNC_ATTR_NONNULL_ALL { for (; *p != NUL; MB_PTR_ADV(p)) { @@ -564,7 +568,7 @@ bool path_has_exp_wildcard(const char_u *p) #else const char *wildcards = "*?["; // Windows. #endif - if (vim_strchr(wildcards, *p) != NULL) { + if (vim_strchr(wildcards, (uint8_t)(*p)) != NULL) { return true; } } @@ -579,10 +583,10 @@ bool path_has_exp_wildcard(const char_u *p) /// @param path The path to search. /// @param flags Flags for regexp expansion. /// - EW_ICASE: Ignore case. -/// - EW_NOERROR: Silence error messeges. +/// - EW_NOERROR: Silence error messages. /// - EW_NOTWILD: Add matches literally. /// @returns the number of matches found. -static size_t path_expand(garray_T *gap, const char_u *path, int flags) +static size_t path_expand(garray_T *gap, const char *path, int flags) FUNC_ATTR_NONNULL_ALL { return do_path_expand(gap, path, 0, flags, false); @@ -606,7 +610,7 @@ static const char *scandir_next_with_dots(Directory *dir) /// Implementation of path_expand(). /// /// Chars before `path + wildoff` do not get expanded. -static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, int flags, +static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, int flags, bool didstar) FUNC_ATTR_NONNULL_ALL { @@ -625,19 +629,19 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, // Make room for file name. When doing encoding conversion the actual // length may be quite a bit longer, thus use the maximum possible length. - char_u *buf = xmalloc(MAXPATHL); + char *buf = xmalloc(MAXPATHL); // Find the first part in the path name that contains a wildcard. // When EW_ICASE is set every letter is considered to be a wildcard. // Copy it into "buf", including the preceding characters. - char_u *p = buf; - char_u *s = buf; - char_u *e = NULL; - const char_u *path_end = path; + char *p = buf; + char *s = buf; + char *e = NULL; + const char *path_end = path; while (*path_end != NUL) { - /* May ignore a wildcard that has a backslash before it; it will - * be removed by rem_backslash() or file_pat_to_reg_pat() below. */ - if (path_end >= path + wildoff && rem_backslash(path_end)) { + // May ignore a wildcard that has a backslash before it; it will + // be removed by rem_backslash() or file_pat_to_reg_pat() below. + if (path_end >= path + wildoff && rem_backslash((char *)path_end)) { *p++ = *path_end++; } else if (vim_ispathsep_nocolon(*path_end)) { if (e != NULL) { @@ -645,11 +649,11 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, } s = p + 1; } else if (path_end >= path + wildoff - && (vim_strchr("*?[{~$", *path_end) != NULL -#ifndef WIN32 + && (vim_strchr("*?[{~$", (uint8_t)(*path_end)) != NULL +#ifndef MSWIN || (!p_fic && (flags & EW_ICASE) && mb_isalpha(utf_ptr2char((char *)path_end))) #endif - )) { + )) { // NOLINT(whitespace/parens) e = p; } len = (size_t)(utfc_ptr2len((char *)path_end)); @@ -661,9 +665,9 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, *e = NUL; // Now we have one wildcard component between "s" and "e". - /* Remove backslashes between "wildoff" and the start of the wildcard - * component. */ - for (p = buf + wildoff; p < s; ++p) { + // Remove backslashes between "wildoff" and the start of the wildcard + // component. + for (p = buf + wildoff; p < s; p++) { if (rem_backslash(p)) { STRMOVE(p, p + 1); e--; @@ -672,7 +676,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, } // Check for "**" between "s" and "e". - for (p = s; p < e; ++p) { + for (p = s; p < e; p++) { if (p[0] == '*' && p[1] == '*') { starstar = true; } @@ -680,7 +684,7 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, // convert the file pattern to a regexp pattern int starts_with_dot = *s == '.'; - char *pat = file_pat_to_reg_pat((char *)s, (char *)e, NULL, false); + char *pat = file_pat_to_reg_pat(s, e, NULL, false); if (pat == NULL) { xfree(buf); return 0; @@ -708,8 +712,8 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, return 0; } - /* If "**" is by itself, this is the first time we encounter it and more - * is following then find matches without any directory. */ + // If "**" is by itself, this is the first time we encounter it and more + // is following then find matches without any directory. if (!didstar && stardepth < 100 && starstar && e - s == 2 && *path_end == '/') { STRCPY(s, path_end + 1); @@ -720,27 +724,27 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, *s = NUL; Directory dir; - char *dirpath = (*buf == NUL ? "." : (char *)buf); + char *dirpath = (*buf == NUL ? "." : buf); if (os_file_is_readable(dirpath) && os_scandir(&dir, dirpath)) { // Find all matching entries. - char_u *name; + char *name; scandir_next_with_dots(NULL); // initialize - while (!got_int && (name = (char_u *)scandir_next_with_dots(&dir)) != NULL) { + while (!got_int && (name = (char *)scandir_next_with_dots(&dir)) != NULL) { if ((name[0] != '.' || starts_with_dot || ((flags & EW_DODOT) && name[1] != NUL && (name[1] != '.' || name[2] != NUL))) // -V557 - && ((regmatch.regprog != NULL && vim_regexec(®match, (char *)name, 0)) + && ((regmatch.regprog != NULL && vim_regexec(®match, name, 0)) || ((flags & EW_NOTWILD) - && FNAMENCMP(path + (s - buf), name, e - s) == 0))) { + && path_fnamencmp(path + (s - buf), name, (size_t)(e - s)) == 0))) { STRCPY(s, name); - len = STRLEN(buf); + len = strlen(buf); if (starstar && stardepth < 100) { - /* For "**" in the pattern first go deeper in the tree to - * find matches. */ - STRCPY(buf + len, "/**"); + // For "**" in the pattern first go deeper in the tree to + // find matches. + STRCPY(buf + len, "/**"); // NOLINT STRCPY(buf + len + 3, path_end); stardepth++; (void)do_path_expand(gap, buf, len + 1, flags, true); @@ -761,8 +765,9 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, backslash_halve(buf + len + 1); } // add existing file or symbolic link - if ((flags & EW_ALLLINKS) ? os_fileinfo_link((char *)buf, &file_info) - : os_path_exists(buf)) { + if ((flags & EW_ALLLINKS) + ? os_fileinfo_link(buf, &file_info) + : os_path_exists(buf)) { addfile(gap, buf, flags); } } @@ -778,21 +783,19 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, size_t wildoff, // slow, thus skip it. size_t matches = (size_t)(gap->ga_len - start_len); if (matches > 0 && !got_int) { - qsort(((char_u **)gap->ga_data) + start_len, matches, - sizeof(char_u *), pstrcmp); + qsort(((char **)gap->ga_data) + start_len, matches, + sizeof(char *), pstrcmp); } return matches; } -/* - * Moves "*psep" back to the previous path separator in "path". - * Returns FAIL is "*psep" ends up at the beginning of "path". - */ -static int find_previous_pathsep(char_u *path, char_u **psep) +// Moves "*psep" back to the previous path separator in "path". +// Returns FAIL is "*psep" ends up at the beginning of "path". +static int find_previous_pathsep(char *path, char **psep) { // skip the current separator if (*psep > path && vim_ispathsep(**psep)) { - --*psep; + (*psep)--; } // find the previous separator @@ -806,25 +809,23 @@ static int find_previous_pathsep(char_u *path, char_u **psep) return FAIL; } -/* - * Returns TRUE if "maybe_unique" is unique wrt other_paths in "gap". - * "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]". - */ -static bool is_unique(char_u *maybe_unique, garray_T *gap, int i) +/// Returns true if "maybe_unique" is unique wrt other_paths in "gap". +/// "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]". +static bool is_unique(char *maybe_unique, garray_T *gap, int i) { - char_u **other_paths = (char_u **)gap->ga_data; + char **other_paths = gap->ga_data; for (int j = 0; j < gap->ga_len; j++) { if (j == i) { continue; // don't compare it with itself } - size_t candidate_len = STRLEN(maybe_unique); - size_t other_path_len = STRLEN(other_paths[j]); + size_t candidate_len = strlen(maybe_unique); + size_t other_path_len = strlen(other_paths[j]); if (other_path_len < candidate_len) { continue; // it's different when it's shorter } - char_u *rival = other_paths[j] + other_path_len - candidate_len; - if (FNAMECMP(maybe_unique, rival) == 0 + char *rival = other_paths[j] + other_path_len - candidate_len; + if (path_fnamecmp(maybe_unique, rival) == 0 && (rival == other_paths[j] || vim_ispathsep(*(rival - 1)))) { return false; // match } @@ -832,33 +833,31 @@ static bool is_unique(char_u *maybe_unique, garray_T *gap, int i) return true; // no match found } -/* - * Split the 'path' option into an array of strings in garray_T. Relative - * paths are expanded to their equivalent fullpath. This includes the "." - * (relative to current buffer directory) and empty path (relative to current - * directory) notations. - * - * TODO: handle upward search (;) and path limiter (**N) notations by - * expanding each into their equivalent path(s). - */ -static void expand_path_option(char_u *curdir, garray_T *gap) +// Split the 'path' option into an array of strings in garray_T. Relative +// paths are expanded to their equivalent fullpath. This includes the "." +// (relative to current buffer directory) and empty path (relative to current +// directory) notations. +// +// TODO(vim): handle upward search (;) and path limiter (**N) notations by +// expanding each into their equivalent path(s). +static void expand_path_option(char *curdir, garray_T *gap) { - char_u *path_option = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; - char_u *buf = xmalloc(MAXPATHL); + char *path_option = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; + char *buf = xmalloc(MAXPATHL); while (*path_option != NUL) { - copy_option_part((char **)&path_option, (char *)buf, MAXPATHL, " ,"); + copy_option_part(&path_option, buf, MAXPATHL, " ,"); if (buf[0] == '.' && (buf[1] == NUL || vim_ispathsep(buf[1]))) { - /* Relative to current buffer: - * "/path/file" + "." -> "/path/" - * "/path/file" + "./subdir" -> "/path/subdir" */ + // Relative to current buffer: + // "/path/file" + "." -> "/path/" + // "/path/file" + "./subdir" -> "/path/subdir" if (curbuf->b_ffname == NULL) { continue; } - char_u *p = (char_u *)path_tail(curbuf->b_ffname); - size_t len = (size_t)(p - (char_u *)curbuf->b_ffname); - if (len + STRLEN(buf) >= MAXPATHL) { + char *p = path_tail(curbuf->b_ffname); + size_t len = (size_t)(p - curbuf->b_ffname); + if (len + strlen(buf) >= MAXPATHL) { continue; } if (buf[1] == NUL) { @@ -870,12 +869,12 @@ static void expand_path_option(char_u *curdir, garray_T *gap) simplify_filename(buf); } else if (buf[0] == NUL) { STRCPY(buf, curdir); // relative to current directory - } else if (path_with_url((char *)buf)) { + } else if (path_with_url(buf)) { continue; // URL can't be used here } else if (!path_is_absolute(buf)) { // Expand relative path to their full path equivalent - size_t len = STRLEN(curdir); - if (len + STRLEN(buf) + 3 > MAXPATHL) { + size_t len = strlen(curdir); + if (len + strlen(buf) + 3 > MAXPATHL) { continue; } STRMOVE(buf + len + 1, buf); @@ -884,31 +883,29 @@ static void expand_path_option(char_u *curdir, garray_T *gap) simplify_filename(buf); } - GA_APPEND(char_u *, gap, vim_strsave(buf)); + GA_APPEND(char *, gap, xstrdup(buf)); } xfree(buf); } -/* - * Returns a pointer to the file or directory name in "fname" that matches the - * longest path in "ga"p, or NULL if there is no match. For example: - * - * path: /foo/bar/baz - * fname: /foo/bar/baz/quux.txt - * returns: ^this - */ -static char_u *get_path_cutoff(char_u *fname, garray_T *gap) +// Returns a pointer to the file or directory name in "fname" that matches the +// longest path in "ga"p, or NULL if there is no match. For example: +// +// path: /foo/bar/baz +// fname: /foo/bar/baz/quux.txt +// returns: ^this +static char *get_path_cutoff(char *fname, garray_T *gap) { int maxlen = 0; - char_u **path_part = (char_u **)gap->ga_data; - char_u *cutoff = NULL; + char **path_part = gap->ga_data; + char *cutoff = NULL; for (int i = 0; i < gap->ga_len; i++) { int j = 0; while ((fname[j] == path_part[i][j] -#ifdef WIN32 +#ifdef MSWIN || (vim_ispathsep(fname[j]) && vim_ispathsep(path_part[i][j])) #endif ) // NOLINT(whitespace/parens) @@ -931,32 +928,30 @@ static char_u *get_path_cutoff(char_u *fname, garray_T *gap) return cutoff; } -/* - * Sorts, removes duplicates and modifies all the fullpath names in "gap" so - * that they are unique with respect to each other while conserving the part - * that matches the pattern. Beware, this is at least O(n^2) wrt "gap->ga_len". - */ -static void uniquefy_paths(garray_T *gap, char_u *pattern) +/// Sorts, removes duplicates and modifies all the fullpath names in "gap" so +/// that they are unique with respect to each other while conserving the part +/// that matches the pattern. Beware, this is at least O(n^2) wrt "gap->ga_len". +static void uniquefy_paths(garray_T *gap, char *pattern) { - char_u **fnames = (char_u **)gap->ga_data; + char **fnames = gap->ga_data; bool sort_again = false; regmatch_T regmatch; garray_T path_ga; - char_u **in_curdir = NULL; - char_u *short_name; + char **in_curdir = NULL; + char *short_name; ga_remove_duplicate_strings(gap); - ga_init(&path_ga, (int)sizeof(char_u *), 1); + ga_init(&path_ga, (int)sizeof(char *), 1); // We need to prepend a '*' at the beginning of file_pattern so that the // regex matches anywhere in the path. FIXME: is this valid for all // possible patterns? - size_t len = STRLEN(pattern); - char_u *file_pattern = xmalloc(len + 2); + size_t len = strlen(pattern); + char *file_pattern = xmalloc(len + 2); file_pattern[0] = '*'; file_pattern[1] = NUL; STRCAT(file_pattern, pattern); - char *pat = file_pat_to_reg_pat((char *)file_pattern, NULL, NULL, true); + char *pat = file_pat_to_reg_pat(file_pattern, NULL, NULL, true); xfree(file_pattern); if (pat == NULL) { return; @@ -969,24 +964,24 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) return; } - char_u *curdir = xmalloc(MAXPATHL); + char *curdir = xmalloc(MAXPATHL); os_dirname(curdir, MAXPATHL); expand_path_option(curdir, &path_ga); - in_curdir = xcalloc((size_t)gap->ga_len, sizeof(char_u *)); + in_curdir = xcalloc((size_t)gap->ga_len, sizeof(char *)); for (int i = 0; i < gap->ga_len && !got_int; i++) { - char_u *path = fnames[i]; + char *path = fnames[i]; int is_in_curdir; - char_u *dir_end = (char_u *)gettail_dir((const char *)path); - char_u *pathsep_p; - char_u *path_cutoff; + char *dir_end = (char *)gettail_dir((const char *)path); + char *pathsep_p; + char *path_cutoff; - len = STRLEN(path); - is_in_curdir = FNAMENCMP(curdir, path, dir_end - path) == 0 + len = strlen(path); + is_in_curdir = path_fnamencmp(curdir, path, (size_t)(dir_end - path)) == 0 && curdir[dir_end - path] == NUL; if (is_in_curdir) { - in_curdir[i] = vim_strsave(path); + in_curdir[i] = xstrdup(path); } // Shorten the filename while maintaining its uniqueness @@ -998,20 +993,20 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) if (pattern[0] == '*' && pattern[1] == '*' && vim_ispathsep_nocolon(pattern[2]) && path_cutoff != NULL - && vim_regexec(®match, (char *)path_cutoff, (colnr_T)0) + && vim_regexec(®match, path_cutoff, (colnr_T)0) && is_unique(path_cutoff, gap, i)) { sort_again = true; - memmove(path, path_cutoff, STRLEN(path_cutoff) + 1); + memmove(path, path_cutoff, strlen(path_cutoff) + 1); } else { // Here all files can be reached without path, so get shortest // unique path. We start at the end of the path. */ pathsep_p = path + len - 1; while (find_previous_pathsep(path, &pathsep_p)) { - if (vim_regexec(®match, (char *)pathsep_p + 1, (colnr_T)0) + if (vim_regexec(®match, pathsep_p + 1, (colnr_T)0) && is_unique(pathsep_p + 1, gap, i) && path_cutoff != NULL && pathsep_p + 1 >= path_cutoff) { sort_again = true; - memmove(path, pathsep_p + 1, STRLEN(pathsep_p)); + memmove(path, pathsep_p + 1, strlen(pathsep_p)); break; } } @@ -1031,8 +1026,8 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) short_name = path_shorten_fname(path, curdir); if (short_name != NULL && short_name > path + 1) { STRCPY(path, "."); - add_pathsep((char *)path); - STRMOVE(path + STRLEN(path), short_name); + add_pathsep(path); + STRMOVE(path + strlen(path), short_name); } } os_breakcheck(); @@ -1040,15 +1035,15 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) // Shorten filenames in /in/current/directory/{filename} for (int i = 0; i < gap->ga_len && !got_int; i++) { - char_u *rel_path; - char_u *path = in_curdir[i]; + char *rel_path; + char *path = in_curdir[i]; if (path == NULL) { continue; } - /* If the {filename} is not unique, change it to ./{filename}. - * Else reduce it to {filename} */ + // If the {filename} is not unique, change it to ./{filename}. + // Else reduce it to {filename} short_name = path_shorten_fname(path, curdir); if (short_name == NULL) { short_name = path; @@ -1058,9 +1053,9 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) continue; } - rel_path = xmalloc(STRLEN(short_name) + STRLEN(PATHSEPSTR) + 2); + rel_path = xmalloc(strlen(short_name) + strlen(PATHSEPSTR) + 2); STRCPY(rel_path, "."); - add_pathsep((char *)rel_path); + add_pathsep(rel_path); STRCAT(rel_path, short_name); xfree(fnames[i]); @@ -1119,21 +1114,21 @@ const char *gettail_dir(const char *const fname) /// Returns the total number of matches. /// /// @param flags EW_* flags -static int expand_in_path(garray_T *const gap, char_u *const pattern, const int flags) +static int expand_in_path(garray_T *const gap, char *const pattern, const int flags) { garray_T path_ga; - char_u *const curdir = xmalloc(MAXPATHL); + char *const curdir = xmalloc(MAXPATHL); os_dirname(curdir, MAXPATHL); - ga_init(&path_ga, (int)sizeof(char_u *), 1); + ga_init(&path_ga, (int)sizeof(char *), 1); expand_path_option(curdir, &path_ga); xfree(curdir); if (GA_EMPTY(&path_ga)) { return 0; } - char_u *const paths = ga_concat_strings(&path_ga); + char *const paths = ga_concat_strings(&path_ga); ga_clear_strings(&path_ga); int glob_flags = 0; @@ -1143,22 +1138,20 @@ static int expand_in_path(garray_T *const gap, char_u *const pattern, const int if (flags & EW_ADDSLASH) { glob_flags |= WILD_ADD_SLASH; } - globpath((char *)paths, pattern, gap, glob_flags); + globpath(paths, pattern, gap, glob_flags); xfree(paths); return gap->ga_len; } -/* - * Return TRUE if "p" contains what looks like an environment variable. - * Allowing for escaping. - */ -static bool has_env_var(char_u *p) +/// Return true if "p" contains what looks like an environment variable. +/// Allowing for escaping. +static bool has_env_var(char *p) { for (; *p; MB_PTR_ADV(p)) { if (*p == '\\' && p[1] != NUL) { p++; - } else if (vim_strchr("$", *p) != NULL) { + } else if (vim_strchr("$", (uint8_t)(*p)) != NULL) { return true; } } @@ -1167,9 +1160,9 @@ static bool has_env_var(char_u *p) #ifdef SPECIAL_WILDCHAR -// Return TRUE if "p" contains a special wildcard character, one that Vim +// Return true if "p" contains a special wildcard character, one that Vim // cannot expand, requires using a shell. -static bool has_special_wildchar(char_u *p) +static bool has_special_wildchar(char *p) { for (; *p; MB_PTR_ADV(p)) { // Disallow line break characters. @@ -1179,13 +1172,13 @@ static bool has_special_wildchar(char_u *p) // Allow for escaping. if (*p == '\\' && p[1] != NUL && p[1] != '\r' && p[1] != '\n') { p++; - } else if (vim_strchr(SPECIAL_WILDCHAR, *p) != NULL) { + } else if (vim_strchr(SPECIAL_WILDCHAR, (uint8_t)(*p)) != NULL) { // A { must be followed by a matching }. - if (*p == '{' && vim_strchr((char *)p, '}') == NULL) { + if (*p == '{' && vim_strchr(p, '}') == NULL) { continue; } // A quote and backtick must be followed by another one. - if ((*p == '`' || *p == '\'') && vim_strchr((char *)p, *p) == NULL) { + if ((*p == '`' || *p == '\'') && vim_strchr(p, (uint8_t)(*p)) == NULL) { continue; } return true; @@ -1216,34 +1209,31 @@ static bool has_special_wildchar(char_u *p) int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, int flags) { garray_T ga; - char_u *p; + char *p; static bool recursive = false; int add_pat; bool did_expand_in_path = false; - /* - * expand_env() is called to expand things like "~user". If this fails, - * it calls ExpandOne(), which brings us back here. In this case, always - * call the machine specific expansion function, if possible. Otherwise, - * return FAIL. - */ - if (recursive) + // expand_env() is called to expand things like "~user". If this fails, + // it calls ExpandOne(), which brings us back here. In this case, always + // call the machine specific expansion function, if possible. Otherwise, + // return FAIL. + if (recursive) { #ifdef SPECIAL_WILDCHAR - { return os_expand_wildcards(num_pat, pat, num_file, file, flags); } + return os_expand_wildcards(num_pat, pat, num_file, file, flags); #else - { return FAIL; } + return FAIL; #endif + } #ifdef SPECIAL_WILDCHAR - /* - * If there are any special wildcard characters which we cannot handle - * here, call machine specific function for all the expansion. This - * avoids starting the shell for each argument separately. - * For `=expr` do use the internal function. - */ + // If there are any special wildcard characters which we cannot handle + // here, call machine specific function for all the expansion. This + // avoids starting the shell for each argument separately. + // For `=expr` do use the internal function. for (int i = 0; i < num_pat; i++) { - if (has_special_wildchar((char_u *)pat[i]) - && !(vim_backtick((char_u *)pat[i]) && pat[i][1] == '=')) { + if (has_special_wildchar(pat[i]) + && !(vim_backtick(pat[i]) && pat[i][1] == '=')) { return os_expand_wildcards(num_pat, pat, num_file, file, flags); } } @@ -1251,20 +1241,18 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i recursive = true; - /* - * The matching file names are stored in a growarray. Init it empty. - */ - ga_init(&ga, (int)sizeof(char_u *), 30); + // The matching file names are stored in a growarray. Init it empty. + ga_init(&ga, (int)sizeof(char *), 30); for (int i = 0; i < num_pat && !got_int; i++) { add_pat = -1; - p = (char_u *)pat[i]; + p = pat[i]; if (vim_backtick(p)) { add_pat = expand_backtick(&ga, p, flags); if (add_pat == -1) { recursive = false; - FreeWild(ga.ga_len, ga.ga_data); + ga_clear_strings(&ga); *num_file = 0; *file = NULL; return FAIL; @@ -1274,41 +1262,38 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i if ((has_env_var(p) && !(flags & EW_NOTENV)) || *p == '~') { p = expand_env_save_opt(p, true); if (p == NULL) { - p = (char_u *)pat[i]; - } + p = pat[i]; + } else { #ifdef UNIX - /* - * On Unix, if expand_env() can't expand an environment - * variable, use the shell to do that. Discard previously - * found file names and start all over again. - */ - else if (has_env_var(p) || *p == '~') { - xfree(p); - ga_clear_strings(&ga); - i = os_expand_wildcards(num_pat, pat, num_file, file, - flags | EW_KEEPDOLLAR); - recursive = false; - return i; - } + // On Unix, if expand_env() can't expand an environment + // variable, use the shell to do that. Discard previously + // found file names and start all over again. + if (has_env_var(p) || *p == '~') { + xfree(p); + ga_clear_strings(&ga); + i = os_expand_wildcards(num_pat, pat, num_file, file, + flags | EW_KEEPDOLLAR); + recursive = false; + return i; + } #endif + } } - /* - * If there are wildcards: Expand file names and add each match to - * the list. If there is no match, and EW_NOTFOUND is given, add - * the pattern. - * If there are no wildcards: Add the file name if it exists or - * when EW_NOTFOUND is given. - */ - if (path_has_exp_wildcard(p)) { + // If there are wildcards or case-insensitive expansion is + // required: Expand file names and add each match to the list. If + // there is no match, and EW_NOTFOUND is given, add the pattern. + // Otherwise: Add the file name if it exists or when EW_NOTFOUND is + // given. + if (path_has_exp_wildcard(p) || (flags & EW_ICASE)) { if ((flags & EW_PATH) && !path_is_absolute(p) && !(p[0] == '.' && (vim_ispathsep(p[1]) || (p[1] == '.' && vim_ispathsep(p[2]))))) { - /* :find completion where 'path' is used. - * Recursiveness is OK here. */ + // :find completion where 'path' is used. + // Recursiveness is OK here. recursive = false; add_pat = expand_in_path(&ga, p, flags); recursive = true; @@ -1322,10 +1307,10 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i } if (add_pat == -1 || (add_pat == 0 && (flags & EW_NOTFOUND))) { - char_u *t = backslash_halve_save(p); + char *t = backslash_halve_save(p); - /* When EW_NOTFOUND is used, always add files and dirs. Makes - * "vim c:/" work. */ + // When EW_NOTFOUND is used, always add files and dirs. Makes + // "vim c:/" work. if (flags & EW_NOTFOUND) { addfile(&ga, t, flags | EW_DIR | EW_FILE); } else { @@ -1340,7 +1325,7 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i if (did_expand_in_path && !GA_EMPTY(&ga) && (flags & EW_PATH)) { uniquefy_paths(&ga, p); } - if (p != (char_u *)pat[i]) { + if (p != pat[i]) { xfree(p); } } @@ -1365,12 +1350,10 @@ void FreeWild(int count, char **files) xfree(files); } -/* - * Return TRUE if we can expand this backtick thing here. - */ -static int vim_backtick(char_u *p) +/// @return true if we can expand this backtick thing here. +static int vim_backtick(char *p) { - return *p == '`' && *(p + 1) != NUL && *(p + STRLEN(p) - 1) == '`'; + return *p == '`' && *(p + 1) != NUL && *(p + strlen(p) - 1) == '`'; } /// Expand an item in `backticks` by executing it as a command. @@ -1378,20 +1361,19 @@ static int vim_backtick(char_u *p) /// Returns number of file names found, -1 if an error is encountered. /// /// @param flags EW_* flags -static int expand_backtick(garray_T *gap, char_u *pat, int flags) +static int expand_backtick(garray_T *gap, char *pat, int flags) { char *p; char *buffer; int cnt = 0; // Create the command: lop off the backticks. - char *cmd = (char *)vim_strnsave(pat + 1, STRLEN(pat) - 2); + char *cmd = xstrnsave(pat + 1, strlen(pat) - 2); if (*cmd == '=') { // `={expr}`: Expand expression buffer = eval_to_string(cmd + 1, &p, true); } else { - buffer = (char *)get_cmd_output((char_u *)cmd, NULL, (flags & EW_SILENT) ? kShellOptSilent : 0, - NULL); + buffer = get_cmd_output(cmd, NULL, (flags & EW_SILENT) ? kShellOptSilent : 0, NULL); } xfree(cmd); if (buffer == NULL) { @@ -1409,7 +1391,7 @@ static int expand_backtick(garray_T *gap, char_u *pat, int flags) if (p > cmd) { char i = *p; *p = NUL; - addfile(gap, (char_u *)cmd, flags); + addfile(gap, cmd, flags); *p = i; cnt++; } @@ -1432,23 +1414,23 @@ static int expand_backtick(garray_T *gap, char_u *pat, int flags) /// backslash twice. /// When 'shellslash' set do it the other way around. /// When the path looks like a URL leave it unmodified. -void slash_adjust(char_u *p) +void slash_adjust(char *p) { - if (path_with_url((const char *)p)) { + if (path_with_url(p)) { return; } if (*p == '`') { // don't replace backslash in backtick quoted strings - const size_t len = STRLEN(p); + const size_t len = strlen(p); if (len > 2 && *(p + len - 1) == '`') { return; } } while (*p) { - if (*p == (char_u)psepcN) { - *p = (char_u)psepc; + if (*p == psepcN) { + *p = psepc; } MB_PTR_ADV(p); } @@ -1464,7 +1446,7 @@ void slash_adjust(char_u *p) /// EW_ALLLINKS add symlink also when the referred file does not exist /// /// @param f filename -void addfile(garray_T *gap, char_u *f, int flags) +void addfile(garray_T *gap, char *f, int flags) { bool isdir; FileInfo file_info; @@ -1472,14 +1454,14 @@ void addfile(garray_T *gap, char_u *f, int flags) // if the file/dir/link doesn't exist, may not add it if (!(flags & EW_NOTFOUND) && ((flags & EW_ALLLINKS) - ? !os_fileinfo_link((char *)f, &file_info) + ? !os_fileinfo_link(f, &file_info) : !os_path_exists(f))) { return; } #ifdef FNAME_ILLEGAL // if the file/dir contains illegal characters, don't add it - if (strpbrk((char *)f, FNAME_ILLEGAL) != NULL) { + if (strpbrk(f, FNAME_ILLEGAL) != NULL) { return; } #endif @@ -1492,35 +1474,31 @@ void addfile(garray_T *gap, char_u *f, int flags) // If the file isn't executable, may not add it. Do accept directories. // When invoked from expand_shellcmd() do not use $PATH. if (!isdir && (flags & EW_EXEC) - && !os_can_exe((char *)f, NULL, !(flags & EW_SHELLCMD))) { + && !os_can_exe(f, NULL, !(flags & EW_SHELLCMD))) { return; } - char_u *p = xmalloc(STRLEN(f) + 1 + isdir); + char *p = xmalloc(strlen(f) + 1 + isdir); STRCPY(p, f); #ifdef BACKSLASH_IN_FILENAME slash_adjust(p); #endif - /* - * Append a slash or backslash after directory names if none is present. - */ + // Append a slash or backslash after directory names if none is present. if (isdir && (flags & EW_ADDSLASH)) { - add_pathsep((char *)p); + add_pathsep(p); } - GA_APPEND(char_u *, gap, p); + GA_APPEND(char *, gap, p); } -/* - * Converts a file name into a canonical form. It simplifies a file name into - * its simplest form by stripping out unneeded components, if any. The - * resulting file name is simplified in place and will either be the same - * length as that supplied, or shorter. - */ -void simplify_filename(char_u *filename) +// Converts a file name into a canonical form. It simplifies a file name into +// its simplest form by stripping out unneeded components, if any. The +// resulting file name is simplified in place and will either be the same +// length as that supplied, or shorter. +void simplify_filename(char *filename) { int components = 0; - char_u *p, *tail, *start; + char *p, *tail, *start; bool stripping_disabled = false; bool relative = true; @@ -1540,8 +1518,8 @@ void simplify_filename(char_u *filename) start = p; // remember start after "c:/" or "/" or "///" do { - /* At this point "p" is pointing to the char following a single "/" - * or "p" is at the "start" of the (absolute or relative) path name. */ + // At this point "p" is pointing to the char following a single "/" + // or "p" is at the "start" of the (absolute or relative) path name. if (vim_ispathsep(*p)) { STRMOVE(p, p + 1); // remove duplicate "/" } else if (p[0] == '.' @@ -1549,10 +1527,10 @@ void simplify_filename(char_u *filename) if (p == start && relative) { p += 1 + (p[1] != NUL); // keep single "." or leading "./" } else { - /* Strip "./" or ".///". If we are at the end of the file name - * and there is no trailing path separator, either strip "/." if - * we are after "start", or strip "." if we are at the beginning - * of an absolute path name . */ + // Strip "./" or ".///". If we are at the end of the file name + // and there is no trailing path separator, either strip "/." if + // we are after "start", or strip "." if we are at the beginning + // of an absolute path name. tail = p + 1; if (p[1] != NUL) { while (vim_ispathsep(*tail)) { @@ -1573,86 +1551,86 @@ void simplify_filename(char_u *filename) if (components > 0) { // strip one preceding component bool do_strip = false; - char_u saved_char; + char saved_char; // Don't strip for an erroneous file name. if (!stripping_disabled) { - /* If the preceding component does not exist in the file - * system, we strip it. On Unix, we don't accept a symbolic - * link that refers to a non-existent file. */ + // If the preceding component does not exist in the file + // system, we strip it. On Unix, we don't accept a symbolic + // link that refers to a non-existent file. saved_char = p[-1]; p[-1] = NUL; FileInfo file_info; - if (!os_fileinfo_link((char *)filename, &file_info)) { + if (!os_fileinfo_link(filename, &file_info)) { do_strip = true; } p[-1] = saved_char; p--; // Skip back to after previous '/'. - while (p > start && !after_pathsep((char *)start, (char *)p)) { + while (p > start && !after_pathsep(start, p)) { MB_PTR_BACK(start, p); } if (!do_strip) { - /* If the component exists in the file system, check - * that stripping it won't change the meaning of the - * file name. First get information about the - * unstripped file name. This may fail if the component - * to strip is not a searchable directory (but a regular - * file, for instance), since the trailing "/.." cannot - * be applied then. We don't strip it then since we - * don't want to replace an erroneous file name by - * a valid one, and we disable stripping of later - * components. */ + // If the component exists in the file system, check + // that stripping it won't change the meaning of the + // file name. First get information about the + // unstripped file name. This may fail if the component + // to strip is not a searchable directory (but a regular + // file, for instance), since the trailing "/.." cannot + // be applied then. We don't strip it then since we + // don't want to replace an erroneous file name by + // a valid one, and we disable stripping of later + // components. saved_char = *tail; *tail = NUL; - if (os_fileinfo((char *)filename, &file_info)) { + if (os_fileinfo(filename, &file_info)) { do_strip = true; } else { stripping_disabled = true; } *tail = saved_char; if (do_strip) { - /* The check for the unstripped file name - * above works also for a symbolic link pointing to - * a searchable directory. But then the parent of - * the directory pointed to by the link must be the - * same as the stripped file name. (The latter - * exists in the file system since it is the - * component's parent directory.) */ + // The check for the unstripped file name + // above works also for a symbolic link pointing to + // a searchable directory. But then the parent of + // the directory pointed to by the link must be the + // same as the stripped file name. (The latter + // exists in the file system since it is the + // component's parent directory.) FileInfo new_file_info; if (p == start && relative) { os_fileinfo(".", &new_file_info); } else { saved_char = *p; *p = NUL; - os_fileinfo((char *)filename, &new_file_info); + os_fileinfo(filename, &new_file_info); *p = saved_char; } if (!os_fileinfo_id_equal(&file_info, &new_file_info)) { do_strip = false; - /* We don't disable stripping of later - * components since the unstripped path name is - * still valid. */ + // We don't disable stripping of later + // components since the unstripped path name is + // still valid. } } } } if (!do_strip) { - /* Skip the ".." or "../" and reset the counter for the - * components that might be stripped later on. */ + // Skip the ".." or "../" and reset the counter for the + // components that might be stripped later on. p = tail; components = 0; } else { - /* Strip previous component. If the result would get empty - * and there is no trailing path separator, leave a single - * "." instead. If we are at the end of the file name and - * there is no trailing path separator and a preceding - * component is left after stripping, strip its trailing - * path separator as well. */ + // Strip previous component. If the result would get empty + // and there is no trailing path separator, leave a single + // "." instead. If we are at the end of the file name and + // there is no trailing path separator and a preceding + // component is left after stripping, strip its trailing + // path separator as well. if (p == start && relative && tail[-1] == '.') { *p++ = '.'; *p = NUL; @@ -1676,7 +1654,7 @@ void simplify_filename(char_u *filename) } } else { components++; // Simple path component. - p = (char_u *)path_next_component((const char *)p); + p = (char *)path_next_component(p); } } while (*p != NUL); } @@ -1684,7 +1662,7 @@ void simplify_filename(char_u *filename) static char *eval_includeexpr(const char *const ptr, const size_t len) { set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len); - char *res = eval_to_string_safe((char *)curbuf->b_p_inex, NULL, + char *res = eval_to_string_safe(curbuf->b_p_inex, NULL, was_set_insecurely(curwin, "includeexpr", OPT_LOCAL)); set_vim_var_string(VV_FNAME, NULL, 0); return res; @@ -1694,20 +1672,20 @@ static char *eval_includeexpr(const char *const ptr, const size_t len) /// Otherwise like file_name_at_cursor(). /// /// @param rel_fname file we are searching relative to -char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count, char_u *rel_fname) +char *find_file_name_in_path(char *ptr, size_t len, int options, long count, char *rel_fname) { - char_u *file_name; - char_u *tofree = NULL; + char *file_name; + char *tofree = NULL; if (len == 0) { return NULL; } if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { - tofree = (char_u *)eval_includeexpr((char *)ptr, len); + tofree = eval_includeexpr(ptr, len); if (tofree != NULL) { ptr = tofree; - len = STRLEN(ptr); + len = strlen(ptr); } } @@ -1715,35 +1693,34 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count, file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, true, rel_fname); - /* - * If the file could not be found in a normal way, try applying - * 'includeexpr' (unless done already). - */ + // If the file could not be found in a normal way, try applying + // 'includeexpr' (unless done already). if (file_name == NULL && !(options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { - tofree = (char_u *)eval_includeexpr((char *)ptr, len); + tofree = eval_includeexpr(ptr, len); if (tofree != NULL) { ptr = tofree; - len = STRLEN(ptr); + len = strlen(ptr); file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, - TRUE, rel_fname); + true, rel_fname); } } if (file_name == NULL && (options & FNAME_MESS)) { - char_u c = ptr[len]; + char c = ptr[len]; ptr[len] = NUL; semsg(_("E447: Can't find file \"%s\" in path"), ptr); ptr[len] = c; } - /* Repeat finding the file "count" times. This matters when it - * appears several times in the path. */ + // Repeat finding the file "count" times. This matters when it + // appears several times in the path. while (file_name != NULL && --count > 0) { xfree(file_name); - file_name = find_file_in_path(ptr, len, options, FALSE, rel_fname); + file_name = + find_file_in_path(ptr, len, options, false, rel_fname); } } else { - file_name = vim_strnsave(ptr, len); + file_name = xstrnsave(ptr, len); } xfree(tofree); @@ -1751,12 +1728,26 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count, return file_name; } -// Check if the "://" of a URL is at the pointer, return URL_SLASH. +/// Checks for a Windows drive letter ("C:/") at the start of the path. +/// +/// @see https://url.spec.whatwg.org/#start-with-a-windows-drive-letter +bool path_has_drive_letter(const char *p) + FUNC_ATTR_NONNULL_ALL +{ + return strlen(p) >= 2 + && ASCII_ISALPHA(p[0]) + && (p[1] == ':' || p[1] == '|') + && (strlen(p) == 2 || ((p[2] == '/') | (p[2] == '\\') | (p[2] == '?') | (p[2] == '#'))); +} + +// Check if the ":/" of a URL is at the pointer, return URL_SLASH. // Also check for ":\\", which MS Internet Explorer accepts, return // URL_BACKSLASH. int path_is_url(const char *p) { - if (strncmp(p, "://", 3) == 0) { + // In the spec ':' is enough to recognize a scheme + // https://url.spec.whatwg.org/#scheme-state + if (strncmp(p, ":/", 2) == 0) { return URL_SLASH; } else if (strncmp(p, ":\\\\", 3) == 0) { return URL_BACKSLASH; @@ -1777,19 +1768,23 @@ int path_with_url(const char *fname) // non-URL text. // first character must be alpha - if (!isalpha(*fname)) { + if (!ASCII_ISALPHA(*fname)) { + return 0; + } + + if (path_has_drive_letter(fname)) { return 0; } // check body: alpha or dash - for (p = fname + 1; (isalpha(*p) || (*p == '-')); p++) {} + for (p = fname + 1; (ASCII_ISALPHA(*p) || (*p == '-')); p++) {} // check last char is not a dash if (p[-1] == '-') { return 0; } - // "://" or ":\\" must follow + // ":/" or ":\\" must follow return path_is_url(p); } @@ -1802,12 +1797,10 @@ bool path_with_extension(const char *path, const char *extension) return strcmp(last_dot + 1, extension) == 0; } -/* - * Return TRUE if "name" is a full (absolute) path name or URL. - */ -bool vim_isAbsName(char_u *name) +/// Return true if "name" is a full (absolute) path name or URL. +bool vim_isAbsName(char *name) { - return path_with_url((char *)name) != 0 || path_is_absolute(name); + return path_with_url(name) != 0 || path_is_absolute(name); } /// Save absolute file name to "buf[len]". @@ -1830,8 +1823,8 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) if (strlen(fname) > (len - 1)) { xstrlcpy(buf, fname, len); // truncate -#ifdef WIN32 - slash_adjust((char_u *)buf); +#ifdef MSWIN + slash_adjust(buf); #endif return FAIL; } @@ -1841,12 +1834,12 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) return OK; } - int rv = path_to_absolute((char_u *)fname, (char_u *)buf, len, force); + int rv = path_to_absolute(fname, buf, len, force); if (rv == FAIL) { xstrlcpy(buf, fname, len); // something failed; use the filename } -#ifdef WIN32 - slash_adjust((char_u *)buf); +#ifdef MSWIN + slash_adjust(buf); #endif return rv; } @@ -1866,7 +1859,7 @@ char *fix_fname(const char *fname) #ifdef UNIX return FullName_save(fname, true); #else - if (!vim_isAbsName((char_u *)fname) + if (!vim_isAbsName((char *)fname) || strstr(fname, "..") != NULL || strstr(fname, "//") != NULL # ifdef BACKSLASH_IN_FILENAME @@ -1879,7 +1872,7 @@ char *fix_fname(const char *fname) fname = xstrdup(fname); # ifdef USE_FNAME_CASE - path_fix_case(fname); // set correct case for file name + path_fix_case((char *)fname); // set correct case for file name # endif return (char *)fname; @@ -1900,7 +1893,7 @@ void path_fix_case(char *name) } // Open the directory where the file is located. - char *slash = (char *)STRRCHR(name, '/'); + char *slash = strrchr(name, '/'); char *tail; Directory dir; bool ok; @@ -1922,13 +1915,13 @@ void path_fix_case(char *name) while ((entry = (char *)os_scandir_next(&dir))) { // Only accept names that differ in case and are the same byte // length. TODO: accept different length name. - if (STRICMP(tail, entry) == 0 && STRLEN(tail) == STRLEN(entry)) { - char_u newname[MAXPATHL + 1]; + if (STRICMP(tail, entry) == 0 && strlen(tail) == strlen(entry)) { + char newname[MAXPATHL + 1]; // Verify the inode is equal. - STRLCPY(newname, name, MAXPATHL + 1); - STRLCPY(newname + (tail - name), entry, - MAXPATHL - (tail - name) + 1); + xstrlcpy(newname, name, MAXPATHL + 1); + xstrlcpy(newname + (tail - name), entry, + (size_t)(MAXPATHL - (tail - name) + 1)); FileInfo file_info_new; if (os_fileinfo_link((char *)newname, &file_info_new) && os_fileinfo_id_equal(&file_info, &file_info_new)) { @@ -1941,22 +1934,18 @@ void path_fix_case(char *name) os_closedir(&dir); } -/* - * Return TRUE if "p" points to just after a path separator. - * Takes care of multi-byte characters. - * "b" must point to the start of the file name - */ +/// Return true if "p" points to just after a path separator. +/// Takes care of multi-byte characters. +/// "b" must point to the start of the file name int after_pathsep(const char *b, const char *p) { return p > b && vim_ispathsep(p[-1]) - && utf_head_off((char_u *)b, (char_u *)p - 1) == 0; + && utf_head_off(b, p - 1) == 0; } -/* - * Return TRUE if file names "f1" and "f2" are in the same directory. - * "f1" may be a short name, "f2" must be a full path. - */ -bool same_directory(char_u *f1, char_u *f2) +/// Return true if file names "f1" and "f2" are in the same directory. +/// "f1" may be a short name, "f2" must be a full path. +bool same_directory(char *f1, char *f2) { char ffname[MAXPATHL]; char *t1; @@ -1967,18 +1956,16 @@ bool same_directory(char_u *f1, char_u *f2) return false; } - (void)vim_FullName((char *)f1, (char *)ffname, MAXPATHL, FALSE); + (void)vim_FullName(f1, (char *)ffname, MAXPATHL, false); t1 = path_tail_with_sep(ffname); - t2 = path_tail_with_sep((char *)f2); - return t1 - ffname == (char_u *)t2 - f2 - && pathcmp((char *)ffname, (char *)f2, (int)(t1 - ffname)) == 0; + t2 = path_tail_with_sep(f2); + return t1 - ffname == t2 - f2 + && pathcmp((char *)ffname, f2, (int)(t1 - ffname)) == 0; } -/* - * Compare path "p[]" to "q[]". - * If "maxlen" >= 0 compare "p[maxlen]" to "q[maxlen]" - * Return value like strcmp(p, q), but consider path separators. - */ +// Compare path "p[]" to "q[]". +// If "maxlen" >= 0 compare "p[maxlen]" to "q[maxlen]" +// Return value like strcmp(p, q), but consider path separators. int pathcmp(const char *p, const char *q, int maxlen) { int i, j; @@ -2057,10 +2044,10 @@ int pathcmp(const char *p, const char *q, int maxlen) /// - Pointer into `full_path` if shortened. /// - `full_path` unchanged if no shorter name is possible. /// - NULL if `full_path` is NULL. -char_u *path_try_shorten_fname(char_u *full_path) +char *path_try_shorten_fname(char *full_path) { - char_u *dirname = xmalloc(MAXPATHL); - char_u *p = full_path; + char *dirname = xmalloc(MAXPATHL); + char *p = full_path; if (os_dirname(dirname, MAXPATHL) == OK) { p = path_shorten_fname(full_path, dirname); @@ -2079,14 +2066,14 @@ char_u *path_try_shorten_fname(char_u *full_path) /// @return /// - Pointer into `full_path` if shortened. /// - NULL if no shorter name is possible. -char_u *path_shorten_fname(char_u *full_path, char_u *dir_name) +char *path_shorten_fname(char *full_path, char *dir_name) { if (full_path == NULL) { return NULL; } assert(dir_name != NULL); - size_t len = STRLEN(dir_name); + size_t len = strlen(dir_name); // If dir_name is a path head, full_path can always be made relative. if (len == (size_t)path_head_length() && is_path_head(dir_name)) { @@ -2095,11 +2082,11 @@ char_u *path_shorten_fname(char_u *full_path, char_u *dir_name) // If full_path and dir_name do not match, it's impossible to make one // relative to the other. - if (FNAMENCMP(dir_name, full_path, len) != 0) { + if (path_fnamencmp(dir_name, full_path, len) != 0) { return NULL; } - char_u *p = full_path + len; + char *p = full_path + len; // If *p is not pointing to a path separator, this means that full_path's // last directory name is longer than *dir_name's last directory, so they @@ -2125,20 +2112,25 @@ char_u *path_shorten_fname(char_u *full_path, char_u *dir_name) /// If FAIL is returned, *num_file and *file are either /// unchanged or *num_file is set to 0 and *file is set /// to NULL or points to "". -int expand_wildcards_eval(char_u **pat, int *num_file, char ***file, int flags) +int expand_wildcards_eval(char **pat, int *num_file, char ***file, int flags) { int ret = FAIL; - char_u *eval_pat = NULL; - char *exp_pat = (char *)(*pat); + char *eval_pat = NULL; + char *exp_pat = *pat; char *ignored_msg; size_t usedlen; + const bool is_cur_alt_file = *exp_pat == '%' || *exp_pat == '#'; + bool star_follows = false; - if (*exp_pat == '%' || *exp_pat == '#' || *exp_pat == '<') { + if (is_cur_alt_file || *exp_pat == '<') { emsg_off++; - eval_pat = eval_vars((char_u *)exp_pat, (char_u *)exp_pat, &usedlen, NULL, &ignored_msg, NULL); + eval_pat = eval_vars(exp_pat, exp_pat, &usedlen, NULL, &ignored_msg, + NULL, + true); emsg_off--; if (eval_pat != NULL) { - exp_pat = (char *)concat_str(eval_pat, (char_u *)exp_pat + usedlen); + star_follows = strcmp(exp_pat + usedlen, "*") == 0; + exp_pat = concat_str(eval_pat, exp_pat + usedlen); } } @@ -2147,6 +2139,16 @@ int expand_wildcards_eval(char_u **pat, int *num_file, char ***file, int flags) } if (eval_pat != NULL) { + if (*num_file == 0 && is_cur_alt_file && star_follows) { + // Expanding "%" or "#" and the file does not exist: Add the + // pattern anyway (without the star) so that this works for remote + // files and non-file buffer names. + *file = xmalloc(sizeof(char *)); + **file = eval_pat; + eval_pat = NULL; + *num_file = 1; + ret = OK; + } xfree(exp_pat); xfree(eval_pat); } @@ -2172,7 +2174,7 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int { int retval; int i, j; - char_u *p; + char *p; int non_suf_match; // number without matching suffix retval = gen_expand_wildcards(num_pat, pat, num_files, files, flags); @@ -2182,19 +2184,17 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int return retval; } - /* - * Remove names that match 'wildignore'. - */ + // Remove names that match 'wildignore'. if (*p_wig) { - char_u *ffname; + char *ffname; // check all files in (*files)[] assert(*num_files == 0 || *files != NULL); for (i = 0; i < *num_files; i++) { - ffname = (char_u *)FullName_save((*files)[i], false); + ffname = FullName_save((*files)[i], false); assert((*files)[i] != NULL); assert(ffname != NULL); - if (match_file_list(p_wig, (char_u *)(*files)[i], ffname)) { + if (match_file_list(p_wig, (*files)[i], ffname)) { // remove this matching file from the list xfree((*files)[i]); for (j = i; j + 1 < *num_files; j++) { @@ -2213,13 +2213,13 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int if (*num_files > 1 && !got_int) { non_suf_match = 0; for (i = 0; i < *num_files; i++) { - if (!match_suffix((char_u *)(*files)[i])) { + if (!match_suffix((*files)[i])) { // Move the name without matching suffix to the front of the list. - p = (char_u *)(*files)[i]; + p = (*files)[i]; for (j = i; j > non_suf_match; j--) { (*files)[j] = (*files)[j - 1]; } - (*files)[non_suf_match++] = (char *)p; + (*files)[non_suf_match++] = p; } } } @@ -2233,29 +2233,27 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int return retval; } -/* - * Return TRUE if "fname" matches with an entry in 'suffixes'. - */ -int match_suffix(char_u *fname) +/// @return true if "fname" matches with an entry in 'suffixes'. +int match_suffix(char *fname) { #define MAXSUFLEN 30 // maximum length of a file suffix - char_u suf_buf[MAXSUFLEN]; + char suf_buf[MAXSUFLEN]; - size_t fnamelen = STRLEN(fname); + size_t fnamelen = strlen(fname); size_t setsuflen = 0; - for (char_u *setsuf = p_su; *setsuf;) { - setsuflen = copy_option_part((char **)&setsuf, (char *)suf_buf, MAXSUFLEN, ".,"); + for (char *setsuf = p_su; *setsuf;) { + setsuflen = copy_option_part(&setsuf, suf_buf, MAXSUFLEN, ".,"); if (setsuflen == 0) { - char_u *tail = (char_u *)path_tail((char *)fname); + char *tail = path_tail(fname); // empty entry: match name without a '.' - if (vim_strchr((char *)tail, '.') == NULL) { + if (vim_strchr(tail, '.') == NULL) { setsuflen = 1; break; } } else { if (fnamelen >= setsuflen - && FNAMENCMP(suf_buf, fname + fnamelen - setsuflen, setsuflen) == 0) { + && path_fnamencmp(suf_buf, fname + fnamelen - setsuflen, setsuflen) == 0) { break; } setsuflen = 0; @@ -2273,14 +2271,14 @@ int path_full_dir_name(char *directory, char *buffer, size_t len) int SUCCESS = 0; int retval = OK; - if (STRLEN(directory) == 0) { - return os_dirname((char_u *)buffer, len); + if (strlen(directory) == 0) { + return os_dirname(buffer, len); } char old_dir[MAXPATHL]; // Get current directory name. - if (os_dirname((char_u *)old_dir, MAXPATHL) == FAIL) { + if (os_dirname(old_dir, MAXPATHL) == FAIL) { return FAIL; } @@ -2293,14 +2291,14 @@ int path_full_dir_name(char *directory, char *buffer, size_t len) // Path does not exist (yet). For a full path fail, // will use the path as-is. For a relative path use // the current directory and append the file name. - if (path_is_absolute((const char_u *)directory)) { + if (path_is_absolute(directory)) { // Do not return immediately since we may be in the wrong directory. retval = FAIL; } else { xstrlcpy(buffer, old_dir, len); append_path(buffer, directory, len); } - } else if (os_dirname((char_u *)buffer, len) == FAIL) { + } else if (os_dirname(buffer, len) == FAIL) { // Do not return immediately since we are in the wrong directory. retval = FAIL; } @@ -2354,9 +2352,9 @@ int append_path(char *path, const char *to_append, size_t max_len) /// @param force also expand when "fname" is already absolute. /// /// @return FAIL for failure, OK for success. -static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, int force) +static int path_to_absolute(const char *fname, char *buf, size_t len, int force) { - char_u *p; + char *p; *buf = NUL; char *relative_directory = xmalloc(len); @@ -2364,49 +2362,42 @@ static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, int fo // expand it if forced or not an absolute path if (force || !path_is_absolute(fname)) { - p = STRRCHR(fname, '/'); -#ifdef WIN32 + p = strrchr(fname, '/'); +#ifdef MSWIN if (p == NULL) { - p = STRRCHR(fname, '\\'); + p = strrchr(fname, '\\'); } #endif if (p != NULL) { - // relative to root - if (p == fname) { - // only one path component - relative_directory[0] = PATHSEP; - relative_directory[1] = NUL; - } else { - assert(p >= fname); - memcpy(relative_directory, fname, (size_t)(p - fname)); - relative_directory[p - fname] = NUL; - } - end_of_path = (char *)(p + 1); + assert(p >= fname); + memcpy(relative_directory, fname, (size_t)(p - fname + 1)); + relative_directory[p - fname + 1] = NUL; + end_of_path = p + 1; } else { relative_directory[0] = NUL; end_of_path = (char *)fname; } - if (FAIL == path_full_dir_name(relative_directory, (char *)buf, len)) { + if (FAIL == path_full_dir_name(relative_directory, buf, len)) { xfree(relative_directory); return FAIL; } } xfree(relative_directory); - return append_path((char *)buf, end_of_path, len); + return append_path(buf, end_of_path, len); } /// Check if file `fname` is a full (absolute) path. /// -/// @return `TRUE` if "fname" is absolute. -int path_is_absolute(const char_u *fname) +/// @return `true` if "fname" is absolute. +int path_is_absolute(const char *fname) { -#ifdef WIN32 +#ifdef MSWIN if (*fname == NUL) { return false; } // A name like "d:/foo" and "//server/share" is absolute - return ((isalpha(fname[0]) && fname[1] == ':' && vim_ispathsep_nocolon(fname[2])) + return ((isalpha((uint8_t)fname[0]) && fname[1] == ':' && vim_ispathsep_nocolon(fname[2])) || (vim_ispathsep_nocolon(fname[0]) && fname[0] == fname[1])); #else // UNIX: This just checks if the file name starts with '/' or '~'. @@ -2426,11 +2417,11 @@ void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) { const char *path = os_getenv("PATH"); - if (path == NULL || path_is_absolute((char_u *)argv0)) { + if (path == NULL || path_is_absolute(argv0)) { xstrlcpy(buf, argv0, bufsize); } else if (argv0[0] == '.' || strchr(argv0, PATHSEP)) { // Relative to CWD. - if (os_dirname((char_u *)buf, MAXPATHL) != OK) { + if (os_dirname(buf, MAXPATHL) != OK) { buf[0] = NUL; } xstrlcat(buf, PATHSEPSTR, bufsize); @@ -2448,11 +2439,11 @@ void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) if (dir_len + 1 > sizeof(NameBuff)) { continue; } - STRLCPY(NameBuff, dir, dir_len + 1); - STRLCAT(NameBuff, PATHSEPSTR, sizeof(NameBuff)); - STRLCAT(NameBuff, argv0, sizeof(NameBuff)); - if (os_can_exe((char *)NameBuff, NULL, false)) { - xstrlcpy(buf, (char *)NameBuff, bufsize); + xstrlcpy(NameBuff, dir, dir_len + 1); + xstrlcat(NameBuff, PATHSEPSTR, sizeof(NameBuff)); + xstrlcat(NameBuff, argv0, sizeof(NameBuff)); + if (os_can_exe(NameBuff, NULL, false)) { + xstrlcpy(buf, NameBuff, bufsize); return; } } while (iter != NULL); diff --git a/src/nvim/path.h b/src/nvim/path.h index 1006ef9ffb..c8d192dffe 100644 --- a/src/nvim/path.h +++ b/src/nvim/path.h @@ -18,8 +18,8 @@ #define EW_NOERROR 0x200 // no error for bad regexp #define EW_NOTWILD 0x400 // add match with literal name if exists #define EW_KEEPDOLLAR 0x800 // do not escape $, $var is expanded -/* Note: mostly EW_NOTFOUND and EW_SILENT are mutually exclusive: EW_NOTFOUND -* is used when executing commands and EW_SILENT for interactive expanding. */ +// Note: mostly EW_NOTFOUND and EW_SILENT are mutually exclusive: EW_NOTFOUND +// is used when executing commands and EW_SILENT for interactive expanding. #define EW_ALLLINKS 0x1000 // also links not pointing to existing file #define EW_SHELLCMD 0x2000 // called from expand_shellcmd(), don't check // if executable is in $PATH @@ -39,4 +39,4 @@ typedef enum file_comparison { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "path.h.generated.h" #endif -#endif +#endif // NVIM_PATH_H diff --git a/src/nvim/plines.c b/src/nvim/plines.c index 70bdbd8b1d..5469d94800 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -10,25 +10,20 @@ #include <string.h> #include "nvim/ascii.h" -#include "nvim/buffer.h" #include "nvim/charset.h" -#include "nvim/cursor.h" #include "nvim/decoration.h" #include "nvim/diff.h" #include "nvim/fold.h" -#include "nvim/func_attr.h" +#include "nvim/globals.h" #include "nvim/indent.h" -#include "nvim/main.h" +#include "nvim/macros.h" #include "nvim/mbyte.h" #include "nvim/memline.h" -#include "nvim/memory.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" -#include "nvim/screen.h" -#include "nvim/strings.h" +#include "nvim/pos.h" #include "nvim/vim.h" -#include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "plines.c.generated.h" @@ -37,6 +32,9 @@ /// Functions calculating vertical size of text when displayed inside a window. /// Calls horizontal size functions defined below. +/// Return the number of window lines occupied by buffer line "lnum". +/// Includes any filler lines. +/// /// @param winheight when true limit to window height int plines_win(win_T *wp, linenr_T lnum, bool winheight) { @@ -53,7 +51,7 @@ int plines_win(win_T *wp, linenr_T lnum, bool winheight) /// @return Number of filler lines above lnum int win_get_fill(win_T *wp, linenr_T lnum) { - int virt_lines = decor_virt_lines(wp, lnum, NULL); + int virt_lines = decor_virt_lines(wp, lnum, NULL, kNone); // be quick when there are no filler lines if (diffopt_filler()) { @@ -71,6 +69,9 @@ bool win_may_fill(win_T *wp) return (wp->w_p_diff && diffopt_filler()) || wp->w_buffer->b_virt_line_blocks; } +/// Return the number of window lines occupied by buffer line "lnum". +/// Does not include filler lines. +/// /// @param winheight when true limit to window height int plines_win_nofill(win_T *wp, linenr_T lnum, bool winheight) { @@ -98,7 +99,7 @@ int plines_win_nofill(win_T *wp, linenr_T lnum, bool winheight) /// "wp". Does not care about folding, 'wrap' or 'diff'. int plines_win_nofold(win_T *wp, linenr_T lnum) { - char_u *s; + char *s; unsigned int col; int width; @@ -106,7 +107,7 @@ int plines_win_nofold(win_T *wp, linenr_T lnum) if (*s == NUL) { // empty line return 1; } - col = win_linetabsize(wp, s, MAXCOL); + col = win_linetabsize(wp, lnum, s, MAXCOL); // If list mode is on, then the '$' at the end of the line may take up one // extra column. @@ -144,24 +145,28 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column) return lines + 1; } - char_u *line = ml_get_buf(wp->w_buffer, lnum, false); - char_u *s = line; + char *line = ml_get_buf(wp->w_buffer, lnum, false); colnr_T col = 0; - while (*s != NUL && --column >= 0) { - col += win_lbr_chartabsize(wp, line, s, col, NULL); - MB_PTR_ADV(s); + chartabsize_T cts; + + 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); } - // If *s 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 + // If *cts.cts_ptr 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. - if (*s == TAB && (State & MODE_NORMAL) + 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(wp, line, s, col, NULL) - 1; + col += win_lbr_chartabsize(&cts, NULL) - 1; } + clear_chartabsize_arg(&cts); // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc. int width = wp->w_width_inner - win_col_off(wp); @@ -223,14 +228,13 @@ int plines_m_win(win_T *wp, linenr_T first, linenr_T last) /// @param col /// /// @return Number of characters. -int win_chartabsize(win_T *wp, char_u *p, colnr_T col) +int win_chartabsize(win_T *wp, char *p, colnr_T col) { buf_T *buf = wp->w_buffer; if (*p == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { return tabstop_padding(col, buf->b_p_ts, buf->b_p_vts_array); - } else { - return ptr2cells((char *)p); } + return ptr2cells(p); } /// Return the number of characters the string 's' will take on the screen, @@ -239,26 +243,26 @@ int win_chartabsize(win_T *wp, char_u *p, colnr_T col) /// @param s /// /// @return Number of characters the string will take on the screen. -int linetabsize(char_u *s) +int linetabsize(char *s) { return linetabsize_col(0, s); } -/// Like linetabsize(), but starting at column "startcol". +/// Like linetabsize(), but "s" starts at column "startcol". /// /// @param startcol /// @param s /// /// @return Number of characters the string will take on the screen. -int linetabsize_col(int startcol, char_u *s) +int linetabsize_col(int startcol, char *s) { - colnr_T col = startcol; - char_u *line = s; // pointer to start of line, for breakindent - - while (*s != NUL) { - col += lbr_chartabsize_adv(line, &s, col); + chartabsize_T cts; + init_chartabsize_arg(&cts, curwin, 0, startcol, s, s); + while (*cts.cts_ptr != NUL) { + cts.cts_vcol += lbr_chartabsize_adv(&cts); } - return (int)col; + clear_chartabsize_arg(&cts); + return cts.cts_vcol; } /// Like linetabsize(), but for a given window instead of the current one. @@ -268,19 +272,38 @@ int linetabsize_col(int startcol, char_u *s) /// @param len /// /// @return Number of characters the string will take on the screen. -unsigned int win_linetabsize(win_T *wp, char_u *line, colnr_T len) +unsigned int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len) { - colnr_T col = 0; - - for (char_u *s = line; - *s != NUL && (len == MAXCOL || s < line + len); - MB_PTR_ADV(s)) { - col += win_lbr_chartabsize(wp, line, s, col, NULL); + chartabsize_T cts; + init_chartabsize_arg(&cts, wp, lnum, 0, line, line); + for (; *cts.cts_ptr != NUL && (len == MAXCOL || cts.cts_ptr < line + len); + MB_PTR_ADV(cts.cts_ptr)) { + cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); } + clear_chartabsize_arg(&cts); + return (unsigned int)cts.cts_vcol; +} - return (unsigned int)col; +/// Prepare the structure passed to chartabsize functions. +/// +/// "line" is the start of the line, "ptr" is the first relevant character. +/// When "lnum" is zero do not use text properties that insert text. +void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum FUNC_ATTR_UNUSED, + colnr_T col, char *line, char *ptr) +{ + cts->cts_win = wp; + cts->cts_vcol = col; + cts->cts_line = line; + cts->cts_ptr = ptr; + cts->cts_cur_text_width = 0; + // TODO(bfredl): actually lookup inline virtual text here + cts->cts_has_virt_text = false; } +/// 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 line @@ -288,16 +311,16 @@ unsigned int win_linetabsize(win_T *wp, char_u *line, colnr_T len) /// @param col /// /// @return The number of characters taken up on the screen. -int lbr_chartabsize(char_u *line, unsigned char *s, colnr_T col) +int lbr_chartabsize(chartabsize_T *cts) { if (!curwin->w_p_lbr && *get_showbreak_value(curwin) == NUL - && !curwin->w_p_bri) { + && !curwin->w_p_bri && !cts->cts_has_virt_text) { if (curwin->w_p_wrap) { - return win_nolbr_chartabsize(curwin, s, col, NULL); + return win_nolbr_chartabsize(cts, NULL); } - return win_chartabsize(curwin, s, col); + return win_chartabsize(curwin, cts->cts_ptr, cts->cts_vcol); } - return win_lbr_chartabsize(curwin, line == NULL ? s: line, s, col, NULL); + return win_lbr_chartabsize(cts, NULL); } /// Call lbr_chartabsize() and advance the pointer. @@ -307,12 +330,12 @@ int lbr_chartabsize(char_u *line, unsigned char *s, colnr_T col) /// @param col /// /// @return The number of characters take up on the screen. -int lbr_chartabsize_adv(char_u *line, char_u **s, colnr_T col) +int lbr_chartabsize_adv(chartabsize_T *cts) { int retval; - retval = lbr_chartabsize(line, *s, col); - MB_PTR_ADV(*s); + retval = lbr_chartabsize(cts); + MB_PTR_ADV(cts->cts_ptr); return retval; } @@ -322,35 +345,44 @@ int lbr_chartabsize_adv(char_u *line, char_u **s, colnr_T col) /// string at start of line. Warning: *headp is only set if it's a non-zero /// value, init to 0 before calling. /// -/// @param wp -/// @param line -/// @param s -/// @param col +/// @param cts /// @param headp /// /// @return The number of characters taken up on the screen. -int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *headp) +int win_lbr_chartabsize(chartabsize_T *cts, int *headp) { + 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; + colnr_T col2; - colnr_T col_adj = 0; // col + screen size of tab + colnr_T col_adj = 0; // vcol + screen size of tab colnr_T colmax; int added; int mb_added = 0; int numberextra; - char_u *ps; + char *ps; int n; + cts->cts_cur_text_width = 0; + // No 'linebreak', 'showbreak' and 'breakindent': return quickly. - if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL) { + 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(wp, s, col, headp); + return win_nolbr_chartabsize(cts, headp); } - return win_chartabsize(wp, s, col); + return win_chartabsize(wp, s, vcol); + } + + // First get normal size, without 'linebreak' or virtual text + int size = win_chartabsize(wp, s, vcol); + if (cts->cts_has_virt_text) { + // TODO(bfredl): inline virtual text } - // First get normal size, without 'linebreak' - int size = win_chartabsize(wp, s, col); - int c = *s; + int c = (uint8_t)(*s); if (*s == TAB) { col_adj = size - 1; } @@ -359,45 +391,45 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he // needs a break here if (wp->w_p_lbr && vim_isbreak(c) - && !vim_isbreak((int)s[1]) + && !vim_isbreak((uint8_t)s[1]) && wp->w_p_wrap && (wp->w_width_inner != 0)) { // Count all characters from first non-blank after a blank up to next // non-blank after a blank. numberextra = win_col_off(wp); - col2 = col; + col2 = vcol; colmax = (colnr_T)(wp->w_width_inner - numberextra - col_adj); - if (col >= colmax) { + if (vcol >= colmax) { colmax += col_adj; n = colmax + win_col_off2(wp); if (n > 0) { - colmax += (((col - colmax) / n) + 1) * n - col_adj; + colmax += (((vcol - colmax) / n) + 1) * n - col_adj; } } for (;;) { ps = s; MB_PTR_ADV(s); - c = *s; + c = (uint8_t)(*s); if (!(c != NUL - && (vim_isbreak(c) || col2 == col || !vim_isbreak((int)(*ps))))) { + && (vim_isbreak(c) || col2 == vcol || !vim_isbreak((uint8_t)(*ps))))) { break; } col2 += win_chartabsize(wp, s, col2); if (col2 >= colmax) { // doesn't fit - size = colmax - col + col_adj; + size = colmax - vcol + col_adj; break; } } } else if ((size == 2) - && (MB_BYTE2LEN(*s) > 1) + && (MB_BYTE2LEN((uint8_t)(*s)) > 1) && wp->w_p_wrap - && in_win_border(wp, col)) { + && in_win_border(wp, vcol)) { // Count the ">" in the last column. size++; mb_added = 1; @@ -406,43 +438,43 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he // May have to add something for 'breakindent' and/or 'showbreak' // string at start of line. // Set *headp to the size of what we add. + // Do not use 'showbreak' at the NUL after the text. added = 0; - - char *const sbr = (char *)get_showbreak_value(wp); - if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && col != 0) { + char *const sbr = c == NUL ? empty_option : get_showbreak_value(wp); + if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && vcol != 0) { colnr_T sbrlen = 0; int numberwidth = win_col_off(wp); numberextra = numberwidth; - col += numberextra + mb_added; + vcol += numberextra + mb_added; - if (col >= (colnr_T)wp->w_width_inner) { - col -= wp->w_width_inner; + if (vcol >= (colnr_T)wp->w_width_inner) { + vcol -= wp->w_width_inner; numberextra = wp->w_width_inner - (numberextra - win_col_off2(wp)); - if (col >= numberextra && numberextra > 0) { - col %= numberextra; + if (vcol >= numberextra && numberextra > 0) { + vcol %= numberextra; } if (*sbr != NUL) { - sbrlen = (colnr_T)mb_charlen((char_u *)sbr); - if (col >= sbrlen) { - col -= sbrlen; + sbrlen = (colnr_T)mb_charlen(sbr); + if (vcol >= sbrlen) { + vcol -= sbrlen; } } - if (col >= numberextra && numberextra > 0) { - col %= numberextra; - } else if (col > 0 && numberextra > 0) { - col += numberwidth - win_col_off2(wp); + if (vcol >= numberextra && numberextra > 0) { + vcol %= numberextra; + } else if (vcol > 0 && numberextra > 0) { + vcol += numberwidth - win_col_off2(wp); } numberwidth -= win_col_off2(wp); } - if (col == 0 || (col + size + sbrlen > (colnr_T)wp->w_width_inner)) { + if (vcol == 0 || (vcol + size + sbrlen > (colnr_T)wp->w_width_inner)) { if (*sbr != NUL) { if (size + sbrlen + numberwidth > (colnr_T)wp->w_width_inner) { // Calculate effective window width. int width = (colnr_T)wp->w_width_inner - sbrlen - numberwidth; - int prev_width = col ? ((colnr_T)wp->w_width_inner - (sbrlen + col)) + int prev_width = vcol ? ((colnr_T)wp->w_width_inner - (sbrlen + vcol)) : 0; if (width <= 0) { @@ -463,7 +495,7 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he } size += added; - if (col != 0) { + if (vcol != 0) { added = 0; } } @@ -485,8 +517,11 @@ int win_lbr_chartabsize(win_T *wp, char_u *line, char_u *s, colnr_T col, int *he /// @param headp /// /// @return The number of characters take up on the screen. -static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int *headp) +static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp) { + win_T *wp = cts->cts_win; + char *s = cts->cts_ptr; + colnr_T col = cts->cts_vcol; int n; if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { @@ -494,11 +529,11 @@ static int win_nolbr_chartabsize(win_T *wp, char_u *s, colnr_T col, int *headp) wp->w_buffer->b_p_ts, wp->w_buffer->b_p_vts_array); } - n = ptr2cells((char *)s); + 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(*s) > 1) && in_win_border(wp, col)) { + if ((n == 2) && (MB_BYTE2LEN((uint8_t)(*s)) > 1) && in_win_border(wp, col)) { if (headp != NULL) { *headp = 1; } diff --git a/src/nvim/plines.h b/src/nvim/plines.h index 32778b69f1..808f6d284e 100644 --- a/src/nvim/plines.h +++ b/src/nvim/plines.h @@ -1,8 +1,24 @@ #ifndef NVIM_PLINES_H #define NVIM_PLINES_H +#include <stdbool.h> + +#include "nvim/buffer_defs.h" #include "nvim/vim.h" +// Argument for lbr_chartabsize(). +typedef struct { + win_T *cts_win; + char *cts_line; // start of the line + char *cts_ptr; // current position in line + + bool cts_has_virt_text; // true if if a property inserts text + int cts_cur_text_width; // width of current inserted text + // TODO(bfredl): iterator in to the marktree for scanning virt text + + int cts_vcol; // virtual column at current position +} chartabsize_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "plines.h.generated.h" #endif diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt index 28f5723046..1db21880bb 100644 --- a/src/nvim/po/CMakeLists.txt +++ b/src/nvim/po/CMakeLists.txt @@ -37,7 +37,6 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) set(ENV{OLD_PO_FILE_INPUT} yes) set(ENV{OLD_PO_FILE_OUTPUT} yes) - set(NVIM_RELATIVE_SOURCES) foreach(SRC ${NVIM_SOURCES} ${NVIM_HEADERS}) file(RELATIVE_PATH RELATIVE_SRC ${CMAKE_CURRENT_SOURCE_DIR} ${SRC}) @@ -46,18 +45,19 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) set(NVIM_POT ${CMAKE_CURRENT_BINARY_DIR}/nvim.pot) + list(SORT NVIM_RELATIVE_SOURCES) add_custom_command( - OUTPUT nvim.pot - COMMAND ${CMAKE_COMMAND} - -DXGETTEXT_PRG=${XGETTEXT_PRG} - -DPOT_FILE=${NVIM_POT} - -DSEARCH_DIR=${CMAKE_CURRENT_SOURCE_DIR} - "\"-DSOURCES=${NVIM_RELATIVE_SOURCES}\"" - -P ${PROJECT_SOURCE_DIR}/cmake/RunXgettext.cmake - DEPENDS ${NVIM_SOURCES}) - - add_custom_target(potfile DEPENDS ${NVIM_POT}) - set_target_properties(potfile PROPERTIES FOLDER po) + OUTPUT ${NVIM_POT} + COMMAND $<TARGET_FILE:nvim> -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+=+" + -S ${CMAKE_CURRENT_SOURCE_DIR}/fixfilenames.vim ${NVIM_POT} ../../../runtime/optwin.vim + VERBATIM + DEPENDS ${NVIM_SOURCES} nvim nvim_runtime_deps) set(LANGUAGE_MO_FILES) set(UPDATE_PO_TARGETS) @@ -68,11 +68,7 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) add_custom_command( OUTPUT ${moFile} - COMMAND ${CMAKE_COMMAND} - -DMSGFMT_PRG=${GETTEXT_MSGFMT_EXECUTABLE} - -DMO_FILE=${moFile} - -DPO_FILE=${poFile} - -P ${PROJECT_SOURCE_DIR}/cmake/RunMsgfmt.cmake + COMMAND ${GETTEXT_MSGFMT_EXECUTABLE} -o ${moFile} ${poFile} DEPENDS ${poFile} ${NVIM_POT}) install_helper( @@ -101,8 +97,6 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) lang inputName outputName inputEnc outputEnc outputCharSet) set(inputFile ${CMAKE_CURRENT_SOURCE_DIR}/${inputName}.po) set(outputFile ${CMAKE_CURRENT_SOURCE_DIR}/${outputName}.po) - string(TOUPPER ${inputEnc} upperInputEnc) - string(TOLOWER ${inputEnc} lowerInputEnc) add_custom_target(update-po-${lang} COMMAND ${CMAKE_COMMAND} @@ -172,11 +166,8 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) set(poFile "${CMAKE_CURRENT_SOURCE_DIR}/${LANGUAGE}.po") add_custom_target(update-po-${LANGUAGE} - COMMAND ${CMAKE_COMMAND} - -DMSGMERGE_PRG=${GETTEXT_MSGMERGE_EXECUTABLE} - -DPO_FILE=${poFile} - -DPOT_FILE=${NVIM_POT} - -P ${PROJECT_SOURCE_DIR}/cmake/RunMsgmerge.cmake + COMMAND ${GETTEXT_MSGMERGE_EXECUTABLE} -q --update --backup=none --sort-by-file + ${poFile} ${NVIM_POT} COMMENT "Updating ${LANGUAGE}.po" DEPENDS ${NVIM_POT}) diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po index 82345f8a46..92a1a6ca3c 100644 --- a/src/nvim/po/af.po +++ b/src/nvim/po/af.po @@ -2794,101 +2794,6 @@ msgstr "soektog het BO getref, gaan voort van ONDER af" msgid "search hit BOTTOM, continuing at TOP" msgstr "soektog het ONDER getref, gaan voort van BO af" -msgid "E550: Missing colon" -msgstr "E550: Ontbrekende dubbelpunt" - -msgid "E551: Illegal component" -msgstr "E551: Ongeldige komponent" - -msgid "E552: digit expected" -msgstr "E552: syfer verwag" - -#, c-format -msgid "Page %d" -msgstr "Bladsy %d" - -msgid "No text to be printed" -msgstr "Geen teks om te druk nie" - -#, fuzzy, c-format -#~ msgid "Printing page %d (%zu%%)" -#~ msgstr "Druk nou bladsy %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Kopie %d van %d" - -#, c-format -msgid "Printed: %s" -msgstr "Gedruk: %s" - -msgid "Printing aborted" -msgstr "Drukkery gestaak" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Kan nie na 'PostScript' afvoerler skryf nie" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Kan nie ler \"%s\" oopmaak nie" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Kan nie 'PostScript' hulpbron-ler \"%s\" lees nie" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: Ler \"%s\" is nie 'n 'PostScript' hulpbron-ler nie" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "" -"E619: Ler \"%s\" is nie 'n ondersteunde 'PostScript' hulpbron-ler nie" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" die hulpbron ler het die verkeerde weergawe" - -#~ msgid "E673: Incompatible multi-byte encoding and character set." -#~ msgstr "" - -#~ msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -#~ msgstr "" - -#~ msgid "E675: No default font specified for multi-byte printing." -#~ msgstr "" - -msgid "E324: Can't open PostScript output file" -msgstr "E324: Kan nie 'PostScript' afvoerler oopmaak nie" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Kan nie ler %s oopmaak nie" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Kan nie 'PostScript' hulpbron-ler \"prolog.ps\" lees nie" - -#, fuzzy -#~ msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -#~ msgstr "E456: Kan nie 'PostScript' hulpbron-ler \"%s\" vind nie" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Kan nie 'PostScript' hulpbron-ler \"%s\" vind nie" - -#, fuzzy, c-format -#~ msgid "E620: Unable to convert to print encoding \"%s\"" -#~ msgstr "E620: Kon nie van wye-greep na \"%s\" enkodering verander nie" - -msgid "Sending to printer..." -msgstr "Besig om te stuur na drukker..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: Kon nie 'PostScript' ler druk nie" - -msgid "Print job sent." -msgstr "Druktaak gestuur." - msgid "Add a new database" msgstr "Voeg 'n nuwe databasis by" @@ -5320,8 +5225,8 @@ msgstr "E424: Te veel verskillende uitlig-eienskappe in gebruik" msgid "E669: Unprintable character in group name" msgstr "E669: Onvertoonbare karakter in groepnaam" -msgid "W18: Invalid character in group name" -msgstr "W18: Ongeldige karakter groepnaam" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ongeldige karakter groepnaam" #~ msgid "E849: Too many highlight and syntax groups" #~ msgstr "" diff --git a/src/nvim/po/ca.po b/src/nvim/po/ca.po index 6c4d6ddd22..964fcc9325 100644 --- a/src/nvim/po/ca.po +++ b/src/nvim/po/ca.po @@ -3013,128 +3013,6 @@ msgstr "la cerca ha arribat a DALT, es continua a BAIX" msgid "search hit BOTTOM, continuing at TOP" msgstr "la cerca ha arribat a BAIX, es continua a DALT" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Falta un carcter \":\"" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Component illegal" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: S'esperava un dgit" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Pgina %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "No hi ha text per imprimir" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Imprimint la pgina %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Cpia %d de %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "S'ha imprs: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "S'ha avortat la impressi" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Error en escriure el fitxer PostScript" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: No s'ha pogut obrir el fitxer \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: No s'ha pogut llegir el fitxer de recursos PostScript \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: El fitxer \"%s\" no s un fitxer de recursos PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: El fitxer de recursos PostScript \"%s\" no est suportat" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: La versi del fitxer de recursos \"%s\" no s vlida" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Joc de carcters i codificaci multi-octet no compatibles." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: printmbcharset no pot estar buit si la codificaci s multi-octet" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" -"E675: No heu especificat cap fosa per defecte per a la impressi multi-octet." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: No s'ha pogut obrir el fitxer PostScript generat" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: No s'ha pogut obrir el fitxer \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: No s'ha trobat el fitxer de recursos PostScript \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: No s'ha trobat el fitxer de recursos PostScript \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: No s'ha trobat el fitxer de recursos PostScript \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: No s'ha pogut convertir a la codificaci d'impressi \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "S'est enviant a la impressora..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Error en imprimir el fitxer PostScript" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "S'ha enviat la tasca d'impressi." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Afegeix una base de dades nova" @@ -5983,9 +5861,9 @@ msgstr "E424: Hi ha massa atributs de ressalt diferents en s" msgid "E669: Unprintable character in group name" msgstr "E669: Carcter no imprimible en el nom del grup" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Hi ha un carcter no vlid en el nom del grup" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Hi ha un carcter no vlid en el nom del grup" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/cleanup.vim b/src/nvim/po/cleanup.vim index 8384286b0d..6df5edd498 100644 --- a/src/nvim/po/cleanup.vim +++ b/src/nvim/po/cleanup.vim @@ -22,7 +22,9 @@ silent g/^msgstr"/s//msgstr "/ silent g/^msgid"/s//msgid "/ silent g/^msgstr ""\(\n"\)\@!/?^msgid?,.s/^/#\~ / +" clean up empty lines silent g/^\n\n\n/.d +silent! %s/\n\+\%$// if s:was_diff setl diff diff --git a/src/nvim/po/cs.cp1250.po b/src/nvim/po/cs.cp1250.po index 859039eb87..b939139fb5 100644 --- a/src/nvim/po/cs.cp1250.po +++ b/src/nvim/po/cs.cp1250.po @@ -3077,131 +3077,6 @@ msgstr "hledn doshlo zatku, pokraovn od konce" msgid "search hit BOTTOM, continuing at TOP" msgstr "hledn doshlo konce, pokraovn od zatku" -#: ../hardcopy.c:240 -#, fuzzy -msgid "E550: Missing colon" -msgstr "Chyb dvojteka" - -#: ../hardcopy.c:252 -#, fuzzy -msgid "E551: Illegal component" -msgstr "neppustn soust" - -#: ../hardcopy.c:259 -#, fuzzy -msgid "E552: digit expected" -msgstr "oekvna slice" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "dn text k vytitn" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Tisknu stranu %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopie %d z %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Vytitno: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Tisk zruen" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Nelze zapisovat do vstupnho PostScriptovho souboru" - -#: ../hardcopy.c:1747 -#, fuzzy, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E456: Nelze otevt soubor \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:1772 -#, fuzzy, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, fuzzy, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Nelze otevt vstupn PostScriptov soubor" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Nelze otevt soubor \"%s\"" - -#: ../hardcopy.c:2583 -#, fuzzy -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:2593 -#, fuzzy -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, fuzzy, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Odeslm na tiskrnu..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Selhal tisk PostScriptovho souboru" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Tiskov loha odeslna." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Pidat novou databzi" @@ -6072,10 +5947,10 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "" -#: ../syntax.c:7434 +#: ../highlight_group.c:1756 #, fuzzy -msgid "W18: Invalid character in group name" -msgstr "E182: Chybn jmno pkazu" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Chybn jmno pkazu" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/cs.po b/src/nvim/po/cs.po index 4d9ad58836..f98dc2828e 100644 --- a/src/nvim/po/cs.po +++ b/src/nvim/po/cs.po @@ -3077,131 +3077,6 @@ msgstr "hledn doshlo zatku, pokraovn od konce" msgid "search hit BOTTOM, continuing at TOP" msgstr "hledn doshlo konce, pokraovn od zatku" -#: ../hardcopy.c:240 -#, fuzzy -msgid "E550: Missing colon" -msgstr "Chyb dvojteka" - -#: ../hardcopy.c:252 -#, fuzzy -msgid "E551: Illegal component" -msgstr "neppustn soust" - -#: ../hardcopy.c:259 -#, fuzzy -msgid "E552: digit expected" -msgstr "oekvna slice" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "dn text k vytitn" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Tisknu stranu %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopie %d z %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Vytitno: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Tisk zruen" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Nelze zapisovat do vstupnho PostScriptovho souboru" - -#: ../hardcopy.c:1747 -#, fuzzy, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E456: Nelze otevt soubor \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:1772 -#, fuzzy, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, fuzzy, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Nelze otevt vstupn PostScriptov soubor" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Nelze otevt soubor \"%s\"" - -#: ../hardcopy.c:2583 -#, fuzzy -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:2593 -#, fuzzy -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, fuzzy, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E457: Nelze st zdrojov PostScriptov soubor \"%s\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Odeslm na tiskrnu..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Selhal tisk PostScriptovho souboru" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Tiskov loha odeslna." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Pidat novou databzi" @@ -6072,10 +5947,10 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "" -#: ../syntax.c:7434 +#: ../highlight_group.c:1756 #, fuzzy -msgid "W18: Invalid character in group name" -msgstr "E182: Chybn jmno pkazu" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Chybn jmno pkazu" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po index dfcc052288..c55918abc9 100644 --- a/src/nvim/po/da.po +++ b/src/nvim/po/da.po @@ -2384,99 +2384,6 @@ msgstr "Størrelse:" msgid "E256: Hangul automata ERROR" msgstr "E256: FEJL ved Hangul automata" -msgid "E550: Missing colon" -msgstr "E550: Manglende kolon" - -msgid "E551: Illegal component" -msgstr "E551: Ulovlig komponent" - -msgid "E552: digit expected" -msgstr "E552: ciffer ventet" - -#, c-format -msgid "Page %d" -msgstr "Side %d" - -msgid "No text to be printed" -msgstr "Ingen tekst at udskrive" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Udskriver side %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Kopi %d af %d" - -#, c-format -msgid "Printed: %s" -msgstr "Udskrev: %s" - -msgid "Printing aborted" -msgstr "Udskrivning afbrudt" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Fejl ved skrivning til PostScript-output-fil" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Kan ikke åbne filen \"%s\"" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Kan ikke læse PostScript-ressourcefilen \"%s\"" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: filen \"%s\" er ikke en PostScript-ressourcefil" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: filen \"%s\" er ikke en understøttet PostScript-ressourcefil" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\"-ressourcefilen har forkert version" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Inkompatibel multibyte-kodning og -tegnsæt." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset må ikke være tom med multibyte-kodning." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Ingen standardskrifttype angivet for multibyte-udskrivning." - -msgid "E324: Can't open PostScript output file" -msgstr "E324: Kan ikke åbne PostScript-output-fil" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Kan ikke åbne filen \"%s\"" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Kan ikke finde PostScript-ressourcefilen \"prolog.ps\"" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Kan ikke finde PostScript-ressourcefilen \"cidfont.ps\"" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Kan ikke finde PostScript-ressourcefilen \"%s.ps\"" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Kan ikke konvertere til udskrivningskodningen \"%s\"" - -msgid "Sending to printer..." -msgstr "Sender til printer..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: Kunne ikke udskrive PostScript-fil" - -msgid "Print job sent." -msgstr "Udskrivningsjob sendt." - msgid "Add a new database" msgstr "Tilføj en ny database" @@ -5599,8 +5506,8 @@ msgstr "E424: For mange forskellige fremhævningsattributter i brug" msgid "E669: Unprintable character in group name" msgstr "E669: Tegn som ikke kan udskrives i gruppenavn" -msgid "W18: Invalid character in group name" -msgstr "W18: Ugyldige tegn i gruppenavn" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ugyldige tegn i gruppenavn" msgid "E849: Too many highlight and syntax groups" msgstr "E849: For mange fremhævnings- og syntaksgrupper" diff --git a/src/nvim/po/de.po b/src/nvim/po/de.po index 2dde77e9f7..efd52d3efe 100644 --- a/src/nvim/po/de.po +++ b/src/nvim/po/de.po @@ -2434,126 +2434,6 @@ msgstr "Suche erreichte den ANFANG und wurde am ENDE fortgesetzt" msgid "search hit BOTTOM, continuing at TOP" msgstr "Suche erreichte das ENDE und wurde am ANFANG fortgesetzt" -#: ../hardcopy.c:296 -msgid "E550: Missing colon" -msgstr "E550: Fehlender Doppelpunkt" - -#: ../hardcopy.c:308 -msgid "E551: Illegal component" -msgstr "E551: Unzulssige Komponente" - -#: ../hardcopy.c:315 -msgid "E552: digit expected" -msgstr "E552: Ziffer erwartet" - -#: ../hardcopy.c:529 -#, c-format -msgid "Page %d" -msgstr "Seite %d" - -#: ../hardcopy.c:653 -msgid "No text to be printed" -msgstr "Kein Text zum Drucken" - -#: ../hardcopy.c:724 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Drucke Seite %d (%d%%)" - -#: ../hardcopy.c:736 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopiere %d von %d" - -#: ../hardcopy.c:789 -#, c-format -msgid "Printed: %s" -msgstr "Gedruckt: %s" - -#: ../hardcopy.c:796 -msgid "Printing aborted" -msgstr "Druck abgebrochen" - -#: ../hardcopy.c:1307 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Fehler beim Schreiben der PostScript-Ausgabedatei" - -#: ../hardcopy.c:1679 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Datei \"%s\" kann nicht geffnet werden" - -#: ../hardcopy.c:1688 ../hardcopy.c:2401 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: PostScript-Ressourcendatei \"%s\" kann nicht gelesen werden" - -#: ../hardcopy.c:1704 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: Datei \"%s\" ist keine PostScript-Ressourcendatei" - -#: ../hardcopy.c:1720 ../hardcopy.c:1737 ../hardcopy.c:1776 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: Datei \"%s\" ist keine untersttzte PostScript-Ressourcendatei" - -#: ../hardcopy.c:1788 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" Ressource-Datei hat die falsche Version" - -#: ../hardcopy.c:2157 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Unzulssiger Multibyte-Zeichensatz" - -#: ../hardcopy.c:2169 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: Bei Multibyte-Zeichensatz darf 'printmbcharset' nicht leer sein." - -#: ../hardcopy.c:2185 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Keine Standardschriftart fr Multibyte-Druck angegeben." - -#: ../hardcopy.c:2357 -msgid "E324: Can't open PostScript output file" -msgstr "E324: PostScript-Ausgabedatei kann nicht geffnet werden" - -#: ../hardcopy.c:2389 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Datei \"%s\" kann nicht geffnet werden" - -#: ../hardcopy.c:2514 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: PostScript-Ressourcendatei \"prolog.ps\" nicht gefunden" - -#: ../hardcopy.c:2524 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: PostScript-Ressourcendatei \"cidfont.ps\" nicht gefunden" - -#: ../hardcopy.c:2553 ../hardcopy.c:2570 ../hardcopy.c:2596 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: PostScript-Ressourcendatei \"%s\" nicht gefunden" - -#: ../hardcopy.c:2585 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Umwandlung zu Drucker-Zeichensatz \"%s\" fehlgeschlagen" - -#: ../hardcopy.c:2808 -msgid "Sending to printer..." -msgstr "Schicke zum Drucker..." - -#: ../hardcopy.c:2812 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Druck der PostScript-Datei fehlgeschlagen" - -#: ../hardcopy.c:2814 -msgid "Print job sent." -msgstr "Druckauftrag abgeschickt" - #: ../if_cscope.c:49 msgid "Add a new database" msgstr "Eine neue Datenbank hinzufgen" @@ -5408,8 +5288,8 @@ msgid "E669: Unprintable character in group name" msgstr "E669: Nicht druckbare Zeichen im Namen der Gruppe" #: ../syntax.c:7304 -msgid "W18: Invalid character in group name" -msgstr "W18: Ungltiges Zeichen im Namen der Gruppe" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ungltiges Zeichen im Namen der Gruppe" #: ../syntax.c:7318 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/en_GB.po b/src/nvim/po/en_GB.po index 66cdba6f92..1c03b305f9 100644 --- a/src/nvim/po/en_GB.po +++ b/src/nvim/po/en_GB.po @@ -2923,128 +2923,6 @@ msgstr "" msgid "search hit BOTTOM, continuing at TOP" msgstr "" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr "" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "" - -#: ../hardcopy.c:1365 -#, fuzzy -msgid "E455: Error writing to PostScript output file" -msgstr "E324: Cannot open PostScript output file" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Cannot open file \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Cannot read PostScript resource file \"%s\"" - -#: ../hardcopy.c:1772 -#, fuzzy, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E457: Cannot read PostScript resource file \"%s\"" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, fuzzy, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E457: Cannot read PostScript resource file \"%s\"" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Cannot open PostScript output file" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Cannot open file \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Cannot find PostScript resource file \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Cannot find PostScript resource file \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Cannot find PostScript resource file \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "" - -#: ../hardcopy.c:2881 -#, fuzzy -msgid "E365: Failed to print PostScript file" -msgstr "E324: Cannot open PostScript output file" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "" - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "" @@ -5730,8 +5608,8 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" msgstr "" #: ../syntax.c:7448 diff --git a/src/nvim/po/eo.po b/src/nvim/po/eo.po index 1c503d0a84..aaa39d6041 100644 --- a/src/nvim/po/eo.po +++ b/src/nvim/po/eo.po @@ -2246,100 +2246,6 @@ msgstr "Stilo:" msgid "Size:" msgstr "Grando:" -msgid "E550: Missing colon" -msgstr "E550: Mankas dupunkto" - -msgid "E551: Illegal component" -msgstr "E551: Nevalida komponento" - -msgid "E552: digit expected" -msgstr "E552: cifero atendita" - -#, c-format -msgid "Page %d" -msgstr "Paĝo %d" - -msgid "No text to be printed" -msgstr "Neniu presenda teksto" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Presas paĝon %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Kopio %d de %d" - -#, c-format -msgid "Printed: %s" -msgstr "Presis: %s" - -msgid "Printing aborted" -msgstr "Presado ĉesigita" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Eraro dum skribo de PostSkripta eliga dosiero" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Ne eblas malfermi dosieron \"%s\"" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Ne eblas legi dosieron de PostSkripta rimedo \"%s\"" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: \"%s\" ne estas dosiero de PostSkripta rimedo" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: \"%s\" ne estas subtenata dosiero de PostSkripta rimedo" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" dosiero de rimedo havas neĝustan version" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Nekongrua plurbajta kodoprezento kaj signaro." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: printmbcharset ne rajtas esti malplena kun plurbajta kodoprezento." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Neniu defaŭlta tiparo specifita por plurbajta presado." - -msgid "E324: Can't open PostScript output file" -msgstr "E324: Ne eblas malfermi eligan PostSkriptan dosieron" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Ne eblas malfermi dosieron \"%s\"" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Dosiero de PostSkripta rimedo \"prolog.ps\" ne troveblas" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Dosiero de PostSkripta rimedo \"cidfont.ps\" ne troveblas" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Dosiero de PostSkripta rimedo \"%s.ps\" ne troveblas" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Ne eblas konverti al la presa kodoprezento \"%s\"" - -msgid "Sending to printer..." -msgstr "Sendas al presilo..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: Presado de PostSkripta dosiero malsukcesis" - -msgid "Print job sent." -msgstr "Laboro de presado sendita." - msgid "Add a new database" msgstr "Aldoni novan datumbazon" @@ -5378,8 +5284,8 @@ msgstr "E424: Tro da malsamaj atributoj de emfazo uzataj" msgid "E669: Unprintable character in group name" msgstr "E669: Nepresebla signo en nomo de grupo" -msgid "W18: Invalid character in group name" -msgstr "W18: Nevalida signo en nomo de grupo" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Nevalida signo en nomo de grupo" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Tro da emfazaj kaj sintaksaj grupoj" diff --git a/src/nvim/po/es.po b/src/nvim/po/es.po index adea651b7c..120a29af15 100644 --- a/src/nvim/po/es.po +++ b/src/nvim/po/es.po @@ -3054,129 +3054,6 @@ msgstr "La búsqueda ha llegado al PRINCIPIO, continuando desde el FINAL" msgid "search hit BOTTOM, continuing at TOP" msgstr "La búsqueda ha llegado al FINAL, continuando desde el PRINCIPIO" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Falta un símbolo de dos puntos" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Componente ilegal" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: Se esperaba un dígito" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Página %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "No hay texto que imprimir" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Imprimiendo la página %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr "Copia %d de %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Impreso: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Impresión interrumpida" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Error escribiendo al archivo PostScript de salida" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: No se pudo abrir el archivo \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: No se pudo leer el archivo de recursos de PostScript \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: El archivo \"%s\" no es un archivo de recursos PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: El archivo \"%s\" no es un recurso PostScript que pueda usar" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: La versión del archivo de recursos \"%s\" es incorrecta" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Codificación y set de caracteres multi-byte incompatibles" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: \"printmbcharset\" no puede estar vacío en una codificación multi-byte" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" -"E675: No se ha definido un tipo de letra predeterminado para impresión " -"multi-byte" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: No se pudo abrir el archivo PostScript de salida" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: No se pudo abrir el archivo %s" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: No se encontró el archivo de recursos PostScript \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: No se encontró el archivo de recursos PostScript \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: No se encontró el archivo de recursos PostScript \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: No se pudo convertir a la codificación de impresión \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Enviando a la impresora..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Falló la impresión del archivo PostScript" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Se ha enviado la tarea de impresión." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Añadir una nueva base de datos" @@ -6053,9 +5930,9 @@ msgstr "E669: Carácter no imprimible en el nombre del grupo" # This is an error, but since there previously was no check only # * give a warning. -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Hay un carácter no válido en el nombre del grupo" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Hay un carácter no válido en el nombre del grupo" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po index f10d4ce977..c8db9a64b5 100644 --- a/src/nvim/po/fi.po +++ b/src/nvim/po/fi.po @@ -2896,99 +2896,6 @@ msgstr "haku pääsi ALKUUN, jatketaan LOPUSTA" msgid "search hit BOTTOM, continuing at TOP" msgstr "haku pääsi LOPPUUN, jatketaan ALUSTA" -msgid "E550: Missing colon" -msgstr "E550: kaksoispiste puuttuu" - -msgid "E551: Illegal component" -msgstr "E551: Virheellinen komponentti" - -msgid "E552: digit expected" -msgstr "E552: pitäisi olla numero" - -#, c-format -msgid "Page %d" -msgstr "Sivu %d" - -msgid "No text to be printed" -msgstr "Ei tekstiä tulostettavaksi" - -#, fuzzy, c-format -#~ msgid "Printing page %d (%zu%%)" -#~ msgstr "Tulostetaan sivua %d (%d %%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Kopio %d/%d" - -#, c-format -msgid "Printed: %s" -msgstr "Tulostettu: %s" - -msgid "Printing aborted" -msgstr "Tulostus peruttu" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Virhe kirjoitettaessa PostScriptiä tiedostoon" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Ei voi avata tiedostoa %s" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Ei voi lukea PostScript-resurssitiedostoa %s" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: tiedosto %s ei ole PostScript-resurssitiedosto" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: tiedosto %s ei ole tuettu PostScript-resurssitiedosto" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: resurssitiedoston %s versio on väärä" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Tukematon monitvauinen merkistökoodaus ja merkistö." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset ei voi olla tyhjä monitavuiselle koodaukselle." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Ei oletusfonttia monitavuiseen tulostukseen" - -msgid "E324: Can't open PostScript output file" -msgstr "E324: PostScript-tulostetiedoston avaus ei onnistu" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Tiedoston %s avaus ei onnistu" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: PostScript-resurssitiedostoa prolog.ps ei löydy" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: PostScript-resurssitiedostoa cidfont.ps ei löydy" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Postscript-resurssitiedosta %s.ps ei löydy" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Tulostuskoodaukseen %s muunto ei onnistu" - -msgid "Sending to printer..." -msgstr "Lähetetään tulostimelle..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScript-tiedoston tulostus epäonnistui" - -msgid "Print job sent." -msgstr "Tulostustyö lähetetty." - msgid "Add a new database" msgstr "Lisää uusi tietokanta" @@ -5369,8 +5276,8 @@ msgstr "E424: Liikaa eri korostusattribuutteja" msgid "E669: Unprintable character in group name" msgstr "E669: Tulostuskelvoton merkki ryhmän nimessä" -msgid "W18: Invalid character in group name" -msgstr "W18: Virheellinen merkki ryhmän nimessä" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Virheellinen merkki ryhmän nimessä" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Liikaa korostuksia ja syntaksiryhmiä" diff --git a/src/nvim/po/fixfilenames.vim b/src/nvim/po/fixfilenames.vim new file mode 100644 index 0000000000..04bc0791c0 --- /dev/null +++ b/src/nvim/po/fixfilenames.vim @@ -0,0 +1,13 @@ +" Invoked with the name "vim.pot" and a list of Vim script names. +" Converts them to a .js file, stripping comments, so that xgettext works. + +set shortmess+=A + +for name in argv()[1:] + let jsname = fnamemodify(name, ":t:r") .. ".js" + exe "%s+" .. jsname .. "+" .. substitute(name, '\\', '/', 'g') .. "+" +endfor + +write +last +quit diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po index 614ba013e6..f4fce68ac5 100644 --- a/src/nvim/po/fr.po +++ b/src/nvim/po/fr.po @@ -1881,103 +1881,6 @@ msgstr "Style :" msgid "Size:" msgstr "Taille :" -msgid "E550: Missing colon" -msgstr "E550: ':' manquant" - -# DB - Il s'agit ici d'un problme lors du parsing d'une option dont le contenu -# est une liste d'lments spars par des virgules. -msgid "E551: Illegal component" -msgstr "E551: lment invalide" - -msgid "E552: digit expected" -msgstr "E552: chiffre attendu" - -#, c-format -msgid "Page %d" -msgstr "Page %d" - -msgid "No text to be printed" -msgstr "Aucun texte imprimer" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Impression de la page %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Copie %d sur %d" - -#, c-format -msgid "Printed: %s" -msgstr "Imprim : %s" - -msgid "Printing aborted" -msgstr "Impression interrompue" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Erreur lors de l'criture du fichier PostScript" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Impossible d'ouvrir le fichier \"%s\"" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Impossible de lire le fichier de ressource PostScript \"%s\"" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: \"%s\" n'est pas un fichier de ressource PostScript" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: \"%s\" n'est pas un fichier de ressource PostScript support" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: La version du fichier de ressource \"%s\" est errone" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Jeu de caractres et encodage multi-octets incompatibles" - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: 'printmbcharset' ne peut pas tre vide avec un encodage multi-octets" - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Aucune police par dfaut pour l'impression multi-octets" - -msgid "E324: Can't open PostScript output file" -msgstr "E324: Impossible d'ouvrir le fichier PostScript de sortie" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Impossible d'ouvrir le fichier \"%s\"" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Le fichier de ressource PostScript \"prolog.ps\" est introuvable" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "" -"E456: Le fichier de ressource PostScript \"cidfont.ps\" est introuvable" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Le fichier de ressource PostScript \"%s.ps\" est introuvable" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: La conversion pour imprimer dans l'encodage \"%s\" a chou" - -msgid "Sending to printer..." -msgstr "Envoi l'imprimante..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: L'impression du fichier PostScript a chou" - -msgid "Print job sent." -msgstr "Tche d'impression envoye." - msgid "E679: recursive loop loading syncolor.vim" msgstr "E679: boucle rcursive lors du chargement de syncolor.vim" @@ -2040,8 +1943,8 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "E669: Caractre non imprimable dans un nom de groupe" -msgid "W18: Invalid character in group name" -msgstr "W18: Caractre invalide dans un nom de groupe" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Caractre invalide dans un nom de groupe" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Trop de groupes de surbrillance et de syntaxe" diff --git a/src/nvim/po/ga.po b/src/nvim/po/ga.po index 1c25ee481c..5107aed75f 100644 --- a/src/nvim/po/ga.po +++ b/src/nvim/po/ga.po @@ -2385,99 +2385,6 @@ msgstr "Mid:" msgid "E256: Hangul automata ERROR" msgstr "E256: EARRID leis na huathoibrein Hangul" -msgid "E550: Missing colon" -msgstr "E550: Idirstad ar iarraidh" - -msgid "E551: Illegal component" -msgstr "E551: Comhphirt neamhcheadaithe" - -msgid "E552: digit expected" -msgstr "E552: ag sil le digit" - -#, c-format -msgid "Page %d" -msgstr "Leathanach %d" - -msgid "No text to be printed" -msgstr "Nl aon tacs le priontil" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Leathanach %d (%d%%) phriontil" - -#, c-format -msgid " Copy %d of %d" -msgstr " Cip %d de %d" - -#, c-format -msgid "Printed: %s" -msgstr "Priontilte: %s" - -msgid "Printing aborted" -msgstr "Priontil tobscortha" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Earrid le linn scrobh chuig aschomhad PostScript" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: N fidir an comhad \"%s\" a oscailt" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: N fidir comhad acmhainne PostScript \"%s\" a lamh" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: Nl comhad \"%s\" ina chomhad acmhainne PostScript" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: T \"%s\" ina chomhad acmhainne PostScript gan tac" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: T an leagan mcheart ar an gcomhad acmhainne \"%s\"" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Ionchd agus tacar carachtar ilbhirt neamh-chomhoirinach." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: n cheadatear printmbcharset a bheith folamh le hionchd ilbhirt." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Nor ramhshocraodh cl le haghaidh priontla ilbhirt." - -msgid "E324: Can't open PostScript output file" -msgstr "E324: N fidir aschomhad PostScript a oscailt" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: N fidir an comhad \"%s\" a oscailt" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Comhad acmhainne PostScript \"prolog.ps\" gan aimsi" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Comhad acmhainne PostScript \"cidfont.ps\" gan aimsi" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Comhad acmhainne PostScript \"%s.ps\" gan aimsi" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: N fidir an t-ionchd priontla \"%s\" a thiont" - -msgid "Sending to printer..." -msgstr " sheoladh chuig an phrintir..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: Theip ar phriontil comhaid PostScript" - -msgid "Print job sent." -msgstr "Seoladh jab priontla." msgid "Add a new database" msgstr "Bunachar sonra nua" @@ -5668,8 +5575,8 @@ msgstr "E424: An iomarca trithe aibhsithe in sid" msgid "E669: Unprintable character in group name" msgstr "E669: Carachtar neamhghrafach in ainm grpa" -msgid "W18: Invalid character in group name" -msgstr "W18: Carachtar neamhbhail in ainm grpa" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Carachtar neamhbhail in ainm grpa" msgid "E849: Too many highlight and syntax groups" msgstr "E849: An iomarca grpa aibhsithe agus comhrire" diff --git a/src/nvim/po/it.po b/src/nvim/po/it.po index 313280c807..2191876724 100644 --- a/src/nvim/po/it.po +++ b/src/nvim/po/it.po @@ -3049,126 +3049,6 @@ msgstr "raggiunta la CIMA nella ricerca, continuo dal FONDO" msgid "search hit BOTTOM, continuing at TOP" msgstr "raggiunto il FONDO nella ricerca, continuo dalla CIMA" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Manca ':'" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Componente non valido" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: aspettavo un numero" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Pagina %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Manca testo da stampare" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Sto stampando pagina %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Copia %d di %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Stampato: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Stampa non completata" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Errore in scrittura a file PostScript di output" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Non riesco ad aprire il file \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Non riesco a leggere file risorse PostScript \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: file \"%s\" non un file di risorse PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: file \"%s\" non un file di risorse PostScript supportato" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: il file di risorse \"%s\" ha una versione sbagliata" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Codifica e set di caratteri multi-byte non compatibili." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset non pu essere nullo con codifica multi-byte." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Font predefinito non specificato per stampa multi-byte." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Non riesco ad aprire file PostScript di output" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Non riesco ad aprire il file \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Non trovo file risorse PostScript \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Non trovo file risorse PostScript \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Non trovo file risorse PostScript \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Impossibile convertire a codifica di stampa \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Invio a stampante..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Non riesco ad aprire file PostScript" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Richiesta di stampa inviata." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Aggiungi un nuovo database" @@ -6046,9 +5926,9 @@ msgstr "E424: Troppi gruppi evidenziazione differenti in uso" msgid "E669: Unprintable character in group name" msgstr "E669: Carattere non stampabile in un nome di gruppo" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Carattere non ammesso in un nome di gruppo" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Carattere non ammesso in un nome di gruppo" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/ja.euc-jp.po b/src/nvim/po/ja.euc-jp.po index d7d0faca80..87b4fc5ccd 100644 --- a/src/nvim/po/ja.euc-jp.po +++ b/src/nvim/po/ja.euc-jp.po @@ -2126,101 +2126,6 @@ msgstr ":" msgid "E256: Hangul automata ERROR" msgstr "E256: ϥ륪ȥޥȥ顼" -msgid "E550: Missing colon" -msgstr "E550: ޤ" - -msgid "E551: Illegal component" -msgstr "E551: ʹʸǤǤ" - -msgid "E552: digit expected" -msgstr "E552: ͤɬפǤ" - -#, c-format -msgid "Page %d" -msgstr "%d ڡ" - -msgid "No text to be printed" -msgstr "ƥȤޤ" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr ": ڡ %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " ԡ %d ( %d )" - -#, c-format -msgid "Printed: %s" -msgstr "ޤ: %s" - -msgid "Printing aborted" -msgstr "ߤޤ" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: PostScriptϥեνߥ顼Ǥ" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: ե \"%s\" ޤ" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: PostScriptΥե \"%s\" ɹޤ" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: ե \"%s\" PostScript եǤϤޤ" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: ե \"%s\" бƤʤ PostScript եǤ" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: ե \"%s\" ϥСۤʤޤ" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: ߴ̵ޥХȥǥʸåȤǤ" - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: ޥХȥǥǤ printmbcharset ˤǤޤ" - -msgid "E675: No default font specified for multi-byte printing." -msgstr "" -"E675: ޥХʸ뤿ΥǥեȥեȤꤵƤޤ" -"" - -msgid "E324: Can't open PostScript output file" -msgstr "E324: PostScriptѤΥեޤ" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: ե \"%s\" ޤ" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: PostScriptΥե \"prolog.ps\" Ĥޤ" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: PostScriptΥե \"cidfont.ps\" Ĥޤ" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: PostScriptΥե \"%s.ps\" Ĥޤ" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: \"%s\" ѴǤޤ" - -msgid "Sending to printer..." -msgstr "ץ..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScriptեΰ˼Ԥޤ" - -msgid "Print job sent." -msgstr "֤ޤ" #, c-format msgid "E799: Invalid ID: %d (must be greater than or equal to 1)" diff --git a/src/nvim/po/ja.po b/src/nvim/po/ja.po index b56345e066..bdf67933f3 100644 --- a/src/nvim/po/ja.po +++ b/src/nvim/po/ja.po @@ -2126,102 +2126,6 @@ msgstr "サイズ:" msgid "E256: Hangul automata ERROR" msgstr "E256: ハングルオートマトンエラー" -msgid "E550: Missing colon" -msgstr "E550: コロンがありません" - -msgid "E551: Illegal component" -msgstr "E551: 不正な構文要素です" - -msgid "E552: digit expected" -msgstr "E552: 数値が必要です" - -#, c-format -msgid "Page %d" -msgstr "%d ページ" - -msgid "No text to be printed" -msgstr "印刷するテキストがありません" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "印刷中: ページ %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " コピー %d (全 %d 中)" - -#, c-format -msgid "Printed: %s" -msgstr "印刷しました: %s" - -msgid "Printing aborted" -msgstr "印刷が中止されました" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: PostScript出力ファイルの書込みエラーです" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: ファイル \"%s\" を開けません" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: PostScriptのリソースファイル \"%s\" を読込めません" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: ファイル \"%s\" は PostScript リソースファイルではありません" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: ファイル \"%s\" は対応していない PostScript リソースファイルです" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: リソースファイル \"%s\" はバージョンが異なります" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: 互換性の無いマルチバイトエンコーディングと文字セットです。" - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: マルチバイトエンコーディングでは printmbcharset を空にできません。" - -msgid "E675: No default font specified for multi-byte printing." -msgstr "" -"E675: マルチバイト文字を印刷するためのデフォルトフォントが指定されていませ" -"ん。" - -msgid "E324: Can't open PostScript output file" -msgstr "E324: PostScript出力用のファイルを開けません" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: ファイル \"%s\" を開けません" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: PostScriptのリソースファイル \"prolog.ps\" が見つかりません" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: PostScriptのリソースファイル \"cidfont.ps\" が見つかりません" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: PostScriptのリソースファイル \"%s.ps\" が見つかりません" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: 印刷エンコード \"%s\" へ変換できません" - -msgid "Sending to printer..." -msgstr "プリンタに送信中..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScriptファイルの印刷に失敗しました" - -msgid "Print job sent." -msgstr "印刷ジョブを送信しました。" - #, c-format msgid "E799: Invalid ID: %d (must be greater than or equal to 1)" msgstr "E799: 無効な ID: %d (1 以上でなければなりません)" diff --git a/src/nvim/po/ko.UTF-8.po b/src/nvim/po/ko.UTF-8.po index b99c22caeb..8a6b228b18 100644 --- a/src/nvim/po/ko.UTF-8.po +++ b/src/nvim/po/ko.UTF-8.po @@ -2973,126 +2973,6 @@ msgstr "처음까지 찾았음, 끝에서 계속" msgid "search hit BOTTOM, continuing at TOP" msgstr "끝까지 찾았음, 처음부터 계속" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: 콜론이 없습니다" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: 이상한 컴포넌트" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: 숫자가 필요합니다" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "페이지 %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "인쇄될 텍스트가 없습니다" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "페이지 %d 인쇄중 (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " 복사 %d / %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "인쇄됨: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "인쇄가 취소되었습니다." - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: 포스트스크립트 출력파일에 쓸 수 없습니다." - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: \"%s\" 파일을 열 수 없습니다" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: 포스트스크립트 리소스 파일 \"%s\"을(를) 읽을 수 없습니다" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: 파일 \"%s\"은(는) 포스트스크립트 리소스 파일이 아닙니다" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: 파일 \"%s\"은(는) 지원되는 포스트스크립트 리소스 파일이 아닙니다" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" 리소스 파일은 버전이 잘못되었습니다" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: 호환되지 않는 다중문자 인코딩과 문자셋." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset는 다중문자 인코딩에서 반드시 설정되어야 합니다." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: 다중문자 인쇄를 위한 글꼴이 설정되어 있지 않습니다" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: 포스트스크립트 출력파일을 열 수 없습니다" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: \"%s\" 파일을 열 수 없습니다" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: 포스트스크립트 리소스 파일 \"prolog.ps\"를 찾을 수 없습니다" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: 포스트스크립트 리소스 파일 \"cidfont.ps\"를 찾을 수 없습니다" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: 포스트스크립트 리소스 파일 \"%s.ps\"를 찾을 수 없습니다" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: \"%s\" 인쇄 인코딩으로 변환할 수 없습니다" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "프린터로 보내는 중..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: 포스트스크립트 파일을 인쇄할 수 없습니다" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "인쇄작업이 끝났습니다." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "새 데이터베이스 더하기" @@ -5913,9 +5793,9 @@ msgstr "E424: 너무 많은 다른 하이라이트 속성이 사용되고 있습 msgid "E669: Unprintable character in group name" msgstr "E669: 그룹 이름에 출력할 수 없는 문자가 있습니다" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: 그룹 이름에 이상한 문자" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: 그룹 이름에 이상한 문자" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/nb.po b/src/nvim/po/nb.po index 2285d755cf..75f4eb28a3 100644 --- a/src/nvim/po/nb.po +++ b/src/nvim/po/nb.po @@ -2997,126 +2997,6 @@ msgstr "Sket traff TOPPEN, fortsetter fra BUNNEN" msgid "search hit BOTTOM, continuing at TOP" msgstr "Sket traff BUNNEN, fortsetter fra TOPPEN" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Mangler kolon" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Ulovlig komponent" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: Siffer forventet" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Side %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Ingen tekst for utskrift" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Skriver ut side %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopi %d av %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Skrevet ut: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Utskrift avbrutt" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Feil under skriving til Postscript-fil" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Kan ikke pne filen \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Kan ikke lese Postscript-ressursfil \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: Filen \"%s\" er ikke en Postscript-ressursfil" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: Det er ikke sttte for Postscript-ressursfilen \"%s\"" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: Ressursfilen \"%s\" er feil versjon" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Inkompatibel multibytekoding og tegnsett" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset kan ikke vre tom med multibytekoding" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Ingen standardfont spesifisert for multibyteutskrift" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Kan ikke pne Postscript-fil for skriving" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Kan ikke pne filen \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Fant ikke Postscript-ressursfilen \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Fant ikke Postscript-ressursfilen \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Fant ikke Postscript-ressursfilen \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Klarte ikke konvertere til utskriftskoding \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Sender til skriver..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Feil under utskrift av Postscript-fil" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Skriverjobb sendt." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Legg til en ny database" @@ -5930,9 +5810,9 @@ msgstr "E424: For mange forskjellige uthevingsattributter i bruk" msgid "E669: Unprintable character in group name" msgstr "E669: Ikke-skrivbart tegn i gruppenavn" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Ugyldig tegn i gruppenavn" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ugyldig tegn i gruppenavn" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/nl.po b/src/nvim/po/nl.po index 00d113c83c..d07c566e28 100644 --- a/src/nvim/po/nl.po +++ b/src/nvim/po/nl.po @@ -2985,126 +2985,6 @@ msgstr "zoeken bereikte TOP, verder vanaf BODEM" msgid "search hit BOTTOM, continuing at TOP" msgstr "zoeken bereikte BODEM, verder vanaf TOP" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: dubbelepunt ontbreekt" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: ongeldige component" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: cijfer verwacht" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Pagina %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Geen tekst om af te drukken" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Afdrukken van pagina %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr "Kopie %d van %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Afgedrukt: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Afdrukken afgebroken" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: wegschrijven Postscript-uitvoerbestand is mislukt" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: openen bestand \"%s\" is mislukt" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: kan 'Postscript resource'-bestand \"%s\" niet lezen" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: bestand \"%s\" is geen 'Postscript resource'-bestand" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: bestand \"%s\" is geen ondersteund 'Postscript resource'-bestand" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: 'resource'-bestand \"%s\" heeft verkeerde versie" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Multi-byte-codering en de tekenverzameling zijn onverenigbaar." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset mag bij multi-byte-codering niet leeg zijn." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: geen standaard lettertype opgegeven voor multi-byte-afdrukken." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: openen van PostScript-uitoverbestand is mislukt" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Bestand \"%s\" kan niet worden geopend" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: 'PostScript resource'-bestand \"prolog.ps\" is niet gevonden" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: 'PostScript resource'-bestand \"cidfont.ps\" is niet gevonden" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: 'PostScript resource'-bestand \"%s.ps\" is niet gevonden" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: omzetten naar afdrukcodering \"%s\" is mislukt" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Naar printer versturen..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Afdrukken van PostScript-bestand is mislukt" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Afdrukopdracht verzonden" - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Nieuwe databank toevoegen" @@ -5913,8 +5793,8 @@ msgstr "" msgid "E669: Unprintable character in group name" msgstr "" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" msgstr "" #: ../syntax.c:7448 diff --git a/src/nvim/po/no.po b/src/nvim/po/no.po index 2285d755cf..75f4eb28a3 100644 --- a/src/nvim/po/no.po +++ b/src/nvim/po/no.po @@ -2997,126 +2997,6 @@ msgstr "Sket traff TOPPEN, fortsetter fra BUNNEN" msgid "search hit BOTTOM, continuing at TOP" msgstr "Sket traff BUNNEN, fortsetter fra TOPPEN" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Mangler kolon" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Ulovlig komponent" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: Siffer forventet" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Side %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Ingen tekst for utskrift" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Skriver ut side %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopi %d av %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Skrevet ut: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Utskrift avbrutt" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Feil under skriving til Postscript-fil" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Kan ikke pne filen \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Kan ikke lese Postscript-ressursfil \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: Filen \"%s\" er ikke en Postscript-ressursfil" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: Det er ikke sttte for Postscript-ressursfilen \"%s\"" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: Ressursfilen \"%s\" er feil versjon" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Inkompatibel multibytekoding og tegnsett" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset kan ikke vre tom med multibytekoding" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Ingen standardfont spesifisert for multibyteutskrift" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Kan ikke pne Postscript-fil for skriving" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Kan ikke pne filen \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Fant ikke Postscript-ressursfilen \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Fant ikke Postscript-ressursfilen \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Fant ikke Postscript-ressursfilen \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Klarte ikke konvertere til utskriftskoding \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Sender til skriver..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Feil under utskrift av Postscript-fil" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Skriverjobb sendt." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Legg til en ny database" @@ -5930,9 +5810,9 @@ msgstr "E424: For mange forskjellige uthevingsattributter i bruk" msgid "E669: Unprintable character in group name" msgstr "E669: Ikke-skrivbart tegn i gruppenavn" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Ugyldig tegn i gruppenavn" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ugyldig tegn i gruppenavn" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/pl.UTF-8.po b/src/nvim/po/pl.UTF-8.po index 5f1779d1bd..c00130ac8c 100644 --- a/src/nvim/po/pl.UTF-8.po +++ b/src/nvim/po/pl.UTF-8.po @@ -2954,126 +2954,6 @@ msgstr "szukanie dobiło GÓRY; kontynuacja od KOŃCA" msgid "search hit BOTTOM, continuing at TOP" msgstr "szukanie dobiło KOŃCA; kontynuacja od GÓRY" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Brak dwukropka" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Niedozwolona część" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: oczekiwałem na cyfrę" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Strona %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Brak tekstu do drukowania" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Drukuję stronę %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopia %d z %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Wydrukowano: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Drukowanie odwołane" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Nie można zapisać do wyjściowego pliku PostScriptu" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Nie mogę otworzyć pliku \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Nie można odczytać pliku zasobów PostScriptu \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: plik \"%s\" nie jest plikiem zasobów PostScriptu" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: plik \"%s\" nie jest wspieranym plikiem zasobów PostScriptu" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" nieprawidłowa wersja pliku zasobów" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Niekompatybilne kodowanie wielobajtowe i zestaw znaków." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset nie może być pusty przy kodowaniu wielobajtowym." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Nie określono domyślnej czcionki dla drukowania wielobajtowego." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Nie można otworzyć pliku PostScript do wyjścia" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Nie mogę otworzyć pliku \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Nie można znaleźć pliku zasobów PostScriptu \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Nie można znaleźć pliku zasobów PostScriptu \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Nie można znaleźć pliku zasobów PostScriptu \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Nie można przekonwertować by drukować kodowanie \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Przesyłam do drukarki..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Drukowanie pliku PostScript nie powiodło się" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Zadanie drukowanie przesłane." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Dodaj nową bazę danych" @@ -5908,9 +5788,9 @@ msgstr "E424: Zbyt wiele różnych atrybutów podkreślania w użyciu" msgid "E669: Unprintable character in group name" msgstr "E669: Niedrukowalny znak w nazwie grupy" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: nieprawidłowy znak w nazwie grupy" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: nieprawidłowy znak w nazwie grupy" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/pt_BR.po b/src/nvim/po/pt_BR.po index 533d916de1..ca5a4508c7 100644 --- a/src/nvim/po/pt_BR.po +++ b/src/nvim/po/pt_BR.po @@ -4417,129 +4417,6 @@ msgstr "E663: No final da lista de modificaes" msgid "Type :quit<Enter> to exit Nvim" msgstr "Digite :quit<Enter> para sair do Vim" -#: ../hardcopy.c:296 -msgid "E550: Missing colon" -msgstr "E550: Dois-pontos faltando" - -#: ../hardcopy.c:308 -msgid "E551: Illegal component" -msgstr "E551: Elemento invlido" - -#: ../hardcopy.c:315 -msgid "E552: digit expected" -msgstr "E552: era esperado um algarismo" - -#: ../hardcopy.c:529 -#, c-format -msgid "Page %d" -msgstr "Pgina %d" - -#: ../hardcopy.c:653 -msgid "No text to be printed" -msgstr "Sem texto para imprimir" - -#: ../hardcopy.c:724 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Imprimindo pgina %d (%d%%)" - -#: ../hardcopy.c:736 -#, c-format -msgid " Copy %d of %d" -msgstr " Cpia %d de %d" - -#: ../hardcopy.c:789 -#, c-format -msgid "Printed: %s" -msgstr "Impresso: %s" - -#: ../hardcopy.c:796 -msgid "Printing aborted" -msgstr "Impresso cancelada" - -#: ../hardcopy.c:1307 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Erro ao escrever no arquivo PostScript" - -#: ../hardcopy.c:1679 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Impossvel abrir arquivo \"%s\"" - -#: ../hardcopy.c:1688 ../hardcopy.c:2402 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Impossvel ler o arquivo de recursos de PostScript \"%s\"" - -#: ../hardcopy.c:1704 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: arquivo \"%s\" no um arquivo de recursos de PostScript" - -#: ../hardcopy.c:1720 ../hardcopy.c:1737 ../hardcopy.c:1776 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "" -"E619: arquivo \"%s\" no um arquivo de recursos de PostScript suportado" - -#: ../hardcopy.c:1788 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: verso errada do arquivo de recursos \"%s\"" - -#: ../hardcopy.c:2157 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" -"E673: Codificao multi-byte incompatvel com o conjunto de caracteres." - -#: ../hardcopy.c:2170 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: 'printmbcharset' no pode estar vazio com codificaes multi-byte." - -#: ../hardcopy.c:2186 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Nenhuma fonte padro especificada para impresso em multi-byte." - -#: ../hardcopy.c:2358 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Impossvel abrir arquivo PostScript para sada" - -#: ../hardcopy.c:2390 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Impossvel abrir arquivo \"%s\"" - -#: ../hardcopy.c:2515 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Arquivo de recursos de PostScript \"prolog.ps\" no encontrado" - -#: ../hardcopy.c:2525 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Arquivo de recursos de PostScript \"cidfont.ps\" no encontrado" - -#: ../hardcopy.c:2554 ../hardcopy.c:2571 ../hardcopy.c:2597 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Arquivo de recursos de PostScript \"%s.ps\" no encontrado" - -#: ../hardcopy.c:2586 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Impossvel converter para a codificao \"%s\" para impresso" - -#: ../hardcopy.c:2809 -msgid "Sending to printer..." -msgstr "Enviando impressora..." - -#: ../hardcopy.c:2813 -msgid "E365: Failed to print PostScript file" -msgstr "E365: No foi possvel imprimir o arquivo PostScript" - -#: ../hardcopy.c:2815 -msgid "Print job sent." -msgstr "Trabalho de impresso enviado." - #. This happens when the FileChangedRO autocommand changes the #. * file in a way it becomes shorter. #: ../undo.c:355 @@ -6070,8 +5947,8 @@ msgid "E669: Unprintable character in group name" msgstr "E669: Caractere no-imprimvel no nome do grupo" #: ../syntax.c:7320 -msgid "W18: Invalid character in group name" -msgstr "W18: Caractere invlido no nome do grupo" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Caractere invlido no nome do grupo" #: ../syntax.c:7334 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/ru.po b/src/nvim/po/ru.po index 7566036d3e..1235111c58 100644 --- a/src/nvim/po/ru.po +++ b/src/nvim/po/ru.po @@ -2979,126 +2979,6 @@ msgstr "Поиск будет продолжен с КОНЦА документ msgid "search hit BOTTOM, continuing at TOP" msgstr "Поиск будет продолжен с НАЧАЛА документа" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Пропущено двоеточие" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Недопустимый компонент" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: Требуется указать цифру" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Страница %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Печатать нечего" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Печать стр. %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Копия %d из %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Напечатано: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Печать прекращена" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Ошибка записи в файл PostScript" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Невозможно открыть файл \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Невозможно прочитать файл ресурсов PostScript \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: Файл \"%s\" не является файлом ресурсов PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: Файл \"%s\" не является допустимым файлом ресурсов PostScript" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: Файл ресурсов \"%s\" неизвестной версии" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Несовместимые многобайтовая кодировка и набор символов." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset не может быть пустым при многобайтовой кодировке." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Нет определения шрифта по умолчанию для многобайтовой печати." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Невозможно открыть файл PostScript" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Невозможно открыть файл \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Файл ресурсов PostScript \"prolog.ps\" не найден" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Файл ресурсов PostScript \"cidfont.ps\" не найден" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Файл ресурсов PostScript \"%s.ps\" не найден" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Невозможно преобразовать в кодировку печать \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Отправка на печать..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Не удалось выполнить печать файла PostScript" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Задание на печать отправлено." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Добавить новую базу данных" @@ -5977,9 +5857,9 @@ msgstr "E424: Используется слишком много разных а msgid "E669: Unprintable character in group name" msgstr "E669: Непечатный символ в имени группы" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Недопустимый символ в имени группы" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Недопустимый символ в имени группы" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/sk.cp1250.po b/src/nvim/po/sk.cp1250.po index ff95c68a12..f0fdc47a1e 100644 --- a/src/nvim/po/sk.cp1250.po +++ b/src/nvim/po/sk.cp1250.po @@ -2994,127 +2994,6 @@ msgstr "hadanie dosiahlo zaiatok, pokraovanie od konca" msgid "search hit BOTTOM, continuing at TOP" msgstr "hadanie dosiahlo koniec, pokraovanie od zaiatku" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Chba dvojbodka" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Neprpustn komponent" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: oakvan slica" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Strana %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "iadny text na tla" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Tlam stranu %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kpia %d z %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Vytlaen: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Tla bola zruen" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Ned sa zapisova do vstupnho PostScriptovho sboru" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Ned sa otvori sbor \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Ned sa ta PostScriptov sbor \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: sbor \"%s\" nie je vo formte PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: sbor \"%s\" nie je podporvan PostScriptov sbor" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" zdrojov sbor m zl slo verzie" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: nekompatibiln viacbajtov kdovanie a znakov sada." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: voba printmbcharset neme by przdna pri viacbajtovom kdovan." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Nie je pecifikovan iadne psmo pre viacbajtov tlaenie." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Ned sa otvori vstupn PostScriptov sbor" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Ned sa otvori sbor \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Nemono njs PostScriptov zdrojov sbor \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Nemono njs PostScriptov zdrojov sbor \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Nemono njs PostScriptov zdrojov sbor \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Nemono skonvertova do kdovania na tlaenie \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Posielam na tlaiare..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScriptov sbor sa nepodarilo vytlai" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Tlaov loha bola odoslan." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Prida nov databzu" @@ -5940,9 +5819,9 @@ msgstr "E424: Pouvanch prli vea odlinch zvrazovacch vlastnost" msgid "E669: Unprintable character in group name" msgstr "E669: Netlaiteln znak v mene skupiny" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Chybn znak v mene skupiny" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Chybn znak v mene skupiny" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/sk.po b/src/nvim/po/sk.po index d35622726f..2d6b4ed901 100644 --- a/src/nvim/po/sk.po +++ b/src/nvim/po/sk.po @@ -2994,127 +2994,6 @@ msgstr "hadanie dosiahlo zaiatok, pokraovanie od konca" msgid "search hit BOTTOM, continuing at TOP" msgstr "hadanie dosiahlo koniec, pokraovanie od zaiatku" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Chba dvojbodka" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Neprpustn komponent" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: oakvan slica" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Strana %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "iadny text na tla" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Tlam stranu %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Kpia %d z %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Vytlaen: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "Tla bola zruen" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Ned sa zapisova do vstupnho PostScriptovho sboru" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Ned sa otvori sbor \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Ned sa ta PostScriptov sbor \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: sbor \"%s\" nie je vo formte PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: sbor \"%s\" nie je podporvan PostScriptov sbor" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" zdrojov sbor m zl slo verzie" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: nekompatibiln viacbajtov kdovanie a znakov sada." - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: voba printmbcharset neme by przdna pri viacbajtovom kdovan." - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Nie je pecifikovan iadne psmo pre viacbajtov tlaenie." - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Ned sa otvori vstupn PostScriptov sbor" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Ned sa otvori sbor \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Nemono njs PostScriptov zdrojov sbor \"prolog.ps\"" - -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Nemono njs PostScriptov zdrojov sbor \"cidfont.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Nemono njs PostScriptov zdrojov sbor \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Nemono skonvertova do kdovania na tlaenie \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Posielam na tlaiare..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScriptov sbor sa nepodarilo vytlai" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Tlaov loha bola odoslan." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Prida nov databzu" @@ -5940,9 +5819,9 @@ msgstr "E424: Pouvanch prli vea odlinch zvrazovacch vlastnost" msgid "E669: Unprintable character in group name" msgstr "E669: Netlaiteln znak v mene skupiny" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Chybn znak v mene skupiny" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Chybn znak v mene skupiny" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/sr.po b/src/nvim/po/sr.po index 3c45e1bf80..4a9dcf4a5d 100644 --- a/src/nvim/po/sr.po +++ b/src/nvim/po/sr.po @@ -2573,100 +2573,6 @@ msgstr "Стил:" msgid "Size:" msgstr "Величина:" -msgid "E550: Missing colon" -msgstr "E550: Недостаје двотачка" - -msgid "E551: Illegal component" -msgstr "E551: Неисправна компонента" - -msgid "E552: digit expected" -msgstr "E552: очекује се цифра" - -#, c-format -msgid "Page %d" -msgstr "Страна %d" - -msgid "No text to be printed" -msgstr "Нема текста за штампу" - -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Штампање стране %d (%d%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Копија %d од %d" - -#, c-format -msgid "Printed: %s" -msgstr "Одштампано: %s" - -msgid "Printing aborted" -msgstr "Штампање прекинуто" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Грешка приликом уписа у PostScript излазну датотеку" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Датотека \"%s\" не може да се отвори" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: PostScript resource датотека \"%s\" не може да се чита" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: датотека \"%s\" није PostScript resource датотека" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: датотека \"%s\" није подржана PostScript resource датотека" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" resource датотека је погрешне верзије" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Вишебајтно кодирање и скуп карактера нису компатибилни." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset не може бити празно са вишебајтним кодирањем." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Није наведен подразумевани фонт за вишебајтно штампање." - -msgid "E324: Can't open PostScript output file" -msgstr "E324: PostScript излазна датотека не може да се отвори" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Датотека \"%s\" не може да се отвори" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: PostScript resource датотека \"prolog.ps\" не може да се пронађе" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "" -"E456: PostScript resource датотека \"cidfont.ps\" не може да се пронађе" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: PostScript resource датотека \"%s.ps\" не може да се пронађе" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Није могућа конверзија у кодирање за штампу \"%s\"" - -msgid "Sending to printer..." -msgstr "Слање штампачу..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScript датотека није успела да се одштампа" - -msgid "Print job sent." -msgstr "Задатак штампе је послат" - msgid "Add a new database" msgstr "Додај нову базу" @@ -6050,8 +5956,8 @@ msgstr "E424: У употреби је превише различитих ат msgid "E669: Unprintable character in group name" msgstr "E669: У имену групе је карактер који не може да се штампа" -msgid "W18: Invalid character in group name" -msgstr "W18: Неважећи карактер у имену групе" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Неважећи карактер у имену групе" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Превише синтаксних и група истицања" diff --git a/src/nvim/po/sv.po b/src/nvim/po/sv.po index d50c9d695d..e46579c067 100644 --- a/src/nvim/po/sv.po +++ b/src/nvim/po/sv.po @@ -2056,8 +2056,8 @@ msgid "E669: Unprintable character in group name" msgstr "E669: Outskrivbart tecken i gruppnamn" #: ../syntax.c:7292 -msgid "W18: Invalid character in group name" -msgstr "W18: Ogiltigt tecken i gruppnamn" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ogiltigt tecken i gruppnamn" #: ../syntax.c:7306 msgid "E849: Too many highlight and syntax groups" @@ -4168,126 +4168,6 @@ msgstr "E782: fel vid lsning av .sug-fil: %s" msgid "E783: duplicate char in MAP entry" msgstr "E783: dubblerat tecken i MAP-post" -#: ../hardcopy.c:296 -msgid "E550: Missing colon" -msgstr "E550: Kolon saknas" - -#: ../hardcopy.c:308 -msgid "E551: Illegal component" -msgstr "E551: Otillten komponent" - -#: ../hardcopy.c:315 -msgid "E552: digit expected" -msgstr "E552: siffra frvntades" - -#: ../hardcopy.c:529 -#, c-format -msgid "Page %d" -msgstr "Sida %d" - -#: ../hardcopy.c:653 -msgid "No text to be printed" -msgstr "Ingen text att skriva ut" - -#: ../hardcopy.c:724 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "Skriver ut sida %d (%d%%)" - -#: ../hardcopy.c:736 -#, c-format -msgid " Copy %d of %d" -msgstr " Kopia %d av %d" - -#: ../hardcopy.c:789 -#, c-format -msgid "Printed: %s" -msgstr "Skrev ut: %s" - -#: ../hardcopy.c:796 -msgid "Printing aborted" -msgstr "Utskrift avbruten" - -#: ../hardcopy.c:1307 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Fel vid skrivning av utdata till PostScript-fil" - -#: ../hardcopy.c:1679 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Kan inte ppna fil \"%s\"" - -#: ../hardcopy.c:1688 ../hardcopy.c:2401 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Kan inte lsa PostScript-resursfil \"%s\"" - -#: ../hardcopy.c:1704 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: fil \"%s\" r inte en PostScript-resursfil" - -#: ../hardcopy.c:1720 ../hardcopy.c:1737 ../hardcopy.c:1776 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: fil \"%s\" r inte en PostScript-resursfil som stds" - -#: ../hardcopy.c:1788 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\"-kllfilen har fel version" - -#: ../hardcopy.c:2157 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Inkompatibel flerbitskodning och teckenuppsttning." - -#: ../hardcopy.c:2169 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset kan inte vara tom med flerbitskodning." - -#: ../hardcopy.c:2185 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Ingen standardfont angiven fr flerbitsutskrifter." - -#: ../hardcopy.c:2357 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Kan inte ppna PostScript-utfil" - -#: ../hardcopy.c:2389 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Kan inte ppna fil \"%s\"" - -#: ../hardcopy.c:2514 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Kan inte hitta PostScript-kllfilen \"prolog.ps\"" - -#: ../hardcopy.c:2524 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Kan inte hitta PostScript-kllfilen \"cidfont.ps\"" - -#: ../hardcopy.c:2553 ../hardcopy.c:2570 ../hardcopy.c:2596 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Kan inte hitta PostScript-kllfilen \"%s.ps\"" - -#: ../hardcopy.c:2585 -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Kunde inte konvertera frn utskriftkodning \"%s\"" - -#: ../hardcopy.c:2808 -msgid "Sending to printer..." -msgstr "Skickar till skrivare..." - -#: ../hardcopy.c:2812 -msgid "E365: Failed to print PostScript file" -msgstr "E365: Misslyckades med att skriva ut PostScript-fil" - -#: ../hardcopy.c:2814 -msgid "Print job sent." -msgstr "Skrivarjobb skickat." - #: ../mark.c:673 msgid "No marks set" msgstr "Inga mrken satta" diff --git a/src/nvim/po/tojavascript.vim b/src/nvim/po/tojavascript.vim new file mode 100644 index 0000000000..7868570be7 --- /dev/null +++ b/src/nvim/po/tojavascript.vim @@ -0,0 +1,18 @@ +" Invoked with the name "vim.pot" and a list of Vim script names. +" Converts them to a .js file, stripping comments, so that xgettext works. +" Javascript is used because, like Vim, it accepts both single and double +" quoted strings. + +set shortmess+=A + +for name in argv()[1:] + exe 'edit ' .. fnameescape(name) + + " Strip comments + g/^\s*"/s/.*// + + " Write as .js file, xgettext recognizes them + exe 'w! ' .. fnamemodify(name, ":t:r") .. ".js" +endfor + +quit diff --git a/src/nvim/po/tr.po b/src/nvim/po/tr.po index fae2fd4967..eb7879efc4 100644 --- a/src/nvim/po/tr.po +++ b/src/nvim/po/tr.po @@ -1,15 +1,15 @@ # Turkish translations for Neovim # Neovim Türkçe çevirileri # Copyright (C) 2019-2022 Emir SARI <emir_sari@icloud.com> -# This file is distributed under the same license as the Vim package. +# This file is distributed under the same license as the Nvim package. # Emir SARI <emir_sari@icloud.com>, 2019-2022 # msgid "" msgstr "" "Project-Id-Version: Neovim Turkish Localization Project\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-13 22:59+0300\n" -"PO-Revision-Date: 2022-07-20 05:00+0300\n" +"POT-Creation-Date: 2022-09-30 20:46+0300\n" +"PO-Revision-Date: 2022-07-22 23:00+0300\n" "Last-Translator: Emir SARI <emir_sari@icloud.com>\n" "Language-Team: Turkish <https://github.com/bitigchi/neovim>\n" "Language: tr\n" @@ -18,6 +18,21 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +msgid "E163: There is only one file to edit" +msgstr "E163: Düzenlenecek yalnızca bir dosya var" + +msgid "E164: Cannot go before first file" +msgstr "E164: İlk dosyadan öncesine gidilemez" + +msgid "E165: Cannot go beyond last file" +msgstr "E165: Son dosyadan öteye gidilemez" + +msgid "E610: No argument to delete" +msgstr "E610: Silinecek bir argüman yok" + +msgid "E249: window layout changed unexpectedly" +msgstr "E249: Pencere yerleşimi beklenmedik bir biçimde değişti" + msgid "--Deleted--" msgstr "--Silindi--" @@ -49,8 +64,9 @@ msgstr "E680: <arabellek=%d>: Geçersiz arabellek numarası " msgid "E217: Can't execute autocommands for ALL events" msgstr "E217: Otokomutlar TÜM olaylar için çalıştırılamıyor" -msgid "No matching autocommands" -msgstr "Eşleşen otokomut yok" +#, c-format +msgid "No matching autocommands: %s" +msgstr "Eşleşen otokomut yok: %s" msgid "E218: autocommand nesting too deep" msgstr "E218: Çok fazla iç içe geçmiş otokomut" @@ -79,24 +95,18 @@ msgstr "E216: Böyle bir olay yok: %s" msgid "E216: No such group or event: %s" msgstr "E216: Böyle bir grup veya olay yok: %s" -msgid "[Location List]" -msgstr "[Konum Listesi]" - -msgid "[Quickfix List]" -msgstr "[Hızlı Düzelt Listesi]" - msgid "E855: Autocommands caused command to abort" msgstr "E855: Otokomutlar komutun durmasına neden oldu" +msgid "E937: Attempt to delete a buffer that is in use" +msgstr "E937: Kullanımda olan bir arabellek silinmeye çalışılıyor" + msgid "E82: Cannot allocate any buffer, exiting..." msgstr "E82: Arabellek ayrılamadı, çıkılıyor..." msgid "E83: Cannot allocate buffer, using other one..." msgstr "E83: Arabellek ayrılamadı, başka bir tane kullanılıyor..." -msgid "E937: Attempt to delete a buffer that is in use" -msgstr "E937: Kullanımda olan bir arabellek silinmeye çalışılıyor" - msgid "E515: No buffers were unloaded" msgstr "E515: Hiçbir arabellek bellekten kaldırılmadı" @@ -125,8 +135,8 @@ msgstr "E88: İlk arabellekten öncesine gidilemez" msgid "" "E89: No write since last change for buffer %<PRId64> (add ! to override)" msgstr "" -"E89: %<PRId64> numaralı arabellek son değişiklikten sonra yazılmadı (geçersiz " -"kılmak için ! ekleyin)" +"E89: %<PRId64> numaralı arabellek son değişiklikten sonra yazılmadı " +"(geçersiz kılmak için ! ekleyin)" #, c-format msgid "E89: %s will be killed (add ! to override)" @@ -192,12 +202,6 @@ msgstr "[Adsız]" msgid "help" msgstr "yardım" -msgid "[Help]" -msgstr "[Yardım]" - -msgid "[Preview]" -msgstr "[Önizleme]" - msgid "All" msgstr "Tüm Belge" @@ -216,6 +220,12 @@ msgstr "[İstem]" msgid "[Scratch]" msgstr "[Geçici alan]" +msgid "[Location List]" +msgstr "[Konum Listesi]" + +msgid "[Quickfix List]" +msgstr "[Hızlı Düzelt Listesi]" + msgid "W10: Warning: Changing a readonly file" msgstr "W10: Uyarı: Saltokunur bir dosya değiştiriliyor" @@ -231,6 +241,15 @@ msgstr "Kapalı akışa veri gönderilemez" msgid "Can't send raw data to rpc channel" msgstr "rpc kanalına ham veri gönderilemez" +msgid "tagname" +msgstr "etiket adı" + +msgid " kind file\n" +msgstr " dosya türü\n" + +msgid "'history' option is zero" +msgstr "'history' seçeneği sıfır" + msgid "E474: Failed to convert list to msgpack string buffer" msgstr "E474: Liste, msgpack dizi arabelleğine dönüştürülemedi" @@ -339,7 +358,7 @@ msgid "E103: Buffer \"%s\" is not in diff mode" msgstr "E103: Arabellek \"%s\" karşılaştırma kipinde değil" msgid "E787: Buffer changed unexpectedly" -msgstr "E787: Arabellek beklenmeyen bir biçimde değiştirildi" +msgstr "E787: Arabellek beklenmedik bir biçimde değiştirildi" #, c-format msgid "E1214: Digraph must be just two characters: %s" @@ -441,140 +460,22 @@ msgstr "E105: :loadkeymap kaynak alınmayan bir dosyada kullanılıyor" msgid "E791: Empty keymap entry" msgstr "E791: Boş düğme eşlem girdisi" -msgid " Keyword completion (^N^P)" -msgstr " Anahtar sözcük tamamlaması (^N^P)" - -msgid " ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)" -msgstr " ^X kipi (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)" - -msgid " Whole line completion (^L^N^P)" -msgstr " Tam satır tamamlaması (^L^N^P)" - -msgid " File name completion (^F^N^P)" -msgstr " Dosya adı tamamlaması (^F^N^P)" - -msgid " Tag completion (^]^N^P)" -msgstr " Etiket tamamlaması (^]^N^P)" - -msgid " Path pattern completion (^N^P)" -msgstr " Yol dizgisi tamamlaması (^N^P)" - -msgid " Definition completion (^D^N^P)" -msgstr " Tanım tamamlaması (^D^N^P)" - -msgid " Dictionary completion (^K^N^P)" -msgstr " Sözlük tamamlaması (^K^N^P)" - -msgid " Thesaurus completion (^T^N^P)" -msgstr " Eşanlamlılar sözlüğü tamamlaması (^T^N^P)" - -msgid " Command-line completion (^V^N^P)" -msgstr " Komut satırı tamamlaması (^V^N^P)" - -msgid " User defined completion (^U^N^P)" -msgstr " Kullanıcı tanımlı tamamlamalar (^U^N^P)" - -msgid " Omni completion (^O^N^P)" -msgstr " Omni tamamlaması (^O^N^P)" - -msgid " Spelling suggestion (s^N^P)" -msgstr " Yazım önerisi (s^N^P)" - -msgid " Keyword Local completion (^N^P)" -msgstr " Dahili anahtar sözcük tamamlaması (^N^P)" - -msgid "Hit end of paragraph" -msgstr "Paragrafın sonuna varıldı" - -msgid "E839: Completion function changed window" -msgstr "E839: Tamamlama işlevi pencereyi değiştirdi" - -msgid "E840: Completion function deleted text" -msgstr "E840: Tamamlama işlevi metni sildi" - -msgid "'dictionary' option is empty" -msgstr "'dictionary' seçeneği boş" - -msgid "'thesaurus' option is empty" -msgstr "'thesaurus' seçeneği boş" - -#, c-format -msgid "Scanning dictionary: %s" -msgstr "Sözlük taranıyor: %s" - -msgid " (insert) Scroll (^E/^Y)" -msgstr " (ekle) Kaydır (^E/^Y)" - -msgid " (replace) Scroll (^E/^Y)" -msgstr " (değiştir) Kaydır (^E/^Y)" - -#, c-format -msgid "Scanning: %s" -msgstr "Taranıyor: %s" - -msgid "Scanning tags." -msgstr "Etiketler taranıyor..." - -msgid "match in file" -msgstr "dosya içinde eşleşme" - -msgid " Adding" -msgstr " Ekleniyor" - -msgid "-- Searching..." -msgstr "-- Aranıyor..." - -msgid "Back at original" -msgstr "Başlangıca geri dönüldü" - -msgid "Word from other line" -msgstr "Sözcük diğer satırdan" - -msgid "The only match" -msgstr "Tek eşleşen" - -#, c-format -msgid "match %d of %d" -msgstr "eşleşme %d/%d" - -#, c-format -msgid "match %d" -msgstr "eşleşme %d" - -msgid "E18: Unexpected characters in :let" -msgstr "E18: :let içinde beklenmeyen karakter" - msgid "E111: Missing ']'" msgstr "E111: ']' eksik" msgid "E719: Cannot use [:] with a Dictionary" msgstr "E719: [:], bir Sözlük ile kullanılamaz" -#, c-format -msgid "E461: Illegal variable name: %s" -msgstr "E461: İzin verilmeyen değişken adı: %s" - -msgid "E995: Cannot modify existing variable" -msgstr "E995: Mevcut değişken değiştirilemiyor" - msgid "E274: No white space allowed before parenthesis" msgstr "E274: Ayraçtan önce boşluğa izin verilmiyor" #, c-format -msgid "E940: Cannot lock or unlock variable %s" -msgstr "E940: Değişken %s kilitlenemiyor veya açılamıyor" - -#, c-format msgid "E80: Error while writing: %s" msgstr "E80: Yazma sırasında hata: %s" msgid "E1098: String, List or Blob required" msgstr "E1098: Dizi, Liste veya İkili Nesne gerekiyor" -#, c-format -msgid "E734: Wrong variable type for %s=" -msgstr "E734: %s= için yanlış değişken türü" - msgid "" "E5700: Expression from 'spellsuggest' must yield lists with exactly two " "values" @@ -582,41 +483,6 @@ msgstr "" "E5700: 'spellsuggest'ten olan ifade, yalnızca iki değerli listelere izin " "vermelidir" -msgid "E991: cannot use =<< here" -msgstr "E991: Burada =<< kullanılamaz" - -msgid "E221: Marker cannot start with lower case letter" -msgstr "E221: İmleyici küçük harfle başlayamaz" - -msgid "E172: Missing marker" -msgstr "E172: İmleyici eksik" - -#, c-format -msgid "E990: Missing end marker '%s'" -msgstr "E990: Son imleyici '%s' eksik" - -msgid "E687: Less targets than List items" -msgstr "E687: Liste ögelerinden daha az hedef var" - -msgid "E688: More targets than List items" -msgstr "E688: Liste ögelerinden daha fazla hedef var" - -msgid "E452: Double ; in list of variables" -msgstr "E452: Değişkenler listesinde çifte ;" - -#, c-format -msgid "E738: Can't list variables for %s" -msgstr "E738: %s için değişkenler listelenemiyor" - -msgid "E996: Cannot lock an environment variable" -msgstr "E996: Ortam değişkeni kilitlenemiyor" - -msgid "E996: Cannot lock an option" -msgstr "E996: Seçenek kilitlenemiyor" - -msgid "E996: Cannot lock a register" -msgstr "E996: Yazmaç kilitlenemiyor" - #, c-format msgid "E121: Undefined variable: %.*s" msgstr "E121: Tanımlanmamış değişken: %.*s" @@ -652,10 +518,6 @@ msgstr "E996: Bir liste veya sözlük kilitlenemiyor" msgid "E690: Missing \"in\" after :for" msgstr "E690: :for sonrası \"in\" eksik" -#, c-format -msgid "E108: No such variable: \"%s\"" -msgstr "E108: Böyle bir değişken yok: \"%s\"" - msgid "E109: Missing ':' after '?'" msgstr "E109: '?' sonrası ':' eksik" @@ -747,26 +609,6 @@ msgstr "Komut çalıştırılıyor: \"%s\"" msgid "E921: Invalid callback argument" msgstr "E921: Geçersiz geri çağırma argümanı" -#, c-format -msgid "E963: setting %s to value with wrong type" -msgstr "E963: %s yanlış türe sahip değere ayarlanıyor" - -#, c-format -msgid "E794: Cannot set variable in the sandbox: \"%.*s\"" -msgstr "E794: Kum havuzunda değişken ayarlanamaz: \"%.*s\"" - -#, c-format -msgid "E795: Cannot delete variable %.*s" -msgstr "E795: %.*s değişkeni silinemiyor" - -#, c-format -msgid "E704: Funcref variable name must start with a capital: %s" -msgstr "E704: Funcref değişkeni BÜYÜK harf ile başlamalıdır: %s" - -#, c-format -msgid "E705: Variable name conflicts with existing function: %s" -msgstr "E705: Değişken adı mevcut işlevle çakışıyor: %s" - msgid "E698: variable nested too deep for making a copy" msgstr "E698: Değişken kopyalama için çok iç içe geçmiş" @@ -852,8 +694,8 @@ msgid "" "E474: Only UTF-8 code points up to U+10FFFF are allowed to appear unescaped: " "%.*s" msgstr "" -"E474: Yalnızca U+10FFFF'e kadar olan UTF-8 kod noktalarına kaçışsız " -"var olma izni verilir: %.*s" +"E474: Yalnızca U+10FFFF'e kadar olan UTF-8 kod noktalarına kaçışsız var olma " +"izni verilir: %.*s" #, c-format msgid "E474: Expected string end: %.*s" @@ -888,8 +730,8 @@ msgid "" "E685: internal error: while converting number \"%.*s\" to integer vim_str2nr " "consumed %i bytes in place of %zu" msgstr "" -"E685: İçsel hata: \"%.*s\" sayısı tamsayıya dönüştürülürken " -"vim_str2nr %i bayt harcadı, %zu bayt harcaması gerekiyordu" +"E685: İçsel hata: \"%.*s\" sayısı tamsayıya dönüştürülürken vim_str2nr %i " +"bayt harcadı, %zu bayt harcaması gerekiyordu" msgid "E474: Attempt to decode a blank string" msgstr "E474: Boş bir dizinin kodu çözülmeye çalışılıyor" @@ -1019,15 +861,15 @@ msgstr "E474: JSON içinde sonsuzluk temsil edilemiyor" #, c-format msgid "" "E474: String \"%.*s\" contains byte that does not start any UTF-8 character" -msgstr "E474: \"%.*s\" dizisi, herhangi bir UTF-8 karakter başlatmayan bayt " -"içeriyor" +msgstr "" +"E474: \"%.*s\" dizisi, herhangi bir UTF-8 karakter başlatmayan bayt içeriyor" #, c-format msgid "" "E474: UTF-8 string contains code point which belongs to a surrogate pair: " "%.*s" -msgstr "E474: UTF-8 dizisi, bir vekil çifte iye olan kod noktası içeriyor: " -"%.*s" +msgstr "" +"E474: UTF-8 dizisi, bir vekil çifte iye olan kod noktası içeriyor: %.*s" msgid "E474: Unable to convert EXT string to JSON" msgstr "E474: EXT dizisi, JSON'a dönüştürülemiyor" @@ -1086,9 +928,6 @@ msgstr "E158: Geçersiz arabellek adı: %s" msgid "Invalid channel stream \"%s\"" msgstr "Geçersiz kanal akışı \"%s\"" -msgid "E785: complete() can only be used in Insert mode" -msgstr "E785: complete() yalnızca Ekleme kipinde kullanılabilir" - msgid "&Ok" msgstr "&Tamam" @@ -1169,10 +1008,6 @@ msgid "E5010: List item %d of the second argument is not a string" msgstr "E5010: İkinci argümanın %d liste ögesi bir dizi değil" #, c-format -msgid "E927: Invalid action: '%s'" -msgstr "E927: Geçersiz eylem: '%s'" - -#, c-format msgid "E962: Invalid action: '%s'" msgstr "E962: Geçersiz eylem: '%s'" @@ -1180,18 +1015,6 @@ msgstr "E962: Geçersiz eylem: '%s'" msgid "connection failed: %s" msgstr "bağlantı başarısız: %s" -msgid "sort() argument" -msgstr "sort() argümanı" - -msgid "uniq() argument" -msgstr "uniq() argümanı" - -msgid "E702: Sort compare function failed" -msgstr "E702: Sıralayıp karşılaştırma işlevi başarısız oldu" - -msgid "E882: Uniq compare function failed" -msgstr "E882: Benzersizlik karşılaştırma işlevi başarısız oldu" - #, c-format msgid "E6100: \"%s\" is not a valid stdpath" msgstr "E6100: \"%s\", geçerli bir stdpath değil" @@ -1225,6 +1048,18 @@ msgid "E80: Error when closing file %s: %s" msgstr "E80: %s dosyası kapatılırken hata: %s" #, c-format +msgid "E1174: String required for argument %d" +msgstr "E1174: %d argümanı için dizi gerekiyor" + +#, c-format +msgid "E1142: Non-empty string required for argument %d" +msgstr "E1142: %d argümanı için boş olmayan dizi gerekiyor" + +#, c-format +msgid "E1210: Number required for argument %d" +msgstr "E1210: %d argümanı için sayı gerekiyor" + +#, c-format msgid "E5142: Failed to open file %s: %s" msgstr "E5142: %s dosyası açılamadı: %s" @@ -1236,6 +1071,18 @@ msgstr "E5143: %s dosyası yazılamadı: %s" msgid "E5144: Failed to close file %s: %s" msgstr "E5144: %s dosyası kapatılamadı: %s" +msgid "sort() argument" +msgstr "sort() argümanı" + +msgid "uniq() argument" +msgstr "uniq() argümanı" + +msgid "E702: Sort compare function failed" +msgstr "E702: Sıralayıp karşılaştırma işlevi başarısız oldu" + +msgid "E882: Uniq compare function failed" +msgstr "E882: Benzersizlik karşılaştırma işlevi başarısız oldu" + msgid "E6000: Argument is not a function or function name" msgstr "E6000: Argüman bir işlev veya işlev adı değil" @@ -1324,7 +1171,8 @@ msgid "E907: Using a special value as a Float" msgstr "E907: Bir Kayan Noktalı Değer olarak bir özel değer kullanılıyor" msgid "E975: Using a Blob as a Float" -msgstr "E975: Bir kayan noktalı değer olarak bir ikili geniş nesne kullanılıyor" +msgstr "" +"E975: Bir kayan noktalı değer olarak bir ikili geniş nesne kullanılıyor" msgid "E808: Number or Float required" msgstr "E808: Sayı veya kayan noktalı değer gerekiyor" @@ -1445,6 +1293,9 @@ msgstr "E126: :endfunction eksik" msgid "W22: Text found after :endfunction: %s" msgstr "W22: :endfunction sonrası metin bulundu: %s" +msgid "E1058: function nesting too deep" +msgstr "E1058: Pek çok iç içe geçmiş işlev" + #, c-format msgid "E707: Function name conflicts with variable: %s" msgstr "E707: İşlev adı şu değişken ile çakışıyor: %s" @@ -1468,6 +1319,72 @@ msgstr "%s işlevi silinemiyor: Şu an program içinde kullanımda" msgid "E133: :return not inside a function" msgstr "E133: :return bir işlev içinde değil" +msgid "E18: Unexpected characters in :let" +msgstr "E18: :let içinde beklenmedik karakter" + +#, c-format +msgid "E940: Cannot lock or unlock variable %s" +msgstr "E940: Değişken %s kilitlenemiyor veya açılamıyor" + +msgid "E991: cannot use =<< here" +msgstr "E991: Burada =<< kullanılamaz" + +msgid "E221: Marker cannot start with lower case letter" +msgstr "E221: İmleyici küçük harfle başlayamaz" + +msgid "E172: Missing marker" +msgstr "E172: İmleyici eksik" + +#, c-format +msgid "E990: Missing end marker '%s'" +msgstr "E990: Son imleyici '%s' eksik" + +msgid "E687: Less targets than List items" +msgstr "E687: Liste ögelerinden daha az hedef var" + +msgid "E688: More targets than List items" +msgstr "E688: Liste ögelerinden daha fazla hedef var" + +msgid "E452: Double ; in list of variables" +msgstr "E452: Değişkenler listesinde çifte ;" + +#, c-format +msgid "E738: Can't list variables for %s" +msgstr "E738: %s için değişkenler listelenemiyor" + +msgid "E996: Cannot lock an environment variable" +msgstr "E996: Ortam değişkeni kilitlenemiyor" + +msgid "E996: Cannot lock an option" +msgstr "E996: Seçenek kilitlenemiyor" + +msgid "E996: Cannot lock a register" +msgstr "E996: Yazmaç kilitlenemiyor" + +#, c-format +msgid "E108: No such variable: \"%s\"" +msgstr "E108: Böyle bir değişken yok: \"%s\"" + +#, c-format +msgid "E963: setting %s to value with wrong type" +msgstr "E963: %s yanlış türe sahip değere ayarlanıyor" + +#, c-format +msgid "E794: Cannot set variable in the sandbox: \"%.*s\"" +msgstr "E794: Kum havuzunda değişken ayarlanamaz: \"%.*s\"" + +#, c-format +msgid "E795: Cannot delete variable %.*s" +msgstr "E795: %.*s değişkeni silinemiyor" + +#, c-format +msgid "E704: Funcref variable name must start with a capital: %s" +msgstr "E704: Funcref değişkeni BÜYÜK harf ile başlamalıdır: %s" + +#, c-format +msgid "E705: Variable name conflicts with existing function: %s" +msgstr "E705: Değişken adı mevcut işlevle çakışıyor: %s" + msgid "tcp address must be host:port" msgstr "tcp adresi makine:kapı olmalı" @@ -1596,51 +1513,9 @@ msgstr "Dizginin bulunduğu her satır: %s" msgid "Pattern not found: %s" msgstr "Dizgi bulunamadı: %s" -msgid "E478: Don't panic!" -msgstr "E478: Panik yok!" - -#, c-format -msgid "E661: Sorry, no '%s' help for %s" -msgstr "E661: Üzgünüm, '%s' yardımı %s için mevcut değil" - -#, c-format -msgid "E149: Sorry, no help for %s" -msgstr "E149: Üzgünüm, %s için yardım mevcut değil" - -#, c-format -msgid "Sorry, help file \"%s\" not found" -msgstr "Üzgünüm, \"%s\" yardım dosyası bulunamadı" - -#, c-format -msgid "E151: No match: %s" -msgstr "E151: Eşleşme bulunamadı: %s" - -#, c-format -msgid "E152: Cannot open %s for writing" -msgstr "E152: %s yazma için açılamıyor" - -#, c-format -msgid "E153: Unable to open %s for reading" -msgstr "E153: %s okuma için açılamıyor" - -#, c-format -msgid "E670: Mix of help file encodings within a language: %s" -msgstr "E670: Bir dilde yardım dosyası kodlamaları karıştı: %s" - -#, c-format -msgid "E154: Duplicate tag \"%s\" in file %s/%s" -msgstr "E154: Şu dosyada yinelenen \"%s\" etiketi: %s/%s" - -#, c-format -msgid "E150: Not a directory: %s" -msgstr "E150: %s, bir dizin değil" - msgid "No old files" msgstr "Eski dosya yok" -msgid "E750: First use \":profile start {fname}\"" -msgstr "E750: İlk kullanım \":profile start {dosyaadı}\"" - #, c-format msgid "Save changes to \"%s\"?" msgstr "Değişiklikler şuraya kaydedilsin mi: \"%s\"?" @@ -1660,102 +1535,24 @@ msgstr "E162: \"%s\" arabelleği son değişiklikten sonra yazılmadı" msgid "Warning: Entered other buffer unexpectedly (check autocommands)" msgstr "Uyarı: Diğer arabelleğe aniden girildi (otokomutları denetleyin)" -msgid "E163: There is only one file to edit" -msgstr "E163: Düzenlenecek yalnızca bir dosya var" - -msgid "E164: Cannot go before first file" -msgstr "E164: İlk dosyadan öncesine gidilemez" - -msgid "E165: Cannot go beyond last file" -msgstr "E165: Son dosyadan öteye gidilemez" - -msgid "E610: No argument to delete" -msgstr "E610: Silinecek bir argüman yok" - #, c-format msgid "E666: compiler not supported: %s" msgstr "E666: Derleyici desteklenmiyor: %s" -#, c-format -msgid "Cannot source a directory: \"%s\"" -msgstr "Dizin kaynak alınamıyor: \"%s\"" - -#, c-format -msgid "could not source \"%s\"" -msgstr "\"%s\" kaynak alınamadı" - -#, c-format -msgid "line %<PRId64>: could not source \"%s\"" -msgstr "%<PRId64>. satır: \"%s\" kaynak alınamadı" - -#, c-format -msgid "sourcing \"%s\"" -msgstr "\"%s\" kaynak alınıyor" - -#, c-format -msgid "line %<PRId64>: sourcing \"%s\"" -msgstr "%<PRId64>. satır: \"%s\" kaynak alınıyor" - -#, c-format -msgid "finished sourcing %s" -msgstr "%s kaynak alınması bitti" - -msgid "modeline" -msgstr "kip satırı" - -msgid "--cmd argument" -msgstr "--cmd argümanı" - -msgid "-c argument" -msgstr "-c argümanı" - -msgid "environment variable" -msgstr "ortam değişkeni" - -msgid "error handler" -msgstr "hata işleyicisi" - -msgid "changed window size" -msgstr "değiştirilen pencere boyutu" - -msgid "Lua" -msgstr "Lua" - -#, c-format -msgid "API client (channel id %<PRIu64>)" -msgstr "API istemcisi (kanal kimliği %<PRIu64>" - -msgid "anonymous :source" -msgstr "anonim :source" - -#, c-format -msgid "anonymous :source (script id %d)" -msgstr "anonim :source (betik kimliği %d)" - -msgid "W15: Warning: Wrong line separator, ^M may be missing" -msgstr "W15: Uyarı: Yanlış satır ayırıcısı, ^M eksik olabilir" - -msgid "E167: :scriptencoding used outside of a sourced file" -msgstr "E167: :scriptencoding kaynak alınmış bir dosyanın dışında kullanıldı" +msgid "E464: Ambiguous use of user-defined command" +msgstr "E464: Kullanıcı tanımlı komutun belirsiz kullanımı" -msgid "E168: :finish used outside of a sourced file" -msgstr "E168: :finish kaynak alınmış bir dosyanın dışında kullanıldı" +msgid "E492: Not an editor command" +msgstr "E492: Bir düzenleyici komutu değil" -#, c-format -msgid "Current %slanguage: \"%s\"" -msgstr "Şu anki %sdil: \"%s\"" +msgid "E498: no :source file name to substitute for \"<sfile>\"" +msgstr "E498: \"<kdosyası>\" yerine koymak için :source dosya adı yok" -#, c-format -msgid "E197: Cannot set language to \"%s\"" -msgstr "E197: \"%s\" diline ayarlanamıyor" +msgid "E489: no call stack to substitute for \"<stack>\"" +msgstr "E489: \"<yığın>\" yerine koymak için çağrı yığını yok" -#, c-format -msgid "E184: No such user-defined command: %s" -msgstr "E184: Böyle bir kullanıcı tanımlı komut yok: %s" - -#, c-format -msgid "E1237: No such user-defined command in current buffer: %s" -msgstr "E1237: Geçerli arabellekte böyle bir kullanıcı tanımlı komut yok: %s" +msgid "E1274: No script file name to substitute for \"<script>\"" +msgstr "E1274: \"<betik>\" yerine koymak için betik dosyası adı yok" msgid "Entering Ex mode. Type \"visual\" to go to Normal mode." msgstr "Ex kipine giriliyor. Normal kipe geri dönmek için \"visual\" yazın." @@ -1770,9 +1567,6 @@ msgstr "Çalıştırılıyor: %s" msgid "line %" msgstr "satır %" -msgid "E169: Command too recursive" -msgstr "E169: Komut çok özyineli" - #, c-format msgid "E605: Exception not caught: %s" msgstr "E605: Kural dışı durum yakalanmadı: %s" @@ -1783,11 +1577,11 @@ msgstr "Kaynak alınan dosyanın sonu" msgid "End of function" msgstr "İşlevin sonu" -msgid "E464: Ambiguous use of user-defined command" -msgstr "E464: Kullanıcı tanımlı komutun belirsiz kullanımı" - -msgid "E492: Not an editor command" -msgstr "E492: Bir düzenleyici komutu değil" +msgid "" +"INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX" +msgstr "" +"DAHİLİ: EX_DFLALL; ADDR_NONE, ADDR_UNSIGNED veya ADDR_QUICKFIX ile birlikte " +"kullanılamaz" msgid "E493: Backwards range given" msgstr "E493: Geriye dönük erim verildi" @@ -1798,12 +1592,6 @@ msgstr "Geriye dönük erim verildi, takas edilebilir" msgid "E494: Use w or w>>" msgstr "E494: w veya w>> kullanın" -msgid "" -"INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX" -msgstr "" -"DAHİLİ: EX_DFLALL; ADDR_NONE, ADDR_UNSIGNED veya ADDR_QUICKFIX ile birlikte " -"kullanılamaz" - msgid "E943: Command table needs to be updated, run 'make'" msgstr "E943: Komut tablosunun güncellenmesi gerekiyor, 'make' çalıştırın" @@ -1811,68 +1599,6 @@ msgid "E319: The command is not available in this version" msgstr "E319: Üzgünüm, komut bu sürümde mevcut değil" #, c-format -msgid "E174: Command already exists: add ! to replace it: %s" -msgstr "E174: Komut zaten mevcut: Değiştirmek için ! ekleyin: %s" - -msgid "" -"\n" -" Name Args Address Complete Definition" -msgstr "" -"\n" -" Ad Dğkl Adres Tam Tanım" - -msgid "No user-defined commands found" -msgstr "Kullanıcı tanımlı bir komut bulunamadı" - -msgid "E175: No attribute specified" -msgstr "E175: Bir öznitelik belirtilmemiş" - -msgid "E176: Invalid number of arguments" -msgstr "E176: Geçersiz argüman sayısı" - -msgid "E177: Count cannot be specified twice" -msgstr "E177: Sayım iki defa belirtilemez" - -msgid "E178: Invalid default value for count" -msgstr "E178: Sayım için geçersiz öntanımlı değer" - -msgid "E179: argument required for -complete" -msgstr "E179: -complete için argüman gerekiyor" - -msgid "E179: argument required for -addr" -msgstr "E179: -addr için argüman gerekiyor" - -#, c-format -msgid "E181: Invalid attribute: %s" -msgstr "E181: Geçersiz öznitelik: %s" - -msgid "E1208: -complete used without -nargs" -msgstr "E1208: -complete, -nargs olmadan kullanıldı" - -msgid "E182: Invalid command name" -msgstr "E182: Geçersiz komut adı" - -msgid "E183: User defined commands must start with an uppercase letter" -msgstr "E183: Kullanıcı tanımlı komutlar BÜYÜK harfle başlamalıdır" - -msgid "E841: Reserved name, cannot be used for user defined command" -msgstr "E841: Ayrılmış ad, kullanıcı tanımlı komut için kullanılamaz" - -#, c-format -msgid "E180: Invalid address type value: %s" -msgstr "E180: Geçersiz adres türü değeri: %s" - -#, c-format -msgid "E180: Invalid complete value: %s" -msgstr "E180: Geçersiz tam değer: %s" - -msgid "E468: Completion argument only allowed for custom completion" -msgstr "E468: Tamamlama argümanına yalnızca özel tamamlamalarda izin verilir" - -msgid "E467: Custom completion requires a function argument" -msgstr "E467: Özel tamamlama bir işlev argümanı gerektirir" - -#, c-format msgid "E185: Cannot find color scheme '%s'" msgstr "E185: '%s' renk düzeni bulunamadı" @@ -1931,9 +1657,6 @@ msgstr "" msgid "E497: no autocommand match name to substitute for \"<amatch>\"" msgstr "E497: \"<oeşi>\" yerine koymak için otokomut eşleşme adı yok" -msgid "E498: no :source file name to substitute for \"<sfile>\"" -msgstr "E498: \"<kdosyası>\" yerine koymak için :source dosya adı yok" - msgid "E842: no line number to use for \"<slnum>\"" msgstr "E842: \"<slnum>\" kullanımı için satır numarası yok" @@ -2051,9 +1774,6 @@ msgstr "E602: :try olmadan :endtry" msgid "E193: :endfunction not inside a function" msgstr "E193: :endfunction, bir işlev içinde değil" -msgid "E788: Not allowed to edit another buffer now" -msgstr "E788: Şu anda başka bir arabellek düzenlenemez" - msgid "E811: Not allowed to change buffer information now" msgstr "E811: Şu anda arabellek bilgisi değiştirilemez" @@ -2088,15 +1808,6 @@ msgstr "E5404: %i parçası bitiş %" msgid "E5406: Chunk %i end %" msgstr "E5406: %i parçası bitiş %" -msgid "tagname" -msgstr "etiket adı" - -msgid " kind file\n" -msgstr " dosya türü\n" - -msgid "'history' option is zero" -msgstr "'history' seçeneği sıfır" - msgid "[Command Line]" msgstr "[Komut Satırı]" @@ -2133,6 +1844,10 @@ msgstr "E347: Başka bir \"%s\" dosyası yol içinde bulunamadı" msgid "E812: Autocommands changed buffer or buffer name" msgstr "E812: Otokomutlar arabelleği veya arabellek adını değiştirdi" +#, c-format +msgid "E676: No matching autocommands for buftype=%s buffer" +msgstr "E676: buftype=%s arabelleği için eşleşen otokomut yok" + msgid "is a directory" msgstr "bir dizin" @@ -2207,14 +1922,11 @@ msgstr "[Yeni Dosya]" msgid "[New]" msgstr "[Yeni]" -msgid "E676: No matching autocommands for acwrite buffer" -msgstr "E676: acwrite arabelleği için eşleşen bir otokomut yok" - msgid "E203: Autocommands deleted or unloaded buffer to be written" msgstr "E203: Otokomutlar arabelleği silmiş veya yazılması için kaldırmışlar" msgid "E204: Autocommand changed number of lines in unexpected way" -msgstr "E204: Otokomut satır sayısını beklenmeyen biçimde değiştirdi" +msgstr "E204: Otokomut satır sayısını beklenmedik biçimde değiştirdi" msgid "is not a file or writable device" msgstr "bir dosya veya yazılabilir aygıt değil" @@ -2422,31 +2134,6 @@ msgstr "E222: Okunan arabelleğe ekle" msgid "E223: recursive mapping" msgstr "E223: Özyineli eşlemleme" -#, c-format -msgid "E224: global abbreviation already exists for %s" -msgstr "E224: %s için global kısaltma hâlihazırda var" - -#, c-format -msgid "E225: global mapping already exists for %s" -msgstr "E225: %s için global eşlemleme hâlihazırda var" - -#, c-format -msgid "E226: abbreviation already exists for %s" -msgstr "E226: %s için kısaltma hâlihazırda var" - -#, c-format -msgid "E227: mapping already exists for %s" -msgstr "E227: %s için eşlemleme hâlihazırda var" - -msgid "No abbreviation found" -msgstr "Kısaltma bulunamadı" - -msgid "No mapping found" -msgstr "Eşlemleme bulunamadı" - -msgid "E228: makemap: Illegal mode" -msgstr "E228: makemap: İzin verilmeyen kip" - msgid "--No lines in buffer--" msgstr "--Arabellek içinde satır yok--" @@ -2473,6 +2160,9 @@ msgstr "" "E12: Geçerli dizin veya etiket aramasında exrc veya vimrc'den komutlara izin " "verilmiyor" +msgid "E169: Command too recursive" +msgstr "E169: Komut çok özyineli" + msgid "E171: Missing :endif" msgstr "E171: :endif eksik" @@ -2572,8 +2262,9 @@ msgstr "E906: rpc kanalı için geçersiz akış, 'rpc' kullanın" #, c-format msgid "" "E5210: dict key '%s' already set for buffered stream in channel %<PRIu64>" -msgstr "E5210: '%s' sözlük anahtarı, %<PRIu64> kanalında halihazırda " -"arabelleklenmiş akış için ayarlandı" +msgstr "" +"E5210: '%s' sözlük anahtarı, %<PRIu64> kanalında halihazırda arabelleklenmiş " +"akış için ayarlandı" #, c-format msgid "E364: Library call failed for \"%s()\"" @@ -2703,6 +2394,17 @@ msgid "E45: 'readonly' option is set (add ! to override)" msgstr "E45: 'readonly' seçeneği ayarlanmış (geçersiz kılmak için ! ekleyin)" #, c-format +msgid "E734: Wrong variable type for %s=" +msgstr "E734: %s= için yanlış değişken türü" + +#, c-format +msgid "E461: Illegal variable name: %s" +msgstr "E461: İzin verilmeyen değişken adı: %s" + +msgid "E995: Cannot modify existing variable" +msgstr "E995: Mevcut değişken değiştirilemiyor" + +#, c-format msgid "E46: Cannot change read-only variable \"%.*s\"" msgstr "E46: Saltokunur değişken \"%.*s\" değiştirilemiyor" @@ -2750,6 +2452,9 @@ msgstr "E48: Kum havuzunda izin verilmiyor" msgid "E523: Not allowed here" msgstr "E523: Burada izin verilmiyor" +msgid "E565: Not allowed to change text or change window" +msgstr "E565: Metin veya pencere değiştirmeye izin verilmiyor" + msgid "E359: Screen mode setting not supported" msgstr "E359: Ekran kipi ayarı desteklenmiyor" @@ -2842,6 +2547,9 @@ msgstr "E919: '%s' içinde dizin bulunamadı: \"%s\"" msgid "E952: Autocommand caused recursive behavior" msgstr "E952: Otokomut özyineli davranışa neden oldu" +msgid "E328: Menu only exists in another mode" +msgstr "E328: Menü yalnızca başka bir kipte mevcut" + msgid "E813: Cannot close autocmd window" msgstr "E813: Otokomut penceresi kapatılamıyor" @@ -2858,6 +2566,9 @@ msgstr "E856: Dosya adı pek uzun" msgid "E806: using Float as a String" msgstr "E806: Kayan Noktalı Değer, bir Dizi yerine kullanılıyor" +msgid "E788: Not allowed to edit another buffer now" +msgstr "E788: Şu anda başka bir arabellek düzenlenemez" + #, c-format msgid "E5500: autocmd has thrown an exception: %s" msgstr "E5500: Otokomut, bir istisna attı: %s" @@ -2869,10 +2580,6 @@ msgid "E5521: <Cmd> mapping must end with <CR> before second <Cmd>" msgstr "E5521: <Cmd> eşlemlemesi ikinci <Cmd>'den önce <CR> ile bitmelidir" #, c-format -msgid "E5522: <Cmd> mapping must not include %s key" -msgstr "E5522: <Cmd> eşlemlemesi %s anahtarını içermemelidir" - -#, c-format msgid "E5555: API call: %s" msgstr "E5555: API çağrısı: %s" @@ -2898,9 +2605,17 @@ msgstr "E1240: Ortaya çıkan metin pek uzun" msgid "E1247: Line number out of range" msgstr "E1247: Satır numarası erim dışında" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Grup adında geçersiz karakter" + msgid "E1249: Highlight group name too long" msgstr "E1249: Vurgulama grubu adı pek uzun" +msgid "E5767: Cannot use :undo! to redo or move to a different undo branch" +msgstr "" +"E5767: Yinelemek veya başka bir geri al dalına taşımak için :undo! " +"kullanılamaz" + msgid "search hit TOP, continuing at BOTTOM" msgstr "Arama dosyanın BAŞINI geçti, dosyanın SONUNDAN sürüyor" @@ -2910,98 +2625,44 @@ msgstr "Arama dosyanın SONUNU geçti, dosyanın BAŞINDAN sürüyor" msgid " line " msgstr " satır " -msgid "E550: Missing colon" -msgstr "E550: İki nokta eksik" - -msgid "E551: Illegal component" -msgstr "E551: Geçersiz bileşen" - -msgid "E552: digit expected" -msgstr "E552: Basamak bekleniyordu" - -#, c-format -msgid "Page %d" -msgstr "Sayfa %d" - -msgid "No text to be printed" -msgstr "Yazdırılacak metin yok" - -#, c-format -msgid "Printing page %d (%zu%%)" -msgstr "Sayfa %d yazdırılıyor (%%%zu)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Kopya %d/%d" +msgid "E478: Don't panic!" +msgstr "E478: Panik yok!" #, c-format -msgid "Printed: %s" -msgstr "Yazdırıldı: %s" - -msgid "Printing aborted" -msgstr "Yazdırma durduruldu" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: PostScript çıktı dosyasına yazarken hata" +msgid "E661: Sorry, no '%s' help for %s" +msgstr "E661: Üzgünüm, '%s' yardımı %s için mevcut değil" #, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: \"%s\" dosyası açılamıyor" +msgid "E149: Sorry, no help for %s" +msgstr "E149: Üzgünüm, %s için yardım mevcut değil" #, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: PostScript özkaynak dosyası \"%s\" okunamıyor" +msgid "Sorry, help file \"%s\" not found" +msgstr "Üzgünüm, \"%s\" yardım dosyası bulunamadı" #, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: \"%s\" dosyası bir PostScript özkaynak dosyası değil" +msgid "E151: No match: %s" +msgstr "E151: Eşleşme bulunamadı: %s" #, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: \"%s\" dosyası desteklenen bir PostScript özkaynak dosyası değil" +msgid "E152: Cannot open %s for writing" +msgstr "E152: %s yazma için açılamıyor" #, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" özkaynak dosyası sürümü hatalı" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Uyumsuz çoklu bayt kodlaması ve karakter kümesi." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset çoklu bayt kodlamada boş olamaz." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Çoklu bayt yazdırma için öntanımlı yazıtipi ayarlanmamış." - -msgid "E324: Can't open PostScript output file" -msgstr "E324: PostScript çıktı dosyası açılamıyor" +msgid "E153: Unable to open %s for reading" +msgstr "E153: %s okuma için açılamıyor" #, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: \"%s\" dosyası açılamıyor" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: PostScript özkaynak dosyası \"prolog.ps\" bulunamıyor" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: PostScript özkaynak dosyası \"cidfont.ps\" bulunamıyor" +msgid "E670: Mix of help file encodings within a language: %s" +msgstr "E670: Bir dilde yardım dosyası kodlamaları karıştı: %s" #, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: PostScript özkaynak dosyası \"%s.ps\" bulunamıyor" +msgid "E154: Duplicate tag \"%s\" in file %s/%s" +msgstr "E154: Şu dosyada yinelenen \"%s\" etiketi: %s/%s" #, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: \"%s\" yazdırma kodlamasına dönüştürülemiyor" - -msgid "Sending to printer..." -msgstr "Yazıcıya gönderiliyor..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: PostScript dosyası yazdırılamadı" - -msgid "Print job sent." -msgstr "Yazdırma işi gönderildi." +msgid "E150: Not a directory: %s" +msgstr "E150: %s, bir dizin değil" msgid "E424: Too many different highlighting attributes in use" msgstr "E424: Çok fazla değişik vurgulama kuralları kullanılıyor" @@ -3025,6 +2686,9 @@ msgstr "E414: Grup ayarları mevcut, vurgulama bağlantısı yok sayıldı" msgid "E415: unexpected equal sign: %s" msgstr "E415: Beklenmedik eşittir imi: %s" +msgid "E423: Illegal argument" +msgstr "E423: İzin verilmeyen argüman" + #, c-format msgid "E416: missing equal sign: %s" msgstr "E416: Eksik eşittir imi: %s" @@ -3054,9 +2718,6 @@ msgstr "E423: İzin verilmeyen argüman: %s" msgid "E669: Unprintable character in group name" msgstr "E669: Grup adında yazdırılamayan karakter" -msgid "W18: Invalid character in group name" -msgstr "W18: Grup adında geçersiz karakter" - msgid "E849: Too many highlight and syntax groups" msgstr "E849: Çok fazla vurgulama ve sözdizim grupları" @@ -3220,6 +2881,114 @@ msgstr "" msgid "Type number and <Enter> (q or empty cancels): " msgstr "Sayı girip <Enter>'a basın (q veya boş iptal eder): " +msgid " Keyword completion (^N^P)" +msgstr " Anahtar sözcük tamamlaması (^N^P)" + +msgid " ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)" +msgstr " ^X kipi (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)" + +msgid " Whole line completion (^L^N^P)" +msgstr " Tam satır tamamlaması (^L^N^P)" + +msgid " File name completion (^F^N^P)" +msgstr " Dosya adı tamamlaması (^F^N^P)" + +msgid " Tag completion (^]^N^P)" +msgstr " Etiket tamamlaması (^]^N^P)" + +msgid " Path pattern completion (^N^P)" +msgstr " Yol dizgisi tamamlaması (^N^P)" + +msgid " Definition completion (^D^N^P)" +msgstr " Tanım tamamlaması (^D^N^P)" + +msgid " Dictionary completion (^K^N^P)" +msgstr " Sözlük tamamlaması (^K^N^P)" + +msgid " Thesaurus completion (^T^N^P)" +msgstr " Eşanlamlılar sözlüğü tamamlaması (^T^N^P)" + +msgid " Command-line completion (^V^N^P)" +msgstr " Komut satırı tamamlaması (^V^N^P)" + +msgid " User defined completion (^U^N^P)" +msgstr " Kullanıcı tanımlı tamamlamalar (^U^N^P)" + +msgid " Omni completion (^O^N^P)" +msgstr " Omni tamamlaması (^O^N^P)" + +msgid " Spelling suggestion (s^N^P)" +msgstr " Yazım önerisi (s^N^P)" + +msgid " Keyword Local completion (^N^P)" +msgstr " Dahili anahtar sözcük tamamlaması (^N^P)" + +msgid "Hit end of paragraph" +msgstr "Paragrafın sonuna varıldı" + +msgid "E840: Completion function deleted text" +msgstr "E840: Tamamlama işlevi metni sildi" + +msgid "'dictionary' option is empty" +msgstr "'dictionary' seçeneği boş" + +msgid "'thesaurus' option is empty" +msgstr "'thesaurus' seçeneği boş" + +#, c-format +msgid "Scanning dictionary: %s" +msgstr "Sözlük taranıyor: %s" + +msgid " (insert) Scroll (^E/^Y)" +msgstr " (ekle) Kaydır (^E/^Y)" + +msgid " (replace) Scroll (^E/^Y)" +msgstr " (değiştir) Kaydır (^E/^Y)" + +msgid "E785: complete() can only be used in Insert mode" +msgstr "E785: complete() yalnızca Ekleme kipinde kullanılabilir" + +#, c-format +msgid "Scanning: %s" +msgstr "Taranıyor: %s" + +msgid "Scanning tags." +msgstr "Etiketler taranıyor..." + +msgid "match in file" +msgstr "dosya içinde eşleşme" + +msgid " Adding" +msgstr " Ekleniyor" + +msgid "-- Searching..." +msgstr "-- Aranıyor..." + +msgid "Back at original" +msgstr "Başlangıca geri dönüldü" + +msgid "Word from other line" +msgstr "Sözcük diğer satırdan" + +msgid "The only match" +msgstr "Tek eşleşen" + +#, c-format +msgid "match %d of %d" +msgstr "eşleşme %d/%d" + +#, c-format +msgid "match %d" +msgstr "eşleşme %d" + +#, c-format +msgid "Current %slanguage: \"%s\"" +msgstr "Şu anki %sdil: \"%s\"" + +#, c-format +msgid "E197: Cannot set language to \"%s\"" +msgstr "E197: \"%s\" diline ayarlanamıyor" + #, c-format msgid "E1502: Lua failed to grow stack to %i" msgstr "E1502: Lua, yığını %i olarak büyütemedi" @@ -3366,16 +3135,14 @@ msgid "Usage:\n" msgstr "Kullanım:\n" msgid " nvim [options] [file ...] Edit file(s)\n" -msgstr "" -"nvim [seçenekler] [dosya ...] Dosyaları düzenle\n" +msgstr "nvim [seçenekler] [dosya ...] Dosyaları düzenle\n" msgid " nvim [options] -t <tag> Edit file where tag is defined\n" msgstr "" "nvim [seçenekler] -t <etiket> Etiketin tanımlandığı dosyayı düzenle\n" msgid " nvim [options] -q [errorfile] Edit file with first error\n" -msgstr "" -"nvim [seçenekler] -q [hatadosyası] İlk hatalı dosyayı düzenle\n" +msgstr "nvim [seçenekler] -q [hatadosyası] İlk hatalı dosyayı düzenle\n" msgid "" "\n" @@ -3385,12 +3152,10 @@ msgstr "" "Seçenekler:\n" msgid " -- Only file names after this\n" -msgstr "" -"-- Yalnızca bundan sonraki dosya adları\n" +msgstr "-- Yalnızca bundan sonraki dosya adları\n" msgid " + Start at end of file\n" -msgstr "" -"+ Dosyanın sonunda başlat\n" +msgstr "+ Dosyanın sonunda başlat\n" msgid " --cmd <cmd> Execute <cmd> before any config\n" msgstr "" @@ -3401,40 +3166,31 @@ msgstr "" "+<komut>, -c <komut> Yapılandırma ve ilk dosya sonrası <komut> çalıştır\n" msgid " -b Binary mode\n" -msgstr "" -"-b İkili kip\n" +msgstr "-b İkili kip\n" msgid " -d Diff mode\n" -msgstr "" -"-d Diff kipi\n" +msgstr "-d Diff kipi\n" msgid " -e, -E Ex mode\n" -msgstr "" -"-e, -E Ex kipi\n" +msgstr "-e, -E Ex kipi\n" msgid " -es, -Es Silent (batch) mode\n" -msgstr "" -"-es, -Es Sessiz (toplu iş) kipi\n" +msgstr "-es, -Es Sessiz (toplu iş) kipi\n" msgid " -h, --help Print this help message\n" -msgstr "" -"-h, --help Bu yardım iletisini yazdır\n" +msgstr "-h, --help Bu yardım iletisini yazdır\n" msgid " -i <shada> Use this shada file\n" -msgstr "" -"-i <shada> Bu shada (paylaşılan veri) dosyasını kullan\n" +msgstr "-i <shada> Bu shada (paylaşılan veri) dosyasını kullan\n" msgid " -m Modifications (writing files) not allowed\n" -msgstr "" -"-m Değişikliklere (dosya yazmaya) izin verme\n" +msgstr "-m Değişikliklere (dosya yazmaya) izin verme\n" msgid " -M Modifications in text not allowed\n" -msgstr "" -"-M Metinde değişikliklere izin verme\n" +msgstr "-M Metinde değişikliklere izin verme\n" msgid " -n No swap file, use memory only\n" -msgstr "" -"-n Takas dosyası yok, yalnızca belleği kullan\n" +msgstr "-n Takas dosyası yok, yalnızca belleği kullan\n" msgid " -o[N] Open N windows (default: one per file)\n" msgstr "" @@ -3450,68 +3206,61 @@ msgstr "" "-p[N] N sekme sayfası aç (öntanımlı: dosya başına bir tane)\n" msgid " -r, -L List swap files\n" -msgstr "" -"-r, -L Takas dosyalarını listele\n" +msgstr "-r, -L Takas dosyalarını listele\n" msgid " -r <file> Recover edit state for this file\n" -msgstr "" -"-r <dosya> Bu dosyanın düzenleme durumunu kurtar\n" +msgstr "-r <dosya> Bu dosyanın düzenleme durumunu kurtar\n" msgid " -R Read-only mode\n" -msgstr "" -"-R Saltokunur kip\n" +msgstr "-R Saltokunur kip\n" msgid " -S <session> Source <session> after loading the first file\n" msgstr "" "-S <oturum> İlk dosyayı yükledikten sonra <oturum>'u kaynak al\n" msgid " -s <scriptin> Read Normal mode commands from <scriptin>\n" -msgstr "" -"-s <betikgir> Normal kip komutlarını <betikgir>'den oku\n" +msgstr "-s <betikgir> Normal kip komutlarını <betikgir>'den oku\n" msgid " -u <config> Use this config file\n" -msgstr "" -"-u <yapılandırma> Bu yapılandırma dosyasını kullan\n" +msgstr "-u <yapılandırma> Bu yapılandırma dosyasını kullan\n" msgid " -v, --version Print version information\n" -msgstr "" -"-v, --version Sürüm bilgisini yazdır\n" +msgstr "-v, --version Sürüm bilgisini yazdır\n" msgid " -V[N][file] Verbose [level][file]\n" -msgstr "" -"-V[N][dosya] Ayrıntılı bilgi ver [düzey][dosya]\n" +msgstr "-V[N][dosya] Ayrıntılı bilgi ver [düzey][dosya]\n" msgid " --api-info Write msgpack-encoded API metadata to stdout\n" +msgstr "--api-info stdout'a msgpack kodlu API üstverisi yaz\n" + +msgid "" +" --clean \"Factory defaults\" (skip user config and plugins, " +"shada)\n" msgstr "" -"--api-info stdout'a msgpack kodlu API üstverisi yaz\n" +" --clean \"Fabrika ayarları\" (yapılandırma/eklentileri atla, " +"shada)\n" msgid " --embed Use stdin/stdout as a msgpack-rpc channel\n" msgstr "" "--embed stdin/stdout'u msgpack-rpc kanalı olarak kullan\n" msgid " --headless Don't start a user interface\n" -msgstr "" -"--headless Bir kullanıcı arayüzü başlatma\n" +msgstr "--headless Bir kullanıcı arayüzü başlatma\n" msgid " --listen <address> Serve RPC API from this address\n" -msgstr "" -"--listen <adres> Bu adresten RPC API'si sun\n" +msgstr "--listen <adres> Bu adresten RPC API'si sun\n" msgid " --noplugin Don't load plugins\n" -msgstr "" -"--noplugin Eklentileri yükleme\n" +msgstr "--noplugin Eklentileri yükleme\n" msgid " --remote[-subcommand] Execute commands remotely on a server\n" -msgstr "" -"--remote[-subcommand] Bir sunucuda komutları uzaktan çalıştır\n" +msgstr "--remote[-subcommand] Bir sunucuda komutları uzaktan çalıştır\n" msgid " --server <address> Specify RPC server to send commands to\n" -msgstr "" -"--server <adres> Komut gönderilecek RPC sunucusunu belirt\n" +msgstr "--server <adres> Komut gönderilecek RPC sunucusunu belirt\n" msgid " --startuptime <file> Write startup timing messages to <file>\n" -msgstr "" -"--startuptime <dosya> Başlangıç zamanlama iletilerini <dosya>'ya yaz\n" +msgstr "--startuptime <dosya> Başlangıç zamanlama iletilerini <dosya>'ya yaz\n" msgid "" "\n" @@ -3520,6 +3269,42 @@ msgstr "" "\n" "Tüm seçenekler için \":help startup-options\" yazın.\n" +#, c-format +msgid "E224: global abbreviation already exists for %s" +msgstr "E224: %s için global kısaltma hâlihazırda var" + +#, c-format +msgid "E225: global mapping already exists for %s" +msgstr "E225: %s için global eşlemleme hâlihazırda var" + +#, c-format +msgid "E226: abbreviation already exists for %s" +msgstr "E226: %s için kısaltma hâlihazırda var" + +#, c-format +msgid "E227: mapping already exists for %s" +msgstr "E227: %s için eşlemleme hâlihazırda var" + +msgid "No abbreviation found" +msgstr "Kısaltma bulunamadı" + +msgid "No mapping found" +msgstr "Eşlemleme bulunamadı" + +msgid "E228: makemap: Illegal mode" +msgstr "E228: makemap: İzin verilmeyen kip" + +msgid "E460: entries missing in mapset() dict argument" +msgstr "E460: mapset() sözlük argümanında girdiler eksik" + +#, c-format +msgid "E357: 'langmap': Matching character missing for %s" +msgstr "E357: 'langmap': %s için eşleşen karakter eksik" + +#, c-format +msgid "E358: 'langmap': Extra characters after semicolon: %s" +msgstr "E358: 'langmap': Noktalı virgülden sonra ek karakterler: %s" + msgid "No marks set" msgstr "İm ayarlanmamış" @@ -3588,6 +3373,29 @@ msgstr "E798: Kimlik, \":match\" için ayrılmış: %<PRId64>" msgid "E798: ID is reserved for \"match\": %<PRId64>" msgstr "E798: Kimlik, \":match\" için ayrılmış: %<PRId64>" +#, c-format +msgid "E1109: List item %d is not a List" +msgstr "E1109: Liste ögesi %d, bir Liste değil" + +#, c-format +msgid "E1110: List item %d does not contain 3 numbers" +msgstr "E1110: Liste ögesi %d, 3 adet sayı içermiyor" + +#, c-format +msgid "E1111: List item %d range invalid" +msgstr "E1111: Liste ögesi %d erimi geçersiz" + +#, c-format +msgid "E1112: List item %d cell width invalid" +msgstr "E1112: Liste ögesi %d hücre genişliği geçersiz" + +#, c-format +msgid "E1113: Overlapping ranges for 0x%lx" +msgstr "E1113: 0x%lx için üst üste binen erimler" + +msgid "E1114: Only values of 0x100 and higher supported" +msgstr "E1114: Yalnızca 0x100 ve üstü değerler desteklenir" + msgid "E293: block was not locked" msgstr "E293: Blok kilitlenmemişti" @@ -4003,8 +3811,8 @@ msgstr "E326: Çok fazla takas dosyası bulundu" msgid "" "E303: Unable to create directory \"%s\" for swap file, recovery impossible: " "%s" -msgstr "E303: Takas dosyası için \"%s\" dizini oluşturulamadı, kurtarma " -"olanaksız: %s" +msgstr "" +"E303: Takas dosyası için \"%s\" dizini oluşturulamadı, kurtarma olanaksız: %s" msgid "Vim: Data too large to fit into virtual memory space\n" msgstr "Vim: Veri, sanal bellek alanına sığmak için çok büyük\n" @@ -4016,9 +3824,6 @@ msgstr "E342: Bellek tükendi!! (%<PRIu64> bayt ayrılıyor)" msgid "E327: Part of menu-item path is not sub-menu" msgstr "E327: Menü öge yolunun bir kısmı alt menü değil" -msgid "E328: Menu only exists in another mode" -msgstr "E328: Menü yalnızca başka bir kipte mevcut" - #, c-format msgid "E329: No menu \"%s\"" msgstr "E329: Menü \"%s\" yok" @@ -4053,9 +3858,15 @@ msgstr "E333: Menü yolu bir menü ögesine çıkmalı" msgid "E334: Menu not found: %s" msgstr "E334: Menü bulunamadı: %s" +msgid "E336: Menu path must lead to a sub-menu" +msgstr "E336: Menü yolu bir alt menüye çıkmalı" + +msgid "E337: Menu not found - check menu names" +msgstr "E337: Menü bulunamadı - menü adlarını denetleyin" + #, c-format msgid "Error detected while processing %s:" -msgstr "%s işlenirken hata tespit edildi:" +msgstr "%s işlenirken hata algılandı:" #, c-format msgid "line %4ld:" @@ -4117,10 +3928,10 @@ msgstr "" "İ&ptal" msgid "E349: No identifier under cursor" -msgstr "E349: İmleç altında bir tanımlayıcı yok" +msgstr "E349: İmleç altında tanımlayıcı yok" msgid "E348: No string under cursor" -msgstr "E348: İmleç altında bir dizi yok" +msgstr "E348: İmleç altında dizi yok" msgid "E352: Cannot erase folds with current 'foldmethod'" msgstr "E352: Şu anki 'foldmethod' ile kıvırmalar silinemiyor" @@ -4177,22 +3988,24 @@ msgstr "%<PRId64> Sütun; " msgid "" "Selected %s%<PRId64> of %<PRId64> Lines; %<PRId64> of %<PRId64> Words; " "%<PRId64> of %<PRId64> Bytes" -msgstr "%s%<PRId64>/%<PRId64> satır; %<PRId64>/%<PRId64> sözcük; " -"%<PRId64>/%<PRId64> bayt seçildi" +msgstr "" +"%s%<PRId64>/%<PRId64> satır; %<PRId64>/%<PRId64> sözcük; %<PRId64>/%<PRId64> " +"bayt seçildi" #, c-format msgid "" "Selected %s%<PRId64> of %<PRId64> Lines; %<PRId64> of %<PRId64> Words; " "%<PRId64> of %<PRId64> Chars; %<PRId64> of %<PRId64> Bytes" msgstr "" -"%s%<PRId64>/%<PRId64> satır; %<PRId64>/%<PRId64> sözcük; " -"%<PRId64>/%<PRId64> karakter; %<PRId64>/%<PRId64> bayt seçildi" +"%s%<PRId64>/%<PRId64> satır; %<PRId64>/%<PRId64> sözcük; %<PRId64>/%<PRId64> " +"karakter; %<PRId64>/%<PRId64> bayt seçildi" #, c-format msgid "" "Col %s of %s; Line %<PRId64> of %<PRId64>; Word %<PRId64> of %<PRId64>; Byte " "%<PRId64> of %<PRId64>" -msgstr "%s/%s sütun; %<PRId64>/%<PRId64> satır; %<PRId64>/%<PRId64> sözcük; " +msgstr "" +"%s/%s sütun; %<PRId64>/%<PRId64> satır; %<PRId64>/%<PRId64> sözcük; " "%<PRId64>/%<PRId64> bayt" #, c-format @@ -4225,59 +4038,6 @@ msgstr "E846: Anahtar kodu ayarlanmamış" msgid "E521: Number required after =" msgstr "E521: = sonrası sayı gerekiyor" -#, c-format -msgid "E539: Illegal character <%s>" -msgstr "E539: İzin verilmeyen karakter <%s>" - -#, c-format -msgid "For option %s" -msgstr "%s seçeneği için" - -msgid "E589: 'backupext' and 'patchmode' are equal" -msgstr "E589: 'backupext' ve 'patchmode' birbirine eşit" - -msgid "E834: Conflicts with value of 'listchars'" -msgstr "E834: 'listchars' değeriyle çakışmalar var" - -msgid "E835: Conflicts with value of 'fillchars'" -msgstr "E835: 'fillchars' değeriyle çakışmalar var" - -msgid "E524: Missing colon" -msgstr "E524: İki nokta eksik" - -msgid "E525: Zero length string" -msgstr "E525: Sıfır uzunlukta dizi" - -#, c-format -msgid "E526: Missing number after <%s>" -msgstr "E526: <%s> sonrası sayı eksik" - -msgid "E527: Missing comma" -msgstr "E527: Virgül eksik" - -msgid "E528: Must specify a ' value" -msgstr "E528: Bir ' değeri belirtmeli" - -msgid "E595: 'showbreak' contains unprintable or wide character" -msgstr "E595: 'showbreak' yazdırılamaz veya geniş karakter içeriyor" - -#, c-format -msgid "E535: Illegal character after <%c>" -msgstr "E535: <%c> sonrası izin verilmeyen karakter" - -msgid "E536: comma required" -msgstr "E536: Virgül gerekiyor" - -#, c-format -msgid "E537: 'commentstring' must be empty or contain %s" -msgstr "E537: 'commentstring' boş olmalı veya %s içermeli" - -msgid "E540: Unclosed expression sequence" -msgstr "E540: Kapatılmamış ifade sıralaması" - -msgid "E542: unbalanced groups" -msgstr "E542: Dengelenmemiş gruplar" - msgid "E590: A preview window already exists" msgstr "E590: Bir önizleme penceresi hâlihazırda mevcut" @@ -4324,13 +4084,52 @@ msgstr "" msgid "E356: get_varp ERROR" msgstr "E356: get_varp HATASI" +msgid "E540: Unclosed expression sequence" +msgstr "E540: Kapatılmamış ifade sıralaması" + +msgid "E542: unbalanced groups" +msgstr "E542: Dengelenmemiş gruplar" + +msgid "E589: 'backupext' and 'patchmode' are equal" +msgstr "E589: 'backupext' ve 'patchmode' birbirine eşit" + +msgid "E595: 'showbreak' contains unprintable or wide character" +msgstr "E595: 'showbreak' yazdırılamaz veya geniş karakter içeriyor" + #, c-format -msgid "E357: 'langmap': Matching character missing for %s" -msgstr "E357: 'langmap': %s için eşleşen karakter eksik" +msgid "E539: Illegal character <%s>" +msgstr "E539: İzin verilmeyen karakter <%s>" #, c-format -msgid "E358: 'langmap': Extra characters after semicolon: %s" -msgstr "E358: 'langmap': Noktalı virgülden sonra ek karakterler: %s" +msgid "For option %s" +msgstr "%s seçeneği için" + +msgid "E524: Missing colon" +msgstr "E524: İki nokta eksik" + +msgid "E525: Zero length string" +msgstr "E525: Sıfır uzunlukta dizi" + +#, c-format +msgid "E526: Missing number after <%s>" +msgstr "E526: <%s> sonrası sayı eksik" + +msgid "E527: Missing comma" +msgstr "E527: Virgül eksik" + +msgid "E528: Must specify a ' value" +msgstr "E528: Bir ' değeri belirtmeli" + +#, c-format +msgid "E535: Illegal character after <%c>" +msgstr "E535: <%c> sonrası izin verilmeyen karakter" + +msgid "E536: comma required" +msgstr "E536: Virgül gerekiyor" + +#, c-format +msgid "E537: 'commentstring' must be empty or contain %s" +msgstr "E537: 'commentstring' boş olmalı veya %s içermeli" #, c-format msgid "dlerror = \"%s\"" @@ -4368,6 +4167,9 @@ msgstr "%a %b %d %H:%M:%S %Y" msgid "E447: Can't find file \"%s\" in path" msgstr "E447: \"%s\" dosyası yol içinde bulunamadı" +msgid "E750: First use \":profile start {fname}\"" +msgstr "E750: İlk kullanım \":profile start {dosyaadı}\"" + msgid "E553: No more items" msgstr "E553: Öge yok" @@ -4383,7 +4185,7 @@ msgstr "E372: Biçim dizisinde pek fazla %%%c" #, c-format msgid "E373: Unexpected %%%c in format string" -msgstr "E373: Biçimlendirme dizisinde beklenmeyen %%%c" +msgstr "E373: Biçimlendirme dizisinde beklenmedik %%%c" msgid "E374: Missing ] in format string" msgstr "E374: Biçimlendirme dizisinde ] eksik" @@ -4446,6 +4248,10 @@ msgid "E777: String or List expected" msgstr "E777: Dizi veya liste bekleniyordu" #, c-format +msgid "E927: Invalid action: '%s'" +msgstr "E927: Geçersiz eylem: '%s'" + +#, c-format msgid "E369: invalid item in %s%%[]" msgstr "E369: %s%%[] içinde geçersiz öge" @@ -4492,13 +4298,16 @@ msgstr "E956: Dizgi özyineli olarak kullanılamaz" msgid "E1204: No Number allowed after .: '\\%%%c'" msgstr "E1204: . sonrası Sayıya izin verilmiyor: '\\%%%c'" +msgid "E1290: substitute nesting too deep" +msgstr "E1290: Değiştirme iç içe geçmesi pek derin" + #, c-format msgid "E554: Syntax error in %s{...}" -msgstr "E554: %s{...} içinde sözdizimi hatası" +msgstr "E554: %s{...} içinde sözdizim hatası" #, c-format msgid "E888: (NFA regexp) cannot repeat %s" -msgstr "E888: (BSO düzenli ifadesi) %s tekrar edemiyor" +msgstr "E888: (BSO düzenli ifadesi) %s yinelenemiyor" msgid "" "E864: \\%#= can only be followed by 0, 1, or 2. The automatic engine will be " @@ -4530,6 +4339,77 @@ msgstr "\"%s\", çalışma zamanı yolu içinde aranıyor" msgid "not found in runtime path: \"%s\"" msgstr "çalışma zamanı yolu içinde bulunamadı: \"%s\"" +#, c-format +msgid "Cannot source a directory: \"%s\"" +msgstr "Dizin kaynak alınamıyor: \"%s\"" + +#, c-format +msgid "could not source \"%s\"" +msgstr "\"%s\" kaynak alınamadı" + +#, c-format +msgid "line %<PRId64>: could not source \"%s\"" +msgstr "%<PRId64>. satır: \"%s\" kaynak alınamadı" + +#, c-format +msgid "sourcing \"%s\"" +msgstr "\"%s\" kaynak alınıyor" + +#, c-format +msgid "line %<PRId64>: sourcing \"%s\"" +msgstr "%<PRId64>. satır: \"%s\" kaynak alınıyor" + +#, c-format +msgid "finished sourcing %s" +msgstr "%s kaynak alınması bitti" + +msgid "modeline" +msgstr "kip satırı" + +msgid "--cmd argument" +msgstr "--cmd argümanı" + +msgid "-c argument" +msgstr "-c argümanı" + +msgid "environment variable" +msgstr "ortam değişkeni" + +msgid "error handler" +msgstr "hata işleyicisi" + +msgid "changed window size" +msgstr "değiştirilen pencere boyutu" + +msgid "Lua" +msgstr "Lua" + +#, c-format +msgid "API client (channel id %<PRIu64>)" +msgstr "API istemcisi (kanal kimliği %<PRIu64>" + +msgid "anonymous :source" +msgstr "anonim :source" + +#, c-format +msgid "anonymous :source (script id %d)" +msgstr "anonim :source (betik kimliği %d)" + +msgid "W15: Warning: Wrong line separator, ^M may be missing" +msgstr "W15: Uyarı: Yanlış satır ayırıcısı, ^M eksik olabilir" + +msgid "E167: :scriptencoding used outside of a sourced file" +msgstr "E167: :scriptencoding kaynak alınmış bir dosyanın dışında kullanıldı" + +msgid "E168: :finish used outside of a sourced file" +msgstr "E168: :finish kaynak alınmış bir dosyanın dışında kullanıldı" + +msgid "E834: Conflicts with value of 'listchars'" +msgstr "E834: 'listchars' değeriyle çakışmalar var" + +msgid "E835: Conflicts with value of 'fillchars'" +msgstr "E835: 'fillchars' değeriyle çakışmalar var" + msgid " TERMINAL" msgstr " UÇBİRİM" @@ -4545,6 +4425,9 @@ msgstr " GERİ AL" msgid " INSERT" msgstr " EKLE" +msgid " (terminal)" +msgstr " (uçbirim)" + msgid " (insert)" msgstr " (ekle)" @@ -4764,13 +4647,14 @@ msgstr "" #, c-format msgid "Did not rename %s to %s because there were errors during writing it" -msgstr "%s dosyasını %s olarak yeniden adlandırmanızın nedeni yazma sırasında " +msgstr "" +"%s dosyasını %s olarak yeniden adlandırmanızın nedeni yazma sırasında " "hatalar olması mı?" #, c-format msgid "Do not forget to remove %s or rename it manually to %s." -msgstr "%s dosyasını kaldırmayı veya el ile %s olarak yeniden adlandırmayı " -"unutmayın." +msgstr "" +"%s dosyasını kaldırmayı veya el ile %s olarak yeniden adlandırmayı unutmayın." #, c-format msgid "System error while reading ShaDa file: %s" @@ -4801,8 +4685,8 @@ msgid "" "Error while reading ShaDa file: there is an item at position %<PRIu64> that " "is stated to be too long" msgstr "" -"Paylaşılan veri dosyası okunurken hata: %<PRIu64> konumunda bir öge var; ancak" -" pek uzun olduğu belirtildi" +"Paylaşılan veri dosyası okunurken hata: %<PRIu64> konumunda bir öge var; " +"ancak pek uzun olduğu belirtildi" #, c-format msgid "" @@ -4915,21 +4799,6 @@ msgstr "E797: SpellFileMissing otokomutu arabelleği sildi" msgid "Warning: region %s not supported" msgstr "Uyarı: %s bölgesi desteklenmiyor" -msgid "Sorry, no suggestions" -msgstr "Üzgünüm, şu an için bir önerim yok" - -#, c-format -msgid "Sorry, only %<PRId64> suggestions" -msgstr "Üzgünüm, yalnızca %<PRId64> öneri" - -#, c-format -msgid "Change \"%.*s\" to:" -msgstr "\"%.*s\" şuna değiştirilecek:" - -#, c-format -msgid " < \"%.*s\"" -msgstr " < \"%.*s\"" - msgid "E752: No previous spell replacement" msgstr "E752: Öncesinde düzeltilmiş bir yazım yok" @@ -4940,6 +4809,9 @@ msgstr "E753: Bulunamadı: %s" msgid "E758: Truncated spell file" msgstr "E758: Kırpılmış yazım dosyası" +msgid "E1280: Illegal character in word" +msgstr "E1280: Sözcükte izin verilmeyen karakter" + #, c-format msgid "Trailing text in %s line %d: %s" msgstr "%s içinde %d. satır ucunda fazladan metin: %s" @@ -5058,10 +4930,10 @@ msgstr "%s içinde %d. satırda yinelenen ek: %s" #, c-format msgid "" -"Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGESTin %s " +"Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in %s " "line %d: %s" msgstr "" -"Ek aynı zamanda %s içinde %d. satırda BAD/RARE/KEEPCASE/NEEDAFFIX/" +"Sonek aynı zamanda %s içinde %d. satırda BAD/RARE/KEEPCASE/NEEDAFFIX/" "NEEDCOMPOUND/NOSUGGEST için de kullanılmış: %s" #, c-format @@ -5257,6 +5129,27 @@ msgstr "E763: Sözcük karakterleri yazım dosyaları arasında ayrım gösteriy msgid "E783: duplicate char in MAP entry" msgstr "E783: MAP girdisinde yinelenen karakter" +msgid "Sorry, no suggestions" +msgstr "Üzgünüm, şu an için bir önerim yok" + +#, c-format +msgid "Sorry, only %<PRId64> suggestions" +msgstr "Üzgünüm, yalnızca %<PRId64> öneri" + +#, c-format +msgid "Change \"%.*s\" to:" +msgstr "\"%.*s\" şuna değiştirilecek:" + +#, c-format +msgid " < \"%.*s\"" +msgstr " < \"%.*s\"" + +msgid "[Help]" +msgstr "[Yardım]" + +msgid "[Preview]" +msgstr "[Önizleme]" + msgid "E766: Insufficient arguments for printf()" msgstr "E766: printf() için yetersiz argüman" @@ -5512,7 +5405,7 @@ msgid "Beep!" msgstr "Bip!" msgid "E881: Line count changed unexpectedly" -msgstr "E881: Satır sayısı beklenmeyen bir biçimde değişti" +msgstr "E881: Satır sayısı beklenmedik bir biçimde değişti" #, c-format msgid "E828: Cannot open undo file for writing: %s" @@ -5631,6 +5524,76 @@ msgstr "E439: Geri al listesi hasarlı" msgid "E440: undo line missing" msgstr "E440: Geri al satırı eksik" +msgid "E1208: -complete used without allowing arguments" +msgstr "E1208: -complete, argümanlara izin vermeden kullanıldı" + +#, c-format +msgid "E184: No such user-defined command: %s" +msgstr "E184: Böyle bir kullanıcı tanımlı komut yok: %s" + +#, c-format +msgid "E1237: No such user-defined command in current buffer: %s" +msgstr "E1237: Geçerli arabellekte böyle bir kullanıcı tanımlı komut yok: %s" + +msgid "" +"\n" +" Name Args Address Complete Definition" +msgstr "" +"\n" +" Ad Dğkl Adres Tam Tanım" + +msgid "No user-defined commands found" +msgstr "Kullanıcı tanımlı bir komut bulunamadı" + +#, c-format +msgid "E180: Invalid address type value: %s" +msgstr "E180: Geçersiz adres türü değeri: %s" + +#, c-format +msgid "E180: Invalid complete value: %s" +msgstr "E180: Geçersiz tam değer: %s" + +msgid "E468: Completion argument only allowed for custom completion" +msgstr "E468: Tamamlama argümanına yalnızca özel tamamlamalarda izin verilir" + +msgid "E467: Custom completion requires a function argument" +msgstr "E467: Özel tamamlama bir işlev argümanı gerektirir" + +msgid "E175: No attribute specified" +msgstr "E175: Bir öznitelik belirtilmemiş" + +msgid "E176: Invalid number of arguments" +msgstr "E176: Geçersiz argüman sayısı" + +msgid "E177: Count cannot be specified twice" +msgstr "E177: Sayım iki defa belirtilemez" + +msgid "E178: Invalid default value for count" +msgstr "E178: Sayım için geçersiz öntanımlı değer" + +msgid "E179: argument required for -complete" +msgstr "E179: -complete için argüman gerekiyor" + +msgid "E179: argument required for -addr" +msgstr "E179: -addr için argüman gerekiyor" + +#, c-format +msgid "E181: Invalid attribute: %s" +msgstr "E181: Geçersiz öznitelik: %s" + +#, c-format +msgid "E174: Command already exists: add ! to replace it: %s" +msgstr "E174: Komut zaten mevcut: Değiştirmek için ! ekleyin: %s" + +msgid "E182: Invalid command name" +msgstr "E182: Geçersiz komut adı" + +msgid "E183: User defined commands must start with an uppercase letter" +msgstr "E183: Kullanıcı tanımlı komutlar BÜYÜK harfle başlamalıdır" + +msgid "E841: Reserved name, cannot be used for user defined command" +msgstr "E841: Ayrılmış ad, kullanıcı tanımlı komut için kullanılamaz" + msgid "" "\n" "\n" @@ -5662,9 +5625,6 @@ msgstr "$VIMRUNTIME öntanımlı konumu: \"" msgid "Nvim is open source and freely distributable" msgstr "Nvim açık kaynaklıdır ve özgürce dağıtılabilir" -msgid "https://neovim.io/#chat" -msgstr "https://neovim.io/#chat" - msgid "type :help nvim<Enter> if you are new! " msgstr "eğer yeniyseniz :help nvim<Enter> " @@ -5864,12 +5824,18 @@ msgstr "E15: Lambda için kapatma kıvrımlı ayracı eksik: %.*s" msgid "E109: Missing ':' after '?': %.*s" msgstr "E109: '?' sonrası ':' eksik: %.*s" +msgid "E1159: Cannot split a window when closing the buffer" +msgstr "E1159: Arabellek kapatılırken bir pencere bölünemez" + msgid "Already only one window" msgstr "Zaten tek pencere" msgid "E441: There is no preview window" msgstr "E441: Önizleme penceresi yok" +msgid "E242: Can't split a window while closing another" +msgstr "E242: Bir pencere kapatılırken başka bir pencere bölünemez" + msgid "E442: Can't split topleft and botright at the same time" msgstr "E442: Üst sol ve alt sağ pencereler aynı anda bölünemez" @@ -5886,4 +5852,4 @@ msgid "E445: Other window contains changes" msgstr "E445: Diğer pencerede değişiklikler var" msgid "E446: No file name under cursor" -msgstr "E446: İmleç altında bir dosya adı yok" +msgstr "E446: İmleç altında dosya adı yok" diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index da87d50683..06f845f113 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -2913,99 +2913,6 @@ msgstr "Пошук дійшов до КІНЦЯ, продовжується з msgid " line " msgstr " рядок " -msgid "E550: Missing colon" -msgstr "E550: Пропущено двокрапку" - -msgid "E551: Illegal component" -msgstr "E551: Некоректний компонент" - -msgid "E552: digit expected" -msgstr "E552: очікується цифра" - -#, c-format -msgid "Page %d" -msgstr "Сторінка %d" - -msgid "No text to be printed" -msgstr "Нічого друкувати" - -#, c-format -msgid "Printing page %d (%zu%%)" -msgstr "Друкується сторінка %d (%zu%%)" - -#, c-format -msgid " Copy %d of %d" -msgstr " Копія %d з %d" - -#, c-format -msgid "Printed: %s" -msgstr "Надруковано: %s" - -msgid "Printing aborted" -msgstr "Друк перервано" - -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Не вдалося записати вихідний файл PostScript" - -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Не вдалося відкрити файл «%s»" - -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Не вдалося прочитати файл ресурсів PostScript «%s»" - -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: «%s» не є файлом ресурсів PostScript" - -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: «%s» не є підтримуваним файлом ресурсів PostScript" - -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: Неправильна версія файлу ресурсів «%s»" - -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: Несумісні багатобайтове кодування й набір символів." - -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" -"E674: printmbcharset не може бути порожнім з багатобайтовим кодуванням." - -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: Не зазначено шрифт для багатобайтового друку." - -msgid "E324: Can't open PostScript output file" -msgstr "E324: Не вдалося відкрити файл PostScript для виводу" - -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Не вдалося відкрити файл «%s»" - -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Не вдалося знайти файл ресурсів PostScript «prolog.ps»" - -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Не вдалося знайти файл ресурсів PostScript «cidfont.ps»" - -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Не вдалося знайти файл ресурсів PostScript «%s.ps»" - -#, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Не вдалося перетворити до кодування друку «%s»" - -msgid "Sending to printer..." -msgstr "Відсилається на принтер..." - -msgid "E365: Failed to print PostScript file" -msgstr "E365: Не вдалося надрукувати файл PostScript" - -msgid "Print job sent." -msgstr "Завдання друку відіслано." msgid "E424: Too many different highlighting attributes in use" msgstr "E424: Використано забагато різних атрибутів кольору" @@ -3058,8 +2965,8 @@ msgstr "E423: Неправильний аргумент: %s" msgid "E669: Unprintable character in group name" msgstr "E669: Недруковний символ у назві групи" -msgid "W18: Invalid character in group name" -msgstr "W18: Некоректний символ у назві групи" +msgid "E5248: Invalid character in group name" +msgstr "E5248: Некоректний символ у назві групи" msgid "E849: Too many highlight and syntax groups" msgstr "E849: Забагато груп підсвічування і синтаксису" @@ -5643,9 +5550,6 @@ msgstr " заміна для $VIMRUNTIME: \"" msgid "Nvim is open source and freely distributable" msgstr "Nvim — це відкрита й вільно розповсюджувана програма" -msgid "https://neovim.io/#chat" -msgstr "https://neovim.io/#chat" - msgid "type :help nvim<Enter> if you are new! " msgstr ":help nvim<Enter> якщо ви вперше! " diff --git a/src/nvim/po/vi.po b/src/nvim/po/vi.po index c693f910d8..44772c99ad 100644 --- a/src/nvim/po/vi.po +++ b/src/nvim/po/vi.po @@ -3020,127 +3020,6 @@ msgstr "tìm kiếm sẽ được tiếp tục từ CUỐI tài liệu" msgid "search hit BOTTOM, continuing at TOP" msgstr "tìm kiếm sẽ được tiếp tục từ ĐẦU tài liệu" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: Thiếu dấu hai chấm" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: Thành phần không cho phép" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: Cần chỉ ra một số" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "Trang %d" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "Không có gì để in" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "In trang %d (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr " Sao chép %d của %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "Đã in: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "In bị dừng" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: Lỗi ghi nhớ vào tập tin PostScript" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: Không thể mở tập tin \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: Không thể đọc tập tin tài nguyên PostScript \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: \"%s\" không phải là tập tin tài nguyên PostScript" - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: \"%s\" không phải là tập tin tài nguyên PostScript được hỗ trợ" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: tập tin tài nguyên \"%s\" có phiên bản không đúng" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: Không thể mở tập tin PostScript" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: Không thể mở tập tin \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: Không tìm thấy tập tin tài nguyên PostScript \"prolog.ps\"" - -#: ../hardcopy.c:2593 -#, fuzzy -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: Không tìm thấy tập tin tài nguyên PostScript \"%s.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: Không tìm thấy tập tin tài nguyên PostScript \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, fuzzy, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: Không thể chuyển từ các ký tự nhiều byte thành bảng mã \"%s\"" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "Gửi tới máy in..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: In tập tin PostScript không thành công" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "Đã gửi công việc in." - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "Thêm một cơ sở dữ liệu mới" @@ -5975,9 +5854,9 @@ msgstr "E424: Sử dụng quá nhiều thuộc tính chiếu sáng cú pháp" msgid "E669: Unprintable character in group name" msgstr "E669: Ký tự không thể tin ra trong tên nhóm" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: Ký tự không cho phép trong tên nhóm" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: Ký tự không cho phép trong tên nhóm" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" diff --git a/src/nvim/po/zh_CN.UTF-8.po b/src/nvim/po/zh_CN.UTF-8.po index 70c1389d7f..af050823d3 100644 --- a/src/nvim/po/zh_CN.UTF-8.po +++ b/src/nvim/po/zh_CN.UTF-8.po @@ -8,6 +8,9 @@ # TRANSLATORS # Edyfox <edyfox@gmail.com> # Yuheng Xie <elephant@linux.net.cn> +# lilydjwg <lilydjwg@gmail.com> +# Ada (Haowen) Yu <me@yuhaowen.com> +# Sizhe Zhao <prc.zhao@outlook.com> # # Original translations. # @@ -15,1185 +18,1939 @@ msgid "" msgstr "" "Project-Id-Version: Vim(Simplified Chinese)\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2014-05-26 14:21+0200\n" -"PO-Revision-Date: 2006-04-21 14:00+0800\n" -"Last-Translator: Yuheng Xie <elephant@linux.net.cn>\n" +"POT-Creation-Date: 2022-11-17 08:00+0800\n" +"PO-Revision-Date: 2023-01-24 13:00+0800\n" +"Last-Translator: Sizhe Zhao <prc.zhao@outlook.com>\n" "Language-Team: Simplified Chinese <i18n-translation@lists.linux.net.cn>\n" -"Language: \n" +"Language: zh_CN\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8-bit\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: ../api/private/helpers.c:201 +#: ../arglist.c:67 +msgid "E1156: Cannot change the argument list recursively" +msgstr "E1156: 不能递归修改参数列表" + +#: ../arglist.c:628 +msgid "E163: There is only one file to edit" +msgstr "E163: 只有一个文件可编辑" + +#: ../arglist.c:630 +msgid "E164: Cannot go before first file" +msgstr "E164: 无法切换,已是第一个文件" + +#: ../arglist.c:632 +msgid "E165: Cannot go beyond last file" +msgstr "E165: 无法切换,已是最后一个文件" + +#: ../arglist.c:782 #, fuzzy -msgid "Unable to get option value" -msgstr "选项参数后的内容无效" +msgid "E610: No argument to delete" +msgstr "E144: :z 不接受非数字的参数" -#: ../api/private/helpers.c:204 -msgid "internal error: unknown option type" -msgstr "内部错误:未知的选项类型" +#: ../arglist.c:980 +msgid "E249: window layout changed unexpectedly" +msgstr "E249: 窗口布局意外地改变了" -#: ../buffer.c:92 -msgid "[Location List]" -msgstr "[Location 列表]" +#: ../autocmd.c:160 +msgid "--Deleted--" +msgstr "--已删除--" -#: ../buffer.c:93 -msgid "[Quickfix List]" -msgstr "[Quickfix 列表]" +#: ../autocmd.c:451 +#, c-format +msgid "auto-removing autocommand: %s <buffer=%d>" +msgstr "自动删除自动命令: %s <buffer=%d>" + +#. the group doesn't exist +#: ../autocmd.c:501 +#, c-format +msgid "E367: No such group: \"%s\"" +msgstr "E367: 无此组: \"%s\"" + +#: ../autocmd.c:505 +#, fuzzy +msgid "E936: Cannot delete the current group" +msgstr "E351: 不能在当前的 'foldmethod' 下删除折叠" + +#: ../autocmd.c:513 +msgid "W19: Deleting augroup that is still in use" +msgstr "W19: 删除依旧在使用中的自动组" + +#. Highlight title +#: ../autocmd.c:896 +msgid "" +"\n" +"--- Autocommands ---" +msgstr "" +"\n" +"--- 自动命令 ---" + +#: ../autocmd.c:1106 +#, c-format +msgid "E680: <buffer=%d>: invalid buffer number " +msgstr "E680: <buffer=%d>: 无效的缓冲区号 " + +#: ../autocmd.c:1254 +msgid "E217: Can't execute autocommands for ALL events" +msgstr "E217: 不能对所有事件执行自动命令" + +#: ../autocmd.c:1276 +#, c-format +msgid "No matching autocommands: %s" +msgstr "没有匹配的自动命令:%s" + +#: ../autocmd.c:1693 +msgid "E218: autocommand nesting too deep" +msgstr "E218: 自动命令嵌套层数过深" + +#: ../autocmd.c:2040 +#, c-format +msgid "%s Autocommands for \"%s\"" +msgstr "%s 自动命令 \"%s\"" + +#: ../autocmd.c:2049 +#, c-format +msgid "Executing %s" +msgstr "执行 %s" + +#: ../autocmd.c:2178 +#, c-format +msgid "autocommand %s" +msgstr "自动命令 %s" -#: ../buffer.c:94 +#: ../autocmd.c:2617 +#, c-format +msgid "E215: Illegal character after *: %s" +msgstr "E215: * 后面有无效字符: %s" + +#: ../autocmd.c:2625 +#, c-format +msgid "E216: No such event: %s" +msgstr "E216: 无此事件: %s" + +#: ../autocmd.c:2627 +#, c-format +msgid "E216: No such group or event: %s" +msgstr "E216: 无此组或事件: %s" + +#: ../buffer.c:109 msgid "E855: Autocommands caused command to abort" msgstr "E855: 自动命令导致命令被停止" -#: ../buffer.c:135 +#: ../buffer.c:111 +#, c-format +msgid "E937: Attempt to delete a buffer that is in use: %s" +msgstr "E937: 试图删除使用中的缓冲区:%s" + +#: ../buffer.c:219 msgid "E82: Cannot allocate any buffer, exiting..." msgstr "E82: 无法分配任何缓冲区,退出程序..." -#: ../buffer.c:138 +#: ../buffer.c:227 msgid "E83: Cannot allocate buffer, using other one..." msgstr "E83: 无法分配缓冲区,使用另一个缓冲区..." -#: ../buffer.c:763 +#: ../buffer.c:1070 msgid "E515: No buffers were unloaded" msgstr "E515: 没有释放任何缓冲区" -#: ../buffer.c:765 +#: ../buffer.c:1072 msgid "E516: No buffers were deleted" msgstr "E516: 没有删除任何缓冲区" -#: ../buffer.c:767 +#: ../buffer.c:1074 msgid "E517: No buffers were wiped out" msgstr "E517: 没有清除任何缓冲区" -#: ../buffer.c:772 -msgid "1 buffer unloaded" -msgstr "释放了 1 个缓冲区" - -#: ../buffer.c:774 -#, c-format -msgid "%d buffers unloaded" -msgstr "释放了 %d 个缓冲区" - -#: ../buffer.c:777 -msgid "1 buffer deleted" -msgstr "删除了 1 个缓冲区" - -#: ../buffer.c:779 -#, c-format -msgid "%d buffers deleted" -msgstr "删除了 %d 个缓冲区" +#: ../buffer.c:1079 +#, fuzzy, c-format +msgid "%d buffer unloaded" +msgid_plural "%d buffers unloaded" +msgstr[0] "释放了 %d 个缓冲区" -#: ../buffer.c:782 -msgid "1 buffer wiped out" -msgstr "清除了 1 个缓冲区" +#: ../buffer.c:1082 +#, fuzzy, c-format +msgid "%d buffer deleted" +msgid_plural "%d buffers deleted" +msgstr[0] "删除了 %d 个缓冲区" -#: ../buffer.c:784 -#, c-format -msgid "%d buffers wiped out" -msgstr "清除了 %d 个缓冲区" +#: ../buffer.c:1085 +#, fuzzy, c-format +msgid "%d buffer wiped out" +msgid_plural "%d buffers wiped out" +msgstr[0] "清除了 %d 个缓冲区" -#: ../buffer.c:806 +#: ../buffer.c:1102 msgid "E90: Cannot unload last buffer" msgstr "E90: 无法释放最后一个缓冲区" -#: ../buffer.c:874 +#: ../buffer.c:1191 msgid "E84: No modified buffer found" msgstr "E84: 没有修改过的缓冲区" #. back where we started, didn't find anything. -#: ../buffer.c:903 +#: ../buffer.c:1224 msgid "E85: There is no listed buffer" msgstr "E85: 没有可列出的缓冲区" -#: ../buffer.c:913 -#, c-format -msgid "E86: Buffer %<PRId64> does not exist" -msgstr "E86: 缓冲区 %<PRId64> 不存在" - -#: ../buffer.c:915 +#: ../buffer.c:1237 msgid "E87: Cannot go beyond last buffer" msgstr "E87: 无法切换,已是最后一个缓冲区" -#: ../buffer.c:917 +#: ../buffer.c:1239 msgid "E88: Cannot go before first buffer" msgstr "E88: 无法切换,已是第一个缓冲区" -#: ../buffer.c:945 +#: ../buffer.c:1277 #, c-format msgid "" "E89: No write since last change for buffer %<PRId64> (add ! to override)" msgstr "E89: 缓冲区 %<PRId64> 已修改但尚未保存 (请加 ! 强制执行)" +#: ../buffer.c:1290 +#, c-format +msgid "E89: %s will be killed (add ! to override)" +msgstr "E89: \"%s\" 会被结束 (请加 ! 强制执行)" + +#: ../buffer.c:1698 +msgid "E948: Job still running (add ! to end the job)" +msgstr "E948: 任务仍在运行(添加 ! 来结束此任务)" + +#: ../buffer.c:1700 +msgid "E37: No write since last change (add ! to override)" +msgstr "E37: 已修改但尚未保存 (可用 ! 强制执行)" + +#: ../buffer.c:1709 +msgid "E948: Job still running" +msgstr "E948: 任务仍在运行" + +#: ../buffer.c:1711 +msgid "E37: No write since last change" +msgstr "E37: 已修改但尚未保存" + #. wrap around (may cause duplicates) -#: ../buffer.c:1423 +#: ../buffer.c:1858 msgid "W14: Warning: List of file names overflow" msgstr "W14: 警告: 文件名过多" -#: ../buffer.c:1555 ../quickfix.c:3361 +#: ../buffer.c:2032 ../quickfix.c:6290 ../window.c:195 #, c-format msgid "E92: Buffer %<PRId64> not found" msgstr "E92: 找不到缓冲区 %<PRId64>" -#: ../buffer.c:1798 +#: ../buffer.c:2289 #, c-format msgid "E93: More than one match for %s" msgstr "E93: 找到不止一个 %s" -#: ../buffer.c:1800 +#: ../buffer.c:2291 #, c-format msgid "E94: No matching buffer for %s" msgstr "E94: 没有匹配的缓冲区 %s" -#: ../buffer.c:2161 +#: ../buffer.c:2786 #, c-format msgid "line %<PRId64>" msgstr "第 %<PRId64> 行" -#: ../buffer.c:2233 +#: ../buffer.c:2856 msgid "E95: Buffer with this name already exists" msgstr "E95: 已有缓冲区使用该名称" -#: ../buffer.c:2498 +#: ../buffer.c:3109 msgid " [Modified]" msgstr " [已修改]" -#: ../buffer.c:2501 +#: ../buffer.c:3111 msgid "[Not edited]" msgstr "[未编辑]" -#: ../buffer.c:2504 -msgid "[New file]" -msgstr "[新文件]" - -#: ../buffer.c:2505 +#: ../buffer.c:3115 msgid "[Read errors]" msgstr "[读错误]" -#: ../buffer.c:2506 ../buffer.c:3217 ../fileio.c:1807 ../screen.c:4895 +#: ../buffer.c:3117 ../fileio.c:1777 ../statusline.c:122 ../statusline.c:1545 msgid "[RO]" msgstr "[只读]" -#: ../buffer.c:2507 ../fileio.c:1807 +#: ../buffer.c:3117 ../fileio.c:1777 msgid "[readonly]" msgstr "[只读]" -#: ../buffer.c:2524 -#, c-format -msgid "1 line --%d%%--" -msgstr "1 行 --%d%%--" - -#: ../buffer.c:2526 -#, c-format -msgid "%<PRId64> lines --%d%%--" -msgstr "%<PRId64> 行 --%d%%--" +#: ../buffer.c:3136 +#, fuzzy, c-format +msgid "%<PRId64> line --%d%%--" +msgid_plural "%<PRId64> lines --%d%%--" +msgstr[0] "%<PRId64> 行 --%d%%--" -#: ../buffer.c:2530 +#: ../buffer.c:3142 #, c-format msgid "line %<PRId64> of %<PRId64> --%d%%-- col " msgstr "行 %<PRId64> / %<PRId64> --%d%%-- 列 " -#: ../buffer.c:2632 ../buffer.c:4292 ../memline.c:1554 +#: ../buffer.c:3234 ../buffer.c:4143 ../memline.c:1456 msgid "[No Name]" msgstr "[未命名]" -#. must be a help buffer -#: ../buffer.c:2667 +#. Must be a help buffer. +#: ../buffer.c:3279 msgid "help" msgstr "帮助" -#: ../buffer.c:3225 ../screen.c:4883 -msgid "[Help]" -msgstr "[帮助]" - -#: ../buffer.c:3254 ../screen.c:4887 -msgid "[Preview]" -msgstr "[预览]" - -#: ../buffer.c:3528 +#: ../buffer.c:3421 msgid "All" msgstr "全部" -#: ../buffer.c:3528 +#: ../buffer.c:3421 msgid "Bot" msgstr "底端" -#: ../buffer.c:3531 +#: ../buffer.c:3423 msgid "Top" msgstr "顶端" -#: ../buffer.c:4244 -msgid "" -"\n" -"# Buffer list:\n" -msgstr "" -"\n" -"# 缓冲区列表:\n" +#: ../buffer.c:3912 +msgid "E382: Cannot write, 'buftype' option is set" +msgstr "E382: 无法写入,已设定选项 'buftype'" -#: ../buffer.c:4289 +#: ../buffer.c:3954 +msgid "[Prompt]" +msgstr "[提示符]" + +#: ../buffer.c:3956 msgid "[Scratch]" -msgstr "" +msgstr "[涂鸦]" -#: ../buffer.c:4529 -msgid "" -"\n" -"--- Signs ---" +#: ../buffer.h:72 +msgid "[Location List]" +msgstr "[Location 列表]" + +#: ../buffer.h:73 +msgid "[Quickfix List]" +msgstr "[Quickfix 列表]" + +#: ../change.c:68 +msgid "W10: Warning: Changing a readonly file" +msgstr "W10: 警告: 正在修改只读文件" + +#: ../channel.c:499 +msgid "can only be opened in headless mode" msgstr "" -"\n" -"--- Signs ---" -#: ../buffer.c:4538 -#, c-format -msgid "Signs for %s:" -msgstr "%s 的 Signs:" +#: ../channel.c:504 +msgid "channel was already open" +msgstr "channel 已打开" -#: ../buffer.c:4543 -#, c-format -msgid " line=%<PRId64> id=%d name=%s" -msgstr " 行=%<PRId64> id=%d 名称=%s" +#: ../channel.c:550 ../channel.c:564 ../channel.c:574 +msgid "Can't send data to closed stream" +msgstr "无法将数据发送到关闭的 stream" + +#: ../channel.c:560 ../channel.c:579 +msgid "Can't send raw data to rpc channel" +msgstr "无法将 raw data 发送到 rpc channel" + +#: ../cmdexpand.c:925 +msgid "tagname" +msgstr "tag 名" + +#: ../cmdexpand.c:928 +msgid " kind file\n" +msgstr " 类型 文件\n" + +#: ../cmdhist.c:616 +msgid "'history' option is zero" +msgstr "选项 'history' 为零" -#: ../cursor_shape.c:68 +#: ../context.c:335 +msgid "E474: Failed to convert list to msgpack string buffer" +msgstr "" + +#: ../cursor_shape.c:136 msgid "E545: Missing colon" msgstr "E545: 缺少冒号" -#: ../cursor_shape.c:70 ../cursor_shape.c:94 +#: ../cursor_shape.c:139 ../cursor_shape.c:164 msgid "E546: Illegal mode" msgstr "E546: 无效的模式" -#: ../cursor_shape.c:134 +#: ../cursor_shape.c:197 ../optionstr.c:510 msgid "E548: digit expected" msgstr "E548: 此处需要数字" -#: ../cursor_shape.c:138 +#: ../cursor_shape.c:202 msgid "E549: Illegal percentage" msgstr "E549: 无效的百分比" -#: ../diff.c:146 +#: ../debugger.c:106 +msgid "Entering Debug mode. Type \"cont\" to continue." +msgstr "进入调试模式。输入 \"cont\" 继续运行。" + +# do not translate +#: ../debugger.c:109 +#, c-format +msgid "Oldval = \"%s\"" +msgstr "" + +#: ../debugger.c:114 +#, c-format +msgid "Newval = \"%s\"" +msgstr "新值 = \"%s\"" + +#: ../debugger.c:124 ../debugger.c:388 +#, c-format +msgid "line %<PRId64>: %s" +msgstr "第 %<PRId64> 行: %s" + +#: ../debugger.c:126 ../debugger.c:390 #, c-format -msgid "E96: Can not diff more than %<PRId64> buffers" -msgstr "E96: 不能比较(diff) %<PRId64> 个以上的缓冲区" +msgid "cmd: %s" +msgstr "命令: %s" -#: ../diff.c:753 +#: ../debugger.c:347 #, fuzzy +msgid "frame is zero" +msgstr "E726: 步长为零" + +#: ../debugger.c:354 +#, c-format +msgid "frame at highest level: %d" +msgstr "帧级别最高:%d" + +#: ../debugger.c:434 +#, c-format +msgid "Breakpoint in \"%s%s\" line %<PRId64>" +msgstr "断点 \"%s%s\" 第 %<PRId64> 行" + +#: ../debugger.c:684 +#, c-format +msgid "E161: Breakpoint not found: %s" +msgstr "E161: 找不到断点: %s" + +#: ../debugger.c:717 +msgid "No breakpoints defined" +msgstr "没有定义断点" + +#: ../debugger.c:725 +#, c-format +msgid "%3d %s %s line %<PRId64>" +msgstr "%3d %s %s 第 %<PRId64> 行" + +#: ../debugger.c:731 +#, c-format +msgid "%3d expr %s" +msgstr "%3d 表达式 %s" + +#: ../diff.c:206 +#, c-format +msgid "E96: Cannot diff more than %<PRId64> buffers" +msgstr "E96: 不能比较 %<PRId64> 个以上的缓冲区" + +#: ../diff.c:767 +#, c-format +msgid "Not enough memory to use internal diff for buffer \"%s\"" +msgstr "为缓冲区 \"%s\" 使用内部比对(diff)时无足够的内存" + +#: ../diff.c:1082 msgid "E810: Cannot read or write temp files" -msgstr "E557: 无法打开 termcap 文件" +msgstr "E810: 无法读写临时文件" -#: ../diff.c:755 +#: ../diff.c:1084 msgid "E97: Cannot create diffs" msgstr "E97: 无法创建 diff" -#: ../diff.c:966 -#, fuzzy +#: ../diff.c:1125 +msgid "E960: Problem creating the internal diff" +msgstr "E960: 创建内部 diff 时遇到问题" + +#: ../diff.c:1284 msgid "E816: Cannot read patch output" -msgstr "E98: 无法读取 diff 的输出" +msgstr "E816: 无法读取 patch 的输出" -#: ../diff.c:1220 +#: ../diff.c:1740 msgid "E98: Cannot read diff output" msgstr "E98: 无法读取 diff 的输出" -#: ../diff.c:2081 +#: ../diff.c:2847 msgid "E99: Current buffer is not in diff mode" -msgstr "E99: 当前缓冲区不在 diff 模式" +msgstr "E99: 当前缓冲区不在差异模式" -#: ../diff.c:2100 -#, fuzzy +#: ../diff.c:2867 msgid "E793: No other buffer in diff mode is modifiable" -msgstr "E100: 没有其它处于 diff 模式的缓冲区" +msgstr "E793: 没有其它处于差异模式的缓冲区可修改" -#: ../diff.c:2102 +#: ../diff.c:2869 msgid "E100: No other buffer in diff mode" -msgstr "E100: 没有其它处于 diff 模式的缓冲区" +msgstr "E100: 没有其它处于差异模式的缓冲区" -#: ../diff.c:2112 +#: ../diff.c:2880 msgid "E101: More than two buffers in diff mode, don't know which one to use" -msgstr "E101: 有两个以上的缓冲区处于 diff 模式,不能决定用哪一个" +msgstr "E101: 有两个以上的缓冲区处于差异模式,不能决定用哪一个" -#: ../diff.c:2141 +#: ../diff.c:2909 #, c-format msgid "E102: Can't find buffer \"%s\"" msgstr "E102: 找不到缓冲区 \"%s\"" -#: ../diff.c:2152 +#: ../diff.c:2920 #, c-format msgid "E103: Buffer \"%s\" is not in diff mode" -msgstr "E103: 缓冲区 \"%s\" 不在 diff 模式" +msgstr "E103: 缓冲区 \"%s\" 不处于差异模式" -#: ../diff.c:2193 +#: ../diff.c:2960 msgid "E787: Buffer changed unexpectedly" msgstr "E787: 意外地改变了缓冲区" -#: ../digraph.c:1598 +#: ../digraph.c:50 +#, c-format +msgid "E1214: Digraph must be just two characters: %s" +msgstr "E1214: 二合字符必须只有两个字符:%s" + +#: ../digraph.c:52 +#, c-format +msgid "E1215: Digraph must be one character: %s" +msgstr "E1215: 二合字符必须是一个字符:%s" + +#: ../digraph.c:54 +msgid "" +"E1216: digraph_setlist() argument must be a list of lists with two items" +msgstr "E1216: digraph_setlist() 参数必须是包含两项的列表的列表" + +#: ../digraph.c:1664 msgid "E104: Escape not allowed in digraph" -msgstr "E104: 复合字符(digraph)中不能使用 Escape" +msgstr "E104: 二合字符中不能使用 Escape" + +#: ../digraph.c:1737 +msgid "Custom" +msgstr "自定义" + +#: ../digraph.c:1794 +msgid "Latin supplement" +msgstr "拉丁文补充" + +#: ../digraph.c:1795 +msgid "Greek and Coptic" +msgstr "希腊和科普特文" + +#: ../digraph.c:1796 +msgid "Cyrillic" +msgstr "西里尔文" + +#: ../digraph.c:1797 +msgid "Hebrew" +msgstr "希伯来文" + +#: ../digraph.c:1798 +msgid "Arabic" +msgstr "阿拉伯文" + +#: ../digraph.c:1799 +msgid "Latin extended" +msgstr "拉丁文扩展" -#: ../digraph.c:1760 +#: ../digraph.c:1800 +msgid "Greek extended" +msgstr "希腊文扩展" + +#: ../digraph.c:1801 +msgid "Punctuation" +msgstr "标点符号" + +#: ../digraph.c:1802 +msgid "Super- and subscripts" +msgstr "上下标" + +#: ../digraph.c:1803 +msgid "Currency" +msgstr "货币符号" + +#: ../digraph.c:1804 ../digraph.c:1809 ../digraph.c:1819 +msgid "Other" +msgstr "其他" + +#: ../digraph.c:1805 +msgid "Roman numbers" +msgstr "罗马数字" + +#: ../digraph.c:1806 +msgid "Arrows" +msgstr "箭头" + +#: ../digraph.c:1807 +msgid "Mathematical operators" +msgstr "数学运算符" + +#: ../digraph.c:1808 +msgid "Technical" +msgstr "技术符号" + +#: ../digraph.c:1810 +msgid "Box drawing" +msgstr "方框绘图" + +#: ../digraph.c:1811 +msgid "Block elements" +msgstr "块状元素" + +#: ../digraph.c:1812 +msgid "Geometric shapes" +msgstr "几何形状" + +#: ../digraph.c:1813 +msgid "Symbols" +msgstr "符号" + +#: ../digraph.c:1814 +msgid "Dingbats" +msgstr "杂锦字符" + +#: ../digraph.c:1815 +msgid "CJK symbols and punctuation" +msgstr "中日韩标点符号" + +#: ../digraph.c:1816 +msgid "Hiragana" +msgstr "日文平假名" + +#: ../digraph.c:1817 +msgid "Katakana" +msgstr "日文片假名" + +#: ../digraph.c:1818 +msgid "Bopomofo" +msgstr "注音符号" + +#: ../digraph.c:2062 msgid "E544: Keymap file not found" msgstr "E544: 找不到 Keymap 文件" -#: ../digraph.c:1785 +#: ../digraph.c:2083 msgid "E105: Using :loadkeymap not in a sourced file" -msgstr "E105: 不是在脚本文件中使用 :loadkeymap " +msgstr "E105: 不是在脚本文件中使用 :loadkeymap" -#: ../digraph.c:1821 +#: ../digraph.c:2118 msgid "E791: Empty keymap entry" msgstr "E791: 空的键位映射项" -#: ../edit.c:82 -msgid " Keyword completion (^N^P)" -msgstr " 关键字补全 (^N^P)" +#. TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead +#. maximum nesting of lists and dicts +#: ../eval.c:92 +msgid "E111: Missing ']'" +msgstr "E111: 缺少 ']'" -#. ctrl_x_mode == 0, ^P/^N compl. -#: ../edit.c:83 -msgid " ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)" -msgstr " ^X 模式 (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)" +#: ../eval.c:93 +msgid "E719: Cannot use [:] with a Dictionary" +msgstr "E719: 不能对 Dictionary 使用 [:]" -#: ../edit.c:85 -msgid " Whole line completion (^L^N^P)" -msgstr " 整行补全 (^L^N^P)" +#: ../eval.c:95 +msgid "E274: No white space allowed before parenthesis" +msgstr "E274: 小括号前不允许空白字符" -#: ../edit.c:86 -msgid " File name completion (^F^N^P)" -msgstr " 文件名补全 (^F^N^P)" +#: ../eval.c:96 +#, c-format +msgid "E80: Error while writing: %s" +msgstr "E80: 写入时出错:%s" -#: ../edit.c:87 -msgid " Tag completion (^]^N^P)" -msgstr " Tag 补全 (^]^N^P)" +#: ../eval.c:97 +msgid "E1098: String, List or Blob required" +msgstr "E1098: 需要字符串、列表或 Blob" -#: ../edit.c:88 -msgid " Path pattern completion (^N^P)" -msgstr " 头文件模式补全 (^N^P)" +#: ../eval.c:98 +#, c-format +msgid "E1169: Expression too recursive: %s" +msgstr "E1169: 表达式递归层数过多:%s" -#: ../edit.c:89 -msgid " Definition completion (^D^N^P)" -msgstr " 定义补全 (^D^N^P)" +#: ../eval.c:100 +#, c-format +msgid "E1203: Dot can only be used on a dictionary: %s" +msgstr "E1203: 点只能用于字典:%s" -#: ../edit.c:91 -msgid " Dictionary completion (^K^N^P)" -msgstr " Dictionary 补全 (^K^N^P)" +#: ../eval.c:1095 +msgid "" +"E5700: Expression from 'spellsuggest' must yield lists with exactly two " +"values" +msgstr "" -#: ../edit.c:92 -msgid " Thesaurus completion (^T^N^P)" -msgstr " Thesaurus 补全 (^T^N^P)" +#: ../eval.c:1327 ../eval/vars.c:1112 +#, c-format +msgid "E121: Undefined variable: %.*s" +msgstr "E121: 变量未定义: %.*s" -#: ../edit.c:93 -msgid " Command-line completion (^V^N^P)" -msgstr " 命令行补全 (^V^N^P)" +#: ../eval.c:1358 +msgid "E689: Can only index a List, Dictionary or Blob" +msgstr "E689: 只能索引列表、字典或 Blob" -#: ../edit.c:94 -msgid " User defined completion (^U^N^P)" -msgstr " 用户自定义补全 (^U^N^P)" +#: ../eval.c:1364 +msgid "E708: [:] must come last" +msgstr "E708: [:] 必须在最后" -#: ../edit.c:95 -msgid " Omni completion (^O^N^P)" -msgstr " 全能补全 (^O^N^P)" +#: ../eval.c:1376 +#, fuzzy +msgid "E713: Cannot use empty key after ." +msgstr "E713: Dictionary 的键不能为空" -#: ../edit.c:96 -msgid " Spelling suggestion (s^N^P)" -msgstr " 拼写建议 (s^N^P)" +#: ../eval.c:1412 +#, fuzzy +msgid "E709: [:] requires a List or Blob value" +msgstr "E709: [:] 需要一个 List 值" -#: ../edit.c:97 -msgid " Keyword Local completion (^N^P)" -msgstr " 关键字局部补全 (^N^P)" +#: ../eval.c:1665 +msgid "E972: Blob value does not have the right number of bytes" +msgstr "E972: Blob 值的字节数不对" -#: ../edit.c:100 -msgid "Hit end of paragraph" -msgstr "已到段落结尾" +#: ../eval.c:1728 +msgid "E996: Cannot lock a range" +msgstr "E996: 不能锁定范围" -#: ../edit.c:101 -msgid "E839: Completion function changed window" -msgstr "E839: 补全函数更改了窗口" +#: ../eval.c:1771 +msgid "E710: List value has more items than target" +msgstr "E710: List 值的项比目标多" -#: ../edit.c:102 -msgid "E840: Completion function deleted text" -msgstr "E840: 补全函数删除了文本" +#: ../eval.c:1776 +msgid "E711: List value has not enough items" +msgstr "E711: List 值没有足够多的项" -#: ../edit.c:1847 -msgid "'dictionary' option is empty" -msgstr "选项 'dictionary' 为空" +#: ../eval.c:1784 +msgid "E996: Cannot lock a list or dict" +msgstr "E996: 不能锁定列表或字典" -#: ../edit.c:1848 -msgid "'thesaurus' option is empty" -msgstr "选项 'thesaurus' 为空" +#: ../eval.c:1866 +msgid "E690: Missing \"in\" after :for" +msgstr "E690: :for 后缺少 \"in\"" -#: ../edit.c:2655 -#, c-format -msgid "Scanning dictionary: %s" -msgstr "正在扫描 dictionary: %s" +#: ../eval.c:2389 +msgid "E109: Missing ':' after '?'" +msgstr "E109: '?' 后缺少 ':'" -#: ../edit.c:3079 -msgid " (insert) Scroll (^E/^Y)" -msgstr " (插入) Scroll (^E/^Y)" +#: ../eval.c:2893 +msgid "E804: Cannot use '%' with Float" +msgstr "E804: 不能对浮点数使用 '%'" -#: ../edit.c:3081 -msgid " (replace) Scroll (^E/^Y)" -msgstr " (替换) Scroll (^E/^Y)" +#: ../eval.c:3037 +msgid "E973: Blob literal should have an even number of hex characters" +msgstr "E973: Blob 字面量应该有偶数个十六进制字符" + +#: ../eval.c:3137 +msgid "E110: Missing ')'" +msgstr "E110: 缺少 ')'" + +#: ../eval.c:3372 +#, fuzzy +msgid "E260: Missing name after ->" +msgstr "E526: <%s> 后面缺少数字" -#: ../edit.c:3587 +#: ../eval.c:3431 +msgid "E695: Cannot index a Funcref" +msgstr "E695: 不能索引 Funcref" + +#: ../eval.c:3442 +msgid "E909: Cannot index a special variable" +msgstr "E909: 不能索引特殊变量" + +#: ../eval.c:3730 #, c-format -msgid "Scanning: %s" -msgstr "正在扫描: %s" +msgid "E112: Option name missing: %s" +msgstr "E112: 缺少选项名称:%s" -#: ../edit.c:3614 -msgid "Scanning tags." -msgstr "扫描标签." +#: ../eval.c:3751 +#, c-format +msgid "E113: Unknown option: %s" +msgstr "E113: 未知的选项:%s" -#: ../edit.c:4519 -msgid " Adding" -msgstr " 增加" +#: ../eval.c:3798 +#, c-format +msgid "E114: Missing quote: %s" +msgstr "E114: 缺少引号:%s" -#. showmode might reset the internal line pointers, so it must -#. * be called before line = ml_get(), or when this address is no -#. * longer needed. -- Acevedo. -#. -#: ../edit.c:4562 -msgid "-- Searching..." -msgstr "-- 查找中..." +#: ../eval.c:3936 +#, c-format +msgid "E115: Missing quote: %s" +msgstr "E115: 缺少引号:%s" -#: ../edit.c:4618 -msgid "Back at original" -msgstr "回到起点" +#: ../eval.c:4031 +#, c-format +msgid "E696: Missing comma in List: %s" +msgstr "E696: 列表中缺少逗号:%s" -#: ../edit.c:4621 -msgid "Word from other line" -msgstr "另一行的词" +#: ../eval.c:4038 +#, c-format +msgid "E697: Missing end of List ']': %s" +msgstr "E697: 列表缺少结束符 ']':%s" -#: ../edit.c:4624 -msgid "The only match" -msgstr "唯一匹配" +#: ../eval.c:4338 +msgid "Not enough memory to set references, garbage collection aborted!" +msgstr "没有足够的内存来设置引用,垃圾回收已中止!" -#: ../edit.c:4680 +#: ../eval.c:4687 #, c-format -msgid "match %d of %d" -msgstr "匹配 %d / %d" +msgid "E720: Missing colon in Dictionary: %s" +msgstr "E720: 字典中缺少冒号:%s" -#: ../edit.c:4684 +#: ../eval.c:4710 #, c-format -msgid "match %d" -msgstr "匹配 %d" +msgid "E721: Duplicate key in Dictionary: \"%s\"" +msgstr "E721: Dictionary 中出现重复的键: \"%s\"" -#: ../eval.c:137 -msgid "E18: Unexpected characters in :let" -msgstr "E18: :let 中出现异常字符" +#: ../eval.c:4728 +#, c-format +msgid "E722: Missing comma in Dictionary: %s" +msgstr "E722: 字典中缺少逗号:%s" -#: ../eval.c:138 +#: ../eval.c:4735 #, c-format -msgid "E684: list index out of range: %<PRId64>" -msgstr "E684: List 索引超出范围: %<PRId64>" +msgid "E723: Missing end of Dictionary '}': %s" +msgstr "E723: 字典缺少结束符 '}':%s" + +#: ../eval.c:4854 +msgid "map() argument" +msgstr "map() 参数" + +#: ../eval.c:4855 +msgid "filter() argument" +msgstr "filter() 参数" -#: ../eval.c:139 +#: ../eval.c:5076 #, c-format -msgid "E121: Undefined variable: %s" -msgstr "E121: 未定义的变量: %s" +msgid "E700: Unknown function: %s" +msgstr "E700: 未知的函数:%s" -#: ../eval.c:140 -msgid "E111: Missing ']'" -msgstr "E111: 缺少 ']'" +#: ../eval.c:5112 +msgid "E922: expected a dict" +msgstr "E922: 需要字典" + +#: ../eval.c:5122 +msgid "E923: Second argument of function() must be a list or a dict" +msgstr "E923: function() 的第二个参数必须是列表或字典" + +#: ../eval.c:5397 +msgid "E5050: {opts} must be the only argument" +msgstr "E5050: {opts} 必须是唯一的参数" -#: ../eval.c:141 +#: ../eval.c:5794 ../os/shell.c:718 #, c-format -msgid "E686: Argument of %s must be a List" -msgstr "E686: %s 的参数必须是 List" +msgid "Executing command: \"%s\"" +msgstr "执行命令:\"%s\"" + +#: ../eval.c:5902 +msgid "E921: Invalid callback argument" +msgstr "E921: 回调参数无效" + +#: ../eval.c:7603 +msgid "E698: variable nested too deep for making a copy" +msgstr "E698: 变量嵌套过深,无法复制" + +#: ../eval.c:8078 +msgid "" +"\n" +"\tLast set from " +msgstr "" +"\n" +"\t最近修改于 " + +#: ../eval.c:8690 +msgid "E5009: $VIMRUNTIME is empty or unset" +msgstr "E5009: $VIMRUNTIME 为空或未设置" -#: ../eval.c:143 +#: ../eval.c:8694 #, c-format -msgid "E712: Argument of %s must be a List or Dictionary" -msgstr "E712: %s 的参数必须是 List 或者 Dictionary" +msgid "E5009: Invalid $VIMRUNTIME: %s" +msgstr "E5009: $VIMRUNTIME 无效:%s" -#: ../eval.c:144 -msgid "E713: Cannot use empty key for Dictionary" -msgstr "E713: Dictionary 的键不能为空" +#: ../eval.c:8696 +msgid "E5009: Invalid 'runtimepath'" +msgstr "E5009: 'runtimepath' 无效" -#: ../eval.c:145 -msgid "E714: List required" -msgstr "E714: 需要 List" +#: ../eval.c:8783 +msgid "E977: Can only compare Blob with Blob" +msgstr "E977: Blob 只能与 Blob 比较" -#: ../eval.c:146 -msgid "E715: Dictionary required" -msgstr "E715: 需要 Dictionary" +#: ../eval.c:8806 +msgid "E691: Can only compare List with List" +msgstr "E691: 列表只能与列表比较" + +#: ../eval.c:8808 +msgid "E692: Invalid operation for List" +msgstr "E692: 操作对列表无效" + +#: ../eval.c:8829 +msgid "E735: Can only compare Dictionary with Dictionary" +msgstr "E735: 字典只能与字典比较" + +#: ../eval.c:8831 +msgid "E736: Invalid operation for Dictionary" +msgstr "E736: 操作对字典无效" -#: ../eval.c:147 +#: ../eval.c:8845 +msgid "E694: Invalid operation for Funcrefs" +msgstr "E694: 操作对 Funcrefs 无效" + +#: ../eval/decode.c:132 #, c-format -msgid "E118: Too many arguments for function: %s" -msgstr "E118: 函数的参数过多: %s" +msgid "E474: Expected comma before list item: %s" +msgstr "E474: 列表项前应有逗号:%s" -#: ../eval.c:148 +#: ../eval/decode.c:140 #, c-format -msgid "E716: Key not present in Dictionary: %s" -msgstr "E716: Dictionary 中不存在键: %s" +msgid "E474: Expected colon before dictionary value: %s" +msgstr "E474: 字典值前应有冒号:%s" -#: ../eval.c:150 +#: ../eval/decode.c:167 #, c-format -msgid "E122: Function %s already exists, add ! to replace it" -msgstr "E122: 函数 %s 已存在,请加 ! 强制替换" +msgid "E474: Expected string key: %s" +msgstr "E474: 需要字符串键:%s" -#: ../eval.c:151 -msgid "E717: Dictionary entry already exists" -msgstr "E717: Dictionary 项已存在" +#: ../eval/decode.c:173 +#, c-format +msgid "E474: Expected comma before dictionary key: %s" +msgstr "E474: 字典键前应有逗号:%s" -#: ../eval.c:152 -msgid "E718: Funcref required" -msgstr "E718: 需要 Funcref" +#: ../eval/decode.c:340 +#, c-format +msgid "E474: Unfinished escape sequence: %.*s" +msgstr "E474: 转义序列不完整:%.*s" -#: ../eval.c:153 -msgid "E719: Cannot use [:] with a Dictionary" -msgstr "E719: 不能对 Dictionary 使用 [:]" +#: ../eval/decode.c:347 +#, c-format +msgid "E474: Unfinished unicode escape sequence: %.*s" +msgstr "E474: Unicode 转义序列不完整:%.*s" -#: ../eval.c:154 +#: ../eval/decode.c:354 #, c-format -msgid "E734: Wrong variable type for %s=" -msgstr "E734: %s= 的变量类型不正确" +msgid "E474: Expected four hex digits after \\u: %.*s" +msgstr "E474: \\u 之后应有四位十六进制数字:%.*s" -#: ../eval.c:155 +#: ../eval/decode.c:375 #, c-format -msgid "E130: Unknown function: %s" -msgstr "E130: 未知的函数: %s" +msgid "E474: Unknown escape sequence: %.*s" +msgstr "E474: 未知的转义序列:%.*s" -#: ../eval.c:156 +#: ../eval/decode.c:382 #, c-format -msgid "E461: Illegal variable name: %s" -msgstr "E461: 无效的变量名: %s" +msgid "E474: ASCII control characters cannot be present inside string: %.*s" +msgstr "E474: 字符串中不能出现 ASCII 控制字符:%.*s" -#: ../eval.c:157 -#, fuzzy -msgid "E806: using Float as a String" -msgstr "E730: 将 List 作 String 使用" +#: ../eval/decode.c:395 +#, c-format +msgid "E474: Only UTF-8 strings allowed: %.*s" +msgstr "E474: 只允许 UTF-8 字符串:%.*s" -#: ../eval.c:1830 -msgid "E687: Less targets than List items" -msgstr "E687: 目标比 List 项数少" +#: ../eval/decode.c:398 +#, c-format +msgid "" +"E474: Only UTF-8 code points up to U+10FFFF are allowed to appear unescaped: " +"%.*s" +msgstr "" -#: ../eval.c:1834 -msgid "E688: More targets than List items" -msgstr "E688: 目标比 List 项数多" +#: ../eval/decode.c:409 +#, c-format +msgid "E474: Expected string end: %.*s" +msgstr "" -#: ../eval.c:1906 -msgid "Double ; in list of variables" -msgstr "变量列表中出现两个 ;" +#: ../eval/decode.c:555 +#, c-format +msgid "E474: Leading zeroes are not allowed: %.*s" +msgstr "E474: 不允许前导零:%.*s" -#: ../eval.c:2078 +#: ../eval/decode.c:584 #, c-format -msgid "E738: Can't list variables for %s" -msgstr "E738: 无法列出 %s 的变量" +msgid "E474: Missing number after minus sign: %.*s" +msgstr "E474: 负号后缺少数字:%.*s" -#: ../eval.c:2391 -msgid "E689: Can only index a List or Dictionary" -msgstr "E689: 只能索引 List 或 Dictionary" +#: ../eval/decode.c:587 +#, c-format +msgid "E474: Missing number after decimal dot: %.*s" +msgstr "E474: 小数点后缺少数字:%.*s" -#: ../eval.c:2396 -msgid "E708: [:] must come last" -msgstr "E708: [:] 必须在最后" +#: ../eval/decode.c:590 +#, c-format +msgid "E474: Missing exponent: %.*s" +msgstr "E474: 缺少指数:%.*s" -#: ../eval.c:2439 -msgid "E709: [:] requires a List value" -msgstr "E709: [:] 需要一个 List 值" +#: ../eval/decode.c:602 +#, c-format +msgid "" +"E685: internal error: while converting number \"%.*s\" to float string2float " +"consumed %zu bytes in place of %zu" +msgstr "" -#: ../eval.c:2674 -msgid "E710: List value has more items than target" -msgstr "E710: List 值的项比目标多" +#: ../eval/decode.c:613 +#, c-format +msgid "" +"E685: internal error: while converting number \"%.*s\" to integer vim_str2nr " +"consumed %i bytes in place of %zu" +msgstr "" -#: ../eval.c:2678 -msgid "E711: List value has not enough items" -msgstr "E711: List 值没有足够多的项" +#: ../eval/decode.c:665 +msgid "E474: Attempt to decode a blank string" +msgstr "E474: 试图解码空字符串" -#: ../eval.c:2867 -msgid "E690: Missing \"in\" after :for" -msgstr "E690: :for 后缺少 \"in\"" +#: ../eval/decode.c:682 +#, c-format +msgid "E474: No container to close: %.*s" +msgstr "E474: 没有容器可以关闭:%.*s" -#: ../eval.c:3063 +#: ../eval/decode.c:687 #, c-format -msgid "E107: Missing parentheses: %s" -msgstr "E107: 缺少括号: %s" +msgid "E474: Closing list with curly bracket: %.*s" +msgstr "E474: 用花括号结束列表:%.*s" -#: ../eval.c:3263 +#: ../eval/decode.c:690 #, c-format -msgid "E108: No such variable: \"%s\"" -msgstr "E108: 无此变量: \"%s\"" +msgid "E474: Closing dictionary with square bracket: %.*s" +msgstr "E474: 用方括号结束字典:%.*s" -#: ../eval.c:3333 -msgid "E743: variable nested too deep for (un)lock" -msgstr "E743: (un)lock 的变量嵌套过深" +#: ../eval/decode.c:694 +#, c-format +msgid "E474: Trailing comma: %.*s" +msgstr "E474: 尾随逗号:%.*s" -#: ../eval.c:3630 -msgid "E109: Missing ':' after '?'" -msgstr "E109: '?' 后缺少 ':'" +#: ../eval/decode.c:697 +#, c-format +msgid "E474: Expected value after colon: %.*s" +msgstr "E474: 冒号后应有值:%.*s" -#: ../eval.c:3893 -msgid "E691: Can only compare List with List" -msgstr "E691: 只能比较 List 和 List" +#: ../eval/decode.c:701 +#, fuzzy, c-format +msgid "E474: Expected value: %.*s" +msgstr "E415: 不该有的等号: %s" -#: ../eval.c:3895 -msgid "E692: Invalid operation for Lists" -msgstr "E692: 对 List 无效的操作" +#: ../eval/decode.c:720 +#, fuzzy, c-format +msgid "E474: Comma not inside container: %.*s" +msgstr "E242: %s 为不能识别的颜色名称" -#: ../eval.c:3915 -msgid "E735: Can only compare Dictionary with Dictionary" -msgstr "E735: 只能比较 Dictionary 和 Dictionary" +#: ../eval/decode.c:725 +#, fuzzy, c-format +msgid "E474: Duplicate comma: %.*s" +msgstr "E721: Dictionary 中出现重复的键: \"%s\"" -#: ../eval.c:3917 -msgid "E736: Invalid operation for Dictionary" -msgstr "E736: 对 Dictionary 无效的操作" +#: ../eval/decode.c:728 +#, c-format +msgid "E474: Comma after colon: %.*s" +msgstr "E474: 冒号后有逗号:%.*s" -#: ../eval.c:3932 -msgid "E693: Can only compare Funcref with Funcref" -msgstr "E693: 只能比较 Funcref 和 Funcref" +#: ../eval/decode.c:732 +#, c-format +msgid "E474: Using comma in place of colon: %.*s" +msgstr "E474: 应使用冒号,但用了逗号:%.*s" -#: ../eval.c:3934 -msgid "E694: Invalid operation for Funcrefs" -msgstr "E694: 对 Funcrefs 无效的操作" +#: ../eval/decode.c:740 +#, c-format +msgid "E474: Leading comma: %.*s" +msgstr "E474: 前导逗号:%.*s" -#: ../eval.c:4277 -#, fuzzy -msgid "E804: Cannot use '%' with Float" -msgstr "E719: 不能对 Dictionary 使用 [:]" +#: ../eval/decode.c:748 +#, fuzzy, c-format +msgid "E474: Colon not inside container: %.*s" +msgstr "E242: %s 为不能识别的颜色名称" -#: ../eval.c:4478 -msgid "E110: Missing ')'" -msgstr "E110: 缺少 ')'" +#: ../eval/decode.c:753 +#, c-format +msgid "E474: Using colon not in dictionary: %.*s" +msgstr "E474: 在字典之外使用冒号:%.*s" -#: ../eval.c:4609 -msgid "E695: Cannot index a Funcref" -msgstr "E695: 不能索引一个 Funcref" +#: ../eval/decode.c:756 +#, fuzzy, c-format +msgid "E474: Unexpected colon: %.*s" +msgstr "E415: 不该有的等号: %s" -#: ../eval.c:4839 +#: ../eval/decode.c:759 #, c-format -msgid "E112: Option name missing: %s" -msgstr "E112: 缺少选项名称: %s" +msgid "E474: Colon after comma: %.*s" +msgstr "E474: 逗号后有冒号:%.*s" -#: ../eval.c:4855 +#: ../eval/decode.c:762 #, c-format -msgid "E113: Unknown option: %s" -msgstr "E113: 未知的选项: %s" +msgid "E474: Duplicate colon: %.*s" +msgstr "E474: 重复的冒号:%.*s" + +#: ../eval/decode.c:775 +#, fuzzy, c-format +msgid "E474: Expected null: %.*s" +msgstr "E415: 不该有的等号: %s" + +#: ../eval/decode.c:787 +#, fuzzy, c-format +msgid "E474: Expected true: %.*s" +msgstr "E415: 不该有的等号: %s" + +#: ../eval/decode.c:799 +#, fuzzy, c-format +msgid "E474: Expected false: %.*s" +msgstr "E415: 不该有的等号: %s" + +#: ../eval/decode.c:879 +#, fuzzy, c-format +msgid "E474: Unidentified byte: %.*s" +msgstr "E121: 未定义的变量: %s" + +#: ../eval/decode.c:898 +#, fuzzy, c-format +msgid "E474: Trailing characters: %.*s" +msgstr "E488: 多余的尾部字符" -#: ../eval.c:4904 +#: ../eval/decode.c:906 #, c-format -msgid "E114: Missing quote: %s" -msgstr "E114: 缺少引号: %s" +msgid "E474: Unexpected end of input: %.*s" +msgstr "E474: 输入意外结束:%.*s" -#: ../eval.c:5020 +#: ../eval/encode.c:123 #, c-format -msgid "E115: Missing quote: %s" -msgstr "E115: 缺少引号: %s" +msgid "key %s" +msgstr "" -#: ../eval.c:5084 +#: ../eval/encode.c:124 #, c-format -msgid "E696: Missing comma in List: %s" -msgstr "E696: List 中缺少逗号: %s" +msgid "key %s at index %i from special map" +msgstr "" -#: ../eval.c:5091 +#: ../eval/encode.c:125 #, c-format -msgid "E697: Missing end of List ']': %s" -msgstr "E697: List 缺少结束符 ']': %s" +msgid "index %i" +msgstr "索引 %i" -#: ../eval.c:6475 +#: ../eval/encode.c:126 +msgid "partial" +msgstr "部分" + +#: ../eval/encode.c:127 #, c-format -msgid "E720: Missing colon in Dictionary: %s" -msgstr "E720: Dictionary 中缺少冒号: %s" +msgid "argument %i" +msgstr "参数 %i" -#: ../eval.c:6499 +#: ../eval/encode.c:128 +msgid "partial self dictionary" +msgstr "" + +#: ../eval/encode.c:203 +msgid "itself" +msgstr "" + +#. Only give this message once for a recursive call to avoid +#. flooding the user with errors. +#: ../eval/encode.c:453 ../eval/encode.c:533 +msgid "E724: unable to correctly dump variable with self-referencing container" +msgstr "" + +#: ../eval/encode.c:563 +msgid "E474: Unable to represent NaN value in JSON" +msgstr "E474: 在 JSON 中无法表示 NaN 值" + +#: ../eval/encode.c:567 +msgid "E474: Unable to represent infinity in JSON" +msgstr "E474: 在 JSON 中无法表示无穷大" + +#: ../eval/encode.c:635 #, c-format -msgid "E721: Duplicate key in Dictionary: \"%s\"" -msgstr "E721: Dictionary 中出现重复的键: \"%s\"" +msgid "" +"E474: String \"%.*s\" contains byte that does not start any UTF-8 character" +msgstr "E474: 字符串 \"%.*s\" 包含不起始任何 UTF-8 字符的字节" -#: ../eval.c:6517 +#: ../eval/encode.c:642 #, c-format -msgid "E722: Missing comma in Dictionary: %s" -msgstr "E722: Dictionary 中缺少逗号: %s" +msgid "" +"E474: UTF-8 string contains code point which belongs to a surrogate pair: " +"%.*s" +msgstr "E474: UTF-8 字符串包含属于代理对的码点:%.*s" -#: ../eval.c:6524 +#: ../eval/encode.c:724 +msgid "E474: Unable to convert EXT string to JSON" +msgstr "E474: 无法将 EXT 字符串转换为 JSON" + +#: ../eval/encode.c:752 #, c-format -msgid "E723: Missing end of Dictionary '}': %s" -msgstr "E723: Dictionary 缺少结束符 '}': %s" +msgid "E474: Error while dumping %s, %s: attempt to dump function reference" +msgstr "" -#: ../eval.c:6555 -msgid "E724: variable nested too deep for displaying" -msgstr "E724: 变量嵌套过深无法显示" +#: ../eval/encode.c:797 +#, fuzzy +msgid "E474: Invalid key in special dictionary" +msgstr "E736: 对 Dictionary 无效的操作" -#: ../eval.c:7188 -#, fuzzy, c-format -msgid "E740: Too many arguments for function %s" -msgstr "E118: 函数的参数过多: %s" +#: ../eval/encode.c:853 +msgid "encode_tv2string() argument" +msgstr "encode_tv2string() 参数" -#: ../eval.c:7190 -#, fuzzy, c-format -msgid "E116: Invalid arguments for function %s" -msgstr "E118: 函数的参数过多: %s" +#: ../eval/encode.c:881 +msgid ":echo argument" +msgstr ":echo 参数" -#: ../eval.c:7377 -#, fuzzy, c-format -msgid "E117: Unknown function: %s" -msgstr "E130: 未知的函数: %s" +#: ../eval/encode.c:905 +msgid "encode_tv2json() argument" +msgstr "encode_tv2json() 参数" -#: ../eval.c:7383 +#: ../eval/encode.c:966 #, c-format -msgid "E119: Not enough arguments for function: %s" -msgstr "E119: 函数 %s 的参数太少" +msgid "E5004: Error while dumping %s, %s: attempt to dump function reference" +msgstr "E5004: dump %s, %s 时出错:试图 dump 函数引用" -#: ../eval.c:7387 +#: ../eval/encode.c:1018 #, c-format -msgid "E120: Using <SID> not in a script context: %s" -msgstr "E120: <SID> 不能在 script 上下文外使用: %s" +msgid "E5005: Unable to dump %s: container references itself in %s" +msgstr "E5005: 无法 dump %s:容器在 %s 中引用了自己" -#: ../eval.c:7391 -#, fuzzy, c-format -msgid "E725: Calling dict function without Dictionary: %s" -msgstr "E720: Dictionary 中缺少冒号: %s" +#: ../eval/executor.c:23 +#, c-format +msgid "E684: list index out of range: %<PRId64>" +msgstr "E684: List 索引超出范围: %<PRId64>" -#: ../eval.c:7453 -#, fuzzy -msgid "E808: Number or Float required" -msgstr "E521: = 后面需要数字" +#: ../eval/funcs.c:147 +#, c-format +msgid "E899: Argument of %s must be a List or Blob" +msgstr "E899: %s 的参数必须是列表或 Blob" -#: ../eval.c:7503 -#, fuzzy +#: ../eval/funcs.c:148 ../match.c:45 +msgid "E957: Invalid window number" +msgstr "E957: 无效的窗口号" + +#: ../eval/funcs.c:149 +#, c-format +msgid "E998: Reduce of an empty %s with no initial value" +msgstr "E998: 在没有初始值的情况下 reduce 空的 %s" + +#: ../eval/funcs.c:151 +#, c-format +msgid "E1023: Using a Number as a Bool: %d" +msgstr "E1023: 将整数作布尔值使用:%d" + +#: ../eval/funcs.c:153 +msgid "E1308: Cannot resize a window in another tab page" +msgstr "E1308: 不能修改其他标签页中窗口的大小" + +#: ../eval/funcs.c:328 ../eval/funcs.c:7006 +#, c-format +msgid "Error converting the call result: %s" +msgstr "转换调用结果时出错:%s" + +#: ../eval/funcs.c:366 ../eval/funcs.c:374 msgid "add() argument" -msgstr "-c 参数" +msgstr "add() 参数" -#: ../eval.c:7907 -msgid "E699: Too many arguments" -msgstr "E699: 参数过多" +#: ../eval/funcs.c:679 ../sign.c:1451 +#, c-format +msgid "E158: Invalid buffer name: %s" +msgstr "E158: 缓冲区名无效:%s" -#: ../eval.c:8073 -msgid "E785: complete() can only be used in Insert mode" -msgstr "E785: complete() 只能在插入模式中使用" +#: ../eval/funcs.c:814 +#, c-format +msgid "Invalid channel stream \"%s\"" +msgstr "channel stream \"%s\" 无效" -#: ../eval.c:8156 +#: ../eval/funcs.c:1109 msgid "&Ok" msgstr "确定(&O)" -#: ../eval.c:8676 -#, c-format -msgid "E737: Key already exists: %s" -msgstr "E737: 键已存在: %s" +#: ../eval/funcs.c:1239 +msgid "Context stack is empty" +msgstr "上下文堆栈为空" -#: ../eval.c:8692 -#, fuzzy -msgid "extend() argument" -msgstr "--cmd 参数" +#: ../eval/funcs.c:1483 +msgid "dictwatcheradd() argument" +msgstr "dictwatcheradd() 参数" -#: ../eval.c:8915 -#, fuzzy -msgid "map() argument" -msgstr "-c 参数" +#: ../eval/funcs.c:2178 +msgid "E900: maxdepth must be non-negative number" +msgstr "E900: maxdepth 必须是非负整数" -#: ../eval.c:8916 -#, fuzzy -msgid "filter() argument" -msgstr "-c 参数" +#: ../eval/funcs.c:2186 +msgid "flatten() argument" +msgstr "flatten() 参数" -#: ../eval.c:9229 -#, c-format -msgid "+-%s%3ld line: " -msgid_plural "+-%s%3ld lines: " -msgstr[0] "+-%s%3ld 行: " +#: ../eval/funcs.c:2197 ../eval/typval.c:2458 +msgid "extend() argument" +msgstr "extend() 参数" -#: ../eval.c:9291 -#, c-format -msgid "E700: Unknown function: %s" -msgstr "E700: 未知的函数: %s" +#: ../eval/funcs.c:2877 ../eval/funcs.c:3891 +msgid "E5000: Cannot find tab number." +msgstr "E5000: 找不到标签页号。" + +#: ../eval/funcs.c:2885 ../eval/funcs.c:3899 +msgid "E5001: Higher scope cannot be -1 if lower scope is >= 0." +msgstr "E5001: 低边界 >= 0 时高边界不能为 -1。" -#: ../eval.c:10729 +#: ../eval/funcs.c:2892 ../eval/funcs.c:3906 +msgid "E5002: Cannot find window number." +msgstr "E5002: 找不到窗口号。" + +#: ../eval/funcs.c:4120 msgid "called inputrestore() more often than inputsave()" msgstr "inputrestore() 的调用次数多于 inputsave()" -#: ../eval.c:10771 -#, fuzzy +#: ../eval/funcs.c:4153 ../eval/funcs.c:4190 msgid "insert() argument" -msgstr "-c 参数" +msgstr "insert() 参数" -#: ../eval.c:10841 +#: ../eval/funcs.c:4260 msgid "E786: Range not allowed" msgstr "E786: 不允许的范围" -#: ../eval.c:11140 +#: ../eval/funcs.c:4743 +msgid "E474: Failed to convert list to string" +msgstr "E474: 无法将列表转换为字符串" + +#: ../eval/funcs.c:4760 +#, c-format +msgid "E474: Failed to parse %.*s" +msgstr "E474: 无法解析 %.*s" + +#: ../eval/funcs.c:4826 msgid "E701: Invalid type for len()" msgstr "E701: len() 的类型无效" -#: ../eval.c:11980 +#: ../eval/funcs.c:5348 +#, c-format +msgid "msgpackdump() argument, index %i" +msgstr "msgpackdump() 参数,索引 %i" + +#: ../eval/funcs.c:5517 +msgid "E5070: Character number must not be less than zero" +msgstr "E5070: 字符数不能小于零" + +#: ../eval/funcs.c:5521 +#, c-format +msgid "E5071: Character number must not be greater than INT_MAX (%i)" +msgstr "E5071: 字符数不能大于 INT_MAX (%i)" + +#: ../eval/funcs.c:5907 msgid "E726: Stride is zero" msgstr "E726: 步长为零" -#: ../eval.c:11982 +#: ../eval/funcs.c:5909 msgid "E727: Start past end" msgstr "E727: 起始值在终止值后" -#: ../eval.c:12024 ../eval.c:15297 +#: ../eval/funcs.c:6006 msgid "<empty>" msgstr "<空>" -#: ../eval.c:12282 -#, fuzzy +#: ../eval/funcs.c:6329 msgid "remove() argument" -msgstr "--cmd 参数" +msgstr "remove() 参数" -#: ../eval.c:12466 +#: ../eval/funcs.c:6447 msgid "E655: Too many symbolic links (cycle?)" msgstr "E655: 符号连接过多(循环?)" -#: ../eval.c:12593 -#, fuzzy +#: ../eval/funcs.c:6577 msgid "reverse() argument" -msgstr "-c 参数" +msgstr "reverse() 参数" -#: ../eval.c:13721 -#, fuzzy -msgid "sort() argument" -msgstr "-c 参数" +#: ../eval/funcs.c:7040 +#, c-format +msgid "E5010: List item %d of the second argument is not a string" +msgstr "E5010: 第二个参数的列表项 %d 不是字符串" -#: ../eval.c:13721 -#, fuzzy -msgid "uniq() argument" -msgstr "-c 参数" +#: ../eval/funcs.c:7916 +#, c-format +msgid "E962: Invalid action: '%s'" +msgstr "E962: 动作无效:'%s'" -#: ../eval.c:13776 -msgid "E702: Sort compare function failed" -msgstr "E702: Sort 比较函数失败" +#: ../eval/funcs.c:8056 +#, c-format +msgid "connection failed: %s" +msgstr "连接失败:%s" -#: ../eval.c:13806 -#, fuzzy -msgid "E882: Uniq compare function failed" -msgstr "E702: Sort 比较函数失败" +#: ../eval/funcs.c:8328 +#, fuzzy, c-format +msgid "E6100: \"%s\" is not a valid stdpath" +msgstr "E236: 字体 \"%s\" 不是等宽字体" -#: ../eval.c:14085 +#: ../eval/funcs.c:8420 ../os/time.c:189 msgid "(Invalid)" msgstr "(无效)" -#: ../eval.c:14590 -msgid "E677: Error writing temp file" -msgstr "E677: 写临时文件出错" +#: ../eval/funcs.c:8774 +#, c-format +msgid "E935: invalid submatch number: %d" +msgstr "E935: 子匹配号无效:%d" -#: ../eval.c:16159 -#, fuzzy -msgid "E805: Using a Float as a Number" -msgstr "E745: 将 List 作数字使用" +#: ../eval/funcs.c:9213 +msgid "Can only call this function in an unmodified buffer" +msgstr "只能在未修改的缓冲区中调用这个函数" -#: ../eval.c:16162 -msgid "E703: Using a Funcref as a Number" -msgstr "E703: 将 Funcref 作数字使用" +#: ../eval/funcs.c:10026 +msgid "writefile() first argument must be a List or a Blob" +msgstr "writefile() 的第一个参数必须是列表或者 blob" -#: ../eval.c:16170 -msgid "E745: Using a List as a Number" -msgstr "E745: 将 List 作数字使用" +#. Using %s, p and not %c, *p to preserve multibyte characters +#: ../eval/funcs.c:10053 +#, c-format +msgid "E5060: Unknown flag: %s" +msgstr "E5060: 未知的标志:%s" -#: ../eval.c:16173 -msgid "E728: Using a Dictionary as a Number" -msgstr "E728: 将 Dictionary 作数字使用" +#: ../eval/funcs.c:10067 +msgid "E482: Can't open file with an empty name" +msgstr "E482: 无法打开名称为空的文件" -#: ../eval.c:16259 -msgid "E729: using Funcref as a String" -msgstr "E729: 将 Funcref 作 String 使用" +#: ../eval/funcs.c:10072 +#, c-format +msgid "E482: Can't open file %s for writing: %s" +msgstr "" -#: ../eval.c:16262 -msgid "E730: using List as a String" -msgstr "E730: 将 List 作 String 使用" +#: ../eval/funcs.c:10085 +#, c-format +msgid "E80: Error when closing file %s: %s" +msgstr "E80: 关闭文件 %s 时出错: %s" -#: ../eval.c:16265 -msgid "E731: using Dictionary as a String" -msgstr "E731: 将 Dictionary 作 String 使用" +#: ../eval/typval.c:44 +#, c-format +msgid "E1174: String required for argument %d" +msgstr "E1174: 参数 %d 需要字符串" -#: ../eval.c:16619 +#: ../eval/typval.c:46 #, c-format -msgid "E706: Variable type mismatch for: %s" -msgstr "E706: 变量类型不匹配: %s" +msgid "E1175: Non-empty string required for argument %d" +msgstr "E1175: 参数 %d 需要非空字符串" -#: ../eval.c:16705 -#, fuzzy, c-format -msgid "E795: Cannot delete variable %s" -msgstr "E738: 无法列出 %s 的变量" +#: ../eval/typval.c:48 +#, c-format +msgid "E1210: Number required for argument %d" +msgstr "E1210: 参数 %d 需要整数" -#: ../eval.c:16724 +#: ../eval/typval.c:50 #, c-format -msgid "E704: Funcref variable name must start with a capital: %s" -msgstr "E704: Funcref 变量名必须以大写字母开头: %s" +msgid "E1222: String or List required for argument %d" +msgstr "E1222: 参数 %d 需要字符串或列表" -#: ../eval.c:16732 +#: ../eval/typval.c:76 #, c-format -msgid "E705: Variable name conflicts with existing function: %s" -msgstr "E705: 变量名与已有函数名冲突: %s" +msgid "E5142: Failed to open file %s: %s" +msgstr "E5142: 无法打开文件 %s:%s" -#: ../eval.c:16763 +#: ../eval/typval.c:98 #, c-format -msgid "E741: Value is locked: %s" -msgstr "E741: 值已锁定: %s" +msgid "E5143: Failed to write to file %s: %s" +msgstr "E5143: 无法写入文件 %s:%s" -#: ../eval.c:16764 ../eval.c:16769 ../message.c:1839 -msgid "Unknown" -msgstr "未知" +#: ../eval/typval.c:109 +#, c-format +msgid "E5144: Failed to close file %s: %s" +msgstr "E5144: 无法关闭文件 %s:%s" + +#: ../eval/typval.c:1150 +msgid "sort() argument" +msgstr "sort() 参数" + +#: ../eval/typval.c:1151 +msgid "uniq() argument" +msgstr "uniq() 参数" + +#: ../eval/typval.c:1242 +msgid "E702: Sort compare function failed" +msgstr "E702: Sort 比较函数失败" + +#: ../eval/typval.c:1265 +msgid "E882: Uniq compare function failed" +msgstr "E882: Uniq 比较函数失败" -#: ../eval.c:16768 +#: ../eval/typval.c:2198 +msgid "E6000: Argument is not a function or function name" +msgstr "" + +#: ../eval/typval.c:2476 #, c-format -msgid "E742: Cannot change value of %s" -msgstr "E742: 无法改变 %s 的值" +msgid "E737: Key already exists: %s" +msgstr "E737: 键已存在:%s" -#: ../eval.c:16838 -msgid "E698: variable nested too deep for making a copy" -msgstr "E698: 变量嵌套过深无法复制" +#: ../eval/typval.c:3315 +msgid "E743: variable nested too deep for (un)lock" +msgstr "E743: 变量嵌套过深,无法锁定/解锁" -#: ../eval.c:17249 +#: ../eval/typval.c:3455 #, c-format -msgid "E123: Undefined function: %s" -msgstr "E123: 函数 %s 尚未定义" +msgid "E741: Value is locked: %.*s" +msgstr "E741: 值已锁定:%.*s" -#: ../eval.c:17260 +#: ../eval/typval.c:3458 #, c-format -msgid "E124: Missing '(': %s" -msgstr "E124: 缺少 '(': %s" +msgid "E742: Cannot change value of %.*s" +msgstr "E742: 无法改变 %.*s 的值" -#: ../eval.c:17293 -#, fuzzy -msgid "E862: Cannot use g: here" -msgstr "E284: 不能设定 IC 值" +#: ../eval/typval.c:3464 ../message.c:2469 +msgid "Unknown" +msgstr "未知" + +#: ../eval/typval.c:3591 +msgid "E805: Expected a Number or a String, Float found" +msgstr "E805: 需要整数或字符串,但是提供了浮点数" + +#: ../eval/typval.c:3595 +msgid "E703: Expected a Number or a String, Funcref found" +msgstr "E703: 需要整数或字符串,但是提供了 Funcref" + +#: ../eval/typval.c:3598 +msgid "E745: Expected a Number or a String, List found" +msgstr "E745: 需要整数或字符串,但是提供了列表" + +#: ../eval/typval.c:3601 +msgid "E728: Expected a Number or a String, Dictionary found" +msgstr "E728: 需要整数或字符串,但是提供了字典" + +#: ../eval/typval.c:3604 +msgid "E974: Expected a Number or a String, Blob found" +msgstr "E974: 需要整数或字符串,但是提供了 Blob" + +#: ../eval/typval.c:3607 +msgid "E5299: Expected a Number or a String, Boolean found" +msgstr "E5299: 需要整数或字符串,但是提供了布尔值" + +#: ../eval/typval.c:3610 +msgid "E5300: Expected a Number or a String" +msgstr "E5300: 需要整数或字符串" + +#: ../eval/typval.c:3625 +msgid "E745: Using a List as a Number" +msgstr "E745: 将列表当作整数使用" + +#: ../eval/typval.c:3626 +msgid "E728: Using a Dictionary as a Number" +msgstr "E728: 将字典当作整数使用" + +#: ../eval/typval.c:3627 +msgid "E805: Using a Float as a Number" +msgstr "E805: 将浮点数当作整数使用" + +#: ../eval/typval.c:3628 +msgid "E974: Using a Blob as a Number" +msgstr "E974: 将 Blob 当作整数使用" + +#: ../eval/typval.c:3629 +msgid "E685: using an invalid value as a Number" +msgstr "E685: 将无效值当作整数使用" + +#: ../eval/typval.c:3670 +msgid "E730: using List as a String" +msgstr "E730: 将列表当作字符串使用" + +#: ../eval/typval.c:3671 +msgid "E731: using Dictionary as a String" +msgstr "E731: 将字典当作字符串使用" + +#: ../eval/typval.c:3673 +msgid "E976: using Blob as a String" +msgstr "E976: 将 Blob 当作字符串使用" + +#: ../eval/typval.c:3674 +msgid "E908: using an invalid value as a String" +msgstr "E908: 将无效值当作字符串使用" + +#: ../eval/typval.c:3815 +msgid "E891: Using a Funcref as a Float" +msgstr "E891: 将 Funcref 当作浮点数使用" + +#: ../eval/typval.c:3818 +msgid "E892: Using a String as a Float" +msgstr "E892: 将字符串当作浮点数使用" + +#: ../eval/typval.c:3821 +msgid "E893: Using a List as a Float" +msgstr "E893: 将列表当作浮点数使用" + +#: ../eval/typval.c:3824 +msgid "E894: Using a Dictionary as a Float" +msgstr "E894: 将字典当作浮点数使用" + +#: ../eval/typval.c:3827 +msgid "E362: Using a boolean value as a Float" +msgstr "E362: 将布尔值当作浮点数使用" + +#: ../eval/typval.c:3830 +msgid "E907: Using a special value as a Float" +msgstr "E907: 将特殊值当作浮点数使用" + +#: ../eval/typval.c:3833 +msgid "E975: Using a Blob as a Float" +msgstr "E975: 将 Blob 当作浮点数使用" + +#: ../eval/typval.h:515 +msgid "E808: Number or Float required" +msgstr "E808: 需要整数或浮点数" -#: ../eval.c:17312 +#: ../eval/userfunc.c:67 #, c-format -msgid "E125: Illegal argument: %s" -msgstr "E125: 无效的参数: %s" +msgid "E122: Function %s already exists, add ! to replace it" +msgstr "E122: 函数 %s 已存在,请加 ! 强制替换" -#: ../eval.c:17323 -#, fuzzy, c-format -msgid "E853: Duplicate argument name: %s" -msgstr "E125: 无效的参数: %s" +#: ../eval/userfunc.c:68 +msgid "E717: Dictionary entry already exists" +msgstr "E717: 字典项已存在" -#: ../eval.c:17416 -msgid "E126: Missing :endfunction" -msgstr "E126: 缺少 :endfunction" +#: ../eval/userfunc.c:69 +msgid "E718: Funcref required" +msgstr "E718: 需要 Funcref" -#: ../eval.c:17537 -#, fuzzy, c-format -msgid "E707: Function name conflicts with variable: %s" -msgstr "E746: 函数名与脚本文件名不匹配: %s" +#: ../eval/userfunc.c:70 +#, c-format +msgid "E130: Unknown function: %s" +msgstr "E130: 函数未知: %s" -#: ../eval.c:17549 +#: ../eval/userfunc.c:72 #, c-format -msgid "E127: Cannot redefine function %s: It is in use" -msgstr "E127: 函数 %s 正在使用中,不能重新定义" +msgid "E1068: No white space allowed before '%s': %s" +msgstr "E1068: '%s' 前不允许有空白:%s" -#: ../eval.c:17604 +#: ../eval/userfunc.c:124 #, c-format -msgid "E746: Function name does not match script file name: %s" -msgstr "E746: 函数名与脚本文件名不匹配: %s" +msgid "E125: Illegal argument: %s" +msgstr "E125: 参数无效: %s" -#: ../eval.c:17716 -msgid "E129: Function name required" -msgstr "E129: 需要函数名" +#: ../eval/userfunc.c:137 +#, c-format +msgid "E853: Duplicate argument name: %s" +msgstr "E853: 参数名重复:%s" -#: ../eval.c:17824 -#, fuzzy, c-format -msgid "E128: Function name must start with a capital or \"s:\": %s" -msgstr "E128: 函数名必须以大写字母开头或者包含冒号: %s" +#: ../eval/userfunc.c:171 +msgid "E989: Non-default argument follows default argument" +msgstr "E989: 默认参数后有非默认参数" -#: ../eval.c:17833 -#, fuzzy, c-format -msgid "E884: Function name cannot contain a colon: %s" -msgstr "E128: 函数名必须以大写字母开头或者包含冒号: %s" +#: ../eval/userfunc.c:498 +#, c-format +msgid "E740: Too many arguments for function %s" +msgstr "E740: 函数 %s 的参数过多" -#: ../eval.c:18336 +#: ../eval/userfunc.c:500 #, c-format -msgid "E131: Cannot delete function %s: It is in use" -msgstr "E131: 无法删除函数 %s: 正在使用中" +msgid "E116: Invalid arguments for function %s" +msgstr "E116: 函数 %s 的参数无效" -#: ../eval.c:18441 +#: ../eval/userfunc.c:859 msgid "E132: Function call depth is higher than 'maxfuncdepth'" msgstr "E132: 函数调用深度超出 'maxfuncdepth'" -#: ../eval.c:18568 +#: ../eval/userfunc.c:1039 #, c-format msgid "calling %s" msgstr "调用 %s" -#: ../eval.c:18651 +#: ../eval/userfunc.c:1154 #, c-format msgid "%s aborted" msgstr "%s 已中止" -#: ../eval.c:18653 +#: ../eval/userfunc.c:1156 #, c-format msgid "%s returning #%<PRId64>" msgstr "%s 返回 #%<PRId64> " -#: ../eval.c:18670 +#: ../eval/userfunc.c:1173 #, c-format msgid "%s returning %s" msgstr "%s 返回 %s" -#: ../eval.c:18691 ../ex_cmds2.c:2695 +#: ../eval/userfunc.c:1196 ../runtime.c:2083 #, c-format msgid "continuing in %s" msgstr "在 %s 中继续" -#: ../eval.c:18795 -msgid "E133: :return not inside a function" -msgstr "E133: :return 不在函数中" +#: ../eval/userfunc.c:1391 +msgid "E699: Too many arguments" +msgstr "E699: 参数过多" -#: ../eval.c:19159 -msgid "" -"\n" -"# global variables:\n" -msgstr "" -"\n" -"# 全局变量:\n" +#: ../eval/userfunc.c:1441 +#, c-format +msgid "E117: Unknown function: %s" +msgstr "E117: 未知的函数:%s" -#: ../eval.c:19254 -msgid "" -"\n" -"\tLast set from " -msgstr "" -"\n" -"\t最近修改于 " +#: ../eval/userfunc.c:1444 +#, c-format +msgid "E276: Cannot use function as a method: %s" +msgstr "E276: 不能将函数用作方法:%s" -#: ../eval.c:19272 -#, fuzzy -msgid "No old files" -msgstr "没有包含文件" +#: ../eval/userfunc.c:1447 +#, c-format +msgid "E933: Function was deleted: %s" +msgstr "E933: 函数已被删除:%s" -#: ../ex_cmds.c:122 +#: ../eval/userfunc.c:1453 #, c-format -msgid "<%s>%s%s %d, Hex %02x, Octal %03o" -msgstr "<%s>%s%s %d, 十六进制 %02x, 八进制 %03o" +msgid "E119: Not enough arguments for function: %s" +msgstr "E119: 函数 %s 的参数不足" -#: ../ex_cmds.c:145 +#: ../eval/userfunc.c:1457 #, c-format -msgid "> %d, Hex %04x, Octal %o" -msgstr "> %d, 十六进制 %04x, 八进制 %o" +msgid "E120: Using <SID> not in a script context: %s" +msgstr "E120: <SID> 不能在 script 上下文外使用:%s" -#: ../ex_cmds.c:146 +#: ../eval/userfunc.c:1461 #, c-format -msgid "> %d, Hex %08x, Octal %o" -msgstr "> %d, 十六进制 %08x, 八进制 %o" +msgid "E725: Calling dict function without Dictionary: %s" +msgstr "E725: 调用字典函数但是没有字典:%s" -#: ../ex_cmds.c:684 -msgid "E134: Move lines into themselves" -msgstr "E134: 把行移动到自已中" +#: ../eval/userfunc.c:1759 +msgid "E129: Function name required" +msgstr "E129: 需要函数名" -#: ../ex_cmds.c:747 -msgid "1 line moved" -msgstr "移动了 1 行" +#: ../eval/userfunc.c:1895 +#, c-format +msgid "E128: Function name must start with a capital or \"s:\": %s" +msgstr "E128: 函数名必须以大写字母或 \"s:\" 开头:%s" -#: ../ex_cmds.c:749 +#: ../eval/userfunc.c:1904 #, c-format -msgid "%<PRId64> lines moved" -msgstr "移动了 %<PRId64> 行" +msgid "E884: Function name cannot contain a colon: %s" +msgstr "E884: 函数名不能包含冒号:%s" + +#: ../eval/userfunc.c:1972 +msgid "E454: function list was modified" +msgstr "E454: 函数列表已被修改" -#: ../ex_cmds.c:1175 +#: ../eval/userfunc.c:2125 #, c-format -msgid "%<PRId64> lines filtered" -msgstr "过滤了 %<PRId64> 行" +msgid "E123: Undefined function: %s" +msgstr "E123: 函数未定义:%s" -#: ../ex_cmds.c:1194 -msgid "E135: *Filter* Autocommands must not change current buffer" -msgstr "E135: *Filter* 自动命令不可以改变当前缓冲区" +#: ../eval/userfunc.c:2135 +#, c-format +msgid "E124: Missing '(': %s" +msgstr "E124: 缺少 '(':%s" -#: ../ex_cmds.c:1244 -msgid "[No write since last change]\n" -msgstr "[已修改但尚未保存]\n" +#: ../eval/userfunc.c:2167 +msgid "E862: Cannot use g: here" +msgstr "E862: 此处不能使用 g:" -# bad to translate -#: ../ex_cmds.c:1424 +#: ../eval/userfunc.c:2197 #, c-format -msgid "%sviminfo: %s in line: " -msgstr "%sviminfo: %s 位于行: " +msgid "E932: Closure function should not be at top level: %s" +msgstr "E932: 闭包函数不能位于顶层:%s" -#: ../ex_cmds.c:1431 -msgid "E136: viminfo: Too many errors, skipping rest of file" -msgstr "E136: viminfo: 错误过多,忽略文件的剩余部分" +#: ../eval/userfunc.c:2272 +msgid "E126: Missing :endfunction" +msgstr "E126: 缺少 :endfunction" -#: ../ex_cmds.c:1458 +#: ../eval/userfunc.c:2327 #, c-format -msgid "Reading viminfo file \"%s\"%s%s%s" -msgstr "读取 viminfo 文件 \"%s\"%s%s%s" +msgid "W22: Text found after :endfunction: %s" +msgstr "W22: :endfunction 后有文本:%s" -#: ../ex_cmds.c:1460 -msgid " info" -msgstr " 信息" +#: ../eval/userfunc.c:2363 +msgid "E1058: function nesting too deep" +msgstr "E1058: 函数嵌套层数过深" -#: ../ex_cmds.c:1461 -msgid " marks" -msgstr " 标记" +#: ../eval/userfunc.c:2472 +#, c-format +msgid "E707: Function name conflicts with variable: %s" +msgstr "E707: 函数名与变量名冲突:%s" -#: ../ex_cmds.c:1462 -#, fuzzy -msgid " oldfiles" -msgstr "没有包含文件" +#: ../eval/userfunc.c:2488 +#, c-format +msgid "E127: Cannot redefine function %s: It is in use" +msgstr "E127: 函数 %s 正在使用中,不能重新定义" -#: ../ex_cmds.c:1463 -msgid " FAILED" -msgstr " 失败" +#: ../eval/userfunc.c:2555 +#, c-format +msgid "E746: Function name does not match script file name: %s" +msgstr "E746: 函数名与脚本文件名不匹配: %s" -#. avoid a wait_return for this message, it's annoying -#: ../ex_cmds.c:1541 +#: ../eval/userfunc.c:2778 #, c-format -msgid "E137: Viminfo file is not writable: %s" -msgstr "E137: Viminfo 文件不可写入: %s" +msgid "E131: Cannot delete function %s: It is in use" +msgstr "E131: 函数 %s 正在使用中,不能删除" -#: ../ex_cmds.c:1626 +#: ../eval/userfunc.c:2784 #, c-format -msgid "E138: Can't write viminfo file %s!" -msgstr "E138: 无法写入 viminfo 文件 %s!" +msgid "Cannot delete function %s: It is being used internally" +msgstr "无法删除函数 %s,内部使用这个函数" + +#: ../eval/userfunc.c:2916 +msgid "E133: :return not inside a function" +msgstr "E133: :return 在函数外" -#: ../ex_cmds.c:1635 +#. TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead +#. maximum nesting of lists and dicts +#: ../eval/vars.c:52 +msgid "E18: Unexpected characters in :let" +msgstr "E18: :let 中出现异常字符" + +#: ../eval/vars.c:53 #, c-format -msgid "Writing viminfo file \"%s\"" -msgstr "写入 viminfo 文件 \"%s\"" +msgid "E940: Cannot lock or unlock variable %s" +msgstr "E940: 不能锁定或解锁变量 %s" -# do not translate to avoid writing Chinese in files -#. Write the info: -#: ../ex_cmds.c:1720 -#, fuzzy, c-format -msgid "# This viminfo file was generated by Vim %s.\n" -msgstr "# 这个 viminfo 文件是由 Vim %s 生成的。\n" +#: ../eval/vars.c:77 +msgid "E991: cannot use =<< here" +msgstr "E991: 此处不能使用 =<<" -# do not translate to avoid writing Chinese in files -#: ../ex_cmds.c:1722 -#, fuzzy -msgid "" -"# You may edit it if you're careful!\n" -"\n" -msgstr "" -"# 如果要自行修改请特别小心!\n" -"\n" +#: ../eval/vars.c:109 +msgid "E221: Marker cannot start with lower case letter" +msgstr "E221: 标记不能以小写字母开头" -# do not translate to avoid writing Chinese in files -#: ../ex_cmds.c:1723 -#, fuzzy -msgid "# Value of 'encoding' when this file was written\n" -msgstr "# 'encoding' 在此文件建立时的值\n" +#: ../eval/vars.c:113 +msgid "E172: Missing marker" +msgstr "E172: 缺少标记" + +#: ../eval/vars.c:124 +#, c-format +msgid "E990: Missing end marker '%s'" +msgstr "E990: 缺少结束标记 '%s'" + +#: ../eval/vars.c:311 +msgid "E687: Less targets than List items" +msgstr "E687: 目标比 List 项数少" + +#: ../eval/vars.c:315 +msgid "E688: More targets than List items" +msgstr "E688: 目标比 List 项数多" + +#: ../eval/vars.c:391 +msgid "E452: Double ; in list of variables" +msgstr "E452: 变量列表中有两个 ;" + +#: ../eval/vars.c:532 +#, c-format +msgid "E738: Can't list variables for %s" +msgstr "E738: 无法列出 %s 的变量" + +#: ../eval/vars.c:584 +msgid "E996: Cannot lock an environment variable" +msgstr "E996: 不能锁定环境变量" + +#: ../eval/vars.c:625 +msgid "E996: Cannot lock an option" +msgstr "E996: 不能锁定选项" + +#: ../eval/vars.c:716 +msgid "E996: Cannot lock a register" +msgstr "E996: 不能锁定寄存器" + +#: ../eval/vars.c:1015 +#, c-format +msgid "E108: No such variable: \"%s\"" +msgstr "E108: 无此变量:\"%s\"" + +#: ../eval/vars.c:1331 +#, c-format +msgid "E963: setting %s to value with wrong type" +msgstr "E963: 将 %s 设置为类型错误的值" + +#: ../eval/vars.c:1414 +#, c-format +msgid "E794: Cannot set variable in the sandbox: \"%.*s\"" +msgstr "E794: 不能在沙盒中设置变量:\"%.*s\"" + +#: ../eval/vars.c:1460 +#, c-format +msgid "E795: Cannot delete variable %.*s" +msgstr "E795: 无法删除变量 %.*s" + +#: ../eval/vars.c:1483 +#, c-format +msgid "E704: Funcref variable name must start with a capital: %s" +msgstr "E704: Funcref 变量名必须以大写字母开头: %s" + +#: ../eval/vars.c:1490 +#, c-format +msgid "E705: Variable name conflicts with existing function: %s" +msgstr "E705: 变量名与已有函数名冲突: %s" + +#: ../event/socket.c:220 +msgid "tcp address must be host:port" +msgstr "TCP 地址必须为 host:port" + +#: ../event/socket.c:231 +msgid "failed to lookup host or port" +msgstr "无法查找主机或端口" + +#: ../event/socket.c:256 +msgid "connection refused" +msgstr "连接被拒绝" + +#: ../ex_cmds.c:164 +#, c-format +msgid "<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s" +msgstr "<%s>%s%s %d, 十六进制 %02x, 八进制 %03o, 二合字符 %s" + +#: ../ex_cmds.c:169 +#, c-format +msgid "<%s>%s%s %d, Hex %02x, Octal %03o" +msgstr "<%s>%s%s %d, 十六进制 %02x, 八进制 %03o" + +#: ../ex_cmds.c:213 +#, c-format +msgid "> %d, Hex %04x, Oct %o, Digr %s" +msgstr "> %d, 十六进制 %04x, 八进制 %o, 二合字符 %s" + +#: ../ex_cmds.c:214 +#, c-format +msgid "> %d, Hex %08x, Oct %o, Digr %s" +msgstr "> %d, 十六进制 %08x, 八进制 %o, 二合字符 %s" + +#: ../ex_cmds.c:220 +#, c-format +msgid "> %d, Hex %04x, Octal %o" +msgstr "> %d, 十六进制 %04x, 八进制 %o" -#: ../ex_cmds.c:1800 -msgid "Illegal starting char" -msgstr "无效的启动字符" +#: ../ex_cmds.c:221 +#, c-format +msgid "> %d, Hex %08x, Octal %o" +msgstr "> %d, 十六进制 %08x, 八进制 %o" -#: ../ex_cmds.c:2162 +#: ../ex_cmds.c:914 +msgid "E134: Cannot move a range of lines into itself" +msgstr "E134: 不能把一系列行移动到自身当中" + +#: ../ex_cmds.c:1021 +#, c-format +msgid "1 line moved" +msgid_plural "%<PRId64> lines moved" +msgstr[0] "移动了 %<PRId64> 行" + +#. will call wait_return() +#: ../ex_cmds.c:1335 +#, c-format +msgid "E482: Can't create file %s" +msgstr "E482: 无法创建文件 %s" + +#: ../ex_cmds.c:1436 +#, c-format +msgid "%<PRId64> lines filtered" +msgstr "过滤了 %<PRId64> 行" + +#: ../ex_cmds.c:1458 +msgid "E135: *Filter* Autocommands must not change current buffer" +msgstr "E135: *Filter* 自动命令不可以改变当前缓冲区" + +#: ../ex_cmds.c:1498 +msgid "[No write since last change]\n" +msgstr "[已修改但尚未保存]\n" + +#: ../ex_cmds.c:1793 +#, c-format +msgid "E503: \"%s\" is not a file or writable device" +msgstr "E503: \"%s\" 不是文件或可写的设备" + +#: ../ex_cmds.c:1877 msgid "Write partial file?" msgstr "要写入部分文件吗?" -#: ../ex_cmds.c:2166 +#: ../ex_cmds.c:1882 msgid "E140: Use ! to write partial buffer" msgstr "E140: 请使用 ! 来写入部分缓冲区" -#: ../ex_cmds.c:2281 +#: ../ex_cmds.c:2006 #, c-format msgid "Overwrite existing file \"%s\"?" msgstr "覆盖已存在的文件 \"%s\" 吗?" -#: ../ex_cmds.c:2317 +#: ../ex_cmds.c:2043 #, c-format msgid "Swap file \"%s\" exists, overwrite anyway?" msgstr "交换文件 \"%s\" 已存在,确实要覆盖吗?" -#: ../ex_cmds.c:2326 +#: ../ex_cmds.c:2052 #, c-format msgid "E768: Swap file exists: %s (:silent! overrides)" msgstr "E768: 交换文件已存在: %s (:silent! 强制执行)" -#: ../ex_cmds.c:2381 +#: ../ex_cmds.c:2110 #, c-format msgid "E141: No file name for buffer %<PRId64>" msgstr "E141: 缓冲区 %<PRId64> 没有文件名" -#: ../ex_cmds.c:2412 +#: ../ex_cmds.c:2144 msgid "E142: File not written: Writing is disabled by 'write' option" msgstr "E142: 文件未写入: 写入被 'write' 选项禁用" -#: ../ex_cmds.c:2434 +#: ../ex_cmds.c:2163 #, c-format msgid "" "'readonly' option is set for \"%s\".\n" @@ -1202,7 +1959,7 @@ msgstr "" "\"%s\" 已设定 'readonly' 选项。\n" "确实要覆盖吗?" -#: ../ex_cmds.c:2439 +#: ../ex_cmds.c:2167 #, c-format msgid "" "File permissions of \"%s\" are read-only.\n" @@ -1213,1069 +1970,744 @@ msgstr "" "它仍然有可能被写入。\n" "你想继续尝试吗?" -#: ../ex_cmds.c:2451 -#, fuzzy, c-format +#: ../ex_cmds.c:2181 +#, c-format msgid "E505: \"%s\" is read-only (add ! to override)" -msgstr "只读 (请加 ! 强制执行)" +msgstr "E505: \"%s\" 只读 (请加 ! 强制执行)" -#: ../ex_cmds.c:3120 +#: ../ex_cmds.c:2902 #, c-format msgid "E143: Autocommands unexpectedly deleted new buffer %s" msgstr "E143: 自动命令意外地删除了新缓冲区 %s" -#: ../ex_cmds.c:3313 +#: ../ex_cmds.c:3118 msgid "E144: non-numeric argument to :z" msgstr "E144: :z 不接受非数字的参数" -#: ../ex_cmds.c:3498 +#: ../ex_cmds.c:3429 msgid "E146: Regular expressions can't be delimited by letters" msgstr "E146: 正则表达式不能用字母作分界" -#: ../ex_cmds.c:3964 +#. Same highlight as wait_return(). +#: ../ex_cmds.c:3952 #, c-format msgid "replace with %s (y/n/a/q/l/^E/^Y)?" msgstr "替换为 %s (y/n/a/q/l/^E/^Y)?" -#: ../ex_cmds.c:4379 +#: ../ex_cmds.c:4462 msgid "(Interrupted) " msgstr "(已中断) " -#: ../ex_cmds.c:4384 -msgid "1 match" -msgstr "1 个匹配," - -#: ../ex_cmds.c:4384 -msgid "1 substitution" -msgstr "1 次替换," - -#: ../ex_cmds.c:4387 -#, c-format -msgid "%<PRId64> matches" -msgstr "%<PRId64> 个匹配," +#: ../ex_cmds.c:4468 +#, fuzzy, c-format +msgid "%<PRId64> match on %<PRId64> line" +msgid_plural "%<PRId64> matches on %<PRId64> line" +msgstr[0] "共 %<PRId64> 行" -#: ../ex_cmds.c:4388 -#, c-format -msgid "%<PRId64> substitutions" -msgstr "%<PRId64> 次替换," +#: ../ex_cmds.c:4470 +#, fuzzy, c-format +msgid "%<PRId64> substitution on %<PRId64> line" +msgid_plural "%<PRId64> substitutions on %<PRId64> line" +msgstr[0] "%<PRId64> 次替换," -#: ../ex_cmds.c:4392 -msgid " on 1 line" -msgstr "共 1 行" +#: ../ex_cmds.c:4473 +#, fuzzy, c-format +msgid "%<PRId64> match on %<PRId64> lines" +msgid_plural "%<PRId64> matches on %<PRId64> lines" +msgstr[0] "共 %<PRId64> 行" -#: ../ex_cmds.c:4395 -#, c-format -msgid " on %<PRId64> lines" -msgstr "共 %<PRId64> 行" +#: ../ex_cmds.c:4475 +#, fuzzy, c-format +msgid "%<PRId64> substitution on %<PRId64> lines" +msgid_plural "%<PRId64> substitutions on %<PRId64> lines" +msgstr[0] "%<PRId64> 次替换," -#: ../ex_cmds.c:4438 -msgid "E147: Cannot do :global recursive" -msgstr "E147: :global 不能递归执行" +#. will increment global_busy to break out of the loop +#: ../ex_cmds.c:4536 +msgid "E147: Cannot do :global recursive with a range" +msgstr "E147: 不能带范围递归执行 :global" -#: ../ex_cmds.c:4467 +#: ../ex_cmds.c:4565 msgid "E148: Regular expression missing from global" msgstr "E148: global 缺少正则表达式" -#: ../ex_cmds.c:4508 +#: ../ex_cmds.c:4610 #, c-format msgid "Pattern found in every line: %s" msgstr "每行都匹配表达式: %s" -#: ../ex_cmds.c:4510 -#, fuzzy, c-format -msgid "Pattern not found: %s" -msgstr "找不到模式" - -# do not translate to avoid writing Chinese in files -#: ../ex_cmds.c:4587 -#, fuzzy -msgid "" -"\n" -"# Last Substitute String:\n" -"$" -msgstr "" -"\n" -"# 最近的替换字符串:\n" -"$" - -#: ../ex_cmds.c:4679 -msgid "E478: Don't panic!" -msgstr "E478: 不要慌!" - -#: ../ex_cmds.c:4717 -#, c-format -msgid "E661: Sorry, no '%s' help for %s" -msgstr "E661: 抱歉,没有 '%s' 的 %s 的说明" - -#: ../ex_cmds.c:4719 +#: ../ex_cmds.c:4612 #, c-format -msgid "E149: Sorry, no help for %s" -msgstr "E149: 抱歉,没有 %s 的说明" - -#: ../ex_cmds.c:4751 -#, c-format -msgid "Sorry, help file \"%s\" not found" -msgstr "抱歉,找不到帮助文件 \"%s\"" - -#: ../ex_cmds.c:5323 -#, c-format -msgid "E150: Not a directory: %s" -msgstr "E150: 不是目录: %s" - -#: ../ex_cmds.c:5446 -#, c-format -msgid "E152: Cannot open %s for writing" -msgstr "E152: 无法打开并写入 %s" - -#: ../ex_cmds.c:5471 -#, c-format -msgid "E153: Unable to open %s for reading" -msgstr "E153: 无法打开并读取 %s" - -#: ../ex_cmds.c:5500 -#, c-format -msgid "E670: Mix of help file encodings within a language: %s" -msgstr "E670: 在一种语言中混合了多种帮助文件编码: %s" - -#: ../ex_cmds.c:5565 -#, c-format -msgid "E154: Duplicate tag \"%s\" in file %s/%s" -msgstr "E154: Tag \"%s\" 在文件 %s/%s 中重复出现" - -#: ../ex_cmds.c:5687 -#, c-format -msgid "E160: Unknown sign command: %s" -msgstr "E160: 未知的 sign 命令: %s" - -#: ../ex_cmds.c:5704 -msgid "E156: Missing sign name" -msgstr "E156: 缺少 sign 名称" - -#: ../ex_cmds.c:5746 -msgid "E612: Too many signs defined" -msgstr "E612: Signs 定义过多" - -#: ../ex_cmds.c:5813 -#, c-format -msgid "E239: Invalid sign text: %s" -msgstr "E239: 无效的 sign 文字: %s" - -#: ../ex_cmds.c:5844 ../ex_cmds.c:6035 -#, c-format -msgid "E155: Unknown sign: %s" -msgstr "E155: 未知的 sign: %s" - -#: ../ex_cmds.c:5877 -msgid "E159: Missing sign number" -msgstr "E159: 缺少 sign 号" - -#: ../ex_cmds.c:5971 -#, c-format -msgid "E158: Invalid buffer name: %s" -msgstr "E158: 无效的缓冲区名: %s" - -#: ../ex_cmds.c:6008 -#, c-format -msgid "E157: Invalid sign ID: %<PRId64>" -msgstr "E157: 无效的 sign ID: %<PRId64>" - -#: ../ex_cmds.c:6066 -msgid " (not supported)" -msgstr " (不支持)" - -#: ../ex_cmds.c:6169 -msgid "[Deleted]" -msgstr "[已删除]" - -#: ../ex_cmds2.c:139 -msgid "Entering Debug mode. Type \"cont\" to continue." -msgstr "进入调试模式。输入 \"cont\" 继续运行。" - -#: ../ex_cmds2.c:143 ../ex_docmd.c:759 -#, c-format -msgid "line %<PRId64>: %s" -msgstr "第 %<PRId64> 行: %s" - -#: ../ex_cmds2.c:145 -#, c-format -msgid "cmd: %s" -msgstr "命令: %s" +msgid "Pattern not found: %s" +msgstr "找不到模式:%s" -#: ../ex_cmds2.c:322 -#, c-format -msgid "Breakpoint in \"%s%s\" line %<PRId64>" -msgstr "断点 \"%s%s\" 第 %<PRId64> 行" +#: ../ex_cmds.c:4921 +msgid "No old files" +msgstr "没有旧文件" -#: ../ex_cmds2.c:581 +#: ../ex_cmds2.c:204 #, c-format -msgid "E161: Breakpoint not found: %s" -msgstr "E161: 找不到断点: %s" - -#: ../ex_cmds2.c:611 -msgid "No breakpoints defined" -msgstr "没有定义断点" +msgid "Save changes to \"%s\"?" +msgstr "将改变保存到 \"%s\" 吗?" -#: ../ex_cmds2.c:617 +#: ../ex_cmds2.c:255 #, c-format -msgid "%3d %s %s line %<PRId64>" -msgstr "%3d %s %s 第 %<PRId64> 行" - -#: ../ex_cmds2.c:942 -#, fuzzy -msgid "E750: First use \":profile start {fname}\"" -msgstr "E750: 请先使用 :profile start <fname>" +msgid "Close \"%s\"?" +msgstr "关闭 \"%s\"?" -#: ../ex_cmds2.c:1269 +#: ../ex_cmds2.c:380 #, c-format -msgid "Save changes to \"%s\"?" -msgstr "将改变保存到 \"%s\" 吗?" - -#: ../ex_cmds2.c:1271 ../ex_docmd.c:8851 -msgid "Untitled" -msgstr "未命名" +msgid "E947: Job still running in buffer \"%s\"" +msgstr "E947: 任务仍在缓冲区 \"%s\" 中运行" -#: ../ex_cmds2.c:1421 +#: ../ex_cmds2.c:381 #, c-format msgid "E162: No write since last change for buffer \"%s\"" msgstr "E162: 缓冲区 \"%s\" 已修改但尚未保存" -#: ../ex_cmds2.c:1480 +#: ../ex_cmds2.c:441 msgid "Warning: Entered other buffer unexpectedly (check autocommands)" msgstr "警告: 意外地进入了其它缓冲区 (请检查自动命令)" -#: ../ex_cmds2.c:1826 -msgid "E163: There is only one file to edit" -msgstr "E163: 只有一个文件可编辑" - -#: ../ex_cmds2.c:1828 -msgid "E164: Cannot go before first file" -msgstr "E164: 无法切换,已是第一个文件" - -#: ../ex_cmds2.c:1830 -msgid "E165: Cannot go beyond last file" -msgstr "E165: 无法切换,已是最后一个文件" - -#: ../ex_cmds2.c:2175 +#: ../ex_cmds2.c:735 #, c-format msgid "E666: compiler not supported: %s" msgstr "E666: 不支持编译器: %s" -#: ../ex_cmds2.c:2257 -#, c-format -msgid "Searching for \"%s\" in \"%s\"" -msgstr "正在查找 \"%s\",在 \"%s\" 中" - -#: ../ex_cmds2.c:2284 -#, c-format -msgid "Searching for \"%s\"" -msgstr "正在查找 \"%s\"" - -#: ../ex_cmds2.c:2307 -#, c-format -msgid "not found in 'runtimepath': \"%s\"" -msgstr "在 'runtimepath' 中找不到 \"%s\"" - -#: ../ex_cmds2.c:2472 -#, c-format -msgid "Cannot source a directory: \"%s\"" -msgstr "不能执行目录: \"%s\"" - -#: ../ex_cmds2.c:2518 -#, c-format -msgid "could not source \"%s\"" -msgstr "不能执行 \"%s\"" - -#: ../ex_cmds2.c:2520 -#, c-format -msgid "line %<PRId64>: could not source \"%s\"" -msgstr "第 %<PRId64> 行: 不能执行 \"%s\"" - -#: ../ex_cmds2.c:2535 -#, c-format -msgid "sourcing \"%s\"" -msgstr "执行 \"%s\"" - -#: ../ex_cmds2.c:2537 -#, c-format -msgid "line %<PRId64>: sourcing \"%s\"" -msgstr "第 %<PRId64> 行: 执行 \"%s\"" - -#: ../ex_cmds2.c:2693 -#, c-format -msgid "finished sourcing %s" -msgstr "结束执行 %s" - -#: ../ex_cmds2.c:2765 -msgid "modeline" -msgstr "modeline" - -#: ../ex_cmds2.c:2767 -msgid "--cmd argument" -msgstr "--cmd 参数" - -#: ../ex_cmds2.c:2769 -msgid "-c argument" -msgstr "-c 参数" - -#: ../ex_cmds2.c:2771 -msgid "environment variable" -msgstr "环境变量" - -#: ../ex_cmds2.c:2773 -msgid "error handler" -msgstr "错误的处理程序" +#: ../ex_docmd.c:90 +msgid "E464: Ambiguous use of user-defined command" +msgstr "E464: 不确定的用户自定义命令" -#: ../ex_cmds2.c:3020 -msgid "W15: Warning: Wrong line separator, ^M may be missing" -msgstr "W15: 警告: 错误的行分隔符,可能是少了 ^M" +#: ../ex_docmd.c:92 +msgid "E492: Not an editor command" +msgstr "E492: 不是编辑器的命令" -#: ../ex_cmds2.c:3139 -msgid "E167: :scriptencoding used outside of a sourced file" -msgstr "E167: 在脚本文件外使用了 :scriptencoding" +#: ../ex_docmd.c:94 +msgid "E498: no :source file name to substitute for \"<sfile>\"" +msgstr "E498: 没有用于替换 \"<sfile>\" 的 :source 文件名" -#: ../ex_cmds2.c:3166 -msgid "E168: :finish used outside of a sourced file" -msgstr "E168: 在脚本文件外使用了 :finish" +#: ../ex_docmd.c:96 +msgid "E489: no call stack to substitute for \"<stack>\"" +msgstr "E489: 没有用于替换 \"<stack>\" 的调用栈" -#: ../ex_cmds2.c:3389 -#, c-format -msgid "Current %slanguage: \"%s\"" -msgstr "当前的 %s语言: \"%s\"" - -#: ../ex_cmds2.c:3404 -#, c-format -msgid "E197: Cannot set language to \"%s\"" -msgstr "E197: 不能设定语言为 \"%s\"" +#: ../ex_docmd.c:98 +msgid "E1274: No script file name to substitute for \"<script>\"" +msgstr "E1274: 没有脚本文件名可用于替换 \"<script>\"" #. don't redisplay the window #. don't wait for return -#: ../ex_docmd.c:387 +#: ../ex_docmd.c:203 msgid "Entering Ex mode. Type \"visual\" to go to Normal mode." msgstr "进入 Ex 模式。输入 \"visual\" 回到正常模式。" -#: ../ex_docmd.c:428 +#: ../ex_docmd.c:244 msgid "E501: At end-of-file" msgstr "E501: 已到文件末尾" -#: ../ex_docmd.c:513 -msgid "E169: Command too recursive" -msgstr "E169: 命令递归层数过多" +#: ../ex_docmd.c:267 +#, c-format +msgid "Executing: %s" +msgstr "执行:%s" + +#: ../ex_docmd.c:269 +msgid "line %" +msgstr "在行号 %" -#: ../ex_docmd.c:1006 +#: ../ex_docmd.c:781 #, c-format msgid "E605: Exception not caught: %s" msgstr "E605: 异常没有被捕获: %s" -#: ../ex_docmd.c:1085 +#: ../ex_docmd.c:853 msgid "End of sourced file" msgstr "脚本文件结束" -#: ../ex_docmd.c:1086 +#: ../ex_docmd.c:854 msgid "End of function" msgstr "函数结束" -#: ../ex_docmd.c:1628 -msgid "E464: Ambiguous use of user-defined command" -msgstr "E464: 不确定的用户自定义命令" - -#: ../ex_docmd.c:1638 -msgid "E492: Not an editor command" -msgstr "E492: 不是编辑器的命令" +#: ../ex_docmd.c:1279 +msgid "" +"INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX" +msgstr "" +"内部信息:不能使用 EX_DFLALL 与 ADDR_NONE, ADDR_UNSIGNED 或 ADDR_QUICKFIX" -#: ../ex_docmd.c:1729 +#: ../ex_docmd.c:2118 msgid "E493: Backwards range given" msgstr "E493: 使用了逆向的范围" -#: ../ex_docmd.c:1733 +#: ../ex_docmd.c:2121 msgid "Backwards range given, OK to swap" msgstr "使用了逆向的范围,确定交换吗" #. append #. typed wrong -#: ../ex_docmd.c:1787 +#: ../ex_docmd.c:2179 msgid "E494: Use w or w>>" msgstr "E494: 请使用 w 或 w>>" -#: ../ex_docmd.c:3454 +#: ../ex_docmd.c:2957 +msgid "E943: Command table needs to be updated, run 'make'" +msgstr "E943: 命令表需要更新,请执行 'make'" + +#: ../ex_docmd.c:3542 msgid "E319: The command is not available in this version" msgstr "E319: 抱歉,命令在此版本中不可用" -#: ../ex_docmd.c:3752 -msgid "E172: Only one file name allowed" -msgstr "E172: 只允许一个文件名" - -#: ../ex_docmd.c:4238 -msgid "1 more file to edit. Quit anyway?" -msgstr "还有 1 个文件未编辑。确实要退出吗?" - -#: ../ex_docmd.c:4242 -#, c-format -msgid "%d more files to edit. Quit anyway?" -msgstr "还有 %d 个文件未编辑。确实要退出吗?" - -#: ../ex_docmd.c:4248 -msgid "E173: 1 more file to edit" -msgstr "E173: 还有 1 个文件未编辑" - -#: ../ex_docmd.c:4250 -#, c-format -msgid "E173: %<PRId64> more files to edit" -msgstr "E173: 还有 %<PRId64> 个文件未编辑" - -#: ../ex_docmd.c:4320 -msgid "E174: Command already exists: add ! to replace it" -msgstr "E174: 命令已存在: 请加 ! 强制替换" - -#: ../ex_docmd.c:4432 -msgid "" -"\n" -" Name Args Range Complete Definition" -msgstr "" -"\n" -" 名称 参数 范围 补全 定义 " - -#: ../ex_docmd.c:4516 -msgid "No user-defined commands found" -msgstr "找不到用户自定义命令" - -#: ../ex_docmd.c:4538 -msgid "E175: No attribute specified" -msgstr "E175: 没有指定属性" - -#: ../ex_docmd.c:4583 -msgid "E176: Invalid number of arguments" -msgstr "E176: 无效的参数个数" - -#: ../ex_docmd.c:4594 -msgid "E177: Count cannot be specified twice" -msgstr "E177: 不能指定两次计数" - -#: ../ex_docmd.c:4603 -msgid "E178: Invalid default value for count" -msgstr "E178: 无效的计数默认值" - -#: ../ex_docmd.c:4625 -msgid "E179: argument required for -complete" -msgstr "E179: -complete 需要参数" - -#: ../ex_docmd.c:4635 -#, c-format -msgid "E181: Invalid attribute: %s" -msgstr "E181: 无效的属性: %s" - -#: ../ex_docmd.c:4678 -msgid "E182: Invalid command name" -msgstr "E182: 无效的命令名" - -#: ../ex_docmd.c:4691 -msgid "E183: User defined commands must start with an uppercase letter" -msgstr "E183: 用户自定义命令必须以大写字母开头" - -#: ../ex_docmd.c:4696 -#, fuzzy -msgid "E841: Reserved name, cannot be used for user defined command" -msgstr "E464: 不确定的用户自定义命令" +#: ../ex_docmd.c:4388 +#, fuzzy, c-format +msgid "%d more file to edit. Quit anyway?" +msgid_plural "%d more files to edit. Quit anyway?" +msgstr[0] "还有 %d 个文件未编辑。确实要退出吗?" -#: ../ex_docmd.c:4751 +#: ../ex_docmd.c:4395 #, c-format -msgid "E184: No such user-defined command: %s" -msgstr "E184: 没有这个用户自定义命令: %s" +msgid "E173: %<PRId64> more file to edit" +msgid_plural "E173: %<PRId64> more files to edit" +msgstr[0] "E173: 还有 %<PRId64> 个文件未编辑" -#: ../ex_docmd.c:5219 +#: ../ex_docmd.c:4430 #, c-format -msgid "E180: Invalid complete value: %s" -msgstr "E180: 无效的补全类型: %s" - -#: ../ex_docmd.c:5225 -msgid "E468: Completion argument only allowed for custom completion" -msgstr "E468: 只有 custom 补全才允许参数" - -#: ../ex_docmd.c:5231 -msgid "E467: Custom completion requires a function argument" -msgstr "E467: Custom 补全需要一个函数参数" - -#: ../ex_docmd.c:5257 -#, fuzzy, c-format msgid "E185: Cannot find color scheme '%s'" -msgstr "E185: 找不到配色方案 %s" +msgstr "E185: 找不到配色方案 '%s'" -#: ../ex_docmd.c:5263 +#: ../ex_docmd.c:4437 msgid "Greetings, Vim user!" msgstr "您好,Vim 用户!" -#: ../ex_docmd.c:5431 +#: ../ex_docmd.c:4662 msgid "E784: Cannot close last tab page" msgstr "E784: 不能关闭最后一个 tab 页" -#: ../ex_docmd.c:5462 +#: ../ex_docmd.c:4687 msgid "Already only one tab page" msgstr "已经只剩一个 tab 页了" -#: ../ex_docmd.c:6004 +#: ../ex_docmd.c:5072 #, c-format msgid "Tab page %d" msgstr "Tab 页 %d" -#: ../ex_docmd.c:6295 +#: ../ex_docmd.c:5296 +msgid "E25: Nvim does not have a built-in GUI" +msgstr "E25: 无法使用图形界面: 编译时没有启用" + +#: ../ex_docmd.c:5307 msgid "No swap file" msgstr "无交换文件" -#: ../ex_docmd.c:6478 -msgid "E747: Cannot change directory, buffer is modified (add ! to override)" -msgstr "E747: 不能改变目录,缓冲区已修改 (请加 ! 强制执行)" - -#: ../ex_docmd.c:6485 +#: ../ex_docmd.c:5518 msgid "E186: No previous directory" msgstr "E186: 前一个目录不存在" -#: ../ex_docmd.c:6530 +#: ../ex_docmd.c:5624 msgid "E187: Unknown" msgstr "E187: 未知" -#: ../ex_docmd.c:6610 +#: ../ex_docmd.c:5689 msgid "E465: :winsize requires two number arguments" msgstr "E465: :winsize 需要两个数字参数" -#: ../ex_docmd.c:6655 -msgid "E188: Obtaining window position not implemented for this platform" -msgstr "E188: 在此平台上不能获得窗口位置" - -#: ../ex_docmd.c:6662 -msgid "E466: :winpos requires two number arguments" -msgstr "E466: :winpos 需要两个数字参数" - -#: ../ex_docmd.c:7241 -#, c-format -msgid "E739: Cannot create directory: %s" -msgstr "E739: 无法创建目录: %s" - -#: ../ex_docmd.c:7268 +#: ../ex_docmd.c:6207 #, c-format msgid "E189: \"%s\" exists (add ! to override)" msgstr "E189: \"%s\" 已存在 (请加 ! 强制执行)" -#: ../ex_docmd.c:7273 +#: ../ex_docmd.c:6213 #, c-format msgid "E190: Cannot open \"%s\" for writing" msgstr "E190: 无法打开并写入 \"%s\"" #. set mark -#: ../ex_docmd.c:7294 +#: ../ex_docmd.c:6231 msgid "E191: Argument must be a letter or forward/backward quote" msgstr "E191: 参数必须是一个字母或者正/反引号" -#: ../ex_docmd.c:7333 +#: ../ex_docmd.c:6315 msgid "E192: Recursive use of :normal too deep" msgstr "E192: :normal 递归层数过深" -#: ../ex_docmd.c:7807 +#: ../ex_docmd.c:6795 msgid "E194: No alternate file name to substitute for '#'" msgstr "E194: 没有用于替换 '#' 的交替文件名" -#: ../ex_docmd.c:7841 +#: ../ex_docmd.c:6834 msgid "E495: no autocommand file name to substitute for \"<afile>\"" msgstr "E495: 没有用于替换 \"<afile>\" 的自动命令文件名" -#: ../ex_docmd.c:7850 +#: ../ex_docmd.c:6842 msgid "E496: no autocommand buffer number to substitute for \"<abuf>\"" msgstr "E496: 没有用于替换 \"<abuf>\" 的自动命令缓冲区号" -#: ../ex_docmd.c:7861 +#: ../ex_docmd.c:6852 msgid "E497: no autocommand match name to substitute for \"<amatch>\"" msgstr "E497: 没有用于替换 \"<amatch>\" 的自动命令匹配名" -#: ../ex_docmd.c:7870 -msgid "E498: no :source file name to substitute for \"<sfile>\"" -msgstr "E498: 没有用于替换 \"<sfile>\" 的 :source 文件名" - -#: ../ex_docmd.c:7876 -#, fuzzy +#: ../ex_docmd.c:6884 msgid "E842: no line number to use for \"<slnum>\"" -msgstr "E498: 没有用于替换 \"<sfile>\" 的 :source 文件名" +msgstr "E842: 没有行号可用于替换 \"<slnum>\"" -#: ../ex_docmd.c:7903 -#, fuzzy, c-format +#: ../ex_docmd.c:6893 +msgid "E961: no line number to use for \"<sflnum>\"" +msgstr "E961: 没有用于替换 \"<sflnum>\" 的行号" + +#: ../ex_docmd.c:6942 +#, fuzzy, no-c-format msgid "E499: Empty file name for '%' or '#', only works with \":p:h\"" msgstr "E499: '%' 或 '#' 为空文件名,只能用于 \":p:h\"" -#: ../ex_docmd.c:7905 +#: ../ex_docmd.c:6944 msgid "E500: Evaluates to an empty string" msgstr "E500: 结果为空字符串" -#: ../ex_docmd.c:8838 -msgid "E195: Cannot open viminfo file for reading" -msgstr "E195: 无法打开并读取 viminfo 文件" +#: ../ex_docmd.c:7018 +msgid "Untitled" +msgstr "未命名" -#: ../ex_eval.c:464 +#: ../ex_eval.c:456 msgid "E608: Cannot :throw exceptions with 'Vim' prefix" msgstr "E608: 不能 :throw 前缀为 'Vim' 的异常" -#. always scroll up, don't overwrite -#: ../ex_eval.c:496 +#: ../ex_eval.c:500 #, c-format msgid "Exception thrown: %s" msgstr "抛出异常: %s" -#: ../ex_eval.c:545 +#: ../ex_eval.c:553 #, c-format msgid "Exception finished: %s" msgstr "完成异常: %s" -#: ../ex_eval.c:546 +#: ../ex_eval.c:553 #, c-format msgid "Exception discarded: %s" msgstr "丢弃异常: %s" -#: ../ex_eval.c:588 ../ex_eval.c:634 +#: ../ex_eval.c:597 ../ex_eval.c:647 #, c-format msgid "%s, line %<PRId64>" msgstr "%s,第 %<PRId64> 行" -#. always scroll up, don't overwrite -#: ../ex_eval.c:608 +#: ../ex_eval.c:620 #, c-format msgid "Exception caught: %s" msgstr "捕获异常: %s" -#: ../ex_eval.c:676 +#: ../ex_eval.c:688 #, c-format msgid "%s made pending" msgstr "%s 待定" -#: ../ex_eval.c:679 -#, fuzzy, c-format +#: ../ex_eval.c:691 +#, c-format msgid "%s resumed" -msgstr " 已返回\n" +msgstr "%s 已恢复" -#: ../ex_eval.c:683 +#: ../ex_eval.c:695 #, c-format msgid "%s discarded" msgstr "%s 舍弃" -#: ../ex_eval.c:708 +#: ../ex_eval.c:720 msgid "Exception" msgstr "异常" -#: ../ex_eval.c:713 +#: ../ex_eval.c:724 msgid "Error and interrupt" msgstr "错误和中断" -#: ../ex_eval.c:715 +#: ../ex_eval.c:726 msgid "Error" msgstr "错误" #. if (pending & CSTP_INTERRUPT) -#: ../ex_eval.c:717 +#: ../ex_eval.c:728 msgid "Interrupt" msgstr "中断" -#: ../ex_eval.c:795 +#: ../ex_eval.c:816 msgid "E579: :if nesting too deep" msgstr "E579: :if 嵌套层数过深" -#: ../ex_eval.c:830 +#: ../ex_eval.c:844 msgid "E580: :endif without :if" msgstr "E580: :endif 缺少对应的 :if" -#: ../ex_eval.c:873 +#: ../ex_eval.c:874 msgid "E581: :else without :if" msgstr "E581: :else 缺少对应的 :if" -#: ../ex_eval.c:876 +#: ../ex_eval.c:877 msgid "E582: :elseif without :if" msgstr "E582: :elseif 缺少对应的 :if" -#: ../ex_eval.c:880 +#: ../ex_eval.c:881 msgid "E583: multiple :else" msgstr "E583: 多个 :else" -#: ../ex_eval.c:883 +#: ../ex_eval.c:884 msgid "E584: :elseif after :else" msgstr "E584: :elseif 在 :else 后面" -#: ../ex_eval.c:941 +#: ../ex_eval.c:951 msgid "E585: :while/:for nesting too deep" msgstr "E585: :while/:for 嵌套层数过深" -#: ../ex_eval.c:1028 +#: ../ex_eval.c:1022 msgid "E586: :continue without :while or :for" msgstr "E586: :continue 缺少对应的 :while 或 :for" -#: ../ex_eval.c:1061 +#: ../ex_eval.c:1052 msgid "E587: :break without :while or :for" msgstr "E587: :break 缺少对应的 :while 或 :for" -#: ../ex_eval.c:1102 +#: ../ex_eval.c:1091 msgid "E732: Using :endfor with :while" msgstr "E732: :while 以 :endfor 结尾" -#: ../ex_eval.c:1104 +#: ../ex_eval.c:1093 msgid "E733: Using :endwhile with :for" msgstr "E733: :for 以 :endwhile 结尾" -#: ../ex_eval.c:1247 +#: ../ex_eval.c:1229 msgid "E601: :try nesting too deep" msgstr "E601: :try 嵌套层数过深" -#: ../ex_eval.c:1317 +#: ../ex_eval.c:1286 msgid "E603: :catch without :try" msgstr "E603: :catch 缺少对应的 :try" #. Give up for a ":catch" after ":finally" and ignore it. -#. * Just parse. -#: ../ex_eval.c:1332 +#. Just parse. +#: ../ex_eval.c:1303 msgid "E604: :catch after :finally" msgstr "E604: :catch 在 :finally 后面" -#: ../ex_eval.c:1451 +#: ../ex_eval.c:1426 msgid "E606: :finally without :try" msgstr "E606: :finally 缺少对应的 :try" #. Give up for a multiple ":finally" and ignore it. -#: ../ex_eval.c:1467 +#: ../ex_eval.c:1445 msgid "E607: multiple :finally" msgstr "E607: 多个 :finally" -#: ../ex_eval.c:1571 +#: ../ex_eval.c:1540 msgid "E602: :endtry without :try" msgstr "E602: :endtry 缺少对应的 :try" -#: ../ex_eval.c:2026 +#: ../ex_eval.c:1984 msgid "E193: :endfunction not inside a function" msgstr "E193: :endfunction 不在函数内" -#: ../ex_getln.c:1643 -msgid "E788: Not allowed to edit another buffer now" -msgstr "E788: 目前不允许编辑别的缓冲区" - -#: ../ex_getln.c:1656 -#, fuzzy +#: ../ex_getln.c:2661 msgid "E811: Not allowed to change buffer information now" -msgstr "E788: 目前不允许编辑别的缓冲区" +msgstr "E811: 目前不允许更改缓冲区的信息" -#: ../ex_getln.c:3178 -msgid "tagname" -msgstr "tag 名" +#: ../ex_getln.c:2939 +#, c-format +msgid "E5408: Unable to get g:Nvim_color_cmdline callback: %s" +msgstr "" -#: ../ex_getln.c:3181 -msgid " kind file\n" -msgstr " 类型 文件\n" +#: ../ex_getln.c:2973 +#, c-format +msgid "E5407: Callback has thrown an exception: %s" +msgstr "" -#: ../ex_getln.c:4799 -msgid "'history' option is zero" -msgstr "选项 'history' 为零" +#: ../ex_getln.c:2986 +msgid "E5400: Callback should return list" +msgstr "E5400: 回调应返回列表" -# do not translate to avoid writing Chinese in files -#: ../ex_getln.c:5046 -#, fuzzy, c-format -msgid "" -"\n" -"# %s History (newest to oldest):\n" +#: ../ex_getln.c:2996 +#, c-format +msgid "E5401: List item %i is not a List" +msgstr "E5401: 列表项 %i 不是列表" + +#: ../ex_getln.c:3001 +#, c-format +msgid "E5402: List item %i has incorrect length: %d /= 3" msgstr "" -"\n" -"# %s 历史记录 (从新到旧):\n" -# do not translate to avoid writing Chinese in files -#: ../ex_getln.c:5047 -#, fuzzy -msgid "Command Line" -msgstr "命令行" +#: ../ex_getln.c:3011 +msgid "E5403: Chunk %i start %" +msgstr "" -# do not translate to avoid writing Chinese in files -#: ../ex_getln.c:5048 -#, fuzzy -msgid "Search String" -msgstr "查找字符串" +#: ../ex_getln.c:3016 +msgid "E5405: Chunk %i start %" +msgstr "" -# do not translate to avoid writing Chinese in files -#: ../ex_getln.c:5049 -#, fuzzy -msgid "Expression" -msgstr "表达式" +#: ../ex_getln.c:3032 +msgid "E5404: Chunk %i end %" +msgstr "" -# do not translate to avoid writing Chinese in files -#: ../ex_getln.c:5050 -#, fuzzy -msgid "Input Line" -msgstr "输入行" +#: ../ex_getln.c:3039 +msgid "E5406: Chunk %i end %" +msgstr "" -#: ../ex_getln.c:5117 -msgid "E198: cmd_pchar beyond the command length" -msgstr "E198: cmd_pchar 超过命令长度" +# do not translate to avoid writing Chinese in files +#. Create empty command-line buffer. +#: ../ex_getln.c:4223 +msgid "[Command Line]" +msgstr "" -#: ../ex_getln.c:5279 +#: ../ex_getln.c:4331 msgid "E199: Active window or buffer deleted" msgstr "E199: 活动窗口或缓冲区已被删除" -#: ../file_search.c:203 +#: ../file_search.c:185 msgid "E854: path too long for completion" msgstr "E854: 补全用的路径太长" -#: ../file_search.c:446 +#: ../file_search.c:419 #, c-format msgid "" "E343: Invalid path: '**[number]' must be at the end of the path or be " "followed by '%s'." msgstr "E343: 无效的路径: '**[number]' 必须在路径末尾或者后面接 '%s'。" -#: ../file_search.c:1505 +#: ../file_search.c:1486 #, c-format msgid "E344: Can't find directory \"%s\" in cdpath" msgstr "E344: cdpath 中找不到目录 \"%s\"" -#: ../file_search.c:1508 +#: ../file_search.c:1489 #, c-format msgid "E345: Can't find file \"%s\" in path" msgstr "E345: 在路径中找不到文件 \"%s\"" -#: ../file_search.c:1512 +#: ../file_search.c:1494 #, c-format msgid "E346: No more directory \"%s\" found in cdpath" msgstr "E346: 在路径中找不到更多的文件 \"%s\"" -#: ../file_search.c:1515 +#: ../file_search.c:1497 #, c-format msgid "E347: No more file \"%s\" found in path" msgstr "E347: 在路径中找不到更多的文件 \"%s\"" -#: ../fileio.c:137 -#, fuzzy +#: ../fileio.c:126 msgid "E812: Autocommands changed buffer or buffer name" -msgstr "E135: *Filter* 自动命令不可以改变当前缓冲区" +msgstr "E812: 自动命令改变了缓冲区或者缓冲区名称" -#: ../fileio.c:368 -msgid "Illegal file name" -msgstr "无效的文件名" +#: ../fileio.c:128 +#, c-format +msgid "E676: No matching autocommands for buftype=%s buffer" +msgstr "" -#: ../fileio.c:395 ../fileio.c:476 ../fileio.c:2543 ../fileio.c:2578 +#: ../fileio.c:262 ../fileio.c:2499 ../fileio.c:2529 msgid "is a directory" msgstr "是目录" -#: ../fileio.c:397 +#: ../fileio.c:363 +msgid "Illegal file name" +msgstr "无效的文件名" + +#: ../fileio.c:399 msgid "is not a file" msgstr "不是文件" -#: ../fileio.c:508 ../fileio.c:3522 -msgid "[New File]" -msgstr "[新文件]" - -#: ../fileio.c:511 +#: ../fileio.c:490 msgid "[New DIRECTORY]" msgstr "[新目录]" -#: ../fileio.c:529 ../fileio.c:532 +#. libuv only returns -errno +#. in Unix and in Windows +#. open() does not set +#. EOVERFLOW +#: ../fileio.c:510 ../fileio.c:516 msgid "[File too big]" msgstr "[文件过大]" -#: ../fileio.c:534 +#: ../fileio.c:518 msgid "[Permission Denied]" msgstr "[权限不足]" -#: ../fileio.c:653 +#: ../fileio.c:661 msgid "E200: *ReadPre autocommands made the file unreadable" msgstr "E200: *ReadPre 自动命令导致文件不可读" -#: ../fileio.c:655 +#: ../fileio.c:663 msgid "E201: *ReadPre autocommands must not change current buffer" msgstr "E201: *ReadPre 自动命令不允许改变当前缓冲区" -#: ../fileio.c:672 -msgid "Nvim: Reading from stdin...\n" -msgstr "Vim: 从标准输入读取...\n" - #. Re-opening the original file failed! -#: ../fileio.c:909 +#: ../fileio.c:865 msgid "E202: Conversion made file unreadable!" -msgstr "E202: 转换导致文件不可读" - -#. fifo or socket -#: ../fileio.c:1782 -msgid "[fifo/socket]" -msgstr "[fifo/socket]" +msgstr "E202: 转换导致文件不可读!" #. fifo -#: ../fileio.c:1788 +#: ../fileio.c:1762 msgid "[fifo]" msgstr "[fifo]" #. or socket -#: ../fileio.c:1794 +#: ../fileio.c:1766 msgid "[socket]" msgstr "[socket]" #. or character special -#: ../fileio.c:1801 -#, fuzzy +#: ../fileio.c:1771 msgid "[character special]" -msgstr "1 个字符" +msgstr "[字符设备]" -#: ../fileio.c:1815 +#: ../fileio.c:1785 msgid "[CR missing]" msgstr "[缺少 CR]'" -#: ../fileio.c:1819 +#: ../fileio.c:1789 msgid "[long lines split]" msgstr "[长行分割]" -#: ../fileio.c:1823 ../fileio.c:3512 +#: ../fileio.c:1793 ../fileio.c:3427 msgid "[NOT converted]" msgstr "[未转换]" -#: ../fileio.c:1826 ../fileio.c:3515 +#: ../fileio.c:1796 ../fileio.c:3430 msgid "[converted]" msgstr "[已转换]" -#: ../fileio.c:1831 +#: ../fileio.c:1801 #, c-format msgid "[CONVERSION ERROR in line %<PRId64>]" msgstr "[第 %<PRId64> 行转换错误]" -#: ../fileio.c:1835 +#: ../fileio.c:1805 #, c-format msgid "[ILLEGAL BYTE in line %<PRId64>]" msgstr "[第 %<PRId64> 行无效字符]" -#: ../fileio.c:1838 +#: ../fileio.c:1808 msgid "[READ ERRORS]" msgstr "[读错误]" -#: ../fileio.c:2104 +#: ../fileio.c:2069 msgid "Can't find temp file for conversion" msgstr "找不到用于转换的临时文件" -#: ../fileio.c:2110 +#: ../fileio.c:2075 msgid "Conversion with 'charconvert' failed" msgstr "'charconvert' 转换失败" -#: ../fileio.c:2113 +#: ../fileio.c:2078 msgid "can't read output of 'charconvert'" msgstr "无法读取 'charconvert' 的输出" -#: ../fileio.c:2437 -msgid "E676: No matching autocommands for acwrite buffer" -msgstr "E676: 找不到 acwrite 缓冲区对应的自动命令" +#: ../fileio.c:2116 +msgid "[New]" +msgstr "[新]" + +#: ../fileio.c:2116 +msgid "[New File]" +msgstr "[新文件]" -#: ../fileio.c:2466 +#: ../fileio.c:2416 msgid "E203: Autocommands deleted or unloaded buffer to be written" msgstr "E203: 自动命令删除或释放了要写入的缓冲区" -#: ../fileio.c:2486 +#: ../fileio.c:2435 msgid "E204: Autocommand changed number of lines in unexpected way" msgstr "E204: 自动命令意外地改变了行数" -#: ../fileio.c:2548 ../fileio.c:2565 +#: ../fileio.c:2503 ../fileio.c:2517 msgid "is not a file or writable device" msgstr "不是文件或可写的设备" -#: ../fileio.c:2601 +#: ../fileio.c:2547 msgid "is read-only (add ! to override)" msgstr "只读 (请加 ! 强制执行)" -#: ../fileio.c:2886 -msgid "E506: Can't write to backup file (add ! to override)" -msgstr "E506: 无法写入备份文件 (请加 ! 强制执行)" - -#: ../fileio.c:2898 -msgid "E507: Close error for backup file (add ! to override)" -msgstr "E507: 关闭备份文件出错 (请加 ! 强制执行)" - -#: ../fileio.c:2901 -msgid "E508: Can't read file for backup (add ! to override)" -msgstr "E508: 无法读取文件以供备份 (请加 ! 强制执行)" +#: ../fileio.c:2701 ../fileio.c:2852 +#, c-format +msgid "E303: Unable to create directory \"%s\" for backup file: %s" +msgstr "E303: 无法为备份文件创建目录 \"%s\":%s" -#: ../fileio.c:2923 +#: ../fileio.c:2792 ../fileio.c:2813 msgid "E509: Cannot create backup file (add ! to override)" msgstr "E509: 无法创建备份文件 (请加 ! 强制执行)" -#: ../fileio.c:3008 +#: ../fileio.c:2912 msgid "E510: Can't make backup file (add ! to override)" msgstr "E510: 无法生成备份文件 (请加 ! 强制执行)" #. Can't write without a tempfile! -#: ../fileio.c:3121 +#: ../fileio.c:3015 msgid "E214: Can't find temp file for writing" msgstr "E214: 找不到用于写入的临时文件" -#: ../fileio.c:3134 +#: ../fileio.c:3031 msgid "E213: Cannot convert (add ! to write without conversion)" msgstr "E213: 无法转换 (请加 ! 强制不转换写入)" -#: ../fileio.c:3169 +#: ../fileio.c:3081 msgid "E166: Can't open linked file for writing" msgstr "E166: 无法打开并写入链接文件" -#: ../fileio.c:3173 -msgid "E212: Can't open file for writing" -msgstr "E212: 无法打开并写入文件" - -#: ../fileio.c:3363 -msgid "E667: Fsync failed" -msgstr "E667: 同步失败" +#: ../fileio.c:3084 +#, c-format +msgid "E212: Can't open file for writing: %s" +msgstr "E212: 无法打开并写入文件:%s" -#: ../fileio.c:3398 -msgid "E512: Close failed" -msgstr "E512: 关闭失败" +#: ../fileio.c:3324 +#, c-format +msgid "E512: Close failed: %s" +msgstr "E512: 关闭失败:%s" -#: ../fileio.c:3436 +#: ../fileio.c:3363 msgid "E513: write error, conversion failed (make 'fenc' empty to override)" msgstr "E513: 写入错误,转换失败 (请将 'fenc' 置空以强制执行)" -#: ../fileio.c:3441 -#, fuzzy, c-format -msgid "" -"E513: write error, conversion failed in line %<PRId64> (make 'fenc' empty to " -"override)" -msgstr "E513: 写入错误,转换失败 (请将 'fenc' 置空以强制执行)" +#. NOLINT(runtime/printf) +#: ../fileio.c:3369 +msgid "E513: write error, conversion failed in line %" +msgstr "" -#: ../fileio.c:3448 +#: ../fileio.c:3376 msgid "E514: write error (file system full?)" msgstr "E514: 写入错误 (文件系统已满?)" -#: ../fileio.c:3506 +#: ../fileio.c:3420 msgid " CONVERSION ERROR" msgstr " 转换错误" -#: ../fileio.c:3509 +#: ../fileio.c:3423 #, fuzzy, c-format msgid " in line %<PRId64>;" msgstr "第 %<PRId64> 行" -#: ../fileio.c:3519 +#: ../fileio.c:3434 msgid "[Device]" msgstr "[设备]" -#: ../fileio.c:3522 -msgid "[New]" -msgstr "[新]" - -#: ../fileio.c:3535 +#: ../fileio.c:3451 msgid " [a]" msgstr " [a]" -#: ../fileio.c:3535 +#: ../fileio.c:3451 msgid " appended" msgstr " 已追加" -#: ../fileio.c:3537 +#: ../fileio.c:3453 msgid " [w]" msgstr " [w]" -#: ../fileio.c:3537 +#: ../fileio.c:3453 msgid " written" msgstr " 已写入" -#: ../fileio.c:3579 +#: ../fileio.c:3496 msgid "E205: Patchmode: can't save original file" msgstr "E205: Patchmode: 无法保存原始文件" -#: ../fileio.c:3602 +#: ../fileio.c:3515 msgid "E206: patchmode: can't touch empty original file" msgstr "E206: Patchmode: 无法生成空的原始文件" -#: ../fileio.c:3616 +#: ../fileio.c:3530 msgid "E207: Can't delete backup file" msgstr "E207: 无法删除备份文件" -#: ../fileio.c:3672 +#. Set highlight for error messages. +#: ../fileio.c:3584 msgid "" "\n" "WARNING: Original file may be lost or damaged\n" @@ -2283,1309 +2715,1511 @@ msgstr "" "\n" "警告: 原始文件可能已丢失或损坏\n" -#: ../fileio.c:3675 +#: ../fileio.c:3586 msgid "don't quit the editor until the file is successfully written!" msgstr "在文件正确写入前请勿退出编辑器!" -#: ../fileio.c:3795 +#: ../fileio.c:3721 msgid "[dos]" msgstr "[dos]" -#: ../fileio.c:3795 +#: ../fileio.c:3721 msgid "[dos format]" msgstr "[dos 格式]" -#: ../fileio.c:3801 +#: ../fileio.c:3726 msgid "[mac]" msgstr "[mac]" -#: ../fileio.c:3801 +#: ../fileio.c:3726 msgid "[mac format]" msgstr "[mac 格式]" -#: ../fileio.c:3807 +#: ../fileio.c:3731 msgid "[unix]" msgstr "[unix]" -#: ../fileio.c:3807 +#: ../fileio.c:3731 msgid "[unix format]" msgstr "[unix 格式]" -#: ../fileio.c:3831 -msgid "1 line, " -msgstr "1 行," - -#: ../fileio.c:3833 +#: ../fileio.c:3753 #, c-format -msgid "%<PRId64> lines, " -msgstr "%<PRId64> 行," - -#: ../fileio.c:3836 -msgid "1 character" -msgstr "1 个字符" +msgid "%<PRId64> line, " +msgid_plural "%<PRId64> lines, " +msgstr[0] "%<PRId64> 行," -#: ../fileio.c:3838 +#: ../fileio.c:3757 #, c-format -msgid "%<PRId64> characters" -msgstr "%<PRId64> 个字符" +msgid "%<PRId64> byte" +msgid_plural "%<PRId64> bytes" +msgstr[0] "%<PRId64> 字节" -#: ../fileio.c:3849 +#: ../fileio.c:3766 msgid "[noeol]" msgstr "[noeol]" -#: ../fileio.c:3849 +#: ../fileio.c:3766 msgid "[Incomplete last line]" msgstr "[最后一行不完整]" -#. don't overwrite messages here -#. must give this prompt -#. don't use emsg() here, don't want to flush the buffers -#: ../fileio.c:3865 +#. Don't overwrite messages here. +#. Must give this prompt. +#. Don't use emsg() here, don't want to flush the buffers. +#: ../fileio.c:3779 msgid "WARNING: The file has been changed since reading it!!!" msgstr "警告: 此文件自读入后已发生变动!!!" -#: ../fileio.c:3867 +#: ../fileio.c:3781 msgid "Do you really want to write to it" msgstr "确实要写入吗" -#: ../fileio.c:4648 +#: ../fileio.c:4629 #, c-format msgid "E208: Error writing to \"%s\"" msgstr "E208: 写入文件 \"%s\" 出错" -#: ../fileio.c:4655 +#: ../fileio.c:4637 #, c-format msgid "E209: Error closing \"%s\"" msgstr "E209: 关闭文件 \"%s\" 出错" -#: ../fileio.c:4657 +#: ../fileio.c:4640 #, c-format msgid "E210: Error reading \"%s\"" msgstr "E210: 读取文件 \"%s\" 出错" -#: ../fileio.c:4883 +#: ../fileio.c:4859 msgid "E246: FileChangedShell autocommand deleted buffer" msgstr "E246: FileChangedShell 自动命令删除了缓冲区" -#: ../fileio.c:4894 +#: ../fileio.c:4876 #, c-format msgid "E211: File \"%s\" no longer available" msgstr "E211: 文件 \"%s\" 已经不存在" -#: ../fileio.c:4906 +#: ../fileio.c:4888 #, c-format msgid "" "W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as " "well" msgstr "W12: 警告: 文件 \"%s\" 已变动,并且在 Vim 中的缓冲区也已变动" -#: ../fileio.c:4907 +#: ../fileio.c:4889 msgid "See \":help W12\" for more info." msgstr "进一步说明请见 \":help W12\"" -#: ../fileio.c:4910 +#: ../fileio.c:4891 #, c-format msgid "W11: Warning: File \"%s\" has changed since editing started" msgstr "W11: 警告: 编辑开始后,文件 \"%s\" 已变动" -#: ../fileio.c:4911 +#: ../fileio.c:4892 msgid "See \":help W11\" for more info." msgstr "进一步说明请见 \":help W11\"" -#: ../fileio.c:4914 +#: ../fileio.c:4894 #, c-format msgid "W16: Warning: Mode of file \"%s\" has changed since editing started" msgstr "W16: 警告: 编辑开始后,文件 \"%s\" 的模式已变动" -#: ../fileio.c:4915 +#: ../fileio.c:4895 msgid "See \":help W16\" for more info." msgstr "进一步说明请见 \":help W16\"" -#: ../fileio.c:4927 +#: ../fileio.c:4908 #, c-format msgid "W13: Warning: File \"%s\" has been created after editing started" msgstr "W13: 警告: 编辑开始后,文件 \"%s\" 已被创建" -#: ../fileio.c:4947 +#: ../fileio.c:4929 msgid "Warning" msgstr "警告" -#: ../fileio.c:4948 +#: ../fileio.c:4930 msgid "" "&OK\n" -"&Load File" +"&Load File\n" +"Load File &and Options" msgstr "" "确定(&O)\n" -"加载文件(&L)" +"加载文件(&L)\n" +"加载文件和选项(&A)" -#: ../fileio.c:5065 +#: ../fileio.c:5050 #, c-format msgid "E462: Could not prepare for reloading \"%s\"" msgstr "E462: 无法为重新加载 \"%s\" 做准备" -#: ../fileio.c:5078 +#: ../fileio.c:5062 #, c-format msgid "E321: Could not reload \"%s\"" msgstr "E321: 无法重新加载 \"%s\"" -#: ../fileio.c:5601 -msgid "--Deleted--" -msgstr "--已删除--" - -#: ../fileio.c:5732 -#, c-format -msgid "auto-removing autocommand: %s <buffer=%d>" -msgstr "自动删除自动命令: %s <buffer=%d>" - -#. the group doesn't exist -#: ../fileio.c:5772 -#, c-format -msgid "E367: No such group: \"%s\"" -msgstr "E367: 无此组: \"%s\"" - -#: ../fileio.c:5897 -#, c-format -msgid "E215: Illegal character after *: %s" -msgstr "E215: * 后面有无效字符: %s" - -#: ../fileio.c:5905 -#, c-format -msgid "E216: No such event: %s" -msgstr "E216: 无此事件: %s" - -#: ../fileio.c:5907 -#, c-format -msgid "E216: No such group or event: %s" -msgstr "E216: 无此组或事件: %s" - -#. Highlight title -#: ../fileio.c:6090 -msgid "" -"\n" -"--- Autocommands ---" -msgstr "" -"\n" -"--- 自动命令 ---" - -#: ../fileio.c:6293 -#, c-format -msgid "E680: <buffer=%d>: invalid buffer number " -msgstr "E680: <buffer=%d>: 无效的缓冲区号 " - -#: ../fileio.c:6370 -msgid "E217: Can't execute autocommands for ALL events" -msgstr "E217: 不能对所有事件执行自动命令" - -#: ../fileio.c:6393 -msgid "No matching autocommands" -msgstr "没有匹配的自动命令" - -#: ../fileio.c:6831 -msgid "E218: autocommand nesting too deep" -msgstr "E218: 自动命令嵌套层数过深" - -#: ../fileio.c:7143 -#, c-format -msgid "%s Autocommands for \"%s\"" -msgstr "%s 自动命令 \"%s\"" - -#: ../fileio.c:7149 -#, c-format -msgid "Executing %s" -msgstr "执行 %s" - -#: ../fileio.c:7211 -#, c-format -msgid "autocommand %s" -msgstr "自动命令 %s" - -#: ../fileio.c:7795 +#: ../fileio.c:5680 msgid "E219: Missing {." msgstr "E219: 缺少 {。" -#: ../fileio.c:7797 +#: ../fileio.c:5682 msgid "E220: Missing }." msgstr "E220: 缺少 }。" -#: ../fold.c:93 +#: ../fold.c:104 msgid "E490: No fold found" msgstr "E490: 找不到折叠" -#: ../fold.c:544 +#: ../fold.c:520 msgid "E350: Cannot create fold with current 'foldmethod'" msgstr "E350: 不能在当前的 'foldmethod' 下创建折叠" -#: ../fold.c:546 +#: ../fold.c:522 msgid "E351: Cannot delete fold with current 'foldmethod'" msgstr "E351: 不能在当前的 'foldmethod' 下删除折叠" -#: ../fold.c:1784 +#: ../fold.c:1789 #, c-format -msgid "+--%3ld lines folded " -msgstr "+--已折叠 %3ld 行" +msgid "+--%3ld line folded" +msgid_plural "+--%3ld lines folded " +msgstr[0] "+--已折叠 %3ld 行" + +#: ../fold.c:3275 +#, c-format +msgid "+-%s%3ld line: " +msgid_plural "+-%s%3ld lines: " +msgstr[0] "+-%s%3ld 行: " #. buffer has already been read -#: ../getchar.c:273 +#: ../getchar.c:235 msgid "E222: Add to read buffer" msgstr "E222: 添加到已读缓冲区中" -#: ../getchar.c:2040 +#: ../getchar.c:2172 msgid "E223: recursive mapping" msgstr "E223: 递归映射" -#: ../getchar.c:2849 -#, c-format -msgid "E224: global abbreviation already exists for %s" -msgstr "E224: 全局缩写 %s 已存在" - -#: ../getchar.c:2852 -#, c-format -msgid "E225: global mapping already exists for %s" -msgstr "E225: 全局映射 %s 已存在" - -#: ../getchar.c:2952 -#, c-format -msgid "E226: abbreviation already exists for %s" -msgstr "E226: 缩写 %s 已存在" - -#: ../getchar.c:2955 -#, c-format -msgid "E227: mapping already exists for %s" -msgstr "E227: 映射 %s 已存在" - -#: ../getchar.c:3008 -msgid "No abbreviation found" -msgstr "找不到缩写" - -#: ../getchar.c:3010 -msgid "No mapping found" -msgstr "找不到映射" - -#: ../getchar.c:3974 -msgid "E228: makemap: Illegal mode" -msgstr "E228: makemap: 无效的模式" - -#. key value of 'cedit' option -#. type of cmdline window or 0 -#. result of cmdline window or 0 -#: ../globals.h:924 +#. /< type of cmdline window or 0 +#. /< result of cmdline window or 0 +#. /< cmdline recursion level +#: ../globals.h:815 msgid "--No lines in buffer--" msgstr "--缓冲区无内容--" -#. -#. * The error messages that can be shared are included here. -#. * Excluded are errors that are only used once and debugging messages. -#. -#: ../globals.h:996 +#. uncrustify:off +#. The error messages that can be shared are included here. +#. Excluded are errors that are only used once and debugging messages. +#: ../globals.h:861 msgid "E470: Command aborted" msgstr "E470: 命令被中止" -#: ../globals.h:997 +#: ../globals.h:862 +msgid "E905: Cannot set this option after startup" +msgstr "E905: 启动后不能设置这个选项" + +#: ../globals.h:863 +msgid "E903: Could not spawn API job" +msgstr "E903: 无法生成 API 任务" + +#: ../globals.h:864 msgid "E471: Argument required" msgstr "E471: 需要参数" -#: ../globals.h:998 +#: ../globals.h:865 msgid "E10: \\ should be followed by /, ? or &" msgstr "E10: \\ 后面应该跟有 /、? 或 &" -#: ../globals.h:1000 +#: ../globals.h:866 msgid "E11: Invalid in command-line window; <CR> executes, CTRL-C quits" msgstr "E11: 在命令行窗口中无效;<CR> 执行,CTRL-C 退出" -#: ../globals.h:1002 +#: ../globals.h:867 msgid "E12: Command not allowed from exrc/vimrc in current dir or tag search" msgstr "E12: 当前目录中的 exrc/vimrc 或 tag 查找中不允许此命令" -#: ../globals.h:1003 +#: ../globals.h:868 +msgid "E169: Command too recursive" +msgstr "E169: 命令递归层数过多" + +#: ../globals.h:869 msgid "E171: Missing :endif" msgstr "E171: 缺少 :endif" -#: ../globals.h:1004 +#: ../globals.h:870 msgid "E600: Missing :endtry" msgstr "E600: 缺少 :endtry" -#: ../globals.h:1005 +#: ../globals.h:871 msgid "E170: Missing :endwhile" msgstr "E170: 缺少 :endwhile" -#: ../globals.h:1006 +#: ../globals.h:872 msgid "E170: Missing :endfor" msgstr "E170: 缺少 :endfor" -#: ../globals.h:1007 +#: ../globals.h:873 msgid "E588: :endwhile without :while" msgstr "E588: :endwhile 缺少对应的 :while" -#: ../globals.h:1008 +#: ../globals.h:874 msgid "E588: :endfor without :for" msgstr "E588: :endfor 缺少对应的 :for" -#: ../globals.h:1009 +#: ../globals.h:875 msgid "E13: File exists (add ! to override)" msgstr "E13: 文件已存在 (请加 ! 强制执行)" -#: ../globals.h:1010 +#: ../globals.h:876 msgid "E472: Command failed" msgstr "E472: 命令执行失败" -#: ../globals.h:1011 +#: ../globals.h:877 msgid "E473: Internal error" msgstr "E473: 内部错误" -#: ../globals.h:1012 +#: ../globals.h:878 +#, c-format +msgid "E685: Internal error: %s" +msgstr "E685: 内部错误: %s" + +#: ../globals.h:879 msgid "Interrupted" msgstr "已中断" -#: ../globals.h:1013 -msgid "E14: Invalid address" -msgstr "E14: 无效的地址" - -#: ../globals.h:1014 +#: ../globals.h:880 msgid "E474: Invalid argument" msgstr "E474: 无效的参数" -#: ../globals.h:1015 +#: ../globals.h:881 #, c-format msgid "E475: Invalid argument: %s" msgstr "E475: 无效的参数: %s" -#: ../globals.h:1016 +#: ../globals.h:882 +#, c-format +msgid "E475: Invalid value for argument %s" +msgstr "E475: 参数 %s 的值无效" + +#: ../globals.h:883 +#, c-format +msgid "E475: Invalid value for argument %s: %s" +msgstr "E475: 参数 %s 的值无效:%s" + +#: ../globals.h:884 +#, c-format +msgid "E983: Duplicate argument: %s" +msgstr "E983: 重复的参数:%s" + +#: ../globals.h:885 #, c-format msgid "E15: Invalid expression: %s" msgstr "E15: 无效的表达式: %s" -#: ../globals.h:1017 +#: ../globals.h:886 msgid "E16: Invalid range" msgstr "E16: 无效的范围" -#: ../globals.h:1018 +#: ../globals.h:887 msgid "E476: Invalid command" msgstr "E476: 无效的命令" -#: ../globals.h:1019 +#: ../globals.h:888 #, c-format msgid "E17: \"%s\" is a directory" msgstr "E17: \"%s\" 是目录" -#: ../globals.h:1020 -#, fuzzy -msgid "E900: Invalid job id" -msgstr "E49: 无效的滚动大小" +#: ../globals.h:889 +msgid "E756: Spell checking is not possible" +msgstr "E756: 拼写检查不可能" -#: ../globals.h:1021 +#: ../globals.h:890 +msgid "E900: Invalid channel id" +msgstr "E900: channel id 无效" + +#: ../globals.h:891 +msgid "E900: Invalid channel id: not a job" +msgstr "E900: channel id 无效:不是任务" + +#: ../globals.h:892 msgid "E901: Job table is full" -msgstr "E901: 任务表已经满" +msgstr "E901: 任务表已满" + +#: ../globals.h:893 +#, c-format +msgid "E903: Process failed to start: %s: \"%s\"" +msgstr "E903: 进程启动失败:%s:\"%s\"" + +#: ../globals.h:894 +msgid "E904: channel is not a pty" +msgstr "E664: channel 不是 pty" + +#: ../globals.h:895 +#, c-format +msgid "E905: Couldn't open stdio channel: %s" +msgstr "E905: 无法打开 stdio channel:%s" + +#: ../globals.h:896 +msgid "E906: invalid stream for channel" +msgstr "E906: stream 对 channel 无效" + +#: ../globals.h:897 +msgid "E906: invalid stream for rpc channel, use 'rpc'" +msgstr "E906: stream 对 rpc channel 无效,请使用 'rpc'" + +#: ../globals.h:898 +#, c-format +msgid "" +"E5210: dict key '%s' already set for buffered stream in channel %<PRIu64>" +msgstr "" -#: ../globals.h:1024 +#: ../globals.h:899 #, c-format msgid "E364: Library call failed for \"%s()\"" msgstr "E364: 调用函数库 \"%s()\" 失败" -#: ../globals.h:1026 +#: ../globals.h:900 +#, c-format +msgid "E667: Fsync failed: %s" +msgstr "E667: Fsync 失败:%s" + +#: ../globals.h:901 +#, c-format +msgid "E739: Cannot create directory %s: %s" +msgstr "E739: 无法创建目录 %s:%s" + +#: ../globals.h:902 msgid "E19: Mark has invalid line number" msgstr "E19: 标记的行号无效" -#: ../globals.h:1027 +#: ../globals.h:903 msgid "E20: Mark not set" msgstr "E20: 没有设定标记" -#: ../globals.h:1029 +#: ../globals.h:904 msgid "E21: Cannot make changes, 'modifiable' is off" msgstr "E21: 不能修改,因为选项 'modifiable' 是关的" -#: ../globals.h:1030 +#: ../globals.h:905 msgid "E22: Scripts nested too deep" msgstr "E22: 脚本嵌套过深" -#: ../globals.h:1031 +#: ../globals.h:906 msgid "E23: No alternate file" msgstr "E23: 没有交替文件" -#: ../globals.h:1032 +#: ../globals.h:907 msgid "E24: No such abbreviation" msgstr "E24: 没有这个缩写" -#: ../globals.h:1033 +#: ../globals.h:908 msgid "E477: No ! allowed" msgstr "E477: 不能使用 \"!\"" -#: ../globals.h:1035 -msgid "E25: Nvim does not have a built-in GUI" -msgstr "E25: 无法使用图形界面: 编译时没有启用" - -#: ../globals.h:1036 +#: ../globals.h:909 #, c-format msgid "E28: No such highlight group name: %s" msgstr "E28: 没有这个高亮群组名: %s" -#: ../globals.h:1037 +#: ../globals.h:910 msgid "E29: No inserted text yet" msgstr "E29: 没有插入过文字" -#: ../globals.h:1038 +#: ../globals.h:911 msgid "E30: No previous command line" msgstr "E30: 没有前一个命令行" -#: ../globals.h:1039 +#: ../globals.h:912 msgid "E31: No such mapping" msgstr "E31: 没有这个映射" -#: ../globals.h:1040 +#: ../globals.h:913 msgid "E479: No match" msgstr "E479: 没有匹配" -#: ../globals.h:1041 +#: ../globals.h:914 #, c-format msgid "E480: No match: %s" msgstr "E480: 没有匹配: %s" -#: ../globals.h:1042 +#: ../globals.h:915 msgid "E32: No file name" msgstr "E32: 没有文件名" -#: ../globals.h:1044 +#: ../globals.h:916 msgid "E33: No previous substitute regular expression" msgstr "E33: 没有前一个替换正则表达式" -#: ../globals.h:1045 +#: ../globals.h:917 msgid "E34: No previous command" msgstr "E34: 没有前一个命令" -#: ../globals.h:1046 +#: ../globals.h:918 msgid "E35: No previous regular expression" msgstr "E35: 没有前一个正则表达式" -#: ../globals.h:1047 +#: ../globals.h:919 msgid "E481: No range allowed" msgstr "E481: 不能使用范围" -#: ../globals.h:1048 +#: ../globals.h:920 msgid "E36: Not enough room" msgstr "E36: 没有足够的空间" -#: ../globals.h:1049 -#, c-format -msgid "E482: Can't create file %s" -msgstr "E482: 无法创建文件 %s" - -#: ../globals.h:1050 +#: ../globals.h:921 msgid "E483: Can't get temp file name" msgstr "E483: 无法获取临时文件名" -#: ../globals.h:1051 +#: ../globals.h:922 #, c-format msgid "E484: Can't open file %s" msgstr "E484: 无法打开文件 %s" -#: ../globals.h:1052 +#: ../globals.h:923 +#, c-format +msgid "E484: Can't open file %s: %s" +msgstr "E484: 无法打开文件 %s:%s" + +#: ../globals.h:924 #, c-format msgid "E485: Can't read file %s" msgstr "E485: 无法读取文件 %s" -#: ../globals.h:1054 -msgid "E37: No write since last change (add ! to override)" -msgstr "E37: 已修改但尚未保存 (可用 ! 强制执行)" - -#: ../globals.h:1055 -#, fuzzy -msgid "E37: No write since last change" -msgstr "[已修改但尚未保存]\n" - -#: ../globals.h:1056 +#: ../globals.h:925 msgid "E38: Null argument" msgstr "E38: 空的参数" -#: ../globals.h:1057 +#: ../globals.h:926 msgid "E39: Number expected" msgstr "E39: 此处需要数字" -#: ../globals.h:1058 +#: ../globals.h:927 #, c-format msgid "E40: Can't open errorfile %s" msgstr "E40: 无法打开错误文件 %s" -#: ../globals.h:1059 +#: ../globals.h:928 msgid "E41: Out of memory!" msgstr "E41: 内存不足!" -#: ../globals.h:1060 +#: ../globals.h:929 msgid "Pattern not found" msgstr "找不到模式" -#: ../globals.h:1061 +#: ../globals.h:930 #, c-format msgid "E486: Pattern not found: %s" msgstr "E486: 找不到模式: %s" -#: ../globals.h:1062 +#: ../globals.h:931 msgid "E487: Argument must be positive" msgstr "E487: 参数必须是正数" -#: ../globals.h:1064 +#: ../globals.h:932 msgid "E459: Cannot go back to previous directory" msgstr "E459: 无法回到前一个目录" -#: ../globals.h:1066 +#: ../globals.h:934 msgid "E42: No Errors" msgstr "E42: 没有错误" -#: ../globals.h:1067 +#: ../globals.h:935 msgid "E776: No location list" -msgstr "E776: 没有 location 列表" +msgstr "E776: 没有位置列表" -#: ../globals.h:1068 +#: ../globals.h:936 msgid "E43: Damaged match string" msgstr "E43: 已损坏的匹配字符串" -#: ../globals.h:1069 +#: ../globals.h:937 msgid "E44: Corrupted regexp program" msgstr "E44: 已损坏的正则表达式程序" -#: ../globals.h:1071 +#: ../globals.h:938 msgid "E45: 'readonly' option is set (add ! to override)" msgstr "E45: 已设定选项 'readonly' (请加 ! 强制执行)" -#: ../globals.h:1073 +#: ../globals.h:939 +#, c-format +msgid "E734: Wrong variable type for %s=" +msgstr "E734: %s= 的变量类型不正确" + +#: ../globals.h:940 #, c-format -msgid "E46: Cannot change read-only variable \"%s\"" -msgstr "E46: 不能改变只读变量 \"%s\"" +msgid "E461: Illegal variable name: %s" +msgstr "E461: 变量名无效: %s" -#: ../globals.h:1075 -#, fuzzy, c-format -msgid "E794: Cannot set variable in the sandbox: \"%s\"" -msgstr "E46: 不能在 sandbox 中设定变量: \"%s\"" +#: ../globals.h:941 +msgid "E995: Cannot modify existing variable" +msgstr "E995: 不能修改已有变量" -#: ../globals.h:1076 +#: ../globals.h:942 +#, c-format +msgid "E46: Cannot change read-only variable \"%.*s\"" +msgstr "E46: 不能改变只读变量 \"%.*s\"" + +#: ../globals.h:943 +msgid "E928: String required" +msgstr "E928: 需要字符串" + +#: ../globals.h:944 +msgid "E715: Dictionary required" +msgstr "E715: 需要字典" + +#: ../globals.h:945 +#, c-format +msgid "E979: Blob index out of range: %<PRId64>" +msgstr "E979: Blob 索引超出范围:%<PRId64>" + +#: ../globals.h:946 +msgid "E978: Invalid operation for Blob" +msgstr "E978: 操作对 Blob 无效" + +#: ../globals.h:947 +#, c-format +msgid "E118: Too many arguments for function: %s" +msgstr "E118: 函数 %s 的参数过多" + +#: ../globals.h:948 +#, c-format +msgid "E716: Key not present in Dictionary: \"%s\"" +msgstr "E716: 字典中不存在键:\"%s\"" + +#: ../globals.h:949 +msgid "E714: List required" +msgstr "E714: 需要列表" + +#: ../globals.h:950 +msgid "E897: List or Blob required" +msgstr "E897: 需要列表或 Blob" + +#: ../globals.h:951 +#, c-format +msgid "E712: Argument of %s must be a List or Dictionary" +msgstr "E712: %s 的参数必须是 List 或者 Dictionary" + +#: ../globals.h:952 +#, c-format +msgid "E896: Argument of %s must be a List, Dictionary or Blob" +msgstr "E896: %s 的参数必须是列表、字典或 Blob" + +#: ../globals.h:953 msgid "E47: Error while reading errorfile" msgstr "E47: 读取错误文件失败" -#: ../globals.h:1078 +#: ../globals.h:954 msgid "E48: Not allowed in sandbox" msgstr "E48: 不允许在 sandbox 中使用" -#: ../globals.h:1080 +#: ../globals.h:955 msgid "E523: Not allowed here" msgstr "E523: 不允许在此使用" -#: ../globals.h:1082 +#: ../globals.h:956 +msgid "E565: Not allowed to change text or change window" +msgstr "E565: 不允许改变文本或窗口" + +#: ../globals.h:957 msgid "E359: Screen mode setting not supported" msgstr "E359: 不支持设定屏幕模式" -#: ../globals.h:1083 +#: ../globals.h:958 msgid "E49: Invalid scroll size" -msgstr "E49: 无效的滚动大小" +msgstr "E49: 滚动大小无效" -#: ../globals.h:1084 +#: ../globals.h:959 msgid "E91: 'shell' option is empty" -msgstr "E91: 选项 'shell' 为空" +msgstr "E91: 'shell' 选项为空" -#: ../globals.h:1085 +#: ../globals.h:960 msgid "E255: Couldn't read in sign data!" msgstr "E255: 无法读取 sign 数据!" -#: ../globals.h:1086 +#: ../globals.h:961 msgid "E72: Close error on swap file" msgstr "E72: 交换文件关闭错误" -#: ../globals.h:1087 +#: ../globals.h:962 msgid "E73: tag stack empty" msgstr "E73: tag 堆栈为空" -#: ../globals.h:1088 +#: ../globals.h:963 msgid "E74: Command too complex" -msgstr "E74: 命令过复杂" +msgstr "E74: 命令过于复杂" -#: ../globals.h:1089 +#: ../globals.h:964 msgid "E75: Name too long" msgstr "E75: 名字过长" -#: ../globals.h:1090 +#: ../globals.h:965 msgid "E76: Too many [" msgstr "E76: [ 过多" -#: ../globals.h:1091 +#: ../globals.h:966 msgid "E77: Too many file names" msgstr "E77: 文件名过多" -#: ../globals.h:1092 +#: ../globals.h:967 msgid "E488: Trailing characters" msgstr "E488: 多余的尾部字符" -#: ../globals.h:1093 +#: ../globals.h:968 +#, c-format +msgid "E488: Trailing characters: %s" +msgstr "E488: 多余的尾部字符:%s" + +#: ../globals.h:969 msgid "E78: Unknown mark" msgstr "E78: 未知的标记" -#: ../globals.h:1094 +#: ../globals.h:970 msgid "E79: Cannot expand wildcards" msgstr "E79: 无法扩展通配符" -#: ../globals.h:1096 +#: ../globals.h:971 msgid "E591: 'winheight' cannot be smaller than 'winminheight'" msgstr "E591: 'winheight' 不能小于 'winminheight'" -#: ../globals.h:1098 +#: ../globals.h:972 msgid "E592: 'winwidth' cannot be smaller than 'winminwidth'" msgstr "E592: 'winwidth' 不能小于 'winminwidth'" -#: ../globals.h:1099 +#: ../globals.h:973 msgid "E80: Error while writing" msgstr "E80: 写入出错" -#: ../globals.h:1100 -msgid "Zero count" -msgstr "计数为零" +#: ../globals.h:974 +msgid "E939: Positive count required" +msgstr "E939: 需要正的计数" -#: ../globals.h:1101 +#: ../globals.h:975 msgid "E81: Using <SID> not in a script context" msgstr "E81: 在脚本环境外使用了 <SID>" -#: ../globals.h:1102 +#: ../globals.h:976 #, c-format -msgid "E685: Internal error: %s" -msgstr "E685: 内部错误: %s" +msgid "E107: Missing parentheses: %s" +msgstr "E107: 缺少括号:%s" -#: ../globals.h:1104 +#: ../globals.h:977 msgid "E363: pattern uses more memory than 'maxmempattern'" msgstr "E363: 表达式的内存使用超出 'maxmempattern'" -#: ../globals.h:1105 +#: ../globals.h:978 msgid "E749: empty buffer" msgstr "E749: 空的缓冲区" -#: ../globals.h:1108 +#: ../globals.h:979 +#, c-format +msgid "E86: Buffer %<PRId64> does not exist" +msgstr "E86: 缓冲区 %<PRId64> 不存在" + +#: ../globals.h:981 msgid "E682: Invalid search pattern or delimiter" msgstr "E682: 无效的搜索表达式或分隔符" -#: ../globals.h:1109 +#: ../globals.h:982 msgid "E139: File is loaded in another buffer" msgstr "E139: 文件已在另一个缓冲区中被加载" -#: ../globals.h:1110 +#: ../globals.h:983 #, c-format msgid "E764: Option '%s' is not set" msgstr "E764: 没有设定选项 '%s'" -#: ../globals.h:1111 -#, fuzzy +#: ../globals.h:984 msgid "E850: Invalid register name" -msgstr "E354: 无效的寄存器名: '%s'" +msgstr "E850: 寄存器名无效" + +#: ../globals.h:985 +#, c-format +msgid "E919: Directory not found in '%s': \"%s\"" +msgstr "E919: '%s' 中未找到目录:\"%s\"" + +#: ../globals.h:986 +msgid "E952: Autocommand caused recursive behavior" +msgstr "E952: 自动命令导致了递归行为" + +#: ../globals.h:987 +msgid "E328: Menu only exists in another mode" +msgstr "E328: 菜单只在其它模式中存在" + +#: ../globals.h:988 +msgid "E813: Cannot close autocmd window" +msgstr "E813: 不能关闭自动命令窗口" -#: ../globals.h:1114 +#: ../globals.h:989 +#, c-format +msgid "E686: Argument of %s must be a List" +msgstr "E686: %s 的参数必须是列表" + +#: ../globals.h:990 +msgid "E519: Option not supported" +msgstr "E519: 不支持该选项" + +#: ../globals.h:991 +msgid "E856: Filename too long" +msgstr "E856: 文件名过长" + +#: ../globals.h:992 +msgid "E806: using Float as a String" +msgstr "E806: 将浮点数当作字符串使用" + +#: ../globals.h:993 +msgid "E788: Not allowed to edit another buffer now" +msgstr "E788: 目前不允许编辑别的缓冲区" + +#: ../globals.h:995 +#, c-format +msgid "E5500: autocmd has thrown an exception: %s" +msgstr "E5500: 自动命令抛出了异常:%s" + +#: ../globals.h:996 +msgid "E5520: <Cmd> mapping must end with <CR>" +msgstr "E5520: <Cmd> 映射必须以 <CR> 结束" + +#: ../globals.h:998 +msgid "E5521: <Cmd> mapping must end with <CR> before second <Cmd>" +msgstr "" + +#: ../globals.h:1000 +#, c-format +msgid "E5555: API call: %s" +msgstr "" + +#: ../globals.h:1002 +#, c-format +msgid "E5560: %s must not be called in a lua loop callback" +msgstr "E5560: Lua 循环回调中不能调用 %s" + +#: ../globals.h:1004 +msgid "E5601: Cannot close window, only floating window would remain" +msgstr "E5601: 无法关闭窗口,不然就只剩下浮动窗口了" + +#: ../globals.h:1005 +msgid "E5602: Cannot exchange or rotate float" +msgstr "" + +#: ../globals.h:1007 +msgid "E1155: Cannot define autocommands for ALL events" +msgstr "E1155: 不能对所有事件定义自动命令" + +#: ../globals.h:1009 +msgid "E1240: Resulting text too long" +msgstr "E1240: 得到的文本过长" + +#: ../globals.h:1011 +msgid "E1247: Line number out of range" +msgstr "E1247: 行号超出范围" + +#: ../globals.h:1013 +msgid "E5248: Invalid character in group name" +msgstr "E5248: 组名中含有无效字符" + +#: ../globals.h:1015 +msgid "E1249: Highlight group name too long" +msgstr "E1249: 高亮组名称过长" + +#: ../globals.h:1018 +msgid "E5767: Cannot use :undo! to redo or move to a different undo branch" +msgstr "" + +#: ../globals.h:1020 msgid "search hit TOP, continuing at BOTTOM" msgstr "已查找到文件开头,再从结尾继续查找" -#: ../globals.h:1115 +#: ../globals.h:1021 msgid "search hit BOTTOM, continuing at TOP" msgstr "已查找到文件结尾,再从开头继续查找" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: 缺少冒号" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: 无效的部分" +#: ../globals.h:1023 +msgid " line " +msgstr " 行 " -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: 应该要有数字" +#: ../help.c:79 +msgid "E478: Don't panic!" +msgstr "E478: 不要慌!" -#: ../hardcopy.c:473 +#: ../help.c:120 #, c-format -msgid "Page %d" -msgstr "第 %d 页" +msgid "E661: Sorry, no '%s' help for %s" +msgstr "E661: 抱歉,没有 '%s' 的 %s 的说明" -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "没有要打印的文字" +#: ../help.c:122 +#, c-format +msgid "E149: Sorry, no help for %s" +msgstr "E149: 抱歉,没有 %s 的说明" -#: ../hardcopy.c:668 +#: ../help.c:154 #, c-format -msgid "Printing page %d (%d%%)" -msgstr "正在打印第 %d 页 (%d%%)" +msgid "Sorry, help file \"%s\" not found" +msgstr "抱歉,找不到帮助文件 \"%s\"" -#: ../hardcopy.c:680 +#: ../help.c:900 ../help.c:1094 #, c-format -msgid " Copy %d of %d" -msgstr "复制 %d / %d" +msgid "E151: No match: %s" +msgstr "E151: 没有匹配:%s" -#: ../hardcopy.c:733 +#: ../help.c:920 #, c-format -msgid "Printed: %s" -msgstr "已打印: %s" +msgid "E152: Cannot open %s for writing" +msgstr "E152: 无法打开并写入 %s" -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "打印中止" +#: ../help.c:941 +#, c-format +msgid "E153: Unable to open %s for reading" +msgstr "E153: 无法打开并读取 %s" -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: 写入 PostScript 输出文件出错" +#: ../help.c:969 +#, c-format +msgid "E670: Mix of help file encodings within a language: %s" +msgstr "E670: 在一种语言中混合了多种帮助文件编码: %s" -#: ../hardcopy.c:1747 +#: ../help.c:1026 #, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: 无法打开文件 \"%s\"" +msgid "E154: Duplicate tag \"%s\" in file %s/%s" +msgstr "E154: Tag \"%s\" 在文件 %s/%s 中重复出现" -#: ../hardcopy.c:1756 ../hardcopy.c:2470 +#: ../help.c:1185 #, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: 无法读取 PostScript 资源文件 \"%s\"" +msgid "E150: Not a directory: %s" +msgstr "E150: 不是目录: %s" + +#: ../highlight.c:95 +msgid "E424: Too many different highlighting attributes in use" +msgstr "E424: 使用了太多不同的高亮度属性" -#: ../hardcopy.c:1772 +#: ../highlight_group.c:910 #, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: 文件 \"%s\" 不是 PostScript 资源文件" +msgid "E411: highlight group not found: %s" +msgstr "E411: 找不到 highlight group: %s" -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 +#: ../highlight_group.c:933 #, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: 文件 \"%s\" 不是已支持的 PostScript 资源文件" +msgid "E412: Not enough arguments: \":highlight link %s\"" +msgstr "E412: 参数太少: \":highlight link %s\"" -#: ../hardcopy.c:1856 +#: ../highlight_group.c:939 #, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" 资源文件版本不正确" +msgid "E413: Too many arguments: \":highlight link %s\"" +msgstr "E413: 参数过多: \":highlight link %s\"" -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "E673: 不兼容的多字节编码和字符集。" +#: ../highlight_group.c:966 +msgid "E414: group has settings, highlight link ignored" +msgstr "E414: 已设定组, 忽略 highlight link" -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "E674: printmbcharset 在多字节编码下不能为空。" +#: ../highlight_group.c:1039 +#, c-format +msgid "E415: unexpected equal sign: %s" +msgstr "E415: 不该有的等号: %s" + +#: ../highlight_group.c:1051 ../highlight_group.c:1099 +msgid "E423: Illegal argument" +msgstr "E423: 无效的参数" -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "E675: 没有指定多字节打印的默认字体。" +#: ../highlight_group.c:1072 +#, c-format +msgid "E416: missing equal sign: %s" +msgstr "E416: 缺少等号: %s" -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: 无法打开 PostScript 输出文件" +#: ../highlight_group.c:1093 +#, c-format +msgid "E417: missing argument: %s" +msgstr "E417: 缺少参数: %s" -#: ../hardcopy.c:2458 +#: ../highlight_group.c:1127 #, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: 无法打开文件 \"%s\"" +msgid "E418: Illegal value: %s" +msgstr "E418: 不合法的值: %s" -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: 找不到 PostScript 资源文件 \"prolog.ps\"" +#: ../highlight_group.c:1175 +msgid "E419: FG color unknown" +msgstr "E419: 错误的前景颜色" -#: ../hardcopy.c:2593 -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: 找不到 PostScript 资源文件 \"cidfont.ps\"" +#: ../highlight_group.c:1183 +msgid "E420: BG color unknown" +msgstr "E420: 错误的背景颜色" -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 +#: ../highlight_group.c:1198 #, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: 找不到 PostScript 资源文件 \"%s.ps\"" +msgid "E421: Color name or number not recognized: %s" +msgstr "E421: 错误的颜色名称或数值: %s" -#: ../hardcopy.c:2654 +#: ../highlight_group.c:1330 #, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620: 无法转换至打印编码 \"%s\"" +msgid "E423: Illegal argument: %s" +msgstr "E423: 无效的参数: %s" + +#: ../highlight_group.c:1845 +msgid "E669: Unprintable character in group name" +msgstr "E669: 组名中存在不可显示字符" -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "发送到打印机……" +#: ../highlight_group.c:1872 +msgid "E849: Too many highlight and syntax groups" +msgstr "E849: 高亮和语法组过多" -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: 无法打印 PostScript 文件" +#: ../input.c:232 +msgid "Type number and <Enter> or click with the mouse (q or empty cancels): " +msgstr "输入数字并按 <Enter> 或点击鼠标(q 或空取消):" -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "打印任务已被发送。" +#: ../input.c:235 +msgid "Type number and <Enter> (q or empty cancels): " +msgstr "输入数字并按 <Enter>(q 或空取消):" -#: ../if_cscope.c:85 -msgid "Add a new database" -msgstr "添加一个新的数据库" +#: ../insexpand.c:99 +msgid " Keyword completion (^N^P)" +msgstr " 关键字补全 (^N^P)" -#: ../if_cscope.c:87 -msgid "Query for a pattern" -msgstr "查询一个模式" +#. CTRL_X_NORMAL, ^P/^N compl. +#: ../insexpand.c:100 +msgid " ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)" +msgstr " ^X 模式 (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)" -#: ../if_cscope.c:89 -msgid "Show this message" -msgstr "显示此信息" +#. CTRL_X_SCROLL: depends on state +#: ../insexpand.c:102 +msgid " Whole line completion (^L^N^P)" +msgstr " 整行补全 (^L^N^P)" -#: ../if_cscope.c:91 -msgid "Kill a connection" -msgstr "结束一个连接" +#: ../insexpand.c:103 +msgid " File name completion (^F^N^P)" +msgstr " 文件名补全 (^F^N^P)" -#: ../if_cscope.c:93 -msgid "Reinit all connections" -msgstr "重置所有连接" +#: ../insexpand.c:104 +msgid " Tag completion (^]^N^P)" +msgstr " Tag 补全 (^]^N^P)" -#: ../if_cscope.c:95 -msgid "Show connections" -msgstr "显示连接" +#: ../insexpand.c:105 +msgid " Path pattern completion (^N^P)" +msgstr " 头文件模式补全 (^N^P)" -#: ../if_cscope.c:101 -#, c-format -msgid "E560: Usage: cs[cope] %s" -msgstr "E560: 用法: cs[cope] %s" +#: ../insexpand.c:106 +msgid " Definition completion (^D^N^P)" +msgstr " 定义补全 (^D^N^P)" -#: ../if_cscope.c:225 -msgid "This cscope command does not support splitting the window.\n" -msgstr "这个 cscope 命令不支持分割窗口。\n" +#. CTRL_X_FINISHED +#: ../insexpand.c:108 +msgid " Dictionary completion (^K^N^P)" +msgstr " Dictionary 补全 (^K^N^P)" -#: ../if_cscope.c:266 -msgid "E562: Usage: cstag <ident>" -msgstr "E562: 用法: cstag <ident>" +#: ../insexpand.c:109 +msgid " Thesaurus completion (^T^N^P)" +msgstr " Thesaurus 补全 (^T^N^P)" -#: ../if_cscope.c:313 -msgid "E257: cstag: tag not found" -msgstr "E257: cstag: 找不到 tag" +#. CTRL_X_EVAL doesn't use msg. +#: ../insexpand.c:110 ../insexpand.c:116 +msgid " Command-line completion (^V^N^P)" +msgstr " 命令行补全 (^V^N^P)" -#: ../if_cscope.c:461 -#, c-format -msgid "E563: stat(%s) error: %d" -msgstr "E563: stat(%s) 错误: %d" +#: ../insexpand.c:111 +msgid " User defined completion (^U^N^P)" +msgstr " 用户自定义补全 (^U^N^P)" -#: ../if_cscope.c:551 -#, c-format -msgid "E564: %s is not a directory or a valid cscope database" -msgstr "E564: %s 不是目录或有效的 cscope 数据库" +#: ../insexpand.c:112 +msgid " Omni completion (^O^N^P)" +msgstr " 全能补全 (^O^N^P)" -#: ../if_cscope.c:566 -#, c-format -msgid "Added cscope database %s" -msgstr "添加了 cscope 数据库 %s" +#: ../insexpand.c:113 +msgid " Spelling suggestion (s^N^P)" +msgstr " 拼写建议 (s^N^P)" + +#: ../insexpand.c:114 +msgid " Keyword Local completion (^N^P)" +msgstr " 关键字局部补全 (^N^P)" -#: ../if_cscope.c:616 +#: ../insexpand.c:190 +msgid "Hit end of paragraph" +msgstr "已到段落结尾" + +#: ../insexpand.c:191 +msgid "E840: Completion function deleted text" +msgstr "E840: 补全函数删除了文本" + +#: ../insexpand.c:469 +msgid "'dictionary' option is empty" +msgstr "选项 'dictionary' 为空" + +#: ../insexpand.c:470 +msgid "'thesaurus' option is empty" +msgstr "选项 'thesaurus' 为空" + +#: ../insexpand.c:1456 #, c-format -msgid "E262: error reading cscope connection %<PRId64>" -msgstr "E262: 读取 cscope 连接 %<PRId64> 出错" +msgid "Scanning dictionary: %s" +msgstr "正在扫描 dictionary: %s" -#: ../if_cscope.c:711 -msgid "E561: unknown cscope search type" -msgstr "E561: 未知的 cscope 查找类型" +#: ../insexpand.c:1854 +msgid " (insert) Scroll (^E/^Y)" +msgstr " (插入) Scroll (^E/^Y)" -#: ../if_cscope.c:752 ../if_cscope.c:789 -msgid "E566: Could not create cscope pipes" -msgstr "E566: 无法创建 cscope 管道" +#: ../insexpand.c:1856 +msgid " (replace) Scroll (^E/^Y)" +msgstr " (替换) Scroll (^E/^Y)" -#: ../if_cscope.c:767 -msgid "E622: Could not fork for cscope" -msgstr "E622: 无法对 cscope 进行 fork" +#: ../insexpand.c:2600 +msgid "E785: complete() can only be used in Insert mode" +msgstr "E785: complete() 只能在插入模式中使用" -#: ../if_cscope.c:849 -#, fuzzy -msgid "cs_create_connection setpgid failed" -msgstr "cs_create_connection 执行失败" +#. reset in msg_trunc_attr() +#: ../insexpand.c:2881 +#, c-format +msgid "Scanning: %s" +msgstr "正在扫描: %s" -#: ../if_cscope.c:853 ../if_cscope.c:889 -msgid "cs_create_connection exec failed" -msgstr "cs_create_connection 执行失败" +#. reset in msg_trunc_attr() +#: ../insexpand.c:2912 +msgid "Scanning tags." +msgstr "扫描标签." -#: ../if_cscope.c:863 ../if_cscope.c:902 -msgid "cs_create_connection: fdopen for to_fp failed" -msgstr "cs_create_connection: fdopen to_fp 失败" +#: ../insexpand.c:3465 +msgid "match in file" +msgstr "在文件中匹配" -#: ../if_cscope.c:865 ../if_cscope.c:906 -msgid "cs_create_connection: fdopen for fr_fp failed" -msgstr "cs_create_connection: fdopen fr_fp 失败" +#: ../insexpand.c:4198 +msgid " Adding" +msgstr " 增加" + +#. showmode might reset the internal line pointers, so it must +#. be called before line = ml_get(), or when this address is no +#. longer needed. -- Acevedo. +#: ../insexpand.c:4243 +msgid "-- Searching..." +msgstr "-- 查找中..." -#: ../if_cscope.c:890 -msgid "E623: Could not spawn cscope process" -msgstr "E623: 无法生成 cscope 进程" +#: ../insexpand.c:4263 +msgid "Back at original" +msgstr "回到起点" + +#: ../insexpand.c:4266 +msgid "Word from other line" +msgstr "另一行的词" -#: ../if_cscope.c:932 -msgid "E567: no cscope connections" -msgstr "E567: 没有 cscope 连接" +#: ../insexpand.c:4269 +msgid "The only match" +msgstr "唯一匹配" -#: ../if_cscope.c:1009 +#: ../insexpand.c:4287 #, c-format -msgid "E469: invalid cscopequickfix flag %c for %c" -msgstr "E469: cscopequickfix 标志 %c 对 %c 无效" +msgid "match %d of %d" +msgstr "匹配 %d / %d" -#: ../if_cscope.c:1058 +#: ../insexpand.c:4291 #, c-format -msgid "E259: no matches found for cscope query %s of %s" -msgstr "E259: cscope 查询 %s %s 没有找到匹配的结果" +msgid "match %d" +msgstr "匹配 %d" -#: ../if_cscope.c:1142 -msgid "cscope commands:\n" -msgstr "cscope 命令:\n" +#: ../locale.c:227 +#, c-format +msgid "Current %slanguage: \"%s\"" +msgstr "当前的 %s语言: \"%s\"" -#: ../if_cscope.c:1150 -#, fuzzy, c-format -msgid "%-5s: %s%*s (Usage: %s)" -msgstr "%-5s: %-30s (用法: %s)" +#: ../locale.c:243 +#, c-format +msgid "E197: Cannot set language to \"%s\"" +msgstr "E197: 不能设定语言为 \"%s\"" + +#: ../lua/converter.c:68 ../lua/converter.c:204 ../lua/converter.c:621 +#, c-format +msgid "E1502: Lua failed to grow stack to %i" +msgstr "" -#: ../if_cscope.c:1155 +#: ../lua/converter.c:380 msgid "" -"\n" -" a: Find assignments to this symbol\n" -" c: Find functions calling this function\n" -" d: Find functions called by this function\n" -" e: Find this egrep pattern\n" -" f: Find this file\n" -" g: Find this definition\n" -" i: Find files #including this file\n" -" s: Find this C symbol\n" -" t: Find this text string\n" +"E5100: Cannot convert given lua table: table should either have a sequence " +"of positive integer keys or contain only string keys" msgstr "" -"\n" -" a: 搜索对此符号的赋值\n" -" c: 搜索调用此函数的函数\n" -" d: 搜索此函数调用的函数\n" -" e: 搜索此 egrep 模式\n" -" f: 搜索此文件\n" -" g: 搜索此定义\n" -" i: 搜索包含此文件的文件\n" -" s: 搜索此 C 符号\n" -" t: 搜索此文本字符串\n" -#: ../if_cscope.c:1226 -msgid "E568: duplicate cscope database not added" -msgstr "E568: 重复的 cscope 数据库未被加入" +#: ../lua/converter.c:409 ../lua/converter.c:415 +msgid "E5101: Cannot convert given lua type" +msgstr "" -#: ../if_cscope.c:1335 +#: ../lua/converter.c:509 ../lua/converter.c:532 #, c-format -msgid "E261: cscope connection %s not found" -msgstr "E261: 找不到 cscope 连接 %s" +msgid "E5102: Lua failed to grow stack to %i" +msgstr "" -#: ../if_cscope.c:1364 +#: ../lua/executor.c:333 #, c-format -msgid "cscope connection %s closed" -msgstr "cscope 连接 %s 已关闭" +msgid "Error executing vim.schedule lua callback: %.*s" +msgstr "" -#. should not reach here -#: ../if_cscope.c:1486 -msgid "E570: fatal error in cs_manage_matches" -msgstr "E570: cs_manage_matches 严重错误" +#: ../lua/executor.c:782 +msgid "E970: Failed to initialize lua interpreter\n" +msgstr "E970: 无法初始化 Lua 解释器\n" -#: ../if_cscope.c:1693 -#, c-format -msgid "Cscope tag: %s" -msgstr "Cscope tag: %s" +#: ../lua/executor.c:787 +msgid "E970: Failed to initialize builtin lua modules\n" +msgstr "E970: 无法初始化内置 Lua 模块\n" -#: ../if_cscope.c:1711 -msgid "" -"\n" -" # line" +#: ../lua/executor.c:972 +#, c-format +msgid "E5114: Error while converting print argument #%i: %.*s" msgstr "" -"\n" -" # 行 " -#: ../if_cscope.c:1713 -msgid "filename / context / line\n" -msgstr "文件名 / 上下文 / 行\n" +#: ../lua/executor.c:1066 +#, fuzzy, c-format +msgid "E5115: Error while loading debug string: %.*s" +msgstr "E782: 当读取.sug 文件时错误" + +#: ../lua/executor.c:1068 +#, fuzzy, c-format +msgid "E5116: Error while calling debug string: %.*s" +msgstr "E782: 当读取.sug 文件时错误" + +#: ../lua/executor.c:1370 +#, fuzzy, c-format +msgid "E5108: Error executing Lua function: %.*s" +msgstr "E208: 写入文件 \"%s\" 出错" + +#: ../lua/executor.c:1390 +#, fuzzy, c-format +msgid "E5107: Error loading lua %.*s" +msgstr "E210: 读取文件 \"%s\" 出错" + +#: ../lua/executor.c:1397 +#, fuzzy, c-format +msgid "E5108: Error executing lua %.*s" +msgstr "E208: 写入文件 \"%s\" 出错" -#: ../if_cscope.c:1809 +#: ../lua/executor.c:1539 #, c-format -msgid "E609: Cscope error: %s" -msgstr "E609: Cscope 错误: %s" +msgid "Error executing lua callback: %.*s" +msgstr "" -#: ../if_cscope.c:2053 -msgid "All cscope databases reset" -msgstr "所有 cscope 数据库已被重置" +#: ../lua/executor.c:1605 +msgid "cannot save undo information" +msgstr "无法保存撤销信息" -#: ../if_cscope.c:2123 -msgid "no cscope connections\n" -msgstr "没有 cscope 连接\n" +#: ../lua/executor.c:1631 +#, fuzzy, c-format +msgid "E5109: Error loading lua: %.*s" +msgstr "E209: 关闭文件 \"%s\" 出错" -#: ../if_cscope.c:2126 -msgid " # pid database name prepend path\n" -msgstr " # pid 数据库名 prepend path\n" +#: ../lua/executor.c:1641 +#, fuzzy, c-format +msgid "E5110: Error executing lua: %.*s" +msgstr "E210: 读取文件 \"%s\" 出错" -#: ../main.c:144 -msgid "Unknown option argument" -msgstr "未知的选项参数" +#: ../lua/executor.c:1653 ../lua/executor.c:1705 +#, fuzzy, c-format +msgid "E5111: Error calling lua: %.*s" +msgstr "E209: 关闭文件 \"%s\" 出错" -#: ../main.c:146 -msgid "Too many edit arguments" -msgstr "编辑参数过多" +#. 1 +#: ../lua/executor.c:1715 +#, fuzzy, c-format +msgid "E5112: Error while creating lua chunk: %.*s" +msgstr "E782: 当读取.sug 文件时错误" + +#: ../lua/executor.c:1726 +#, fuzzy, c-format +msgid "E5113: Error while calling lua chunk: %.*s" +msgstr "E782: 当读取.sug 文件时错误" + +#: ../lua/executor.c:1791 +#, c-format +msgid "Error executing vim._expand_pat: %.*s" +msgstr "" -#: ../main.c:148 +#: ../lua/executor.c:1928 +#, c-format +msgid "Error executing vim.on_key Lua callback: %.*s" +msgstr "" + +#: ../lua/executor.c:2149 +#, c-format +msgid "Error executing Lua callback: %.*s" +msgstr "" + +#. Error messages +#: ../main.c:126 msgid "Argument missing after" msgstr "缺少必要的参数" -#: ../main.c:150 +#: ../main.c:127 msgid "Garbage after option argument" msgstr "选项参数后的内容无效" -#: ../main.c:152 +#: ../main.c:128 +msgid "Unknown option argument" +msgstr "未知的选项参数" + +#: ../main.c:129 +msgid "Too many edit arguments" +msgstr "编辑参数过多" + +#: ../main.c:131 msgid "Too many \"+command\", \"-c command\" or \"--cmd command\" arguments" msgstr "\"+command\"、\"-c command\" 或 \"--cmd command\" 参数过多" -#: ../main.c:154 -msgid "Invalid argument for" -msgstr "无效的参数" - -#: ../main.c:294 +#: ../main.c:1004 #, c-format -msgid "%d files to edit\n" -msgstr "还有 %d 个文件等待编辑\n" +msgid "E5421: Failed to open stdin: %s" +msgstr "E5421: 无法打开标准输入:%s" -#: ../main.c:1342 -msgid "Attempt to open script file again: \"" -msgstr "试图再次打开脚本文件: \"" +#: ../main.c:1284 +#, c-format +msgid "Attempt to open script file again: \"%s %s\"\n" +msgstr "试图再次打开脚本文件:\"%s %s\"\n" -#: ../main.c:1350 -msgid "Cannot open for reading: \"" -msgstr "无法打开并读取: \"" +#: ../main.c:1303 +#, c-format +msgid "Cannot open for reading: \"%s\": %s\n" +msgstr "无法打开并读取:\"%s\":%s\n" -#: ../main.c:1393 +#: ../main.c:1337 msgid "Cannot open for script output: \"" msgstr "无法打开并输出脚本: \"" -#: ../main.c:1622 -msgid "Vim: Warning: Output is not to a terminal\n" -msgstr "Vim: 警告: 输出不是到终端(屏幕)\n" - -#: ../main.c:1624 -msgid "Vim: Warning: Input is not from a terminal\n" -msgstr "Vim: 警告: 输入不是来自终端(键盘)\n" +#: ../main.c:1388 +msgid "--embed conflicts with -es/-Es" +msgstr "" #. just in case.. -#: ../main.c:1891 +#: ../main.c:1795 msgid "pre-vimrc command line" msgstr "pre-vimrc 命令行" -#: ../main.c:1964 +#: ../main.c:1916 +#, c-format +msgid "E5422: Conflicting configs: \"%s\" \"%s\"" +msgstr "" + +#: ../main.c:1985 #, c-format msgid "E282: Cannot read from \"%s\"" msgstr "E282: 无法读取 \"%s\"" -#: ../main.c:2149 +#: ../main.c:2071 +#, fuzzy msgid "" "\n" -"More info with: \"vim -h\"\n" +"More info with \"" msgstr "" "\n" "更多信息请见: \"vim -h\"\n" -#: ../main.c:2178 -msgid "[file ..] edit specified file(s)" -msgstr "[文件 ..] 编辑指定的文件" +#. kill us with CTRL-C here, if you like +#: ../main.c:2094 +msgid "Usage:\n" +msgstr "用法:\n" -#: ../main.c:2179 -msgid "- read text from stdin" -msgstr "- 从标准输入(stdin)读取文本" +#: ../main.c:2095 +msgid " nvim [options] [file ...] Edit file(s)\n" +msgstr " nvim [options] [文件 ..] 编辑指定的文件\n" -#: ../main.c:2180 -msgid "-t tag edit file where tag is defined" -msgstr "-t tag 编辑 tag 定义处的文件" +#: ../main.c:2096 +msgid " nvim [options] -t <tag> Edit file where tag is defined\n" +msgstr " nvim [options] -t tag 编辑 tag 定义处的文件\n" -#: ../main.c:2181 -msgid "-q [errorfile] edit file with first error" -msgstr "-q [errorfile] 编辑第一个出错处的文件" +#: ../main.c:2097 +msgid " nvim [options] -q [errorfile] Edit file with first error\n" +msgstr " nvim [options] -q [errorfile] 编辑第一个出错处的文件\n" -#: ../main.c:2187 +#: ../main.c:2098 msgid "" "\n" -"\n" -"Usage:" +"Options:\n" msgstr "" "\n" -"\n" -"用法:" +"选项:\n" -#: ../main.c:2189 -msgid " vim [arguments] " -msgstr " vim [参数] " - -#: ../main.c:2193 -msgid "" -"\n" -" or:" -msgstr "" -"\n" -" 或:" +#: ../main.c:2099 +msgid " -- Only file names after this\n" +msgstr " -- 在这以后只有文件名\n" -#: ../main.c:2196 -msgid "" -"\n" -"\n" -"Arguments:\n" -msgstr "" -"\n" -"\n" -"参数:\n" +#: ../main.c:2100 +msgid " + Start at end of file\n" +msgstr " + 从文件末尾开始\n" -#: ../main.c:2197 -msgid "--\t\t\tOnly file names after this" -msgstr "--\t\t\t在这以后只有文件名" +#: ../main.c:2101 +msgid " --cmd <cmd> Execute <cmd> before any config\n" +msgstr " --cmd <cmd> 加载任何配置前执行 <cmd>\n" -#: ../main.c:2199 -msgid "--literal\t\tDon't expand wildcards" -msgstr "--literal\t\t不扩展通配符" +#: ../main.c:2102 +msgid " +<cmd>, -c <cmd> Execute <cmd> after config and first file\n" +msgstr " +<cmd>, -c <cmd> 加载第一个配置和第一个文件后执行 <cmd>\n" -#: ../main.c:2201 -msgid "-v\t\t\tVi mode (like \"vi\")" -msgstr "-v\t\t\tVi 模式 (同 \"vi\")" +#: ../main.c:2104 +msgid " -b Binary mode\n" +msgstr " -b 二进制模式\n" -#: ../main.c:2202 -msgid "-e\t\t\tEx mode (like \"ex\")" -msgstr "-e\t\t\tEx 模式 (同 \"ex\")" +#: ../main.c:2105 +msgid " -d Diff mode\n" +msgstr " -d 差异模式\n" -#: ../main.c:2203 -msgid "-E\t\t\tImproved Ex mode" -msgstr "" +#: ../main.c:2106 +msgid " -e, -E Ex mode\n" +msgstr " -e, -E Ex 模式\n" -#: ../main.c:2204 -msgid "-s\t\t\tSilent (batch) mode (only for \"ex\")" -msgstr "-s\t\t\t安静(批处理)模式 (只能与 \"ex\" 一起使用)" +#: ../main.c:2107 +msgid " -es, -Es Silent (batch) mode\n" +msgstr " -es, -Es 安静(批处理)模式\n" -#: ../main.c:2205 -msgid "-d\t\t\tDiff mode (like \"vimdiff\")" -msgstr "-d\t\t\tDiff 模式 (同 \"vimdiff\")" +#: ../main.c:2108 +msgid " -h, --help Print this help message\n" +msgstr " -h, --help 打印此帮助信息\n" -#: ../main.c:2206 -msgid "-y\t\t\tEasy mode (like \"evim\", modeless)" -msgstr "-y\t\t\t容易模式 (同 \"evim\",无模式)" +#: ../main.c:2109 +msgid " -i <shada> Use this shada file\n" +msgstr " -i <shada> 使用这个 shada 文件\n" -#: ../main.c:2207 -msgid "-R\t\t\tReadonly mode (like \"view\")" -msgstr "-R\t\t\t只读模式 (同 \"view\")" +#: ../main.c:2110 +msgid " -m Modifications (writing files) not allowed\n" +msgstr " -m 不可修改(写入文件)\n" -#: ../main.c:2209 -msgid "-m\t\t\tModifications (writing files) not allowed" -msgstr "-m\t\t\t不可修改(写入文件)" +#: ../main.c:2111 +msgid " -M Modifications in text not allowed\n" +msgstr " -M 文本不可修改\n" -#: ../main.c:2210 -msgid "-M\t\t\tModifications in text not allowed" -msgstr "-M\t\t\t文本不可修改" +#: ../main.c:2112 +msgid " -n No swap file, use memory only\n" +msgstr " -n 不使用交换文件,只使用内存\n" -#: ../main.c:2211 -msgid "-b\t\t\tBinary mode" -msgstr "-b\t\t\t二进制模式" +#: ../main.c:2113 +msgid " -o[N] Open N windows (default: one per file)\n" +msgstr " -o[N] 打开 N 个窗口(默认:每个文件一个)\n" -#: ../main.c:2212 -msgid "-l\t\t\tLisp mode" -msgstr "-l\t\t\tLisp 模式" +#: ../main.c:2114 +#, fuzzy +msgid "" +" -O[N] Open N vertical windows (default: one per file)\n" +msgstr "-o[N]\t\t打开 N 个窗口 (默认值: 每个文件一个)" -#: ../main.c:2213 -msgid "-C\t\t\tCompatible with Vi: 'compatible'" -msgstr "-C\t\t\t兼容传统的 Vi: 'compatible'" +#: ../main.c:2115 +msgid " -p[N] Open N tab pages (default: one per file)\n" +msgstr " -p[N] 打开 N 个标签页(默认:每个文件一个)\n" -#: ../main.c:2214 -msgid "-N\t\t\tNot fully Vi compatible: 'nocompatible'" -msgstr "-N\t\t\t不完全兼容传统的 Vi: 'nocompatible'" +#: ../main.c:2116 +msgid " -r, -L List swap files\n" +msgstr " -r, -L 列出交换文件\n" -#: ../main.c:2215 -msgid "-V[N][fname]\t\tBe verbose [level N] [log messages to fname]" -msgstr "-V[N][fname]\t\t详细 [level N] [log messages to fname]" +#: ../main.c:2117 +msgid " -r <file> Recover edit state for this file\n" +msgstr " -r <file> 恢复这个文件的编辑状态\n" -#: ../main.c:2216 -msgid "-D\t\t\tDebugging mode" -msgstr "-D\t\t\t调试模式" +#: ../main.c:2118 +msgid " -R Read-only mode\n" +msgstr " -R 只读模式\n" -#: ../main.c:2217 -msgid "-n\t\t\tNo swap file, use memory only" -msgstr "-n\t\t\t不使用交换文件,只使用内存" +#: ../main.c:2119 +msgid " -S <session> Source <session> after loading the first file\n" +msgstr " -S <session> 加载第一个文件后执行文件 <session>\n" -#: ../main.c:2218 -msgid "-r\t\t\tList swap files and exit" -msgstr "-r\t\t\t列出交换文件并退出" +#: ../main.c:2120 +msgid " -s <scriptin> Read Normal mode commands from <scriptin>\n" +msgstr " -s <scriptin> 从文件 <scriptin> 读入正常模式的命令\n" -#: ../main.c:2219 -msgid "-r (with file name)\tRecover crashed session" -msgstr "-r (跟文件名)\t\t恢复崩溃的会话" +#: ../main.c:2121 +msgid " -u <config> Use this config file\n" +msgstr " -u <config> 使用此配置文件\n" -#: ../main.c:2220 -msgid "-L\t\t\tSame as -r" -msgstr "-L\t\t\t同 -r" +#: ../main.c:2122 +msgid " -v, --version Print version information\n" +msgstr " -v, --version 打印版本信息\n" -#: ../main.c:2221 -msgid "-A\t\t\tstart in Arabic mode" -msgstr "-A\t\t\t以 Arabic 模式启动" +#: ../main.c:2123 +msgid " -V[N][file] Verbose [level][file]\n" +msgstr " -V[N][file] 啰嗦 [等级][文件]\n" -#: ../main.c:2222 -msgid "-H\t\t\tStart in Hebrew mode" -msgstr "-H\t\t\t以 Hebrew 模式启动" +#: ../main.c:2125 +msgid " --api-info Write msgpack-encoded API metadata to stdout\n" +msgstr " --api-info 将 msgpack 编码的 API 元数据写入标准输出\n" -#: ../main.c:2223 -msgid "-F\t\t\tStart in Farsi mode" -msgstr "-F\t\t\t以 Farsi 模式启动" +#: ../main.c:2126 +msgid "" +" --clean \"Factory defaults\" (skip user config and plugins, " +"shada)\n" +msgstr "" +" --clean \"出厂设置\"(跳过用户配置、plugin 和 shada)\n" -#: ../main.c:2224 -msgid "-T <terminal>\tSet terminal type to <terminal>" -msgstr "-T <terminal>\t设定终端类型为 <terminal>" +#: ../main.c:2127 +msgid " --embed Use stdin/stdout as a msgpack-rpc channel\n" +msgstr "" -#: ../main.c:2225 -msgid "-u <vimrc>\t\tUse <vimrc> instead of any .vimrc" -msgstr "-u <vimrc>\t\t使用 <vimrc> 替代任何 .vimrc" +#: ../main.c:2128 +msgid " --headless Don't start a user interface\n" +msgstr " --headless 不要启动用户界面\n" -#: ../main.c:2226 -msgid "--noplugin\t\tDon't load plugin scripts" -msgstr "--noplugin\t\t不加载 plugin 脚本" +#: ../main.c:2129 +msgid " --listen <address> Serve RPC API from this address\n" +msgstr " --listen <address> 在这个地址提供 RPC API\n" -#: ../main.c:2227 -msgid "-p[N]\t\tOpen N tab pages (default: one for each file)" -msgstr "-P[N]\t\t打开 N 个标签页 (默认值: 每个文件一个)" +#: ../main.c:2130 +msgid " --noplugin Don't load plugins\n" +msgstr " --noplugin 不加载 plugin 脚本\n" -#: ../main.c:2228 -msgid "-o[N]\t\tOpen N windows (default: one for each file)" -msgstr "-o[N]\t\t打开 N 个窗口 (默认值: 每个文件一个)" +#: ../main.c:2131 +msgid " --remote[-subcommand] Execute commands remotely on a server\n" +msgstr "" -#: ../main.c:2229 -msgid "-O[N]\t\tLike -o but split vertically" -msgstr "-O[N]\t\t同 -o 但垂直分割" +#: ../main.c:2132 +msgid " --server <address> Specify RPC server to send commands to\n" +msgstr "" -#: ../main.c:2230 -msgid "+\t\t\tStart at end of file" -msgstr "+\t\t\t启动后跳到文件末尾" +#: ../main.c:2133 +msgid " --startuptime <file> Write startup timing messages to <file>\n" +msgstr " --startuptime <file> 将启动时间信息写入到文件 <file>\n" -#: ../main.c:2231 -msgid "+<lnum>\t\tStart at line <lnum>" -msgstr "+<lnum>\t\t启动后跳到第 <lnum> 行" +#: ../main.c:2134 +msgid "" +"\n" +"See \":help startup-options\" for all options.\n" +msgstr "" -#: ../main.c:2232 -msgid "--cmd <command>\tExecute <command> before loading any vimrc file" -msgstr "--cmd <command>\t加载任何 vimrc 文件前执行 <command>" +#: ../mapping.c:645 +#, c-format +msgid "E224: global abbreviation already exists for %s" +msgstr "E224: 全局缩写 %s 已存在" -#: ../main.c:2233 -msgid "-c <command>\t\tExecute <command> after loading the first file" -msgstr "-c <command>\t\t加载第一个文件后执行 <command>" +#: ../mapping.c:648 +#, c-format +msgid "E225: global mapping already exists for %s" +msgstr "E225: 全局映射 %s 已存在" -#: ../main.c:2235 -msgid "-S <session>\t\tSource file <session> after loading the first file" -msgstr "-S <session>\t\t加载第一个文件后执行文件 <session>" +#: ../mapping.c:762 +#, c-format +msgid "E226: abbreviation already exists for %s" +msgstr "E226: 缩写 %s 已存在" -#: ../main.c:2236 -msgid "-s <scriptin>\tRead Normal mode commands from file <scriptin>" -msgstr "-s <scriptin>\t从文件 <scriptin> 读入正常模式的命令" +#: ../mapping.c:764 +#, c-format +msgid "E227: mapping already exists for %s" +msgstr "E227: 映射 %s 已存在" -#: ../main.c:2237 -msgid "-w <scriptout>\tAppend all typed commands to file <scriptout>" -msgstr "-w <scriptout>\t将所有输入的命令追加到文件 <scriptout>" +#: ../mapping.c:844 +msgid "No abbreviation found" +msgstr "找不到缩写" -#: ../main.c:2238 -msgid "-W <scriptout>\tWrite all typed commands to file <scriptout>" -msgstr "-W <scriptout>\t将所有输入的命令写入到文件 <scriptout>" +#: ../mapping.c:846 +msgid "No mapping found" +msgstr "找不到映射" -#: ../main.c:2240 -msgid "--startuptime <file>\tWrite startup timing messages to <file>" -msgstr "--startuptime <file>\t将启动时间信息写入到文件 <file>" +#: ../mapping.c:1751 +msgid "E228: makemap: Illegal mode" +msgstr "E228: makemap: 无效的模式" -#: ../main.c:2242 -msgid "-i <viminfo>\t\tUse <viminfo> instead of .viminfo" -msgstr "-i <viminfo>\t\t使用 <viminfo> 取代 .viminfo" +#: ../mapping.c:2181 +msgid "E460: entries missing in mapset() dict argument" +msgstr "E460: mapset() dict 参数缺少项" -#: ../main.c:2243 -msgid "-h or --help\tPrint Help (this message) and exit" -msgstr "-h 或 --help\t打印帮助(本信息)并退出" +#: ../mapping.c:2389 +#, c-format +msgid "E357: 'langmap': Matching character missing for %s" +msgstr "E357: 'langmap': 找不到 %s 对应的字符" -#: ../main.c:2244 -msgid "--version\t\tPrint version information and exit" -msgstr "--version\t\t打印版本信息并退出" +#: ../mapping.c:2409 +#, c-format +msgid "E358: 'langmap': Extra characters after semicolon: %s" +msgstr "E358: 'langmap': 分号后有多余的字符: %s" -#: ../mark.c:676 +#: ../mark.c:879 msgid "No marks set" msgstr "没有设定标记" -#: ../mark.c:678 +#: ../mark.c:881 #, c-format msgid "E283: No marks matching \"%s\"" msgstr "E283: 没有匹配 \"%s\" 的标记" #. Highlight title -#: ../mark.c:687 +#: ../mark.c:895 msgid "" "\n" "mark line col file/text" @@ -3594,7 +4228,7 @@ msgstr "" "标记 行 列 文件/文本" #. Highlight title -#: ../mark.c:789 +#: ../mark.c:1002 msgid "" "\n" " jump line col file/text" @@ -3603,7 +4237,7 @@ msgstr "" " 跳转 行 列 文件/文本" #. Highlight title -#: ../mark.c:831 +#: ../mark.c:1054 msgid "" "\n" "change line col text" @@ -3611,109 +4245,159 @@ msgstr "" "\n" " 改变 行 列 文本" -#: ../mark.c:1238 -msgid "" -"\n" -"# File marks:\n" +#: ../match.c:73 +#, c-format +msgid "E799: Invalid ID: %<PRId64> (must be greater than or equal to 1)" +msgstr "E799: ID 无效:%<PRId64>(必须大于等于 1)" + +#: ../match.c:85 +#, c-format +msgid "E801: ID already taken: %<PRId64>" +msgstr "E801: ID 已被使用:%<PRId64>" + +#: ../match.c:140 +#, c-format +msgid "E5030: Empty list at position %d" msgstr "" -"\n" -"# 文件标记:\n" -#. Write the jumplist with -' -#: ../mark.c:1271 -msgid "" -"\n" -"# Jumplist (newest first):\n" +#: ../match.c:182 +#, c-format +msgid "E5031: List or number required at position %d" +msgstr "E5031: 位置 %d 需要列表或整数" + +#: ../match.c:252 +#, c-format +msgid "E802: Invalid ID: %<PRId64> (must be greater than or equal to 1)" +msgstr "E802: 无效的 ID:%<PRId64>(必须大于等于 1)" + +#: ../match.c:263 +#, c-format +msgid "E803: ID not found: %<PRId64>" +msgstr "E803: 找不到 ID:%<PRId64>" + +#: ../match.c:978 +#, c-format +msgid "E474: List item %d is either not a dictionary or an empty one" msgstr "" -"\n" -"# 跳转列表 (从新到旧):\n" -#: ../mark.c:1352 -msgid "" -"\n" -"# History of marks within files (newest to oldest):\n" +#: ../match.c:987 +#, c-format +msgid "E474: List item %d is missing one of the required keys" msgstr "" -"\n" -"# 文件内的标记历史记录 (从新到旧):\n" -#: ../mark.c:1431 -msgid "Missing '>'" -msgstr "缺少 '>'" +#: ../match.c:1093 +#, c-format +msgid "E798: ID is reserved for \":match\": %<PRId64>" +msgstr "E798: ID 为 \":match\" 保留:%<PRId64>" -#: ../memfile.c:426 +#: ../match.c:1144 +#, c-format +msgid "E798: ID is reserved for \"match\": %<PRId64>" +msgstr "E798: ID 为 \"match\" 保留:%<PRId64>" + +#: ../mbyte.c:98 +#, c-format +msgid "E1109: List item %d is not a List" +msgstr "E1109: 列表项 %d 不是列表" + +#: ../mbyte.c:100 +#, c-format +msgid "E1110: List item %d does not contain 3 numbers" +msgstr "E1110: 列表项 %d 不包含三个整数" + +#: ../mbyte.c:102 +#, c-format +msgid "E1111: List item %d range invalid" +msgstr "E1111: 列表项 %d 范围无效" + +#: ../mbyte.c:104 +#, c-format +msgid "E1112: List item %d cell width invalid" +msgstr "E1112: 列表项 %d 单元格宽度无效" + +#: ../mbyte.c:106 +#, c-format +msgid "E1113: Overlapping ranges for 0x%lx" +msgstr "E1113: 重叠范围为 0x%lx" + +#: ../mbyte.c:108 +msgid "E1114: Only values of 0x100 and higher supported" +msgstr "E1114: 只支持 0x100 或更高的值" + +#: ../memfile.c:334 msgid "E293: block was not locked" msgstr "E293: 块未被锁定" -#: ../memfile.c:799 +#: ../memfile.c:582 msgid "E294: Seek error in swap file read" msgstr "E294: 交换文件读取定位错误" -#: ../memfile.c:803 +#: ../memfile.c:589 msgid "E295: Read error in swap file" msgstr "E295: 交换文件读取错误" -#: ../memfile.c:849 +#: ../memfile.c:641 msgid "E296: Seek error in swap file write" msgstr "E296: 交换文件写入定位错误" -#: ../memfile.c:865 +#: ../memfile.c:657 msgid "E297: Write error in swap file" msgstr "E297: 交换文件写入错误" -#: ../memfile.c:1036 +#: ../memfile.c:801 msgid "E300: Swap file already exists (symlink attack?)" msgstr "E300: 交换文件已存在 (符号连接攻击?)" -#: ../memline.c:318 +#: ../memline.c:288 msgid "E298: Didn't get block nr 0?" msgstr "E298: 找不到块 0?" -#: ../memline.c:361 +#: ../memline.c:328 msgid "E298: Didn't get block nr 1?" msgstr "E298: 找不到块 1?" -#: ../memline.c:377 +#: ../memline.c:342 msgid "E298: Didn't get block nr 2?" msgstr "E298: 找不到块 2?" #. could not (re)open the swap file, what can we do???? -#: ../memline.c:465 +#: ../memline.c:424 msgid "E301: Oops, lost the swap file!!!" msgstr "E301: 噢,交换文件不见了!!!" -#: ../memline.c:477 +#: ../memline.c:430 msgid "E302: Could not rename swap file" msgstr "E302: 无法重命名交换文件" -#: ../memline.c:554 +#: ../memline.c:504 #, c-format msgid "E303: Unable to open swap file for \"%s\", recovery impossible" msgstr "E303: 无法打开 \"%s\" 的交换文件,恢复将不可能" -#: ../memline.c:666 +#: ../memline.c:609 msgid "E304: ml_upd_block0(): Didn't get block 0??" msgstr "E304: ml_upd_block0(): 找不到块 0?" #. no swap files found -#: ../memline.c:830 +#: ../memline.c:745 #, c-format msgid "E305: No swap file found for %s" msgstr "E305: 找不到 %s 的交换文件" -#: ../memline.c:839 +#: ../memline.c:755 msgid "Enter number of swap file to use (0 to quit): " msgstr "请输入要使用的交换文件编号 (0 退出): " -#: ../memline.c:879 +#: ../memline.c:791 #, c-format msgid "E306: Cannot open %s" msgstr "E306: 无法打开 %s" -#: ../memline.c:897 +#: ../memline.c:805 msgid "Unable to read block 0 from " msgstr "无法读取块 0: " -#: ../memline.c:900 +#: ../memline.c:807 msgid "" "\n" "Maybe no changes were made or Vim did not update the swap file." @@ -3721,28 +4405,28 @@ msgstr "" "\n" "可能你没做过任何修改或是 Vim 还来不及更新交换文件。" -#: ../memline.c:909 +#: ../memline.c:816 msgid " cannot be used with this version of Vim.\n" msgstr " 不能在该版本的 Vim 中使用。\n" -#: ../memline.c:911 +#: ../memline.c:818 msgid "Use Vim version 3.0.\n" msgstr "使用 Vim 3.0。\n" -#: ../memline.c:916 +#: ../memline.c:823 #, c-format msgid "E307: %s does not look like a Vim swap file" msgstr "E307: %s 看起来不像是 Vim 交换文件" -#: ../memline.c:922 +#: ../memline.c:829 msgid " cannot be used on this computer.\n" msgstr " 不能在这台电脑上使用。\n" -#: ../memline.c:924 +#: ../memline.c:831 msgid "The file was created on " msgstr "此文件创建于 " -#: ../memline.c:928 +#: ../memline.c:835 msgid "" ",\n" "or the file has been damaged." @@ -3750,100 +4434,92 @@ msgstr "" ",\n" "或是此文件已损坏。" -#: ../memline.c:945 +#: ../memline.c:849 msgid " has been damaged (page size is smaller than minimum value).\n" msgstr "已损坏(页面大小小于最小值)。\n" -#: ../memline.c:974 +#: ../memline.c:879 #, c-format msgid "Using swap file \"%s\"" msgstr "使用交换文件 \"%s\"" -#: ../memline.c:980 +#: ../memline.c:886 #, c-format msgid "Original file \"%s\"" msgstr "原始文件 \"%s\"" -#: ../memline.c:995 +#: ../memline.c:899 msgid "E308: Warning: Original file may have been changed" msgstr "E308: 警告: 原始文件可能已被修改" -#: ../memline.c:1061 +#: ../memline.c:958 #, c-format msgid "E309: Unable to read block 1 from %s" msgstr "E309: 无法从 %s 读取块 1" # do not translate to avoid writing Chinese in files -#: ../memline.c:1065 -#, fuzzy +#: ../memline.c:962 msgid "???MANY LINES MISSING" -msgstr "???缺少了太多行" +msgstr "" # do not translate to avoid writing Chinese in files -#: ../memline.c:1076 -#, fuzzy +#: ../memline.c:974 msgid "???LINE COUNT WRONG" -msgstr "???行数错误" +msgstr "" # do not translate to avoid writing Chinese in files -#: ../memline.c:1082 -#, fuzzy +#: ../memline.c:980 msgid "???EMPTY BLOCK" -msgstr "???空的块" +msgstr "" # do not translate to avoid writing Chinese in files -#: ../memline.c:1103 -#, fuzzy +#: ../memline.c:1000 msgid "???LINES MISSING" -msgstr "???缺少了一些行" +msgstr "" -#: ../memline.c:1128 +#: ../memline.c:1023 #, c-format msgid "E310: Block 1 ID wrong (%s not a .swp file?)" msgstr "E310: 块 1 ID 错误 (%s 不是交换文件?)" # do not translate to avoid writing Chinese in files -#: ../memline.c:1133 -#, fuzzy +#: ../memline.c:1028 msgid "???BLOCK MISSING" -msgstr "???缺少块" +msgstr "" # do not translate to avoid writing Chinese in files -#: ../memline.c:1147 -#, fuzzy +#: ../memline.c:1038 msgid "??? from here until ???END lines may be messed up" -msgstr "??? 从这里到 ???END 的行可能已混乱" +msgstr "" # do not translate to avoid writing Chinese in files -#: ../memline.c:1164 -#, fuzzy +#: ../memline.c:1052 msgid "??? from here until ???END lines may have been inserted/deleted" -msgstr "??? 从这里到 ???END 的行可能已被插入/删除过" +msgstr "" # do not translate to avoid writing Chinese in files -#: ../memline.c:1181 -#, fuzzy +#: ../memline.c:1071 msgid "???END" -msgstr "???END" +msgstr "" -#: ../memline.c:1238 +#: ../memline.c:1125 msgid "E311: Recovery Interrupted" msgstr "E311: 恢复已被中断" -#: ../memline.c:1243 +#: ../memline.c:1129 msgid "" "E312: Errors detected while recovering; look for lines starting with ???" msgstr "E312: 恢复时发生错误;请注意开头为 ??? 的行" -#: ../memline.c:1245 +#: ../memline.c:1131 msgid "See \":help E312\" for more information." msgstr "更多信息请见 \":help E312\"" -#: ../memline.c:1249 +#: ../memline.c:1135 msgid "Recovery completed. You should check if everything is OK." msgstr "恢复完毕。请确定一切正常。" -#: ../memline.c:1251 +#: ../memline.c:1136 msgid "" "\n" "(You might want to write out this file under another name\n" @@ -3851,71 +4527,74 @@ msgstr "" "\n" "(你可能想要将这个文件另存为别的文件名\n" -#: ../memline.c:1252 -#, fuzzy +#: ../memline.c:1137 msgid "and run diff with the original file to check for changes)" -msgstr "再运行 diff 与原文件比较以检查是否有改变)\n" +msgstr "再运行 diff 与原文件比较以检查是否有改变)" -#: ../memline.c:1254 +#: ../memline.c:1139 msgid "Recovery completed. Buffer contents equals file contents." msgstr "恢复完成。缓冲区内容与文件内容相同。" -#: ../memline.c:1255 -#, fuzzy +#: ../memline.c:1141 msgid "" "\n" "You may want to delete the .swp file now.\n" "\n" msgstr "" -"然后把 .swp 文件删掉。\n" +"\n" +"你现在可以删除 .swp 文件了。\n" "\n" #. use msg() to start the scrolling properly -#: ../memline.c:1327 +#: ../memline.c:1206 msgid "Swap files found:" msgstr "找到交换文件:" -#: ../memline.c:1446 +#: ../memline.c:1311 msgid " In current directory:\n" msgstr " 位于当前目录:\n" -#: ../memline.c:1448 +#: ../memline.c:1313 msgid " Using specified name:\n" msgstr " 使用指定的名字:\n" -#: ../memline.c:1450 +#: ../memline.c:1316 msgid " In directory " msgstr " 位于目录 " -#: ../memline.c:1465 +#: ../memline.c:1331 msgid " -- none --\n" msgstr " -- 无 --\n" -#: ../memline.c:1527 +#: ../memline.c:1429 msgid " owned by: " msgstr " 所有者: " -#: ../memline.c:1529 +#: ../memline.c:1431 msgid " dated: " msgstr " 日期: " -#: ../memline.c:1532 ../memline.c:3231 +#: ../memline.c:1433 ../memline.c:1436 ../memline.c:3084 msgid " dated: " msgstr " 日期: " -#: ../memline.c:1548 +#: ../memline.c:1448 msgid " [from Vim version 3.0]" msgstr " [来自 Vim 版本 3.0]" -#: ../memline.c:1550 +#: ../memline.c:1450 msgid " [does not look like a Vim swap file]" msgstr " [不像是 Vim 交换文件]" -#: ../memline.c:1552 +#: ../memline.c:1452 +msgid " [garbled strings (not nul terminated)]" +msgstr "" + +#: ../memline.c:1454 msgid " file name: " msgstr " 文件名: " -#: ../memline.c:1558 +#: ../memline.c:1461 msgid "" "\n" " modified: " @@ -3923,15 +4602,15 @@ msgstr "" "\n" " 修改过: " -#: ../memline.c:1559 +#: ../memline.c:1462 msgid "YES" msgstr "是" -#: ../memline.c:1559 +#: ../memline.c:1462 msgid "no" msgstr "否" -#: ../memline.c:1562 +#: ../memline.c:1465 msgid "" "\n" " user name: " @@ -3939,11 +4618,11 @@ msgstr "" "\n" " 用户名: " -#: ../memline.c:1568 +#: ../memline.c:1471 msgid " host name: " msgstr " 主机名: " -#: ../memline.c:1570 +#: ../memline.c:1473 msgid "" "\n" " host name: " @@ -3951,7 +4630,7 @@ msgstr "" "\n" " 主机名: " -#: ../memline.c:1575 +#: ../memline.c:1479 msgid "" "\n" " process ID: " @@ -3959,11 +4638,11 @@ msgstr "" "\n" " 进程 ID: " -#: ../memline.c:1579 -msgid " (still running)" -msgstr " (仍在运行)" +#: ../memline.c:1482 +msgid " (STILL RUNNING)" +msgstr " (还!在!运!行!)" -#: ../memline.c:1586 +#: ../memline.c:1488 msgid "" "\n" " [not usable on this computer]" @@ -3971,97 +4650,97 @@ msgstr "" "\n" " [不能在本机上使用]" -#: ../memline.c:1590 +#: ../memline.c:1492 msgid " [cannot be read]" msgstr " [无法读取]" -#: ../memline.c:1593 +#: ../memline.c:1496 msgid " [cannot be opened]" msgstr " [无法打开]" -#: ../memline.c:1698 +#: ../memline.c:1638 msgid "E313: Cannot preserve, there is no swap file" msgstr "E313: 无法保留,没有交换文件" -#: ../memline.c:1747 +#: ../memline.c:1687 msgid "File preserved" msgstr "文件已保留" -#: ../memline.c:1749 +#: ../memline.c:1689 msgid "E314: Preserve failed" msgstr "E314: 保留失败" -#: ../memline.c:1819 +#: ../memline.c:1741 #, c-format msgid "E315: ml_get: invalid lnum: %<PRId64>" msgstr "E315: ml_get: 无效的 lnum: %<PRId64>" -#: ../memline.c:1851 -#, c-format -msgid "E316: ml_get: cannot find line %<PRId64>" +#: ../memline.c:1777 +#, fuzzy, c-format +msgid "E316: ml_get: cannot find line %<PRId64> in buffer %d %s" msgstr "E316: ml_get: 找不到第 %<PRId64> 行" -#: ../memline.c:2236 +#: ../memline.c:2128 msgid "E317: pointer block id wrong 3" msgstr "E317: 指针块 id 错误 3" -#: ../memline.c:2311 +#: ../memline.c:2202 msgid "stack_idx should be 0" msgstr "stack_idx 应该是 0" -#: ../memline.c:2369 +#: ../memline.c:2257 msgid "E318: Updated too many blocks?" msgstr "E318: 更新了太多的块?" -#: ../memline.c:2511 +#: ../memline.c:2432 msgid "E317: pointer block id wrong 4" msgstr "E317: 指针块 id 错误 4" -#: ../memline.c:2536 +#: ../memline.c:2458 msgid "deleted block 1?" msgstr "删除了块 1?" -#: ../memline.c:2707 +#: ../memline.c:2603 #, c-format msgid "E320: Cannot find line %<PRId64>" msgstr "E320: 找不到第 %<PRId64> 行" -#: ../memline.c:2916 +#: ../memline.c:2795 msgid "E317: pointer block id wrong" msgstr "E317: 指针块 id 错误" -#: ../memline.c:2930 +#: ../memline.c:2810 msgid "pe_line_count is zero" msgstr "pe_line_count 为零" -#: ../memline.c:2955 +#: ../memline.c:2833 #, c-format msgid "E322: line number out of range: %<PRId64> past the end" msgstr "E322: 行号超出范围: %<PRId64> 超出结尾" -#: ../memline.c:2959 +#: ../memline.c:2836 #, c-format msgid "E323: line count wrong in block %<PRId64>" msgstr "E323: 块 %<PRId64> 行数错误" -#: ../memline.c:2999 +#: ../memline.c:2874 msgid "Stack size increases" msgstr "堆栈大小增加" -#: ../memline.c:3038 +#: ../memline.c:2906 msgid "E317: pointer block id wrong 2" msgstr "E317: 指针块 id 错误 2" -#: ../memline.c:3070 +#: ../memline.c:2938 #, c-format msgid "E773: Symlink loop for \"%s\"" msgstr "E773: \"%s\" 符号连接出现循环" -#: ../memline.c:3221 +#: ../memline.c:3072 msgid "E325: ATTENTION" msgstr "E325: 注意" -#: ../memline.c:3222 +#: ../memline.c:3073 msgid "" "\n" "Found a swap file by the name \"" @@ -4069,44 +4748,42 @@ msgstr "" "\n" "发现交换文件 \"" -#: ../memline.c:3226 +#: ../memline.c:3077 msgid "While opening file \"" msgstr "正在打开文件 \"" -#: ../memline.c:3239 +#: ../memline.c:3082 +#, fuzzy +msgid " CANNOT BE FOUND" +msgstr " 找不到" + +#: ../memline.c:3089 msgid " NEWER than swap file!\n" msgstr " 比交换文件新!\n" -#: ../memline.c:3244 -#, fuzzy +#. Some of these messages are long to allow translation to +#. other languages. +#: ../memline.c:3094 msgid "" "\n" "(1) Another program may be editing the same file. If this is the case,\n" " be careful not to end up with two different instances of the same\n" -" file when making changes." +" file when making changes. Quit, or continue with caution.\n" msgstr "" "\n" "(1) 另一个程序可能也在编辑同一个文件。\n" " 如果是这样,修改时请注意避免同一个文件产生两个不同的版本。\n" -"\n" +" 退出,或者小心地继续。\n" -#: ../memline.c:3245 -#, fuzzy -msgid " Quit, or continue with caution.\n" -msgstr " 退出,或小心地继续。\n" - -#: ../memline.c:3246 -#, fuzzy +#: ../memline.c:3098 msgid "(2) An edit session for this file crashed.\n" -msgstr "" -"\n" -"(2) 上次编辑此文件时崩溃。\n" +msgstr "(2) 上次编辑此文件时崩溃。\n" -#: ../memline.c:3247 +#: ../memline.c:3099 msgid " If this is the case, use \":recover\" or \"vim -r " msgstr " 如果是这样,请用 \":recover\" 或 \"vim -r " -#: ../memline.c:3249 +#: ../memline.c:3101 msgid "" "\"\n" " to recover the changes (see \":help recovery\").\n" @@ -4114,11 +4791,11 @@ msgstr "" "\"\n" " 恢复修改的内容 (请见 \":help recovery\")。\n" -#: ../memline.c:3250 +#: ../memline.c:3102 msgid " If you did this already, delete the swap file \"" msgstr " 如果你已经进行了恢复,请删除交换文件 \"" -#: ../memline.c:3252 +#: ../memline.c:3104 msgid "" "\"\n" " to avoid this message.\n" @@ -4126,23 +4803,23 @@ msgstr "" "\"\n" " 以避免再看到此消息。\n" -#: ../memline.c:3450 ../memline.c:3452 +#: ../memline.c:3270 +msgid "Found a swap file that is not useful, deleting it" +msgstr "找到一个没用的交换文件,删了" + +#: ../memline.c:3296 msgid "Swap file \"" msgstr "交换文件 \"" -#: ../memline.c:3451 ../memline.c:3455 +#: ../memline.c:3297 msgid "\" already exists!" msgstr "\" 已存在!" -#: ../memline.c:3457 +#: ../memline.c:3309 msgid "VIM - ATTENTION" msgstr "VIM - 注意" -#: ../memline.c:3459 -msgid "Swap file already exists!" -msgstr "交换文件已存在!" - -#: ../memline.c:3464 +#: ../memline.c:3312 msgid "" "&Open Read-Only\n" "&Edit anyway\n" @@ -4156,7 +4833,7 @@ msgstr "" "退出(&Q)\n" "中止(&A)" -#: ../memline.c:3467 +#: ../memline.c:3314 msgid "" "&Open Read-Only\n" "&Edit anyway\n" @@ -4172,57 +4849,62 @@ msgstr "" "退出(&Q)\n" "中止(&A)" -#. -#. * Change the ".swp" extension to find another file that can be used. -#. * First decrement the last char: ".swo", ".swn", etc. -#. * If that still isn't enough decrement the last but one char: ".svz" -#. * Can happen when editing many "No Name" buffers. -#. +#. Change the ".swp" extension to find another file that can be used. +#. First decrement the last char: ".swo", ".swn", etc. +#. If that still isn't enough decrement the last but one char: ".svz" +#. Can happen when editing many "No Name" buffers. #. ".s?a" #. ".saa": tried enough, give up -#: ../memline.c:3528 +#: ../memline.c:3370 msgid "E326: Too many swap files found" msgstr "E326: 找到太多交换文件" -#: ../memory.c:227 +#: ../memline.c:3386 +#, c-format +msgid "" +"E303: Unable to create directory \"%s\" for swap file, recovery impossible: " +"%s" +msgstr "E303: 无法为交换文件创建目录 \"%s\",恢复将不可能:%s" + +#: ../memory.c:197 +msgid "Vim: Data too large to fit into virtual memory space\n" +msgstr "Vim:数据太大无法放入虚拟内存空间\n" + +#: ../memory.c:527 #, c-format msgid "E342: Out of memory! (allocating %<PRIu64> bytes)" msgstr "E342: 内存不足!(分配 %<PRIu64> 字节)" -#: ../menu.c:62 +#: ../menu.c:52 msgid "E327: Part of menu-item path is not sub-menu" msgstr "E327: 菜单项的某部分路径不是子菜单" -#: ../menu.c:63 -msgid "E328: Menu only exists in another mode" -msgstr "E328: 菜单只在其它模式中存在" - -#: ../menu.c:64 +#: ../menu.c:53 #, c-format msgid "E329: No menu \"%s\"" msgstr "E329: 没有菜单 \"%s\"" #. Only a mnemonic or accelerator is not valid. -#: ../menu.c:329 -#, fuzzy +#: ../menu.c:310 msgid "E792: Empty menu name" -msgstr "E749: 空的缓冲区" +msgstr "E792: 空的菜单名称" -#: ../menu.c:340 +#: ../menu.c:321 msgid "E330: Menu path must not lead to a sub-menu" msgstr "E330: 菜单路径不能指向子菜单" -#: ../menu.c:365 +#: ../menu.c:347 msgid "E331: Must not add menu items directly to menu bar" msgstr "E331: 不能把菜单项直接加到菜单栏中" -#: ../menu.c:370 +#: ../menu.c:352 msgid "E332: Separator cannot be part of a menu path" msgstr "E332: 分隔线不能是菜单路径的一部分" +#. When there are no menus at all, the title still needs to be shown. #. Now we have found the matching menu, and we list the mappings #. Highlight title -#: ../menu.c:762 +#: ../menu.c:805 msgid "" "\n" "--- Menus ---" @@ -4230,69 +4912,85 @@ msgstr "" "\n" "--- 菜单 ---" -#: ../menu.c:1313 +#: ../menu.c:1568 +#, c-format +msgid "E335: Menu not defined for %s mode" +msgstr "E335: %s 模式中菜单未定义" + +#: ../menu.c:1588 msgid "E333: Menu path must lead to a menu item" msgstr "E333: 菜单路径必须指向菜单项" -#: ../menu.c:1330 +#: ../menu.c:1608 #, c-format msgid "E334: Menu not found: %s" msgstr "E334: 找不到菜单: %s" -#: ../menu.c:1396 -#, c-format -msgid "E335: Menu not defined for %s mode" -msgstr "E335: %s 模式中菜单未定义" - -#: ../menu.c:1426 +#: ../menu.c:1670 msgid "E336: Menu path must lead to a sub-menu" msgstr "E336: 菜单路径必须指向子菜单" -#: ../menu.c:1447 +#: ../menu.c:1694 msgid "E337: Menu not found - check menu names" msgstr "E337: 找不到菜单 - 请检查菜单名称" -#: ../message.c:423 +#: ../message.c:562 #, c-format msgid "Error detected while processing %s:" msgstr "处理 %s 时发生错误:" -#: ../message.c:445 +#: ../message.c:584 #, c-format msgid "line %4ld:" msgstr "第 %4ld 行:" -#: ../message.c:617 +#: ../message.c:777 #, c-format msgid "E354: Invalid register name: '%s'" msgstr "E354: 无效的寄存器名: '%s'" -#: ../message.c:986 +#: ../message.c:1319 msgid "Interrupt: " msgstr "已中断: " -#: ../message.c:988 +#: ../message.c:1322 msgid "Press ENTER or type command to continue" msgstr "请按 ENTER 或其它命令继续" -#: ../message.c:1843 +#: ../message.c:1367 +#, c-format +msgid "%ld more line" +msgid_plural "%ld more lines" +msgstr[0] "多了 %ld 行" + +#: ../message.c:1371 +#, c-format +msgid "%ld line less" +msgid_plural "%ld fewer lines" +msgstr[0] "少了 %ld 行" + +#: ../message.c:1375 +msgid " (Interrupted)" +msgstr " (已中断)" + +#: ../message.c:2473 #, c-format msgid "%s line %<PRId64>" msgstr "%s 第 %<PRId64> 行" -#: ../message.c:2392 +#: ../message.c:3057 msgid "-- More --" msgstr "-- 更多 --" -#: ../message.c:2398 +#: ../message.c:3062 msgid " SPACE/d/j: screen/page/line down, b/u/k: up, q: quit " msgstr " 空格/d/j: 屏幕/页/行 下翻,b/u/k: 上翻,q: 退出 " -#: ../message.c:3021 ../message.c:3031 +#: ../message.c:3768 ../message.c:3779 msgid "Question" msgstr "问题" -#: ../message.c:3023 +#: ../message.c:3770 msgid "" "&Yes\n" "&No" @@ -4300,7 +4998,7 @@ msgstr "" "是(&Y)\n" "否(&N)" -#: ../message.c:3033 +#: ../message.c:3781 msgid "" "&Yes\n" "&No\n" @@ -4310,7 +5008,7 @@ msgstr "" "否(&N)\n" "取消(&C)" -#: ../message.c:3045 +#: ../message.c:3795 msgid "" "&Yes\n" "&No\n" @@ -4324,207 +5022,123 @@ msgstr "" "全部丢弃(&D)\n" "取消(&C)" -#: ../message.c:3058 -msgid "E766: Insufficient arguments for printf()" -msgstr "E766: printf() 的参数不足" - -#: ../message.c:3119 -#, fuzzy -msgid "E807: Expected Float argument for printf()" -msgstr "E766: printf() 的参数不足" - -#: ../message.c:3873 -msgid "E767: Too many arguments to printf()" -msgstr "E767: printf() 的参数过多" - -#: ../misc1.c:2256 -msgid "W10: Warning: Changing a readonly file" -msgstr "W10: 警告: 正在修改一个只读文件" - -#: ../misc1.c:2537 -#, fuzzy -msgid "Type number and <Enter> or click with mouse (empty cancels): " -msgstr "请输入数字或点击鼠标 (<Enter> 取消): " - -#: ../misc1.c:2539 -#, fuzzy -msgid "Type number and <Enter> (empty cancels): " -msgstr "请选择数字 (<Enter> 取消): " - -#: ../misc1.c:2585 -msgid "1 more line" -msgstr "多了 1 行" - -#: ../misc1.c:2588 -msgid "1 line less" -msgstr "少了 1 行" - -#: ../misc1.c:2593 -#, c-format -msgid "%<PRId64> more lines" -msgstr "多了 %<PRId64> 行" - -#: ../misc1.c:2596 -#, c-format -msgid "%<PRId64> fewer lines" -msgstr "少了 %<PRId64> 行" - -#: ../misc1.c:2599 -msgid " (Interrupted)" -msgstr " (已中断)" - -#: ../misc1.c:2635 -msgid "Beep!" -msgstr "Beep!" - -#: ../misc2.c:738 -#, c-format -msgid "Calling shell to execute: \"%s\"" -msgstr "调用 shell 执行: \"%s\"" - -#: ../normal.c:183 +#. nv_*(): functions called to handle Normal and Visual mode commands. +#. n_*(): functions called to handle Normal mode commands. +#. v_*(): functions called to handle Visual mode commands. +#: ../normal.c:120 msgid "E349: No identifier under cursor" msgstr "E349: 光标处没有识别字" -#: ../normal.c:1866 -msgid "E774: 'operatorfunc' is empty" -msgstr "E774: 'operatorfunc' 为空" - -#: ../normal.c:2637 -msgid "Warning: terminal cannot highlight" -msgstr "警告: 你的终端不能显示高亮" - -#: ../normal.c:2807 +#: ../normal.c:1633 msgid "E348: No string under cursor" msgstr "E348: 光标处没有字符串" -#: ../normal.c:3937 +#: ../normal.c:2983 msgid "E352: Cannot erase folds with current 'foldmethod'" msgstr "E352: 不能在当前的 'foldmethod' 下删除 fold" -#: ../normal.c:5897 +#: ../normal.c:4915 msgid "E664: changelist is empty" msgstr "E664: 改变列表为空" -#: ../normal.c:5899 +#: ../normal.c:4917 msgid "E662: At start of changelist" msgstr "E662: 已在改变列表的开始处" -#: ../normal.c:5901 +#: ../normal.c:4919 msgid "E663: At end of changelist" msgstr "E663: 已在改变列表的末尾处" -#: ../normal.c:7053 -msgid "Type :quit<Enter> to exit Nvim" -msgstr "输入 :quit<Enter> 退出 Vim" - -#: ../ops.c:248 -#, c-format -msgid "1 line %sed 1 time" -msgstr "1 行 %s 了 1 次" +#: ../normal.c:6081 +msgid "Type :qa! and press <Enter> to abandon all changes and exit Nvim" +msgstr "输入 :qa! 并按 <Enter> 来放弃所有更改并退出 Nvim" -#: ../ops.c:250 -#, c-format -msgid "1 line %sed %d times" -msgstr "1 行 %s 了 %d 次" +#: ../normal.c:6084 +msgid "Type :qa and press <Enter> to exit Nvim" +msgstr "输入 :qa 并按 <Enter> 退出 Nvim" -#: ../ops.c:253 -#, c-format -msgid "%<PRId64> lines %sed 1 time" -msgstr "%<PRId64> 行 %s 了 1 次" +#: ../ops.c:263 +#, fuzzy, c-format +msgid "%<PRId64> line %sed %d time" +msgid_plural "%<PRId64> line %sed %d times" +msgstr[0] "%<PRId64> 行 %s 了 %d 次" -#: ../ops.c:256 -#, c-format -msgid "%<PRId64> lines %sed %d times" -msgstr "%<PRId64> 行 %s 了 %d 次" +#: ../ops.c:265 +#, fuzzy, c-format +msgid "%<PRId64> lines %sed %d time" +msgid_plural "%<PRId64> lines %sed %d times" +msgstr[0] "%<PRId64> 行 %s 了 %d 次" -#: ../ops.c:592 +#: ../ops.c:662 #, c-format msgid "%<PRId64> lines to indent... " msgstr "缩进 %<PRId64> 行…… " -#: ../ops.c:634 -msgid "1 line indented " -msgstr "缩进了 1 行 " - -#: ../ops.c:636 +#: ../ops.c:705 #, c-format -msgid "%<PRId64> lines indented " -msgstr "缩进了 %<PRId64> 行 " +msgid "%<PRId64> line indented " +msgid_plural "%<PRId64> lines indented " +msgstr[0] "缩进了 %<PRId64> 行 " -#: ../ops.c:938 +#: ../ops.c:1088 msgid "E748: No previously used register" msgstr "E748: 没有前一个使用的寄存器" -#. must display the prompt -#: ../ops.c:1433 -msgid "cannot yank; delete anyway" -msgstr "无法复制;改为删除" - -#: ../ops.c:1929 -msgid "1 line changed" -msgstr "改变了 1 行" - -#: ../ops.c:1931 +#: ../ops.c:2122 #, c-format -msgid "%<PRId64> lines changed" -msgstr "改变了 %<PRId64> 行" +msgid "%<PRId64> line changed" +msgid_plural "%<PRId64> lines changed" +msgstr[0] "改变了 %<PRId64> 行" -#: ../ops.c:2521 -msgid "block of 1 line yanked" -msgstr "复制了 1 行的块" - -#: ../ops.c:2523 -msgid "1 line yanked" -msgstr "复制了 1 行" - -#: ../ops.c:2525 +#: ../ops.c:2786 #, c-format -msgid "block of %<PRId64> lines yanked" -msgstr "复制了 %<PRId64> 行的块" +msgid " into \"%c" +msgstr "进 \"%c " -#: ../ops.c:2528 -#, c-format -msgid "%<PRId64> lines yanked" -msgstr "复制了 %<PRId64> 行" +#: ../ops.c:2795 +#, fuzzy, c-format +msgid "block of %<PRId64> line yanked%s" +msgid_plural "block of %<PRId64> lines yanked%s" +msgstr[0] "复制了 %<PRId64> 行的块" + +#: ../ops.c:2799 +#, fuzzy, c-format +msgid "%<PRId64> line yanked%s" +msgid_plural "%<PRId64> lines yanked%s" +msgstr[0] "复制了 %<PRId64> 行" -#: ../ops.c:2710 +#: ../ops.c:3148 #, c-format msgid "E353: Nothing in register %s" msgstr "E353: 寄存器 %s 里没有东西" #. Highlight title -#: ../ops.c:3185 +#: ../ops.c:3779 msgid "" "\n" -"--- Registers ---" +"Type Name Content" msgstr "" "\n" -"--- 寄存器 ---" +"类型 名称 内容" -#: ../ops.c:4455 -msgid "Illegal register name" -msgstr "无效的寄存器名" +#: ../ops.c:4453 +#, c-format +msgid "%<PRId64> lines changed" +msgid_plural "%<PRId64> lines changed" +msgstr[0] "改变了 %<PRId64> 行" -#: ../ops.c:4533 +#: ../ops.c:5034 msgid "" -"\n" -"# Registers:\n" -msgstr "" -"\n" -"# 寄存器:\n" - -#: ../ops.c:4575 -#, c-format -msgid "E574: Unknown register type %d" -msgstr "E574: 未知的寄存器类型 %d" +"E883: search pattern and expression register may not contain two or more " +"lines" +msgstr "E883: 搜索模式和表达式寄存器只能包含一行文本" -#: ../ops.c:5089 +#: ../ops.c:5462 #, c-format msgid "%<PRId64> Cols; " msgstr "%<PRId64> 列; " -#: ../ops.c:5097 +#: ../ops.c:5471 #, c-format msgid "" "Selected %s%<PRId64> of %<PRId64> Lines; %<PRId64> of %<PRId64> Words; " @@ -4533,7 +5147,7 @@ msgstr "" "选择了 %s%<PRId64>/%<PRId64> 行; %<PRId64>/%<PRId64> 个词; %<PRId64>/" "%<PRId64> 个字节" -#: ../ops.c:5105 +#: ../ops.c:5480 #, c-format msgid "" "Selected %s%<PRId64> of %<PRId64> Lines; %<PRId64> of %<PRId64> Words; " @@ -4542,7 +5156,7 @@ msgstr "" "选择了 %s%<PRId64>/%<PRId64> 行; %<PRId64>/%<PRId64> 个词; %<PRId64>/" "%<PRId64> 个字符; %<PRId64>/%<PRId64> 个字节" -#: ../ops.c:5123 +#: ../ops.c:5500 #, c-format msgid "" "Col %s of %s; Line %<PRId64> of %<PRId64>; Word %<PRId64> of %<PRId64>; Byte " @@ -4551,7 +5165,7 @@ msgstr "" "第 %s/%s 列; 第 %<PRId64>/%<PRId64> 行; 第 %<PRId64>/%<PRId64> 个词; 第 " "%<PRId64>/%<PRId64> 个字节" -#: ../ops.c:5133 +#: ../ops.c:5510 #, c-format msgid "" "Col %s of %s; Line %<PRId64> of %<PRId64>; Word %<PRId64> of %<PRId64>; Char " @@ -4560,156 +5174,67 @@ msgstr "" "第 %s/%s 列; 第 %<PRId64>/%<PRId64> 行; 第 %<PRId64>/%<PRId64> 个词; 第 " "%<PRId64>/%<PRId64> 个字符; 第 %<PRId64>/%<PRId64> 个字节" -#: ../ops.c:5146 +#: ../ops.c:5528 #, c-format msgid "(+%<PRId64> for BOM)" msgstr "" -#: ../option.c:1238 -msgid "%<%f%h%m%=Page %N" -msgstr "%<%f%h%m%=页 %N" - -#: ../option.c:1574 -msgid "Thanks for flying Vim" -msgstr "感谢您选择 Vim" +#: ../ops.c:5648 +msgid "E774: 'operatorfunc' is empty" +msgstr "E774: 'operatorfunc' 为空" -#. found a mismatch: skip -#: ../option.c:2698 +#: ../option.c:107 msgid "E518: Unknown option" msgstr "E518: 未知的选项" -#: ../option.c:2709 -msgid "E519: Option not supported" -msgstr "E519: 不支持该选项" - -#: ../option.c:2740 +#: ../option.c:109 msgid "E520: Not allowed in a modeline" msgstr "E520: 不允许在 modeline 中使用" -#: ../option.c:2815 +#: ../option.c:111 +msgid "E992: Not allowed in a modeline when 'modelineexpr' is off" +msgstr "E992: 当 'modelineexpr' 关闭时不允许在 modeline 中使用" + +#: ../option.c:113 msgid "E846: Key code not set" msgstr "E846: 未设置键位代码" -#: ../option.c:2924 +#: ../option.c:115 msgid "E521: Number required after =" msgstr "E521: = 后面需要数字" -#: ../option.c:3226 ../option.c:3864 -msgid "E522: Not found in termcap" -msgstr "E522: Termcap 里面找不到" - -#: ../option.c:3335 -#, c-format -msgid "E539: Illegal character <%s>" -msgstr "E539: 无效的字符 <%s>" - -#: ../option.c:3862 -msgid "E529: Cannot set 'term' to empty string" -msgstr "E529: 不能设定 'term' 为空字符串" - -#: ../option.c:3885 -msgid "E589: 'backupext' and 'patchmode' are equal" -msgstr "E589: 'backupext' 和 'patchmode' 相等" - -#: ../option.c:3964 -msgid "E834: Conflicts with value of 'listchars'" -msgstr "E834: 与'listchars'中的值发生冲突" - -#: ../option.c:3966 -msgid "E835: Conflicts with value of 'fillchars'" -msgstr "E835: 与'fillchars'中的值冲突" - -#: ../option.c:4163 -msgid "E524: Missing colon" -msgstr "E524: 缺少冒号" - -#: ../option.c:4165 -msgid "E525: Zero length string" -msgstr "E525: 字符串长度为零" - -#: ../option.c:4220 -#, c-format -msgid "E526: Missing number after <%s>" -msgstr "E526: <%s> 后面缺少数字" - -#: ../option.c:4232 -msgid "E527: Missing comma" -msgstr "E527: 缺少逗号" - -#: ../option.c:4239 -msgid "E528: Must specify a ' value" -msgstr "E528: 必须指定一个 ' 值" - -#: ../option.c:4271 -msgid "E595: contains unprintable or wide character" -msgstr "E595: 包含不可显示字符或宽字符" - -#: ../option.c:4469 -#, c-format -msgid "E535: Illegal character after <%c>" -msgstr "E535: <%c> 后面有无效的字符" - -#: ../option.c:4534 -msgid "E536: comma required" -msgstr "E536: 需要逗号" - -#: ../option.c:4543 -#, c-format -msgid "E537: 'commentstring' must be empty or contain %s" -msgstr "E537: 'commentstring' 必须为空或包含 %s" - -#: ../option.c:4928 -msgid "E540: Unclosed expression sequence" -msgstr "E540: 没有结束的表达式序列" - -#: ../option.c:4932 -msgid "E541: too many items" -msgstr "E541: 项目过多" - -#: ../option.c:4934 -msgid "E542: unbalanced groups" -msgstr "E542: 错乱的组" - -#: ../option.c:5148 +#: ../option.c:117 msgid "E590: A preview window already exists" msgstr "E590: 预览窗口已存在" -#: ../option.c:5311 +#: ../option.c:2116 msgid "W17: Arabic requires UTF-8, do ':set encoding=utf-8'" msgstr "W17: Arabic 需要 UTF-8,请执行 ':set encoding=utf-8'" -#: ../option.c:5623 +#: ../option.c:2523 #, c-format msgid "E593: Need at least %d lines" msgstr "E593: 至少需要 %d 行" -#: ../option.c:5631 +#: ../option.c:2531 #, c-format msgid "E594: Need at least %d columns" msgstr "E594: 至少需要 %d 列" -#: ../option.c:6011 +#: ../option.c:3064 ../option.c:3616 #, c-format msgid "E355: Unknown option: %s" msgstr "E355: 未知的选项: %s" #. There's another character after zeros or the string -#. * is empty. In both cases, we are trying to set a -#. * num option using a string. -#: ../option.c:6037 -#, fuzzy, c-format +#. is empty. In both cases, we are trying to set a +#. num option using a string. +#: ../option.c:3092 +#, c-format msgid "E521: Number required: &%s = '%s'" -msgstr "E521: = 后面需要数字" - -#: ../option.c:6149 -msgid "" -"\n" -"--- Terminal codes ---" -msgstr "" -"\n" -"--- 终端编码 ---" +msgstr "E521: 需要整数:&%s = '%s'" -#: ../option.c:6151 +#: ../option.c:3198 msgid "" "\n" "--- Global option values ---" @@ -4717,7 +5242,7 @@ msgstr "" "\n" "--- 全局选项值 ---" -#: ../option.c:6153 +#: ../option.c:3200 msgid "" "\n" "--- Local option values ---" @@ -4725,7 +5250,7 @@ msgstr "" "\n" "--- 局部选项值 ---" -#: ../option.c:6155 +#: ../option.c:3202 msgid "" "\n" "--- Options ---" @@ -4733,653 +5258,1070 @@ msgstr "" "\n" "--- 选项 ---" -#: ../option.c:6816 +#: ../option.c:4096 msgid "E356: get_varp ERROR" msgstr "E356: get_varp 错误" -#: ../option.c:7696 +#: ../optionstr.c:62 +msgid "E540: Unclosed expression sequence" +msgstr "E540: 没有结束的表达式序列" + +#: ../optionstr.c:64 +msgid "E542: unbalanced groups" +msgstr "E542: 错乱的组" + +#: ../optionstr.c:66 +msgid "E589: 'backupext' and 'patchmode' are equal" +msgstr "E589: 'backupext' 和 'patchmode' 相等" + +#: ../optionstr.c:68 +msgid "E595: 'showbreak' contains unprintable or wide character" +msgstr "E595: 'showbreak' 包含不可显示字符或宽字符" + +#: ../optionstr.c:205 #, c-format -msgid "E357: 'langmap': Matching character missing for %s" -msgstr "E357: 'langmap': 找不到 %s 对应的字符" +msgid "E539: Illegal character <%s>" +msgstr "E539: 无效的字符 <%s>" -#: ../option.c:7715 +#: ../optionstr.c:337 #, c-format -msgid "E358: 'langmap': Extra characters after semicolon: %s" -msgstr "E358: 'langmap': 分号后有多余的字符: %s" +msgid "For option %s" +msgstr "对选项 %s" -#: ../os/shell.c:194 -msgid "" -"\n" -"Cannot execute shell " +#: ../optionstr.c:959 +msgid "E524: Missing colon" +msgstr "E524: 缺少冒号" + +#: ../optionstr.c:961 +msgid "E525: Zero length string" +msgstr "E525: 字符串长度为零" + +#: ../optionstr.c:1040 +#, c-format +msgid "E526: Missing number after <%s>" +msgstr "E526: <%s> 后面缺少数字" + +#: ../optionstr.c:1053 +msgid "E527: Missing comma" +msgstr "E527: 缺少逗号" + +#: ../optionstr.c:1061 +msgid "E528: Must specify a ' value" +msgstr "E528: 必须指定一个 ' 值" + +#: ../optionstr.c:1243 +#, c-format +msgid "E535: Illegal character after <%c>" +msgstr "E535: <%c> 后面有无效的字符" + +#: ../optionstr.c:1351 +msgid "E536: comma required" +msgstr "E536: 需要逗号" + +#: ../optionstr.c:1359 +#, c-format +msgid "E537: 'commentstring' must be empty or contain %s" +msgstr "E537: 'commentstring' 必须为空或包含 %s" + +# do not translate +#: ../os/dl.c:53 ../os/dl.c:61 +#, c-format +msgid "dlerror = \"%s\"" msgstr "" -"\n" -"无法执行 shell" -#: ../os/shell.c:439 +#: ../os/fileio.c:434 +#, c-format +msgid "E5420: Failed to write to file: %s" +msgstr "E5420: 无法写入文件:%s" + +#: ../os/input.c:564 +msgid "Vim: Error reading input, exiting...\n" +msgstr "Vim:读取输入时出错,正在退出……\n" + +#: ../os/shell.c:696 msgid "" "\n" "shell returned " msgstr "" "\n" -"Shell 已返回" +"Shell 返回了 " -#: ../os_unix.c:465 ../os_unix.c:471 +#: ../os/shell.c:878 msgid "" "\n" -"Could not get security context for " +"shell failed to start: " msgstr "" - -#: ../os_unix.c:479 -msgid "" "\n" -"Could not set security context for " -msgstr "" +"Shell 无法启动:" -# do not translate -#: ../os_unix.c:1558 ../os_unix.c:1647 +#. Can happen if system() tries to send input to a shell command that was +#. backgrounded (:call system("cat - &", "foo")). #3529 #5241 +#: ../os/shell.c:1316 #, c-format -msgid "dlerror = \"%s\"" -msgstr "dlerror = \"%s\"" +msgid "E5677: Error writing input to shell-command: %s" +msgstr "E5677: 向 Shell 命令输入时出错:%s" + +#: ../os/time.c:191 +msgid "%a %b %d %H:%M:%S %Y" +msgstr "%Y-%m-%d %H:%M:%S" -#: ../path.c:1449 +#: ../path.c:1711 #, c-format msgid "E447: Can't find file \"%s\" in path" msgstr "E447: 在路径中找不到文件 \"%s\"" -#: ../quickfix.c:359 +#: ../profile.c:307 +msgid "E750: First use \":profile start {fname}\"" +msgstr "E750: 请先使用 \":profile start {fname}\"" + +#: ../quickfix.c:246 +msgid "E553: No more items" +msgstr "E553: 没有更多的项" + +#: ../quickfix.c:278 +msgid "E925: Current quickfix list was changed" +msgstr "E925: 当前 quickfix 列表已改变" + +#: ../quickfix.c:280 +msgid "E926: Current location list was changed" +msgstr "E926: 当前位置列表已改变" + +#. Each errorformat pattern can occur only once +#: ../quickfix.c:382 #, c-format msgid "E372: Too many %%%c in format string" msgstr "E372: 格式化字符串里有太多 %%%c " -#: ../quickfix.c:371 +#: ../quickfix.c:389 #, c-format msgid "E373: Unexpected %%%c in format string" msgstr "E373: 格式化字符串不应该出现 %%%c " -#: ../quickfix.c:420 +#: ../quickfix.c:447 msgid "E374: Missing ] in format string" -msgstr "E374: 格式化字符串里少了 ]" +msgstr "E374: 格式化字符串中缺少 ]" -#: ../quickfix.c:431 +#: ../quickfix.c:457 #, c-format msgid "E375: Unsupported %%%c in format string" msgstr "E375: 格式化字符串里有不支持的 %%%c " -#: ../quickfix.c:448 +#: ../quickfix.c:476 #, c-format msgid "E376: Invalid %%%c in format string prefix" msgstr "E376: 格式化字符串开头里有不正确的 %%%c " -#: ../quickfix.c:454 +#: ../quickfix.c:526 #, c-format msgid "E377: Invalid %%%c in format string" msgstr "E377: 格式化字符串里有不正确的 %%%c " #. nothing found -#: ../quickfix.c:477 +#: ../quickfix.c:629 msgid "E378: 'errorformat' contains no pattern" msgstr "E378: 'errorformat' 未设定" -#: ../quickfix.c:695 +#: ../quickfix.c:1564 msgid "E379: Missing or empty directory name" msgstr "E379: 找不到目录名称或是空的目录名称" -#: ../quickfix.c:1305 -msgid "E553: No more items" -msgstr "E553: 没有更多的项" +#: ../quickfix.c:2737 +msgid "E924: Current window was closed" +msgstr "E924: 当前窗口已关闭" -#: ../quickfix.c:1674 +#: ../quickfix.c:2809 #, c-format msgid "(%d of %d)%s%s: " msgstr "(%d / %d)%s%s: " -#: ../quickfix.c:1676 +#: ../quickfix.c:2811 msgid " (line deleted)" msgstr " (行已删除)" -#: ../quickfix.c:1863 +#: ../quickfix.c:3252 +#, c-format +msgid "%serror list %d of %d; %d errors " +msgstr "%s 错误列表 %d / %d;共 %d 个错误" + +#: ../quickfix.c:3287 msgid "E380: At bottom of quickfix stack" msgstr "E380: Quickfix 堆栈底端" -#: ../quickfix.c:1869 +#: ../quickfix.c:3293 msgid "E381: At top of quickfix stack" msgstr "E381: Quickfix 堆栈顶端" -#: ../quickfix.c:1880 -#, c-format -msgid "error list %d of %d; %d errors" -msgstr "错误列表 %d / %d;共 %d 个错误" +#: ../quickfix.c:3327 +msgid "No entries" +msgstr "没有项目" -#: ../quickfix.c:2427 -msgid "E382: Cannot write, 'buftype' option is set" -msgstr "E382: 无法写入,已设定选项 'buftype'" - -#: ../quickfix.c:2812 +#: ../quickfix.c:5335 msgid "E683: File name missing or invalid pattern" msgstr "E683: 缺少文件名或模式无效" -#: ../quickfix.c:2911 +#: ../quickfix.c:5398 #, c-format msgid "Cannot open file \"%s\"" msgstr "无法打开文件 \"%s\"" -#: ../quickfix.c:3429 +#: ../quickfix.c:6680 +msgid "cannot have both a list and a \"what\" argument" +msgstr "不能同时有列表和 \"what\" 参数" + +#: ../quickfix.c:6796 msgid "E681: Buffer is not loaded" msgstr "E681: 缓冲区未加载" -#: ../quickfix.c:3487 +#: ../quickfix.c:6959 msgid "E777: String or List expected" -msgstr "E777: 此处需要 String 或者 List" +msgstr "E777: 此处需要字符串或列表" -#: ../regexp.c:359 +#: ../quickfix.c:7246 +#, c-format +msgid "E927: Invalid action: '%s'" +msgstr "E927: 动作无效:'%s'" + +#: ../regexp.c:103 #, c-format msgid "E369: invalid item in %s%%[]" msgstr "E369: %s%%[] 中有无效的项" -#: ../regexp.c:374 +#: ../regexp.c:107 #, c-format msgid "E769: Missing ] after %s[" msgstr "E769: %s[ 后缺少 ]" -#: ../regexp.c:375 +#: ../regexp.c:108 +msgid "E944: Reverse range in character class" +msgstr "E944: 字符类中有逆向范围" + +#: ../regexp.c:109 +msgid "E945: Range too large in character class" +msgstr "E945: 字符类中的范围太大了" + +#: ../regexp.c:110 #, c-format msgid "E53: Unmatched %s%%(" msgstr "E53: 不匹配的 %s%%(" -#: ../regexp.c:376 +#: ../regexp.c:111 #, c-format msgid "E54: Unmatched %s(" msgstr "E54: 不匹配的 %s(" -#: ../regexp.c:377 +#: ../regexp.c:112 #, c-format msgid "E55: Unmatched %s)" msgstr "E55: 不匹配的 %s)" -#: ../regexp.c:378 +#: ../regexp.c:113 msgid "E66: \\z( not allowed here" msgstr "E66: 此处不允许 \\z(" -#: ../regexp.c:379 -msgid "E67: \\z1 et al. not allowed here" -msgstr "E67: 此处不允许 \\z1 等" +#: ../regexp.c:114 +msgid "E67: \\z1 - \\z9 not allowed here" +msgstr "E67: 此处不允许 \\z1 - \\z9" -#: ../regexp.c:380 +#: ../regexp.c:115 #, c-format msgid "E69: Missing ] after %s%%[" msgstr "E69: %s%%[ 后缺少 ]" -#: ../regexp.c:381 +#: ../regexp.c:116 #, c-format msgid "E70: Empty %s%%[]" msgstr "E70: 空的 %s%%[]" -#: ../regexp.c:1209 ../regexp.c:1224 -msgid "E339: Pattern too long" -msgstr "E339: 模式太长" - -#: ../regexp.c:1371 -msgid "E50: Too many \\z(" -msgstr "E50: 太多 \\z(" +#: ../regexp.c:117 +msgid "E956: Cannot use pattern recursively" +msgstr "E956: 不能递归地使用模式" -#: ../regexp.c:1378 +#: ../regexp.c:119 #, c-format -msgid "E51: Too many %s(" -msgstr "E51: 太多 %s(" +msgid "E1204: No Number allowed after .: '\\%%%c'" +msgstr "E1204: . 后不能加整数: '\\%%%c'" -#: ../regexp.c:1427 -msgid "E52: Unmatched \\z(" -msgstr "E52: 不匹配的 \\z(" - -#: ../regexp.c:1637 +#: ../regexp.c:121 #, c-format -msgid "E59: invalid character after %s@" -msgstr "E59: %s@ 后面有无效的字符" +msgid "E1273: (NFA regexp) missing value in '\\%%%c'" +msgstr "E1273: (NFA 正则) '\\%%%c' 中缺少值" -#: ../regexp.c:1672 +#: ../regexp.c:123 #, c-format -msgid "E60: Too many complex %s{...}s" -msgstr "E60: 太多复杂的 %s{...}s" +msgid "E1281: Atom '\\%%#=%c' must be at the start of the pattern" +msgstr "E1281: 原子 '\\%%#=%c' 必须位于模式的开头" -#: ../regexp.c:1687 -#, c-format -msgid "E61: Nested %s*" -msgstr "E61: 嵌套的 %s*" +#: ../regexp.c:124 +msgid "E1290: substitute nesting too deep" +msgstr "E1290: 替换嵌套层数过深" -#: ../regexp.c:1690 +#: ../regexp.c:498 #, c-format -msgid "E62: Nested %s%c" -msgstr "E62: 嵌套的 %s%c" +msgid "E654: missing delimiter after search pattern: %s" +msgstr "E654: 搜索模式后缺少分割符:%s" -#: ../regexp.c:1800 -msgid "E63: invalid use of \\_" -msgstr "E63: 不正确地使用 \\_" +#: ../regexp.c:919 +#, c-format +msgid "E554: Syntax error in %s{...}" +msgstr "E554: %s{...} 中语法错误" -#: ../regexp.c:1850 +#: ../regexp.c:1294 #, c-format -msgid "E64: %s%c follows nothing" -msgstr "E64: %s%c 前面无内容" +msgid "E888: (NFA regexp) cannot repeat %s" +msgstr "E888: (NFA regexp) 不能重复 %s" -#: ../regexp.c:1902 -msgid "E65: Illegal back reference" -msgstr "E65: 无效的回引" +#: ../regexp.c:2302 +msgid "" +"E864: \\%#= can only be followed by 0, 1, or 2. The automatic engine will be " +"used " +msgstr "E864: \\%#= 后面只能是0,1,或者2。自动引擎将会被使用" -#: ../regexp.c:1943 -msgid "E68: Invalid character after \\z" -msgstr "E68: \\z 后面有无效的字符" +#: ../regexp.c:2383 +msgid "Switching to backtracking RE engine for pattern: " +msgstr "为此模式切换到回溯正则引擎:" -#: ../regexp.c:2049 ../regexp_nfa.c:1296 +#: ../runtime.c:266 #, c-format -msgid "E678: Invalid character after %s%%[dxouU]" -msgstr "E678: %s%%[dxouU] 后面有无效的字符" +msgid "Searching for \"%s\" in \"%s\"" +msgstr "正在查找 \"%s\",在 \"%s\" 中" -#: ../regexp.c:2107 +#: ../runtime.c:303 ../runtime.c:436 #, c-format -msgid "E71: Invalid character after %s%%" -msgstr "E71: %s%% 后面有无效的字符" +msgid "Searching for \"%s\"" +msgstr "正在查找 \"%s\"" -#: ../regexp.c:3017 +#: ../runtime.c:334 #, c-format -msgid "E554: Syntax error in %s{...}" -msgstr "E554: %s{...} 中语法错误" +msgid "not found in '%s': \"%s\"" +msgstr "在 '%s' 中找不到:\"%s\"" -#: ../regexp.c:3805 -msgid "External submatches:\n" -msgstr "外部符合:\n" +#: ../runtime.c:400 +#, c-format +msgid "Searching for \"%s\" in runtime path" +msgstr "正在查找 \"%s\",在 runtime path 中" -#: ../regexp.c:7022 -msgid "" -"E864: \\%#= can only be followed by 0, 1, or 2. The automatic engine will be " -"used " -msgstr "" -"E864: \\%#= 后面只能是0,1,或者2。自动引擎将会被使用" +#: ../runtime.c:464 +#, c-format +msgid "not found in runtime path: \"%s\"" +msgstr "在 runtime path 中找不到:\"%s\"" -#: ../regexp_nfa.c:239 -msgid "E865: (NFA) Regexp end encountered prematurely" -msgstr "E865: (NFA) 过早地遇到了正则表达式的结尾" +#: ../runtime.c:1912 +#, c-format +msgid "Cannot source a directory: \"%s\"" +msgstr "不能执行目录: \"%s\"" -#: ../regexp_nfa.c:240 +#: ../runtime.c:1947 #, c-format -msgid "E866: (NFA regexp) Misplaced %c" -msgstr "E866: (NFA regexp) %c 放错了位置" +msgid "could not source \"%s\"" +msgstr "不能执行 \"%s\"" -#: ../regexp_nfa.c:242 +#: ../runtime.c:1949 #, c-format -msgid "E877: (NFA regexp) Invalid character class: %<PRId64>" -msgstr "E877: (NFA regexp) 不可用的字符类: %<PRId64>" +msgid "line %<PRId64>: could not source \"%s\"" +msgstr "第 %<PRId64> 行: 不能执行 \"%s\"" -#: ../regexp_nfa.c:1261 +#: ../runtime.c:1963 #, c-format -msgid "E867: (NFA) Unknown operator '\\z%c'" -msgstr "E867: (NFA) 未知的操作符 '\\z%c'" +msgid "sourcing \"%s\"" +msgstr "执行 \"%s\"" -#: ../regexp_nfa.c:1387 +#: ../runtime.c:1965 #, c-format -msgid "E867: (NFA) Unknown operator '\\%%%c'" -msgstr "E867: (NFA) 未知的操作符 '\\%%%c'" +msgid "line %<PRId64>: sourcing \"%s\"" +msgstr "第 %<PRId64> 行: 执行 \"%s\"" -#: ../regexp_nfa.c:1802 +#: ../runtime.c:2081 #, c-format -msgid "E869: (NFA) Unknown operator '\\@%c'" -msgstr "E869: (NFA) 未知的操作符 '\\@%c'" +msgid "finished sourcing %s" +msgstr "结束执行 %s" -#: ../regexp_nfa.c:1831 -msgid "E870: (NFA regexp) Error reading repetition limits" -msgstr "E870: (NFA regexp) 读取重复限制时出错" +#: ../runtime.c:2210 +msgid "modeline" +msgstr "modeline" -#. Can't have a multi follow a multi. -#: ../regexp_nfa.c:1895 -msgid "E871: (NFA regexp) Can't have a multi follow a multi !" -msgstr "E871: (NFA regexp) 不能多个跟多个!" +#: ../runtime.c:2212 +msgid "--cmd argument" +msgstr "--cmd 参数" -#. Too many `(' -#: ../regexp_nfa.c:2037 -msgid "E872: (NFA regexp) Too many '('" -msgstr "E872: (NFA regexp) 太多 '('" +#: ../runtime.c:2214 +msgid "-c argument" +msgstr "-c 参数" -#: ../regexp_nfa.c:2042 -#, fuzzy -msgid "E879: (NFA regexp) Too many \\z(" -msgstr "E50: 太多 \\z(" +#: ../runtime.c:2216 +msgid "environment variable" +msgstr "环境变量" -#: ../regexp_nfa.c:2066 -msgid "E873: (NFA regexp) proper termination error" -msgstr "E873: (NFA regexp) 未适当终止" +#: ../runtime.c:2218 +msgid "error handler" +msgstr "错误的处理程序" -#: ../regexp_nfa.c:2599 -msgid "E874: (NFA) Could not pop the stack !" -msgstr "E874: (NFA) 无法出栈!" +#: ../runtime.c:2220 +msgid "changed window size" +msgstr "改变了窗口大小" -#: ../regexp_nfa.c:3298 -msgid "" -"E875: (NFA regexp) (While converting from postfix to NFA), too many states " -"left on stack" -msgstr "E875: (NFA regexp) (从后缀转换到 NFA 时),栈上遗留了太多状态" +#: ../runtime.c:2222 +msgid "Lua" +msgstr "Lua" -#: ../regexp_nfa.c:3302 -msgid "E876: (NFA regexp) Not enough space to store the whole NFA " -msgstr "E876: (NFA regexp) 没有足够的空间存储整个NFA " +#: ../runtime.c:2224 +#, c-format +msgid "API client (channel id %<PRIu64>)" +msgstr "" -#: ../regexp_nfa.c:4571 ../regexp_nfa.c:4869 -msgid "" -"Could not open temporary log file for writing, displaying on stderr ... " +#: ../runtime.c:2227 +msgid "anonymous :source" msgstr "" -"无法打开临时日志文件进行写入,显示在 stderr 中..." -#: ../regexp_nfa.c:4840 +#: ../runtime.c:2231 #, c-format -msgid "(NFA) COULD NOT OPEN %s !" -msgstr "(NFA) 不能打开 %s !" +msgid "anonymous :source (script id %d)" +msgstr "" -#: ../regexp_nfa.c:6049 -#, fuzzy -msgid "Could not open temporary log file for writing " -msgstr "E214: 找不到用于写入的临时文件" +#: ../runtime.c:2417 +msgid "W15: Warning: Wrong line separator, ^M may be missing" +msgstr "W15: 警告: 错误的行分隔符,可能是少了 ^M" + +#: ../runtime.c:2457 +msgid "E167: :scriptencoding used outside of a sourced file" +msgstr "E167: 在脚本文件外使用了 :scriptencoding" -#: ../screen.c:7435 +#: ../runtime.c:2482 +msgid "E168: :finish used outside of a sourced file" +msgstr "E168: 在脚本文件外使用了 :finish" + +#: ../screen.c:57 +msgid "E834: Conflicts with value of 'listchars'" +msgstr "E834: 与'listchars'中的值发生冲突" + +#: ../screen.c:58 +msgid "E835: Conflicts with value of 'fillchars'" +msgstr "E835: 与'fillchars'中的值冲突" + +#: ../screen.c:521 +msgid " TERMINAL" +msgstr " 终端" + +#: ../screen.c:523 msgid " VREPLACE" msgstr " V-替换" -#: ../screen.c:7437 +#: ../screen.c:525 msgid " REPLACE" msgstr " 替换" -#: ../screen.c:7440 +#: ../screen.c:528 msgid " REVERSE" msgstr " 反向" -#: ../screen.c:7441 +#: ../screen.c:530 msgid " INSERT" msgstr " 插入" -#: ../screen.c:7443 +#: ../screen.c:534 +msgid " (terminal)" +msgstr " (终端)" + +#: ../screen.c:536 msgid " (insert)" msgstr " (插入)" -#: ../screen.c:7445 +#: ../screen.c:539 msgid " (replace)" msgstr " (替换)" -#: ../screen.c:7447 +#: ../screen.c:541 msgid " (vreplace)" msgstr " (V-替换)" -#: ../screen.c:7449 +#: ../screen.c:544 msgid " Hebrew" -msgstr " Hebrew" +msgstr " 希伯来文" -#: ../screen.c:7454 +#: ../screen.c:548 msgid " Arabic" -msgstr " Arabic" - -#: ../screen.c:7456 -msgid " (lang)" -msgstr " (语言)" +msgstr " 阿拉伯文" -#: ../screen.c:7459 +#: ../screen.c:555 msgid " (paste)" msgstr " (粘帖)" -#: ../screen.c:7469 +#: ../screen.c:567 msgid " VISUAL" msgstr " 可视" -#: ../screen.c:7470 +#: ../screen.c:569 msgid " VISUAL LINE" msgstr " 可视 行" -#: ../screen.c:7471 +#: ../screen.c:571 msgid " VISUAL BLOCK" msgstr " 可视 块" -#: ../screen.c:7472 +#: ../screen.c:573 msgid " SELECT" msgstr " 选择" -#: ../screen.c:7473 +#: ../screen.c:575 msgid " SELECT LINE" msgstr " 选择 行" -#: ../screen.c:7474 +#: ../screen.c:577 msgid " SELECT BLOCK" msgstr " 选择 块" -#: ../screen.c:7486 ../screen.c:7541 +#: ../screen.c:673 msgid "recording" msgstr "记录中" -#: ../search.c:487 +#: ../search.c:563 #, c-format msgid "E383: Invalid search string: %s" msgstr "E383: 无效的查找字符串: %s" -#: ../search.c:832 +#: ../search.c:928 #, c-format msgid "E384: search hit TOP without match for: %s" msgstr "E384: 已查找到文件开头仍找不到 %s" -#: ../search.c:835 +#: ../search.c:931 #, c-format msgid "E385: search hit BOTTOM without match for: %s" msgstr "E385: 已查找到文件结尾仍找不到 %s" -#: ../search.c:1200 +#: ../search.c:1382 msgid "E386: Expected '?' or '/' after ';'" msgstr "E386: 在 ';' 后面应该有 '?' 或 '/'" -#: ../search.c:4085 +#: ../search.c:3540 msgid " (includes previously listed match)" msgstr " (包括上次列出符合项)" #. cursor at status line -#: ../search.c:4104 +#: ../search.c:3557 msgid "--- Included files " msgstr "--- 包含文件 " -#: ../search.c:4106 +#: ../search.c:3559 msgid "not found " msgstr "找不到 " -#: ../search.c:4107 +#: ../search.c:3561 msgid "in path ---\n" msgstr "在路径 ---\n" -#: ../search.c:4168 +#: ../search.c:3620 msgid " (Already listed)" msgstr " (已列出)" -#: ../search.c:4170 +#: ../search.c:3622 msgid " NOT FOUND" msgstr " 找不到" -#: ../search.c:4211 +#: ../search.c:3664 #, c-format msgid "Scanning included file: %s" msgstr "查找包含文件: %s" -#: ../search.c:4216 +#: ../search.c:3669 #, c-format msgid "Searching included file %s" msgstr "查找包含的文件 %s" -#: ../search.c:4405 +#: ../search.c:3862 msgid "E387: Match is on current line" msgstr "E387: 当前行匹配" -#: ../search.c:4517 +#: ../search.c:3988 msgid "All included files were found" msgstr "所有包含文件都已找到" -#: ../search.c:4519 +#: ../search.c:3990 msgid "No included files" msgstr "没有包含文件" -#: ../search.c:4527 +#: ../search.c:3998 msgid "E388: Couldn't find definition" msgstr "E388: 找不到定义" -#: ../search.c:4529 +#: ../search.c:4000 msgid "E389: Couldn't find pattern" msgstr "E389: 找不到 pattern" -#: ../search.c:4668 -#, fuzzy -msgid "Substitute " -msgstr "1 次替换," +#: ../shada.c:699 +msgid "too few bytes read" +msgstr "" + +#: ../shada.c:721 +#, c-format +msgid "System error while skipping in ShaDa file: %s" +msgstr "" + +#: ../shada.c:725 ../shada.c:3271 +#, c-format +msgid "" +"Error while reading ShaDa file: last entry specified that it occupies " +"%<PRIu64> bytes, but file ended earlier" +msgstr "" + +#: ../shada.c:770 +#, c-format +msgid "System error while closing ShaDa file: %s" +msgstr "" + +#: ../shada.c:805 +#, c-format +msgid "System error while writing ShaDa file: %s" +msgstr "" + +#: ../shada.c:841 +#, c-format +msgid "Reading ShaDa file \"%s\"%s%s%s%s" +msgstr "读取 ShaDa 文件 \"%s\"%s%s%s%s" + +#: ../shada.c:843 +msgid " info" +msgstr " 信息" + +#: ../shada.c:844 +msgid " marks" +msgstr " 标记" + +#: ../shada.c:845 +msgid " oldfiles" +msgstr " 旧文件" + +#: ../shada.c:846 +msgid " FAILED" +msgstr " 失败" + +#: ../shada.c:852 +#, c-format +msgid "System error while opening ShaDa file %s for reading: %s" +msgstr "" + +#: ../shada.c:1536 +msgid "additional elements of ShaDa " +msgstr "" -#: ../search.c:4681 +#: ../shada.c:1556 +msgid "additional data of ShaDa " +msgstr "" + +#: ../shada.c:1625 +#, c-format +msgid "Failed to write variable %s" +msgstr "" + +#: ../shada.c:1914 +#, c-format +msgid "" +"Failed to parse ShaDa file due to a msgpack parser error at position " +"%<PRIu64>" +msgstr "" + +#: ../shada.c:1929 +#, c-format +msgid "" +"Failed to parse ShaDa file: incomplete msgpack string at position %<PRIu64>" +msgstr "" + +#: ../shada.c:1936 +#, c-format +msgid "" +"Failed to parse ShaDa file: extra bytes in msgpack string at position " +"%<PRIu64>" +msgstr "" + +#: ../shada.c:2989 +#, c-format +msgid "" +"System error while opening ShaDa file %s for reading to merge before writing " +"it: %s" +msgstr "" + +#. Tried names from .tmp.a to .tmp.z, all failed. Something must be +#. wrong then. +#: ../shada.c:3021 +#, c-format +msgid "E138: All %s.tmp.X files exist, cannot write ShaDa file!" +msgstr "" + +#: ../shada.c:3032 +#, c-format +msgid "System error while opening temporary ShaDa file %s for writing: %s" +msgstr "" + +#: ../shada.c:3047 +#, c-format +msgid "Failed to create directory %s for writing ShaDa file: %s" +msgstr "" + +#: ../shada.c:3061 +#, c-format +msgid "System error while opening ShaDa file %s for writing: %s" +msgstr "" + +#: ../shada.c:3077 +#, c-format +msgid "Writing ShaDa file \"%s\"" +msgstr "写入 ShaDa 文件 \"%s\"" + +#: ../shada.c:3104 +#, c-format +msgid "E137: ShaDa file is not writable: %s" +msgstr "E137: ShaDa 文件不可写入:%s" + +#: ../shada.c:3116 +#, c-format +msgid "Failed setting uid and gid for file %s: %s" +msgstr "无法为文件 %s 设置 uid 和 gid:%s" + +#: ../shada.c:3124 +#, c-format +msgid "Can't rename ShaDa file from %s to %s!" +msgstr "" + +#: ../shada.c:3132 +#, c-format +msgid "Did not rename %s because %s does not look like a ShaDa file" +msgstr "" + +#: ../shada.c:3135 +#, c-format +msgid "Did not rename %s to %s because there were errors during writing it" +msgstr "" + +#: ../shada.c:3141 +#, c-format +msgid "Do not forget to remove %s or rename it manually to %s." +msgstr "" + +#: ../shada.c:3267 +#, c-format +msgid "System error while reading ShaDa file: %s" +msgstr "" + +#: ../shada.c:3304 +#, c-format +msgid "System error while reading integer from ShaDa file: %s" +msgstr "" + +#: ../shada.c:3308 #, c-format msgid "" +"Error while reading ShaDa file: expected positive integer at position " +"%<PRIu64>, but got nothing" +msgstr "" + +#: ../shada.c:3335 +#, c-format +msgid "" +"Error while reading ShaDa file: expected positive integer at position " +"%<PRIu64>" +msgstr "" + +#: ../shada.c:3530 +#, c-format +msgid "" +"Error while reading ShaDa file: there is an item at position %<PRIu64> that " +"is stated to be too long" +msgstr "" + +#. kSDItemUnknown cannot possibly pass that far because it is -1 and that +#. will fail in msgpack_read_uint64. But kSDItemMissing may and it will +#. otherwise be skipped because (1 << 0) will never appear in flags. +#: ../shada.c:3544 +#, c-format +msgid "" +"Error while reading ShaDa file: there is an item at position %<PRIu64> that " +"must not be there: Missing items are for internal uses only" +msgstr "" + +#: ../shada.c:3914 +#, c-format +msgid "" +"Error while reading ShaDa file: buffer list at position %<PRIu64> contains " +"entry that is not a dictionary" +msgstr "" + +#: ../shada.c:3941 +#, c-format +msgid "" +"Error while reading ShaDa file: buffer list at position %<PRIu64> contains " +"entry with invalid line number" +msgstr "" + +#: ../shada.c:3948 +#, c-format +msgid "" +"Error while reading ShaDa file: buffer list at position %<PRIu64> contains " +"entry with invalid column number" +msgstr "" + +#: ../shada.c:3955 +#, c-format +msgid "" +"Error while reading ShaDa file: buffer list at position %<PRIu64> contains " +"entry that does not have a file name" +msgstr "" + +#: ../sign.c:292 +msgid "[Deleted]" +msgstr "[已删除]" + +#: ../sign.c:709 +msgid "" "\n" -"# Last %sSearch Pattern:\n" -"~" +"--- Signs ---" msgstr "" "\n" -"# 最后 %s搜索模式:\n" -"~" +"--- Signs ---" -#: ../spell.c:951 +#: ../sign.c:718 +#, c-format +msgid "Signs for %s:" +msgstr "%s 的 Signs:" + +#: ../sign.c:730 +#, c-format +msgid " group=%s" +msgstr " 组=%s" + +#: ../sign.c:736 +#, c-format +msgid " line=%ld id=%d%s name=%s priority=%d" +msgstr " 行=%ld id=%d%s 名称=%s 优先级=%d" + +#: ../sign.c:879 +msgid "E612: Too many signs defined" +msgstr "E612: Signs 定义过多" + +#: ../sign.c:933 +#, c-format +msgid "E239: Invalid sign text: %s" +msgstr "E239: 无效的 sign 文字: %s" + +#: ../sign.c:1036 ../sign.c:1053 ../sign.c:1085 +#, c-format +msgid "E155: Unknown sign: %s" +msgstr "E155: 未知的 sign: %s" + +#: ../sign.c:1114 +#, c-format +msgid "E885: Not possible to change sign %s" +msgstr "E885: 不可能改变标号 %s" + +#: ../sign.c:1161 +msgid "E159: Missing sign number" +msgstr "E159: 缺少 sign 号" + +#: ../sign.c:1171 +#, c-format +msgid "E157: Invalid sign ID: %<PRId64>" +msgstr "E157: 无效的 sign ID: %<PRId64>" + +#: ../sign.c:1182 +msgid "E934: Cannot jump to a buffer that does not have a name" +msgstr "E934: 无法跳转到没有名字的缓冲区" + +#: ../sign.c:1476 +#, c-format +msgid "E160: Unknown sign command: %s" +msgstr "E160: 未知的 sign 命令: %s" + +#: ../sign.c:1489 +msgid "E156: Missing sign name" +msgstr "E156: 缺少 sign 名称" + +#: ../sign.c:1676 +msgid " (not supported)" +msgstr " (不支持)" + +#. mode values for find_word +#. find word case-folded +#. find keep-case word +#. find word after prefix +#. find case-folded compound word +#. find keep-case compound word +#: ../spell.c:194 msgid "E759: Format error in spell file" msgstr "E759: 拼写文件格式错误" -#: ../spell.c:952 +#: ../spell.c:1553 +#, c-format +msgid "Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\"" +msgstr "警告: 找不到单词列表 \"%s.%s.spl\" or \"%s.ascii.spl\"" + +#: ../spell.c:1968 +msgid "E797: SpellFileMissing autocommand deleted buffer" +msgstr "E797: SpellFileMissing 自动命令删除了缓冲区" + +#. This is probably an error. Give a warning and +#. accept the words anyway. +#: ../spell.c:1992 +#, c-format +msgid "Warning: region %s not supported" +msgstr "警告: 区域 %s 不支持" + +#: ../spell.c:2579 +msgid "E752: No previous spell replacement" +msgstr "E752: 之前没有拼写替换" + +#: ../spell.c:2624 +#, c-format +msgid "E753: Not found: %s" +msgstr "E753: 找不到: %s" + +#: ../spellfile.c:329 msgid "E758: Truncated spell file" msgstr "E758: 已截断的拼写文件" -#: ../spell.c:953 +#: ../spellfile.c:330 +msgid "E1280: Illegal character in word" +msgstr "E1280: 词中有非法字符" + +#: ../spellfile.c:331 #, c-format msgid "Trailing text in %s line %d: %s" msgstr "%s 第 %d 行,多余的后续字符: %s" -#: ../spell.c:954 +#: ../spellfile.c:332 #, c-format msgid "Affix name too long in %s line %d: %s" msgstr "%s 第 %d 行,附加项名字太长: %s" -#: ../spell.c:955 -msgid "E761: Format error in affix file FOL, LOW or UPP" -msgstr "E761: 附加文件 FOL、LOW 或 UPP 中格式错误" - -#: ../spell.c:957 -msgid "E762: Character in FOL, LOW or UPP is out of range" -msgstr "E762: FOL、LOW 或 UPP 中字符超出范围" - -#: ../spell.c:958 +#: ../spellfile.c:333 msgid "Compressing word tree..." msgstr "压缩单词树……" -#: ../spell.c:1951 -msgid "E756: Spell checking is not enabled" -msgstr "E756: 拼写检查未启用" - -#: ../spell.c:2249 -#, c-format -msgid "Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\"" -msgstr "警告: 找不到单词列表 \"%s.%s.spl\" or \"%s.ascii.spl\"" - -#: ../spell.c:2473 +#: ../spellfile.c:622 #, c-format msgid "Reading spell file \"%s\"" msgstr "读取拼写文件 \"%s\"" -#: ../spell.c:2496 +#: ../spellfile.c:647 msgid "E757: This does not look like a spell file" msgstr "E757: 这看起来不像是拼写文件" -#: ../spell.c:2501 +#: ../spellfile.c:650 +#, c-format +msgid "E5042: Failed to read spell file %s: %s" +msgstr "E5042: 无法读取拼写文件 %s:%s" + +#: ../spellfile.c:658 msgid "E771: Old spell file, needs to be updated" msgstr "E771: 旧版本的拼写文件,需要更新" -#: ../spell.c:2504 +#: ../spellfile.c:661 msgid "E772: Spell file is for newer version of Vim" msgstr "E772: 为更高版本的 Vim 所用的拼写文件" -#: ../spell.c:2602 +#: ../spellfile.c:769 msgid "E770: Unsupported section in spell file" msgstr "E770: 拼写文件中存在不支持的节" -#: ../spell.c:3762 +#: ../spellfile.c:935 #, c-format -msgid "Warning: region %s not supported" -msgstr "警告: 区域 %s 不支持" +msgid "E778: This does not look like a .sug file: %s" +msgstr "E778: 看起来不像是 .sug 文件: %s" -#: ../spell.c:4550 +#: ../spellfile.c:941 +#, c-format +msgid "E779: Old .sug file, needs to be updated: %s" +msgstr "E779: 旧的.sug 文件,需要更新: %s" + +#: ../spellfile.c:945 +#, c-format +msgid "E780: .sug file is for newer version of Vim: %s" +msgstr "E780: .sug 文件适用于较新的vim 版本: %s" + +#: ../spellfile.c:954 +#, c-format +msgid "E781: .sug file doesn't match .spl file: %s" +msgstr "E781: .sug 文件不能匹配 .spl 文件: %s" + +#: ../spellfile.c:964 +#, c-format +msgid "E782: error while reading .sug file: %s" +msgstr "E782: 读取 .sug 文件时出错:%s" + +#: ../spellfile.c:2067 #, c-format msgid "Reading affix file %s..." msgstr "读取附加文件 %s ……" -#: ../spell.c:4589 ../spell.c:5635 ../spell.c:6140 +#: ../spellfile.c:2103 ../spellfile.c:3177 #, c-format msgid "Conversion failure for word in %s line %d: %s" msgstr "单词 %s 转换失败,第 %d 行: %s" -#: ../spell.c:4630 ../spell.c:6170 +#: ../spellfile.c:2150 ../spellfile.c:3750 #, c-format msgid "Conversion in %s not supported: from %s to %s" msgstr "不支持 %s 中的转换: 从 %s 到 %s" -#: ../spell.c:4642 +#: ../spellfile.c:2163 #, c-format msgid "Invalid value for FLAG in %s line %d: %s" msgstr "%s 第 %d 行,FLAG 的值无效: %s" -#: ../spell.c:4655 +#: ../spellfile.c:2177 #, c-format msgid "FLAG after using flags in %s line %d: %s" msgstr "%s 第 %d 行,在使用标志后出现 FLAG: %s" -#: ../spell.c:4723 +#: ../spellfile.c:2238 #, c-format msgid "" "Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line " "%d" -msgstr "在 PFX 项之后定义 COMPOUNDFORBIDFLAG (%s 第%d行)可能会给出错误的结果" -"%d" +msgstr "" +"在 PFX 项之后定义 COMPOUNDFORBIDFLAG (%s 第 %d 行)可能会给出错误的结果" -#: ../spell.c:4731 +#: ../spellfile.c:2246 #, c-format msgid "" "Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line " "%d" -msgstr "在 PFX 项之后定义 COMPOUNDPERMITFLAG (%s 第%d行)可能会给出错误的结果" -"%d" +msgstr "" +"在 PFX 项之后定义 COMPOUNDPERMITFLAG (%s 第 %d 行)可能会给出错误的结果" -#: ../spell.c:4747 -#, fuzzy, c-format +#: ../spellfile.c:2261 +#, c-format msgid "Wrong COMPOUNDRULES value in %s line %d: %s" -msgstr "%s 第 %d 行,错误的 COMPOUNDMIN 值: %s" +msgstr "%s 第 %d 行,错误的 COMPOUNDRULES 值: %s" -#: ../spell.c:4771 +#: ../spellfile.c:2285 #, c-format msgid "Wrong COMPOUNDWORDMAX value in %s line %d: %s" msgstr "%s 第 %d 行,错误的 COMPOUNDWORDMAX 值: %s" -#: ../spell.c:4777 +#: ../spellfile.c:2292 #, c-format msgid "Wrong COMPOUNDMIN value in %s line %d: %s" msgstr "%s 第 %d 行,错误的 COMPOUNDMIN 值: %s" -#: ../spell.c:4783 +#: ../spellfile.c:2299 #, c-format msgid "Wrong COMPOUNDSYLMAX value in %s line %d: %s" msgstr "%s 第 %d 行,错误的 COMPOUNDSYLMAX 值: %s" -#: ../spell.c:4795 +#: ../spellfile.c:2312 #, c-format msgid "Wrong CHECKCOMPOUNDPATTERN value in %s line %d: %s" msgstr "%s 第 %d 行,错误的 CHECKCOMPOUNDPATTERN 值: %s" -#: ../spell.c:4847 +#: ../spellfile.c:2368 #, c-format msgid "Different combining flag in continued affix block in %s line %d: %s" msgstr "%s 第 %d 行,在连续的附加块中出现不同的组合标志: %s" -#: ../spell.c:4850 +#: ../spellfile.c:2372 #, c-format msgid "Duplicate affix in %s line %d: %s" msgstr "%s 第 %d 行,重复的附加项: %s" -#: ../spell.c:4871 +#: ../spellfile.c:2391 #, c-format msgid "" "Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in %s " @@ -5388,334 +6330,337 @@ msgstr "" "%s 第 %d 行,附加项被 BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST 使" "用: %s" -#: ../spell.c:4893 +#: ../spellfile.c:2421 #, c-format msgid "Expected Y or N in %s line %d: %s" msgstr "%s 第 %d 行,此处需要 Y 或 N: %s" -#: ../spell.c:4968 +#: ../spellfile.c:2497 #, c-format msgid "Broken condition in %s line %d: %s" msgstr "%s 第 %d 行,错误的条件: %s" -#: ../spell.c:5091 +#: ../spellfile.c:2613 #, c-format msgid "Expected REP(SAL) count in %s line %d" msgstr "%s 第 %d 行,此处需要 REP(SAL) 计数" -#: ../spell.c:5120 +#: ../spellfile.c:2648 #, c-format msgid "Expected MAP count in %s line %d" msgstr "%s 第 %d 行,此处需要 MAP 计数" -#: ../spell.c:5132 +#: ../spellfile.c:2661 #, c-format msgid "Duplicate character in MAP in %s line %d" msgstr "%s 第 %d 行,MAP 中存在重复的字符" -#: ../spell.c:5176 +#: ../spellfile.c:2706 #, c-format msgid "Unrecognized or duplicate item in %s line %d: %s" msgstr "%s 第 %d 行,无法识别或重复的项: %s" -#: ../spell.c:5197 -#, c-format -msgid "Missing FOL/LOW/UPP line in %s" -msgstr "%s 中缺少 FOL/LOW/UPP 行" - -#: ../spell.c:5220 +#: ../spellfile.c:2738 msgid "COMPOUNDSYLMAX used without SYLLABLE" msgstr "在没有 SYLLABLE 的情况下使用了 COMPOUNDSYLMAX" -#: ../spell.c:5236 +#: ../spellfile.c:2756 msgid "Too many postponed prefixes" msgstr "太多延迟前缀" -#: ../spell.c:5238 +#: ../spellfile.c:2758 msgid "Too many compound flags" msgstr "太多组合标志" -#: ../spell.c:5240 +#: ../spellfile.c:2760 msgid "Too many postponed prefixes and/or compound flags" msgstr "太多延迟前缀和/或组合标志" -#: ../spell.c:5250 +#: ../spellfile.c:2771 #, c-format msgid "Missing SOFO%s line in %s" msgstr "%s 中缺少 SOFO%s 行" -#: ../spell.c:5253 +#: ../spellfile.c:2774 #, c-format msgid "Both SAL and SOFO lines in %s" msgstr "%s 同时出现 SQL 和 SOFO 行" -#: ../spell.c:5331 +#: ../spellfile.c:2855 #, c-format msgid "Flag is not a number in %s line %d: %s" msgstr "%s 第 %d 行,标志不是数字: %s" -#: ../spell.c:5334 +#: ../spellfile.c:2858 #, c-format msgid "Illegal flag in %s line %d: %s" msgstr "%s 第 %d 行,无效的标志: %s" -#: ../spell.c:5493 ../spell.c:5501 +#: ../spellfile.c:3029 ../spellfile.c:3038 #, c-format msgid "%s value differs from what is used in another .aff file" msgstr "%s 的值与另一个 .aff 文件中使用的值不相同" -#: ../spell.c:5602 +#: ../spellfile.c:3142 #, c-format msgid "Reading dictionary file %s..." msgstr "读取字典文件 %s ……" -#: ../spell.c:5611 +#: ../spellfile.c:3150 #, c-format msgid "E760: No word count in %s" msgstr "E760: %s 中没有单词计数" -#: ../spell.c:5669 +#: ../spellfile.c:3214 #, c-format -msgid "line %6d, word %6d - %s" -msgstr "第 %6d 行,第 %6d 个单词 - %s" +msgid "line %6d, word %6ld - %s" +msgstr "第 %6d 行,第 %6ld 个单词 - %s" -#: ../spell.c:5691 +#: ../spellfile.c:3237 #, c-format msgid "Duplicate word in %s line %d: %s" msgstr "%s 第 %d 行,重复的单词: %s" -#: ../spell.c:5694 +#: ../spellfile.c:3240 #, c-format msgid "First duplicate word in %s line %d: %s" msgstr "%s 第 %d 行,首次重复的单词: %s" -#: ../spell.c:5746 +#: ../spellfile.c:3301 #, c-format msgid "%d duplicate word(s) in %s" msgstr "存在 %d 个重复的单词,在 %s 中" -#: ../spell.c:5748 +#: ../spellfile.c:3304 #, c-format msgid "Ignored %d word(s) with non-ASCII characters in %s" msgstr "忽略了含有非 ASCII 字符的 %d 个单词,在 %s 中" -#: ../spell.c:6115 +#: ../spellfile.c:3695 #, c-format msgid "Reading word file %s..." msgstr "读取单词文件 %s ……" -#: ../spell.c:6155 +#: ../spellfile.c:3723 #, c-format -msgid "Duplicate /encoding= line ignored in %s line %d: %s" +msgid "Conversion failure for word in %s line %ld: %s" +msgstr "单词 %s 转换失败,第 %ld 行: %s" + +#: ../spellfile.c:3737 +#, c-format +msgid "Duplicate /encoding= line ignored in %s line %ld: %s" msgstr "%s 第 %ld 行,重复的 /encoding= 行已被忽略: %s" -#: ../spell.c:6159 +#: ../spellfile.c:3740 #, c-format -msgid "/encoding= line after word ignored in %s line %d: %s" -msgstr "%s 第 %d 行,单词后的 /encoding= 行已被忽略: %s" +msgid "/encoding= line after word ignored in %s line %ld: %s" +msgstr "%s 第 %ld 行,单词后的 /encoding= 行已被忽略: %s" -#: ../spell.c:6180 +#: ../spellfile.c:3761 #, c-format -msgid "Duplicate /regions= line ignored in %s line %d: %s" -msgstr "%s 第 %d 行,重复的 /regions= 行已被忽略: %s" +msgid "Duplicate /regions= line ignored in %s line %ld: %s" +msgstr "%s 第 %ld 行,重复的 /regions= 行已被忽略: %s" -#: ../spell.c:6185 +#: ../spellfile.c:3766 #, c-format -msgid "Too many regions in %s line %d: %s" -msgstr "%s 第 %d 行,太多区域: %s" +msgid "Too many regions in %s line %ld: %s" +msgstr "%s 第 %ld 行,太多区域: %s" -#: ../spell.c:6198 +#: ../spellfile.c:3779 #, c-format -msgid "/ line ignored in %s line %d: %s" -msgstr "%s 第 %d 行,/ 行已被忽略: %s" +msgid "/ line ignored in %s line %ld: %s" +msgstr "%s 第 %ld 行,/ 行已被忽略: %s" -#: ../spell.c:6224 +#: ../spellfile.c:3806 #, c-format -msgid "Invalid region nr in %s line %d: %s" -msgstr "%s 第 %d 行,无效的区域号: %s" +msgid "Invalid region nr in %s line %ld: %s" +msgstr "%s 第 %ld 行,区域号无效:%s" -#: ../spell.c:6230 +#: ../spellfile.c:3812 #, c-format -msgid "Unrecognized flags in %s line %d: %s" -msgstr "%s 第 %d 行,不可识别的标志: %s" +msgid "Unrecognized flags in %s line %ld: %s" +msgstr "%s 第 %ld 行,不可识别的标志: %s" -#: ../spell.c:6257 +#: ../spellfile.c:3839 #, c-format msgid "Ignored %d words with non-ASCII characters" msgstr "忽略了含有非 ASCII 字符的 %d 个单词" -#: ../spell.c:6656 +#: ../spellfile.c:4231 #, c-format -msgid "Compressed %d of %d nodes; %d (%d%%) remaining" -msgstr "压缩了 %d/%d 个节点;剩余 %d (%d%%)" +msgid "Compressed %s of %ld nodes; %ld (%ld%%) remaining" +msgstr "压缩了 %s / %ld 个节点;剩余 %ld (%ld%%)" -#: ../spell.c:7340 +#: ../spellfile.c:4933 msgid "Reading back spell file..." msgstr "读取拼写文件……" #. Go through the trie of good words, soundfold each word and add it to #. the soundfold trie. -#: ../spell.c:7357 +#: ../spellfile.c:4951 msgid "Performing soundfolding..." msgstr "正在 soundfolding……" -#: ../spell.c:7368 +#: ../spellfile.c:4964 #, c-format msgid "Number of words after soundfolding: %<PRId64>" msgstr "soundfolding 后的单词数: %<PRId64>" -#: ../spell.c:7476 +#: ../spellfile.c:5072 #, c-format msgid "Total number of words: %d" msgstr "单词总数: %d" -#: ../spell.c:7655 +#: ../spellfile.c:5213 #, c-format msgid "Writing suggestion file %s..." msgstr "写入建议文件 %s ……" -#: ../spell.c:7707 ../spell.c:7927 +#: ../spellfile.c:5269 ../spellfile.c:5457 #, c-format msgid "Estimated runtime memory use: %d bytes" msgstr "估计运行时内存用量: %d 字节" -#: ../spell.c:7820 +#: ../spellfile.c:5353 msgid "E751: Output file name must not have region name" msgstr "E751: 输出文件名不能含有区域名" -#: ../spell.c:7822 -msgid "E754: Only up to 8 regions supported" -msgstr "E754: 最多只支持 8 个区域" +#: ../spellfile.c:5355 +#, c-format +msgid "E754: Only up to %d regions supported" +msgstr "E754: 最多只支持 %d 个区域" -#: ../spell.c:7846 +#: ../spellfile.c:5379 #, c-format msgid "E755: Invalid region in %s" msgstr "E755: %s 出现无效的范围" -#: ../spell.c:7907 +#: ../spellfile.c:5436 msgid "Warning: both compounding and NOBREAK specified" msgstr "警告: 同时指定了 compounding 和 NOBREAK" -#: ../spell.c:7920 +#: ../spellfile.c:5450 #, c-format msgid "Writing spell file %s..." msgstr "写入拼写文件 %s ……" -#: ../spell.c:7925 +#: ../spellfile.c:5455 msgid "Done!" msgstr "完成!" -#: ../spell.c:8034 +#: ../spellfile.c:5576 #, c-format msgid "E765: 'spellfile' does not have %<PRId64> entries" msgstr "E765: 'spellfile' 没有 %<PRId64> 项" -#: ../spell.c:8074 -#, fuzzy, c-format +#: ../spellfile.c:5621 +#, c-format msgid "Word '%.*s' removed from %s" -msgstr "从 %s 中删除了单词" +msgstr "单词 '%.*s' 从 %s 中删除了" -#: ../spell.c:8117 -#, fuzzy, c-format +#: ../spellfile.c:5625 +msgid "Seek error in spellfile" +msgstr "拼写文件中定位错误" + +#: ../spellfile.c:5671 +#, c-format msgid "Word '%.*s' added to %s" -msgstr "向 %s 中添加了单词" +msgstr "单词 '%.*s' 添加到了 %s" -#: ../spell.c:8381 +#: ../spellfile.c:5803 msgid "E763: Word characters differ between spell files" msgstr "E763: 拼写文件之间的字符不相同" -#: ../spell.c:8684 +#. This should have been checked when generating the .spl +#. file. +#: ../spellfile.c:5899 +msgid "E783: duplicate char in MAP entry" +msgstr "E783: MAP 条目中有重复的字符" + +#: ../spellsuggest.c:530 msgid "Sorry, no suggestions" msgstr "抱歉,没有建议" -#: ../spell.c:8687 +#: ../spellsuggest.c:533 #, c-format msgid "Sorry, only %<PRId64> suggestions" msgstr "抱歉,只有 %<PRId64> 条建议" #. for when 'cmdheight' > 1 #. avoid more prompt -#: ../spell.c:8704 +#: ../spellsuggest.c:547 #, c-format msgid "Change \"%.*s\" to:" msgstr "将 \"%.*s\" 改为:" -#: ../spell.c:8737 +#: ../spellsuggest.c:581 #, c-format msgid " < \"%.*s\"" msgstr " < \"%.*s\"" -#: ../spell.c:8882 -msgid "E752: No previous spell replacement" -msgstr "E752: 之前没有拼写替换" +#: ../statusline.c:110 ../statusline.c:1553 +msgid "[Help]" +msgstr "[帮助]" -#: ../spell.c:8925 -#, c-format -msgid "E753: Not found: %s" -msgstr "E753: 找不到: %s" +#: ../statusline.c:114 ../statusline.c:1588 +msgid "[Preview]" +msgstr "[预览]" -#: ../spell.c:9276 -#, c-format -msgid "E778: This does not look like a .sug file: %s" -msgstr "E778: 看起来不像是 .sug 文件: %s" +#: ../strings.c:496 +msgid "E766: Insufficient arguments for printf()" +msgstr "E766: printf() 的参数不足" -#: ../spell.c:9282 -#, c-format -msgid "E779: Old .sug file, needs to be updated: %s" -msgstr "E779: 旧的.sug 文件,需要更新: %s" +#: ../strings.c:618 +msgid "E807: Expected Float argument for printf()" +msgstr "E807: 期盼浮点数作为printf()参数" -#: ../spell.c:9286 -#, c-format -msgid "E780: .sug file is for newer version of Vim: %s" -msgstr "E780: .sug 文件适用于较新的vim 版本: %s" +#: ../strings.c:1400 +msgid "E767: Too many arguments to printf()" +msgstr "E767: printf() 的参数过多" -#: ../spell.c:9295 +#: ../syntax.c:61 #, c-format -msgid "E781: .sug file doesn't match .spl file: %s" -msgstr "E781: .sug 文件不能匹配 .spl 文件: %s" - -#: ../spell.c:9305 -#, fuzzy, c-format -msgid "E782: error while reading .sug file: %s" -msgstr "E47: 读取错误文件失败" - -#. This should have been checked when generating the .spl -#. file. -#: ../spell.c:11575 -msgid "E783: duplicate char in MAP entry" -msgstr "E783: MAP 条目中有重复的字符" +msgid "E390: Illegal argument: %s" +msgstr "E390: 无效的参数: %s" -#: ../syntax.c:266 +#: ../syntax.c:241 msgid "No Syntax items defined for this buffer" msgstr "这个缓冲区没有定义任何语法项" -#: ../syntax.c:3083 ../syntax.c:3104 ../syntax.c:3127 -#, c-format -msgid "E390: Illegal argument: %s" -msgstr "E390: 无效的参数: %s" +#: ../syntax.c:2706 +msgid "'redrawtime' exceeded, syntax highlighting disabled" +msgstr "'redrawtime' 已过,语法高亮被禁用" -#: ../syntax.c:3299 +#: ../syntax.c:2941 +msgid "syntax iskeyword not set" +msgstr "syntax iskeyword 未设置" + +#: ../syntax.c:3118 #, c-format msgid "E391: No such syntax cluster: %s" msgstr "E391: 无此语法 cluster: \"%s\"" -#: ../syntax.c:3433 +#: ../syntax.c:3234 msgid "syncing on C-style comments" msgstr "C风格注释同步中" -#: ../syntax.c:3439 +#: ../syntax.c:3240 msgid "no syncing" msgstr "没有同步" -#: ../syntax.c:3441 +#: ../syntax.c:3243 +msgid "syncing starts at the first line" +msgstr "同步开始于第一行" + +#: ../syntax.c:3245 msgid "syncing starts " msgstr "同步开始" -#: ../syntax.c:3443 ../syntax.c:3506 +#: ../syntax.c:3247 ../syntax.c:3316 msgid " lines before top line" msgstr "行号超出范围" -#: ../syntax.c:3448 +#: ../syntax.c:3253 msgid "" "\n" "--- Syntax sync items ---" @@ -5723,7 +6668,7 @@ msgstr "" "\n" "--- 语法同步项目 (Syntax sync items) ---" -#: ../syntax.c:3452 +#: ../syntax.c:3257 msgid "" "\n" "syncing on items" @@ -5731,7 +6676,7 @@ msgstr "" "\n" "同步中:" -#: ../syntax.c:3457 +#: ../syntax.c:3262 msgid "" "\n" "--- Syntax items ---" @@ -5739,276 +6684,211 @@ msgstr "" "\n" "--- 语法项目 ---" -#: ../syntax.c:3475 +#: ../syntax.c:3279 #, c-format msgid "E392: No such syntax cluster: %s" msgstr "E392: 无此语法 cluster: \"%s\"" -#: ../syntax.c:3497 +#: ../syntax.c:3303 +msgid "from the first line" +msgstr "从第一行" + +#: ../syntax.c:3306 msgid "minimal " msgstr "最小" -#: ../syntax.c:3503 +#: ../syntax.c:3313 msgid "maximal " msgstr "最大" -#: ../syntax.c:3513 -#, fuzzy +#: ../syntax.c:3324 msgid "; match " -msgstr "匹配 %d" +msgstr ";匹配 " -#: ../syntax.c:3515 -#, fuzzy +#: ../syntax.c:3326 msgid " line breaks" -msgstr "少于一行" +msgstr " 个换行" -#: ../syntax.c:4076 +#: ../syntax.c:3875 msgid "E395: contains argument not accepted here" msgstr "E395: 使用了不正确的参数" -#: ../syntax.c:4096 -#, fuzzy +#: ../syntax.c:3894 msgid "E844: invalid cchar value" -msgstr "E474: 无效的参数" +msgstr "E844: cchar 值无效" -#: ../syntax.c:4107 +#: ../syntax.c:3905 msgid "E393: group[t]here not accepted here" msgstr "E393: 使用了不正确的参数" -#: ../syntax.c:4126 +#: ../syntax.c:3927 #, c-format msgid "E394: Didn't find region item for %s" msgstr "E394: 找不到 %s 的 region item" -#: ../syntax.c:4188 +#: ../syntax.c:3988 msgid "E397: Filename required" msgstr "E397: 需要文件名称" -#: ../syntax.c:4221 -#, fuzzy +#: ../syntax.c:4019 msgid "E847: Too many syntax includes" -msgstr "E77: 文件名过多" +msgstr "E847: syntax include 过多" -#: ../syntax.c:4303 -#, fuzzy, c-format +#: ../syntax.c:4107 +#, c-format msgid "E789: Missing ']': %s" -msgstr "E747: 缺少 ']': %s" +msgstr "E789: 缺少 ']':%s" + +#: ../syntax.c:4112 +#, c-format +msgid "E890: trailing char after ']': %s]%s" +msgstr "E890: ']' 后有尾随的字符:%s]%s" -#: ../syntax.c:4531 +#: ../syntax.c:4320 #, c-format msgid "E398: Missing '=': %s" msgstr "E398: 缺少 '=': %s" -#: ../syntax.c:4666 +#: ../syntax.c:4450 #, c-format msgid "E399: Not enough arguments: syntax region %s" msgstr "E399: syntax region %s 的参数太少" -#: ../syntax.c:4870 -#, fuzzy +#: ../syntax.c:4629 msgid "E848: Too many syntax clusters" -msgstr "E391: 无此语法 cluster: \"%s\"" +msgstr "E848: syntax cluster 过多" -#: ../syntax.c:4954 +#: ../syntax.c:4714 msgid "E400: No cluster specified" msgstr "E400: 没有指定的属性" #. end delimiter not found -#: ../syntax.c:4986 +#: ../syntax.c:4746 #, c-format msgid "E401: Pattern delimiter not found: %s" msgstr "E401: 找不到分隔符号: %s" -#: ../syntax.c:5049 +#: ../syntax.c:4816 #, c-format msgid "E402: Garbage after pattern: %s" msgstr "E402: '%s' 后面的东西不能识别" -#: ../syntax.c:5120 +#: ../syntax.c:4893 msgid "E403: syntax sync: line continuations pattern specified twice" msgstr "E403: 语法同步: 连接行符号指定了两次" -#: ../syntax.c:5169 +#: ../syntax.c:4942 #, c-format msgid "E404: Illegal arguments: %s" msgstr "E404: 无效的参数: %s" -#: ../syntax.c:5217 +#: ../syntax.c:4978 #, c-format msgid "E405: Missing equal sign: %s" msgstr "E405: 缺少等号: %s" -#: ../syntax.c:5222 +#: ../syntax.c:4983 #, c-format msgid "E406: Empty argument: %s" msgstr "E406: 空的参数: %s" -#: ../syntax.c:5240 +#: ../syntax.c:4998 #, c-format msgid "E407: %s not allowed here" msgstr "E407: %s 不能在此出现" -#: ../syntax.c:5246 +#: ../syntax.c:5004 #, c-format msgid "E408: %s must be first in contains list" msgstr "E408: %s 必须是列表里的第一个" -#: ../syntax.c:5304 +#: ../syntax.c:5067 #, c-format msgid "E409: Unknown group name: %s" msgstr "E409: 不正确的组名: %s" -#: ../syntax.c:5512 +#: ../syntax.c:5272 #, c-format msgid "E410: Invalid :syntax subcommand: %s" msgstr "E410: 不正确的 :syntax 子命令: %s" -#: ../syntax.c:5854 +#: ../syntax.c:5660 msgid "" " TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN" msgstr "" +" 总 计 计 数 匹 配 最 慢 的 平 均 名 字 模 式" -#: ../syntax.c:6146 -msgid "E679: recursive loop loading syncolor.vim" -msgstr "E679: 加载 syncolor.vim 时出现嵌套循环" - -#: ../syntax.c:6256 -#, c-format -msgid "E411: highlight group not found: %s" -msgstr "E411: 找不到 highlight group: %s" - -#: ../syntax.c:6278 -#, c-format -msgid "E412: Not enough arguments: \":highlight link %s\"" -msgstr "E412: 参数太少: \":highlight link %s\"" - -#: ../syntax.c:6284 -#, c-format -msgid "E413: Too many arguments: \":highlight link %s\"" -msgstr "E413: 参数过多: \":highlight link %s\"" - -#: ../syntax.c:6302 -msgid "E414: group has settings, highlight link ignored" -msgstr "E414: 已设定组, 忽略 highlight link" - -#: ../syntax.c:6367 -#, c-format -msgid "E415: unexpected equal sign: %s" -msgstr "E415: 不该有的等号: %s" - -#: ../syntax.c:6395 -#, c-format -msgid "E416: missing equal sign: %s" -msgstr "E416: 缺少等号: %s" - -#: ../syntax.c:6418 -#, c-format -msgid "E417: missing argument: %s" -msgstr "E417: 缺少参数: %s" - -#: ../syntax.c:6446 -#, c-format -msgid "E418: Illegal value: %s" -msgstr "E418: 不合法的值: %s" - -#: ../syntax.c:6496 -msgid "E419: FG color unknown" -msgstr "E419: 错误的前景颜色" - -#: ../syntax.c:6504 -msgid "E420: BG color unknown" -msgstr "E420: 错误的背景颜色" - -#: ../syntax.c:6564 -#, c-format -msgid "E421: Color name or number not recognized: %s" -msgstr "E421: 错误的颜色名称或数值: %s" - -#: ../syntax.c:6714 -#, c-format -msgid "E422: terminal code too long: %s" -msgstr "E422: 终端编码太长: %s" - -#: ../syntax.c:6753 -#, c-format -msgid "E423: Illegal argument: %s" -msgstr "E423: 无效的参数: %s" - -#: ../syntax.c:6925 -msgid "E424: Too many different highlighting attributes in use" -msgstr "E424: 使用了太多不同的高亮度属性" - -#: ../syntax.c:7427 -msgid "E669: Unprintable character in group name" -msgstr "E669: 组名中存在不可显示字符" - -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: 组名中含有无效字符" - -#: ../syntax.c:7448 -msgid "E849: Too many highlight and syntax groups" -msgstr "E849: 高亮和语法组过多" - -#: ../tag.c:104 +#: ../tag.c:117 msgid "E555: at bottom of tag stack" msgstr "E555: 已在 tag 堆栈底部" -#: ../tag.c:105 +#: ../tag.c:118 msgid "E556: at top of tag stack" msgstr "E556: 已在 tag 堆栈顶部" -#: ../tag.c:380 +#: ../tag.c:120 +msgid "E986: cannot modify the tag stack within tagfunc" +msgstr "E986: 在 tagfunc 中不能修改 tag 堆栈" + +#: ../tag.c:122 +msgid "E987: invalid return value from tagfunc" +msgstr "E987: tagfunc 返回了无效的值" + +#: ../tag.c:124 +msgid "E1299: Window unexpectedly closed while searching for tags" +msgstr "E1299: 搜索 tag 时窗口意外关闭" + +#: ../tag.c:427 msgid "E425: Cannot go before first matching tag" msgstr "E425: 已到第一个匹配的 tag" -#: ../tag.c:504 +#: ../tag.c:568 #, c-format msgid "E426: tag not found: %s" msgstr "E426: 找不到 tag: %s" -#: ../tag.c:528 -msgid " # pri kind tag" -msgstr " # pri kind tag" - -#: ../tag.c:531 -msgid "file\n" -msgstr "文件\n" - -#: ../tag.c:829 +#: ../tag.c:610 msgid "E427: There is only one matching tag" msgstr "E427: 只有一个匹配的 tag" -#: ../tag.c:831 +#: ../tag.c:612 msgid "E428: Cannot go beyond last matching tag" msgstr "E428: 己到最后一个匹配的 tag" -#: ../tag.c:850 +#: ../tag.c:641 #, c-format msgid "File \"%s\" does not exist" msgstr "文件 \"%s\" 不存在" #. Give an indication of the number of matching tags -#: ../tag.c:859 +#: ../tag.c:649 #, c-format msgid "tag %d of %d%s" msgstr "找到 tag: %d / %d%s" -#: ../tag.c:862 +#: ../tag.c:652 msgid " or more" msgstr " 或更多" -#: ../tag.c:864 +#: ../tag.c:654 msgid " Using tag with different case!" msgstr " 以不同大小写来使用 tag!" -#: ../tag.c:909 +#: ../tag.c:701 #, c-format msgid "E429: File \"%s\" does not exist" msgstr "E429: 文件 \"%s\" 不存在" +#: ../tag.c:749 +msgid " # pri kind tag" +msgstr " # pri kind tag" + +#: ../tag.c:752 +msgid "file\n" +msgstr "文件\n" + #. Highlight title -#: ../tag.c:960 +#: ../tag.c:1064 msgid "" "\n" " # TO tag FROM line in file/text" @@ -6016,1620 +6896,2495 @@ msgstr "" "\n" " # 到 tag 从 行 在 文件/文本" -#: ../tag.c:1303 +#: ../tag.c:1635 #, c-format msgid "Searching tags file %s" msgstr "查找 tag 文件 %s" -#: ../tag.c:1545 -msgid "Ignoring long line in tags file" -msgstr "忽略较长的行在 tags 文件中" - -#: ../tag.c:1915 +#: ../tag.c:2155 #, c-format msgid "E431: Format error in tags file \"%s\"" msgstr "E431: Tag 文件 \"%s\" 格式错误" -#: ../tag.c:1917 +#: ../tag.c:2156 #, c-format msgid "Before byte %<PRId64>" msgstr "在第 %<PRId64> 字节之前" -#: ../tag.c:1929 +#: ../tag.c:2168 #, c-format msgid "E432: Tags file not sorted: %s" msgstr "E432: Tag 文件未排序: %s" #. never opened any tags file -#: ../tag.c:1960 +#: ../tag.c:2195 msgid "E433: No tags file" msgstr "E433: 没有 tag 文件" -#: ../tag.c:2536 +#: ../tag.c:2794 msgid "E434: Can't find tag pattern" msgstr "E434: 找不到 tag 模式" -#: ../tag.c:2544 +#: ../tag.c:2800 msgid "E435: Couldn't find tag, just guessing!" msgstr "E435: 找不到 tag,试着猜!" -#: ../tag.c:2797 -#, fuzzy, c-format +#: ../tag.c:3070 +#, c-format msgid "Duplicate field name: %s" -msgstr "%s 第 %d 行,重复的附加项: %s" - -#: ../term.c:1442 -msgid "' not known. Available builtin terminals are:" -msgstr "' 未知。可用的内建终端有:" - -#: ../term.c:1463 -msgid "defaulting to '" -msgstr "默认值为: '" - -#: ../term.c:1731 -msgid "E557: Cannot open termcap file" -msgstr "E557: 无法打开 termcap 文件" - -#: ../term.c:1735 -msgid "E558: Terminal entry not found in terminfo" -msgstr "E558: 在 terminfo 中找不到终端项" +msgstr "字段名重复:%s" -#: ../term.c:1737 -msgid "E559: Terminal entry not found in termcap" -msgstr "E559: 在 termcap 中找不到终端项" +#: ../testing.c:38 +msgid "" +"E856: assert_fails() second argument must be a string or a list with one or " +"two strings" +msgstr "" +"E856: assert_fails() 的第二个参数必须是字符串或包含一或两个字符串的列表" -#: ../term.c:1878 -#, c-format -msgid "E436: No \"%s\" entry in termcap" -msgstr "E436: termcap 中没有 \"%s\" 项" +#: ../testing.c:40 +msgid "E1115: assert_fails() fourth argument must be a number" +msgstr "E1115: assert_fails() 的第四个参数必须是整数" -#: ../term.c:2249 -msgid "E437: terminal capability \"cm\" required" -msgstr "E437: 终端需要能力 \"cm\"" +#: ../testing.c:42 +msgid "E1116: assert_fails() fifth argument must be a string" +msgstr "E1116: assert_fails() 的第五个参数必须是字符串" -#. Highlight title -#: ../term.c:4376 -msgid "" -"\n" -"--- Terminal keys ---" -msgstr "" -"\n" -"--- 终端按键 ---" +#: ../testing.c:44 +msgid "E1142: Calling test_garbagecollect_now() while v:testing is not set" +msgstr "E1142: 调用 test_garbagecollect_now(),但 v:testing 未设置" -#: ../ui.c:481 -msgid "Vim: Error reading input, exiting...\n" -msgstr "Vim: 读错误,退出中...\n" +#: ../ui.c:342 +msgid "Beep!" +msgstr "Beep!" #. This happens when the FileChangedRO autocommand changes the -#. * file in a way it becomes shorter. -#: ../undo.c:379 -#, fuzzy +#. file in a way it becomes shorter. +#: ../undo.c:357 msgid "E881: Line count changed unexpectedly" -msgstr "E787: 意外地改变了缓冲区" +msgstr "E881: 行数意外地改变了" -#: ../undo.c:627 -#, fuzzy, c-format +#: ../undo.c:628 +#, c-format msgid "E828: Cannot open undo file for writing: %s" -msgstr "E212: 无法打开并写入文件" +msgstr "E828: 无法打开并写入撤销文件:%s" -#: ../undo.c:717 +#: ../undo.c:710 +#, c-format +msgid "E5003: Unable to create directory \"%s\" for undo file: %s" +msgstr "E303: 无法为交换文件创建目录 \"%s\":%s" + +#: ../undo.c:749 #, c-format msgid "E825: Corrupted undo file (%s): %s" msgstr "E825: 已损坏的撤销文件 (%s): %s" -#: ../undo.c:1039 +#: ../undo.c:1169 msgid "Cannot write undo file in any directory in 'undodir'" msgstr "不能写入撤销文件到 'undodir' 中的任何文件夹" -#: ../undo.c:1074 +#: ../undo.c:1205 #, c-format msgid "Will not overwrite with undo file, cannot read: %s" msgstr "不能重写撤销文件, 不可读取: %s" -#: ../undo.c:1092 +#: ../undo.c:1222 #, c-format msgid "Will not overwrite, this is not an undo file: %s" msgstr "这个文件: %s 不是撤销文件,不可以重写" -#: ../undo.c:1108 +#: ../undo.c:1239 msgid "Skipping undo file write, nothing to undo" msgstr "跳过写入撤销文件,没有任何撤销" -#: ../undo.c:1121 -#, fuzzy, c-format +#: ../undo.c:1252 +#, c-format msgid "Writing undo file: %s" -msgstr "写入 viminfo 文件 \"%s\"" +msgstr "写入撤销文件:%s" -#: ../undo.c:1213 -#, fuzzy, c-format +#: ../undo.c:1340 +#, c-format msgid "E829: write error in undo file: %s" -msgstr "E297: 交换文件写入错误" +msgstr "E829: 撤销文件写入错误:%s" -#: ../undo.c:1280 +#: ../undo.c:1387 #, c-format msgid "Not reading undo file, owner differs: %s" -msgstr "不能读取撤销文件, 所有者不同: %s" +msgstr "不能读取撤销文件,所有者不同:%s" -#: ../undo.c:1292 -#, fuzzy, c-format +#: ../undo.c:1400 +#, c-format msgid "Reading undo file: %s" -msgstr "读取单词文件 %s ……" +msgstr "读取撤销文件:%s" -#: ../undo.c:1299 -#, fuzzy, c-format +#: ../undo.c:1407 +#, c-format msgid "E822: Cannot open undo file for reading: %s" -msgstr "E195: 无法打开并读取 viminfo 文件" +msgstr "E822: 无法打开并读取撤销文件:%s" -#: ../undo.c:1308 -#, fuzzy, c-format +#: ../undo.c:1421 +#, c-format msgid "E823: Not an undo file: %s" -msgstr "E753: 找不到: %s" +msgstr "E823: 不是撤销文件:%s" -#: ../undo.c:1313 -#, fuzzy, c-format +#: ../undo.c:1426 +#, c-format msgid "E824: Incompatible undo file: %s" -msgstr "E484: 无法打开文件 %s" +msgstr "E824: 不兼容的撤销文件:%s" -#: ../undo.c:1328 +#: ../undo.c:1442 msgid "File contents changed, cannot use undo info" msgstr "文件内容已改变,不能使用撤销信息" -#: ../undo.c:1497 -#, fuzzy, c-format +#: ../undo.c:1636 +#, c-format msgid "Finished reading undo file %s" -msgstr "结束执行 %s" +msgstr "读取撤销文件 %s 结束" -#: ../undo.c:1586 ../undo.c:1812 +#: ../undo.c:1871 ../undo.c:2106 msgid "Already at oldest change" msgstr "已位于最旧的改变" -#: ../undo.c:1597 ../undo.c:1814 +#: ../undo.c:1882 ../undo.c:2108 msgid "Already at newest change" msgstr "已位于最新的改变" -#: ../undo.c:1806 -#, fuzzy, c-format +#: ../undo.c:2100 +#, c-format msgid "E830: Undo number %<PRId64> not found" -msgstr "找不到撤销号 %<PRId64>" +msgstr "E830: 找不到撤销号 %<PRId64>" -#: ../undo.c:1979 +#: ../undo.c:2280 msgid "E438: u_undo: line numbers wrong" -msgstr "E438: u_undo: 行号错误" +msgstr "E438: u_undo:行号错误" -#: ../undo.c:2183 +#: ../undo.c:2538 msgid "more line" msgstr "行被加入" -#: ../undo.c:2185 +#: ../undo.c:2540 msgid "more lines" msgstr "行被加入" -#: ../undo.c:2187 +#: ../undo.c:2542 msgid "line less" msgstr "行被去掉" -#: ../undo.c:2189 +#: ../undo.c:2544 msgid "fewer lines" msgstr "行被去掉" -#: ../undo.c:2193 +#: ../undo.c:2548 msgid "change" msgstr "行发生改变" -#: ../undo.c:2195 +#: ../undo.c:2550 msgid "changes" msgstr "行发生改变" -#: ../undo.c:2225 +#: ../undo.c:2589 #, c-format msgid "%<PRId64> %s; %s #%<PRId64> %s" msgstr "%<PRId64> %s;%s #%<PRId64> %s" -#: ../undo.c:2228 +#: ../undo.c:2592 msgid "before" msgstr "before" -#: ../undo.c:2228 +#: ../undo.c:2592 msgid "after" msgstr "after" -#: ../undo.c:2325 +#: ../undo.c:2613 +#, c-format +msgid "%<PRId64> second ago" +msgid_plural "%<PRId64> seconds ago" +msgstr[0] "%<PRId64> 秒前" + +#: ../undo.c:2697 msgid "Nothing to undo" msgstr "无可撤销" -#: ../undo.c:2330 +#: ../undo.c:2702 msgid "number changes when saved" msgstr " 编号 变更 时间 保存" -#: ../undo.c:2360 -#, fuzzy, c-format -msgid "%<PRId64> seconds ago" -msgstr "%<PRId64> 列; " - -#: ../undo.c:2372 -#, fuzzy +#: ../undo.c:2724 msgid "E790: undojoin is not allowed after undo" -msgstr "E407: %s 不能在此出现" +msgstr "E790: undo 后不允许 undojoin" -#: ../undo.c:2466 +#: ../undo.c:2807 msgid "E439: undo list corrupt" msgstr "E439: 撤销列表损坏" -#: ../undo.c:2495 +#: ../undo.c:2830 msgid "E440: undo line missing" msgstr "E440: 找不到要撤销的行" -#: ../version.c:600 +#: ../usercmd.c:46 +msgid "E1208: -complete used without allowing arguments" +msgstr "E1208: 用了 -complete 却不允许使用参数" + +#: ../usercmd.c:48 +#, c-format +msgid "E184: No such user-defined command: %s" +msgstr "E184: 没有这个用户自定义命令: %s" + +#: ../usercmd.c:50 +#, c-format +msgid "E1237: No such user-defined command in current buffer: %s" +msgstr "E1237: 当前缓冲区中无此用户定义命令:%s" + +#: ../usercmd.c:416 msgid "" "\n" -"Included patches: " +" Name Args Address Complete Definition" msgstr "" "\n" -"包含补丁: " +" 名称 参数 范围 补全 定义 " -#: ../version.c:627 -#, fuzzy -msgid "" -"\n" -"Extra patches: " -msgstr "外部符合:\n" +#: ../usercmd.c:561 +msgid "No user-defined commands found" +msgstr "找不到用户自定义命令" + +#: ../usercmd.c:585 +#, c-format +msgid "E180: Invalid address type value: %s" +msgstr "E180: 无效的地址类型:%s" + +#: ../usercmd.c:633 +#, c-format +msgid "E180: Invalid complete value: %s" +msgstr "E180: 无效的补全类型:%s" + +#: ../usercmd.c:639 +msgid "E468: Completion argument only allowed for custom completion" +msgstr "E468: 只有 custom 补全才允许参数" + +#: ../usercmd.c:645 +msgid "E467: Custom completion requires a function argument" +msgstr "E467: Custom 补全需要一个函数参数" + +#: ../usercmd.c:662 +msgid "E175: No attribute specified" +msgstr "E175: 没有指定属性" -#: ../version.c:639 ../version.c:864 -msgid "Modified by " -msgstr "修改者 " +#: ../usercmd.c:710 +msgid "E176: Invalid number of arguments" +msgstr "E176: 无效的参数个数" -#: ../version.c:646 +#: ../usercmd.c:721 +msgid "E177: Count cannot be specified twice" +msgstr "E177: 不能指定两次计数" + +#: ../usercmd.c:730 +msgid "E178: Invalid default value for count" +msgstr "E178: 无效的计数默认值" + +#: ../usercmd.c:763 +msgid "E179: argument required for -complete" +msgstr "E179: -complete 需要参数" + +#: ../usercmd.c:774 +msgid "E179: argument required for -addr" +msgstr "E179: -addr 需要参数" + +#: ../usercmd.c:786 +#, c-format +msgid "E181: Invalid attribute: %s" +msgstr "E181: 无效的属性: %s" + +#: ../usercmd.c:866 +#, c-format +msgid "E174: Command already exists: add ! to replace it: %s" +msgstr "E174: 命令已存在:请加 ! 强制替换:%s" + +#: ../usercmd.c:955 +msgid "E182: Invalid command name" +msgstr "E182: 无效的命令名" + +#: ../usercmd.c:966 +msgid "E183: User defined commands must start with an uppercase letter" +msgstr "E183: 用户自定义命令必须以大写字母开头" + +#: ../usercmd.c:968 +msgid "E841: Reserved name, cannot be used for user defined command" +msgstr "E841: 名称已被保留,不能用于用户自定义命令" + +#: ../version.c:2636 msgid "" "\n" -"Compiled " +"\n" +"Features: " msgstr "" "\n" -"编译" - -#: ../version.c:649 -msgid "by " -msgstr "者 " +"\n" +"功能: " -#: ../version.c:660 +#: ../version.c:2740 msgid "" "\n" -"Huge version " +"Compiled " msgstr "" "\n" -"巨型版本 " - -#: ../version.c:661 -msgid "without GUI." -msgstr "无图形界面。" +"编译" -#: ../version.c:662 -msgid " Features included (+) or not (-):\n" -msgstr " 可使用(+)与不可使用(-)的功能:\n" +#: ../version.c:2743 +msgid "by " +msgstr "者 " -#: ../version.c:667 +#: ../version.c:2757 msgid " system vimrc file: \"" msgstr " 系统 vimrc 文件: \"" -#: ../version.c:672 -msgid " user vimrc file: \"" -msgstr " 用户 vimrc 文件: \"" - -#: ../version.c:677 -msgid " 2nd user vimrc file: \"" -msgstr " 第二用户 vimrc 文件: \"" - -#: ../version.c:682 -msgid " 3rd user vimrc file: \"" -msgstr " 第三用户 vimrc 文件: \"" - -#: ../version.c:687 -msgid " user exrc file: \"" -msgstr " 用户 exrc 文件: \"" - -#: ../version.c:692 -msgid " 2nd user exrc file: \"" -msgstr " 第二用户 exrc 文件: \"" - -#: ../version.c:699 +#: ../version.c:2764 msgid " fall-back for $VIM: \"" msgstr " $VIM 预设值: \"" -#: ../version.c:705 +#: ../version.c:2770 msgid " f-b for $VIMRUNTIME: \"" msgstr " $VIMRUNTIME 预设值: \"" -#: ../version.c:709 -msgid "Compilation: " -msgstr "编译方式: " - -#: ../version.c:712 -msgid "Linking: " -msgstr "链接方式: " +#: ../version.c:2805 +msgid "Nvim is open source and freely distributable" +msgstr "Nvim 是可自由分发的开放源代码软件" -#: ../version.c:717 -msgid " DEBUG BUILD" -msgstr " 调试版本" +#: ../version.c:2808 +msgid "type :help nvim<Enter> if you are new! " +msgstr "输入 :help nvim<Enter> 了解 Neovim! " -#: ../version.c:767 -msgid "VIM - Vi IMproved" -msgstr "VIM - Vi IMproved" +#: ../version.c:2809 +msgid "type :checkhealth<Enter> to optimize Nvim" +msgstr "输入 :checkhealth<Enter> 优化 Neovim " -#: ../version.c:769 -msgid "version " -msgstr "版本 " +#: ../version.c:2810 +msgid "type :q<Enter> to exit " +msgstr "输入 :q<Enter> 退出 " -#: ../version.c:770 -msgid "by Bram Moolenaar et al." -msgstr "维护人 Bram Moolenaar 等" +#: ../version.c:2811 +msgid "type :help<Enter> for help " +msgstr "输入 :help<Enter> 查看帮助 " -#: ../version.c:774 -msgid "Vim is open source and freely distributable" -msgstr "Vim 是可自由分发的开放源代码软件" +#: ../version.c:2813 +#, c-format +msgid "type :help news<Enter> to see changes in v%s.%s" +msgstr "输入 :help news<Enter> 查看 v%s.%s 的变化" -#: ../version.c:776 +#: ../version.c:2816 msgid "Help poor children in Uganda!" msgstr "帮助乌干达的可怜儿童!" -#: ../version.c:777 +#: ../version.c:2817 msgid "type :help iccf<Enter> for information " msgstr "输入 :help iccf<Enter> 查看说明 " -#: ../version.c:779 -msgid "type :q<Enter> to exit " -msgstr "输入 :q<Enter> 退出 " - -#: ../version.c:780 -msgid "type :help<Enter> or <F1> for on-line help" -msgstr "输入 :help<Enter> 或 <F1> 查看在线帮助 " - -#: ../version.c:781 -msgid "type :help version7<Enter> for version info" -msgstr "输入 :help version7<Enter> 查看版本信息 " - -#: ../version.c:784 -msgid "Running in Vi compatible mode" -msgstr "运行于 Vi 兼容模式" - -#: ../version.c:785 -msgid "type :set nocp<Enter> for Vim defaults" -msgstr "输入 :set nocp<Enter> 恢复默认的 Vim " - -#: ../version.c:786 -msgid "type :help cp-default<Enter> for info on this" -msgstr "输入 :help cp-default<Enter> 查看相关说明 " - -#: ../version.c:827 +#: ../version.c:2850 msgid "Sponsor Vim development!" msgstr "赞助 Vim 的开发!" -#: ../version.c:828 +#: ../version.c:2851 msgid "Become a registered Vim user!" msgstr "成为 Vim 的注册用户!" -#: ../version.c:831 +#: ../version.c:2854 msgid "type :help sponsor<Enter> for information " msgstr "输入 :help sponsor<Enter> 查看说明 " -#: ../version.c:832 +#: ../version.c:2855 msgid "type :help register<Enter> for information " msgstr "输入 :help register<Enter> 查看说明 " -#: ../version.c:834 +#: ../version.c:2857 msgid "menu Help->Sponsor/Register for information " msgstr "菜单 Help->Sponsor/Register 查看说明 " -#: ../window.c:119 +#: ../viml/parser/expressions.c:292 +#, c-format +msgid "E15: Invalid control character present in input: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:519 +#, c-format +msgid "E112: Option name missing: %.*s" +msgstr "E112: 缺少选项名称:%.*s" + +#: ../viml/parser/expressions.c:678 ../viml/parser/expressions.c:694 +#, c-format +msgid "E15: Unexpected EOC character: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:706 +#, c-format +msgid "E15: Unidentified character: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:1335 +#, c-format +msgid "E15: Operator is not associative: %.*s" +msgstr "" + +#. / Record missing operator: for things like +#. / +#. / :echo @a @a +#. / +#. / (allowed) or +#. / +#. / :echo (@a @a) +#. / +#. / (parsed as OpMissing(@a, @a)). +#. Multiple expressions allowed, return without calling +#. viml_parser_advance(). +#: ../viml/parser/expressions.c:1440 +#, c-format +msgid "E15: Missing operator: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2081 +#, c-format +msgid "E15: Expected lambda arguments list or arrow: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2101 +#, c-format +msgid "E15: Expected value part of assignment lvalue: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2120 +#, c-format +msgid "E15: Expected assignment operator or subscript: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2171 +msgid "E15: Unexpected " +msgstr "" + +#: ../viml/parser/expressions.c:2181 +#, c-format +msgid "E15: Unexpected multiplication-like operator: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2241 +msgid "E15: Environment variable name missing" +msgstr "E15: 缺少环境变量名称" + +#: ../viml/parser/expressions.c:2259 +#, c-format +msgid "E15: Expected value, got comparison operator: %.*s" +msgstr "" + +#. Value level: comma appearing here is not valid. +#. Note: in Vim string(,x) will give E116, this is not the case here. +#: ../viml/parser/expressions.c:2286 +#, c-format +msgid "E15: Expected value, got comma: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2320 +#, c-format +msgid "E15: Comma outside of call, lambda or literal: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2404 +#, c-format +msgid "E15: Colon outside of dictionary or ternary operator: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2451 +#, c-format +msgid "E15: Expected value, got closing bracket: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2464 +#, c-format +msgid "E475: Unable to assign to empty list: %.*s" +msgstr "E475: 无法赋值给空列表:%.*s" + +#: ../viml/parser/expressions.c:2474 ../viml/parser/expressions.c:2609 +#, c-format +msgid "E15: Unexpected closing figure brace: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2507 +#, c-format +msgid "E475: Nested lists not allowed when assigning: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2557 +#, c-format +msgid "E15: Expected value, got closing figure brace: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2587 +#, c-format +msgid "E15: Don't know what figure brace means: %.*s" +msgstr "" + +#. Only first branch is valid. +#: ../viml/parser/expressions.c:2706 +#, c-format +msgid "E15: Unexpected arrow: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2707 +#, c-format +msgid "E15: Arrow outside of lambda: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2788 +#, c-format +msgid "E15: Unexpected dot: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2791 +#, c-format +msgid "E15: Cannot concatenate in assignments: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2813 +#, c-format +msgid "E15: Expected value, got parenthesis: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2846 +#, c-format +msgid "E15: Unexpected closing parenthesis: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2889 +#, c-format +msgid "E15: Expected value, got question mark: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2911 +#, c-format +msgid "E114: Missing double quote: %.*s" +msgstr "E114: 缺少双引号:%.*s" + +#: ../viml/parser/expressions.c:2912 +#, c-format +msgid "E115: Missing single quote: %.*s" +msgstr "E115: 缺少单引号:%.*s" + +#: ../viml/parser/expressions.c:2931 +#, c-format +msgid "E475: Expected closing bracket to end list assignment lvalue: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2934 +#, c-format +msgid "E15: Misplaced assignment: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2938 +#, c-format +msgid "E15: Unexpected assignment: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2965 +#, c-format +msgid "E15: Expected value, got EOC: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:2988 +#, c-format +msgid "E116: Missing closing parenthesis for function call: %.*s" +msgstr "E116: 函数调用缺少结束括号:%.*s" + +#: ../viml/parser/expressions.c:2993 +#, c-format +msgid "E110: Missing closing parenthesis for nested expression: %.*s" +msgstr "E110: 嵌套表达式缺少结束括号:%.*s" + +#: ../viml/parser/expressions.c:3001 +#, c-format +msgid "E697: Missing end of List ']': %.*s" +msgstr "E697: List 缺少结束符 ']': %.*s" + +#: ../viml/parser/expressions.c:3008 +#, c-format +msgid "E723: Missing end of Dictionary '}': %.*s" +msgstr "E723: 字典缺少结束符 '}':%.*s" + +#: ../viml/parser/expressions.c:3013 +#, c-format +msgid "E15: Missing closing figure brace: %.*s" +msgstr "" + +#: ../viml/parser/expressions.c:3018 +#, c-format +msgid "E15: Missing closing figure brace for lambda: %.*s" +msgstr "" + +#. Actually Vim throws E109 in more cases. +#: ../viml/parser/expressions.c:3068 +#, c-format +msgid "E109: Missing ':' after '?': %.*s" +msgstr "E109: '?' 后缺少 ':':%.*s" + +#: ../window.c:97 +msgid "E1159: Cannot split a window when closing the buffer" +msgstr "E1159: 关闭缓冲区时不能分割窗口" + +#: ../window.c:99 msgid "Already only one window" msgstr "已经只剩一个窗口了" -#: ../window.c:224 +#: ../window.c:259 msgid "E441: There is no preview window" msgstr "E441: 没有预览窗口" -#: ../window.c:559 +#: ../window.c:988 +msgid "E242: Can't split a window while closing another" +msgstr "E242: 在关闭窗口时不能分割另一个窗口" + +#: ../window.c:1025 msgid "E442: Can't split topleft and botright at the same time" msgstr "E442: 不能同时进行 topleft 和 botright 分割" -#: ../window.c:1228 +#: ../window.c:1868 msgid "E443: Cannot rotate when another window is split" msgstr "E443: 有其它分割窗口时不能旋转" -#: ../window.c:1803 +#: ../window.c:2657 msgid "E444: Cannot close last window" msgstr "E444: 不能关闭最后一个窗口" -#: ../window.c:1810 -#, fuzzy -msgid "E813: Cannot close autocmd window" -msgstr "E444: 不能关闭最后一个窗口" - -#: ../window.c:1814 -#, fuzzy +#: ../window.c:2671 msgid "E814: Cannot close window, only autocmd window would remain" -msgstr "E444: 不能关闭最后一个窗口" +msgstr "E814: 无法关闭窗口,不然就只剩下自动命令窗口了" -#: ../window.c:2717 +#: ../window.c:3867 msgid "E445: Other window contains changes" msgstr "E445: 其它窗口有改变的内容" -#: ../window.c:4805 +#: ../window.c:6498 msgid "E446: No file name under cursor" msgstr "E446: 光标处没有文件名" -#~ msgid "Patch file" -#~ msgstr "Patch 文件" +#: ../../../runtime/optwin.vim:72 +msgid "(local to window)" +msgstr "(局部于窗口)" -#~ msgid "" -#~ "&OK\n" -#~ "&Cancel" -#~ msgstr "" -#~ "确定(&O)\n" -#~ "取消(&C)" +#: ../../../runtime/optwin.vim:73 +msgid "(local to buffer)" +msgstr "(局部于缓冲区)" -#~ msgid "E240: No connection to Vim server" -#~ msgstr "E240: 没有到 Vim 服务器的连接" +#: ../../../runtime/optwin.vim:74 +msgid "(global or local to buffer)" +msgstr "(全局或局部于缓冲区)" -#~ msgid "E241: Unable to send to %s" -#~ msgstr "E241: 无法发送到 %s" +#: ../../../runtime/optwin.vim:153 +msgid "" +"\" Each \"set\" line shows the current value of an option (on the left)." +msgstr "\" 每个 \"set\" 行显示左侧选项的当前值" -#~ msgid "E277: Unable to read a server reply" -#~ msgstr "E277: 无法读取服务器响应" +#: ../../../runtime/optwin.vim:154 +msgid "\" Hit <Enter> on a \"set\" line to execute it." +msgstr "\" 在 \"set\" 行上按 <回车> 来执行。" -#~ msgid "E258: Unable to send to client" -#~ msgstr "E258: 无法发送到客户端" +#: ../../../runtime/optwin.vim:155 +msgid "\" A boolean option will be toggled." +msgstr "\" 布尔选项将被切换。" -#~ msgid "Save As" -#~ msgstr "另存为" +#: ../../../runtime/optwin.vim:156 +msgid "" +"\" For other options you can edit the value before hitting " +"<Enter>." +msgstr "\" 对于其他选项,您可以在按 <回车> 之前编辑该值。" -#~ msgid "Edit File" -#~ msgstr "编辑文件" +#: ../../../runtime/optwin.vim:157 +msgid "\" Hit <Enter> on a help line to open a help window on this option." +msgstr "\" 在帮助行上按 <回车> 来打开关于此选项的帮助窗口。" -#~ msgid " (NOT FOUND)" -#~ msgstr " (找不到)" +#: ../../../runtime/optwin.vim:158 +msgid "\" Hit <Enter> on an index line to jump there." +msgstr "\" 在索引行上按 <回车> 来跳转到那里。" -#~ msgid "Source Vim script" -#~ msgstr "执行 Vim 脚本" +#: ../../../runtime/optwin.vim:159 +msgid "\" Hit <Space> on a \"set\" line to refresh it." +msgstr "\" 在 \"set\" 行上按 <空格> 来刷新。 " -#~ msgid "Edit File in new window" -#~ msgstr "在新窗口编辑文件" +#: ../../../runtime/optwin.vim:228 +msgid "important" +msgstr "重要选项" -#~ msgid "Append File" -#~ msgstr "追加文件" +#: ../../../runtime/optwin.vim:229 +msgid "behave very Vi compatible (not advisable)" +msgstr "非常兼容 Vi(不建议)" -#~ msgid "Window position: X %d, Y %d" -#~ msgstr "窗口位置: X %d, Y %d" +#: ../../../runtime/optwin.vim:231 +msgid "list of flags to specify Vi compatibility" +msgstr "指定 Vi 兼容性的标志列表" -#~ msgid "Save Redirection" -#~ msgstr "保存重定向" +#: ../../../runtime/optwin.vim:233 +msgid "paste mode, insert typed text literally" +msgstr "粘贴模式,按本义插入输入的文本" -#~ msgid "Save View" -#~ msgstr "保存视图" +#: ../../../runtime/optwin.vim:235 +msgid "key sequence to toggle paste mode" +msgstr "切换粘贴模式的按键序列" -#~ msgid "Save Session" -#~ msgstr "保存会话" +#: ../../../runtime/optwin.vim:241 +msgid "list of directories used for runtime files and plugins" +msgstr "运行时文件和插件使用的目录列表" -#~ msgid "Save Setup" -#~ msgstr "保存设定" +#: ../../../runtime/optwin.vim:243 +msgid "list of directories used for plugin packages" +msgstr "插件包使用的目录列表" -#~ msgid "E196: No digraphs in this version" -#~ msgstr "E196: 此版本无复合字符(digraph)" +#: ../../../runtime/optwin.vim:245 +msgid "name of the main help file" +msgstr "主帮助文件的名称" -#~ msgid "Reading from stdin..." -#~ msgstr "从标准输入读取..." +#: ../../../runtime/optwin.vim:249 +msgid "moving around, searching and patterns" +msgstr "移动、搜索以及正则表达式" -#~ msgid "[NL found]" -#~ msgstr "[找到 NL]" +#: ../../../runtime/optwin.vim:250 +msgid "list of flags specifying which commands wrap to another line" +msgstr "指定哪些命令折行的标志列表" -#~ msgid "[crypted]" -#~ msgstr "[已加密]" +#: ../../../runtime/optwin.vim:252 +msgid "" +"many jump commands move the cursor to the first non-blank\n" +"character of a line" +msgstr "许多跳转命令将光标移动到第一个非空的位置行中的字符" -#~ msgid "NetBeans disallows writes of unmodified buffers" -#~ msgstr "NetBeans 不允许未修改的缓冲区写入" +#: ../../../runtime/optwin.vim:254 +msgid "nroff macro names that separate paragraphs" +msgstr "用于分隔段落的 nroff 宏名" -#~ msgid "Partial writes disallowed for NetBeans buffers" -#~ msgstr "NetBeans 不允许缓冲区部分写入" +#: ../../../runtime/optwin.vim:256 +msgid "nroff macro names that separate sections" +msgstr "用于分隔小节的 nroff 宏名" -#~ msgid "E460: The resource fork would be lost (add ! to override)" -#~ msgstr "E460: Resource fork 会丢失 (请加 ! 强制执行)" +#: ../../../runtime/optwin.vim:258 +msgid "list of directory names used for file searching" +msgstr "用于文件搜索的目录名称列表" -#~ msgid "E229: Cannot start the GUI" -#~ msgstr "E229: 无法启动图形界面" +#: ../../../runtime/optwin.vim:261 +msgid ":cd without argument goes to the home directory" +msgstr "不带参数的 :cd 进入主目录" -#~ msgid "E230: Cannot read from \"%s\"" -#~ msgstr "E230: 无法读取文件 \"%s\"" +#: ../../../runtime/optwin.vim:263 +msgid "list of directory names used for :cd" +msgstr "目录名称列表用于 :cd" -#~ msgid "E665: Cannot start GUI, no valid font found" -#~ msgstr "E665: 无法启动图形界面,找不到有效的字体" +#: ../../../runtime/optwin.vim:266 +msgid "change to directory of file in buffer" +msgstr "切换到缓冲区的文件所在的目录" -#~ msgid "E231: 'guifontwide' invalid" -#~ msgstr "E231: 无效的 'guifontwide'" +#: ../../../runtime/optwin.vim:269 +msgid "search commands wrap around the end of the buffer" +msgstr "搜索在缓冲区折行的命令" -#~ msgid "E599: Value of 'imactivatekey' is invalid" -#~ msgstr "E599: 'imactivatekey' 的值无效" +#: ../../../runtime/optwin.vim:271 +msgid "show match for partly typed search command" +msgstr "显示匹配部分键入的搜索命令" -#~ msgid "E254: Cannot allocate color %s" -#~ msgstr "E254: 无法分配颜色 %s" +#: ../../../runtime/optwin.vim:273 +msgid "change the way backslashes are used in search patterns" +msgstr "改变反斜杠在搜索模式中的使用方式" -#~ msgid "No match at cursor, finding next" -#~ msgstr "在光标处没有匹配,查找下一个" +#: ../../../runtime/optwin.vim:275 +msgid "select the default regexp engine used" +msgstr "选择默认的正则表达式引擎" -#~ msgid "<cannot open> " -#~ msgstr "<无法打开>" +#: ../../../runtime/optwin.vim:277 +msgid "ignore case when using a search pattern" +msgstr "使用搜索模式时忽略大小写" -#~ msgid "E616: vim_SelFile: can't get font %s" -#~ msgstr "E616: vim_SelFile: 无法获取字体 %s" +#: ../../../runtime/optwin.vim:279 +msgid "override 'ignorecase' when pattern has upper case characters" +msgstr "当模式包含大写字符时,覆盖 'ignorecase'" -#~ msgid "E614: vim_SelFile: can't return to current directory" -#~ msgstr "E614: vim_SelFile: 无法返回当前目录" +#: ../../../runtime/optwin.vim:281 +msgid "what method to use for changing case of letters" +msgstr "用什么方法来改变字母的大小写" -#~ msgid "Pathname:" -#~ msgstr "路径:" +#: ../../../runtime/optwin.vim:283 +msgid "maximum amount of memory in Kbyte used for pattern matching" +msgstr "模式匹配使用的最大内存(以千字节为单位)" -#~ msgid "E615: vim_SelFile: can't get current directory" -#~ msgstr "E615: vim_SelFile: 无法获取当前目录" +#: ../../../runtime/optwin.vim:285 +msgid "pattern for a macro definition line" +msgstr "宏定义行的模式" -#~ msgid "OK" -#~ msgstr "确定" +#: ../../../runtime/optwin.vim:289 +msgid "pattern for an include-file line" +msgstr "包含文件行的模式" -#~ msgid "Cancel" -#~ msgstr "取消" +#: ../../../runtime/optwin.vim:292 +msgid "expression used to transform an include line to a file name" +msgstr "用于将包含行转换为文件名的表达式" -#~ msgid "Scrollbar Widget: Could not get geometry of thumb pixmap." -#~ msgstr "滚动条部件: 无法获取滑块图像的几何大小" +#: ../../../runtime/optwin.vim:298 +msgid "tags" +msgstr "标签" -#~ msgid "Vim dialog" -#~ msgstr "Vim 对话框" +#: ../../../runtime/optwin.vim:299 +msgid "use binary searching in tags files" +msgstr "在标签文件中使用二分法查找" -#~ msgid "E232: Cannot create BalloonEval with both message and callback" -#~ msgstr "E232: 不能同时使用消息和回调函数来创建 BalloonEval" +#: ../../../runtime/optwin.vim:301 +msgid "number of significant characters in a tag name or zero" +msgstr "标签名称中的有效字符数,默认为零" -#~ msgid "Vim dialog..." -#~ msgstr "Vim 对话框..." +#: ../../../runtime/optwin.vim:303 +msgid "list of file names to search for tags" +msgstr "用于搜索标签的文件名列表" -#~ msgid "Input _Methods" -#~ msgstr "输入法(_M)" +#: ../../../runtime/optwin.vim:306 +msgid "" +"how to handle case when searching in tags files:\n" +"\"followic\" to follow 'ignorecase', \"ignore\" or \"match\"" +msgstr "" +"在标签文件中搜索如何处理大小写: \"followic\" 跟随 'ignorecase', \"ignore\" 或" +"者 \"match\"" -#~ msgid "VIM - Search and Replace..." -#~ msgstr "VIM - 查找和替换..." +#: ../../../runtime/optwin.vim:309 +msgid "file names in a tags file are relative to the tags file" +msgstr "标签文件里的文件名是相对于标签文件的路径" -#~ msgid "VIM - Search..." -#~ msgstr "VIM - 查找..." +#: ../../../runtime/optwin.vim:311 +msgid "a :tag command will use the tagstack" +msgstr ":tag 命令将使用 tagstack" -#~ msgid "Find what:" -#~ msgstr "查找内容:" +#: ../../../runtime/optwin.vim:313 +msgid "when completing tags in Insert mode show more info" +msgstr "在插入模式补全标签时显示更多信息" -#~ msgid "Replace with:" -#~ msgstr "替换为:" +#: ../../../runtime/optwin.vim:316 +msgid "a function to be used to perform tag searches" +msgstr "用于执行标签搜索的函数" -#~ msgid "Match whole word only" -#~ msgstr "匹配完整的词" +#: ../../../runtime/optwin.vim:322 +msgid "displaying text" +msgstr "显示文本" -#~ msgid "Match case" -#~ msgstr "匹配大小写" +#: ../../../runtime/optwin.vim:323 +msgid "number of lines to scroll for CTRL-U and CTRL-D" +msgstr "按 CTRL-U 和 CTRL-D 滚动的行数" -#~ msgid "Direction" -#~ msgstr "方向" +#: ../../../runtime/optwin.vim:326 +msgid "number of screen lines to show around the cursor" +msgstr "在光标周围显示的屏幕行数" -#~ msgid "Up" -#~ msgstr "向上" +#: ../../../runtime/optwin.vim:328 +msgid "long lines wrap" +msgstr "长行自动换行" -#~ msgid "Down" -#~ msgstr "向下" +#: ../../../runtime/optwin.vim:331 +msgid "wrap long lines at a character in 'breakat'" +msgstr "在 'breakat' 中的字符处对长行折行" -#~ msgid "Find Next" -#~ msgstr "查找下一个" +#: ../../../runtime/optwin.vim:334 +msgid "preserve indentation in wrapped text" +msgstr "在折行文本中保持缩进" -#~ msgid "Replace" -#~ msgstr "替换" +#: ../../../runtime/optwin.vim:337 +msgid "adjust breakindent behaviour" +msgstr "调整 breakindent 的行为" -#~ msgid "Replace All" -#~ msgstr "全部替换" +#: ../../../runtime/optwin.vim:340 +msgid "which characters might cause a line break" +msgstr "哪些字符可能导致换行" -#~ msgid "Vim: Received \"die\" request from session manager\n" -#~ msgstr "Vim: 从会话管理器收到 \"die\" 请求\n" +#: ../../../runtime/optwin.vim:342 +msgid "string to put before wrapped screen lines" +msgstr "放在折回的屏幕行之前的字符串" -#~ msgid "Close" -#~ msgstr "关闭" +#: ../../../runtime/optwin.vim:344 +msgid "minimal number of columns to scroll horizontally" +msgstr "水平滚动的最小列数" -#~ msgid "New tab" -#~ msgstr "新建标签" +#: ../../../runtime/optwin.vim:346 +msgid "minimal number of columns to keep left and right of the cursor" +msgstr "保留在光标左右的最小列数" -#~ msgid "Open Tab..." -#~ msgstr "打开标签..." +#: ../../../runtime/optwin.vim:348 +msgid "" +"include \"lastline\" to show the last line even if it doesn't fit\n" +"include \"uhex\" to show unprintable characters as a hex number" +msgstr "" +"包含 \"lastline\" 来显示最后一行,即使它显示不下\n" +"包含 \"uhex\" 以十六进制显示不可打印字符" -#~ msgid "Vim: Main window unexpectedly destroyed\n" -#~ msgstr "Vim: 主窗口被意外地摧毁\n" +#: ../../../runtime/optwin.vim:350 +msgid "characters to use for the status line, folds and filler lines" +msgstr "用于状态行、折叠和填充行的字符" -#~ msgid "Font Selection" -#~ msgstr "选择字体" +#: ../../../runtime/optwin.vim:352 +msgid "number of lines used for the command-line" +msgstr "用于命令行的行数" -#~ msgid "Used CUT_BUFFER0 instead of empty selection" -#~ msgstr "使用 CUT_BUFFER0 来取代空选择" +#: ../../../runtime/optwin.vim:354 +msgid "width of the display" +msgstr "显示的宽度" -#~ msgid "&Filter" -#~ msgstr "过滤(&F)" +#: ../../../runtime/optwin.vim:356 +msgid "number of lines in the display" +msgstr "显示的行数" -#~ msgid "&Cancel" -#~ msgstr "取消(&C)" +#: ../../../runtime/optwin.vim:358 +msgid "number of lines to scroll for CTRL-F and CTRL-B" +msgstr "按 CTRL-F 和 CTRL-B 滚动的行数" -#~ msgid "Directories" -#~ msgstr "目录" +#: ../../../runtime/optwin.vim:360 +msgid "don't redraw while executing macros" +msgstr "在执行宏时不要重新绘制" -#~ msgid "Filter" -#~ msgstr "过滤器" +#: ../../../runtime/optwin.vim:363 +msgid "timeout for 'hlsearch' and :match highlighting in msec" +msgstr "'hlsearch' 和 :match 高亮的超时时间(以毫秒计)" -#~ msgid "&Help" -#~ msgstr "帮助(&H)" +#: ../../../runtime/optwin.vim:366 +msgid "" +"delay in msec for each char written to the display\n" +"(for debugging)" +msgstr "每个字符写到显示的延时(以毫秒计;用于调试)" -#~ msgid "Files" -#~ msgstr "文件" +#: ../../../runtime/optwin.vim:368 +msgid "show <Tab> as ^I and end-of-line as $" +msgstr "以 ^I 显示 <Tab>, 以 $ 显示行尾" -#~ msgid "&OK" -#~ msgstr "确定(&O)" +#: ../../../runtime/optwin.vim:371 +msgid "list of strings used for list mode" +msgstr "用于 list 模式的字符串列表" -#~ msgid "Selection" -#~ msgstr "选择" +#: ../../../runtime/optwin.vim:373 +msgid "show the line number for each line" +msgstr "对每一行显示行号" -#~ msgid "Find &Next" -#~ msgstr "查找下一个(&N)" +#: ../../../runtime/optwin.vim:376 +msgid "show the relative line number for each line" +msgstr "显示每行的相对行号" -#~ msgid "&Replace" -#~ msgstr "替换(&R)" +#: ../../../runtime/optwin.vim:380 +msgid "number of columns to use for the line number" +msgstr "用于行号的列数" -#~ msgid "Replace &All" -#~ msgstr "全部替换(&A)" +#: ../../../runtime/optwin.vim:385 +msgid "controls whether concealable text is hidden" +msgstr "控制是否隐藏可隐藏文本" -#~ msgid "&Undo" -#~ msgstr "撤销(&U)" +#: ../../../runtime/optwin.vim:388 +msgid "modes in which text in the cursor line can be concealed" +msgstr "可隐藏光标行的文本的模式" -#~ msgid "E671: Cannot find window title \"%s\"" -#~ msgstr "E671: 找不到窗口标题 \"%s\"" +#: ../../../runtime/optwin.vim:394 +msgid "syntax, highlighting and spelling" +msgstr "语法、高亮和拼写" -#~ msgid "E243: Argument not supported: \"-%s\"; Use the OLE version." -#~ msgstr "E243: 不支持的参数: \"-%s\";请使用 OLE 版本。" +#: ../../../runtime/optwin.vim:395 +msgid "\"dark\" or \"light\"; the background color brightness" +msgstr "\"dark\" 或者 \"light\";背景色亮度" -#~ msgid "E672: Unable to open window inside MDI application" -#~ msgstr "E672: 无法在 MDI 应用程序中打开窗口" +#: ../../../runtime/optwin.vim:397 +msgid "type of file; triggers the FileType event when set" +msgstr "文件类型; 在设置时触发 FileType 事件" -#~ msgid "Close tab" -#~ msgstr "关闭标签" +#: ../../../runtime/optwin.vim:401 +msgid "name of syntax highlighting used" +msgstr "使用中的语法高亮显示的名称" -#~ msgid "Open tab..." -#~ msgstr "打开标签..." +#: ../../../runtime/optwin.vim:404 +msgid "maximum column to look for syntax items" +msgstr "寻找语法项的最大列" -#~ msgid "Find string (use '\\\\' to find a '\\')" -#~ msgstr "查找字符串 (使用 '\\\\' 来查找 '\\')" +#: ../../../runtime/optwin.vim:408 +msgid "which highlighting to use for various occasions" +msgstr "在不同场合使用哪些高亮提示" -#~ msgid "Find & Replace (use '\\\\' to find a '\\')" -#~ msgstr "查找和替换字符串 (使用 '\\\\' 来查找 '\\')" +#: ../../../runtime/optwin.vim:410 +msgid "highlight all matches for the last used search pattern" +msgstr "高亮显示最后使用的搜索模式的所有匹配项" -#~ msgid "Not Used" -#~ msgstr "未使用" +#: ../../../runtime/optwin.vim:413 +msgid "use GUI colors for the terminal" +msgstr "为终端使用 GUI 颜色" -#~ msgid "Directory\t*.nothing\n" -#~ msgstr "目录\t*.nothing\n" +#: ../../../runtime/optwin.vim:417 +msgid "highlight the screen column of the cursor" +msgstr "突出显示光标的屏幕列" -#~ msgid "" -#~ "Vim E458: Cannot allocate colormap entry, some colors may be incorrect" -#~ msgstr "Vim E458: 无法分配颜色表项,某些颜色可能不正确" +#: ../../../runtime/optwin.vim:420 +msgid "highlight the screen line of the cursor" +msgstr "突出显示光标的屏幕行" -#~ msgid "E250: Fonts for the following charsets are missing in fontset %s:" -#~ msgstr "E250: Fontset %s 缺少下列字符集的字体:" +#: ../../../runtime/optwin.vim:423 +msgid "specifies which area 'cursorline' highlights" +msgstr "指定 'cursorline' 高亮显示的区域" -#~ msgid "E252: Fontset name: %s" -#~ msgstr "E252: Fontset 名称: %s" +#: ../../../runtime/optwin.vim:426 +msgid "columns to highlight" +msgstr "要高亮的列" -#~ msgid "Font '%s' is not fixed-width" -#~ msgstr "'%s' 不是固定宽度的字体" +#: ../../../runtime/optwin.vim:429 +msgid "highlight spelling mistakes" +msgstr "高亮拼写错误" -#~ msgid "E253: Fontset name: %s\n" -#~ msgstr "E253: Fontset 名称: %s\n" +#: ../../../runtime/optwin.vim:432 +msgid "list of accepted languages" +msgstr "接受的语言列表" -#~ msgid "Font0: %s\n" -#~ msgstr "字体0: %s\n" +#: ../../../runtime/optwin.vim:435 +msgid "file that \"zg\" adds good words to" +msgstr "\"zg\" 添加正确单词的文件" -#~ msgid "Font1: %s\n" -#~ msgstr "字体1: %s\n" +#: ../../../runtime/optwin.vim:438 +msgid "pattern to locate the end of a sentence" +msgstr "定位句子尾部的模式" -#~ msgid "Font%<PRId64> width is not twice that of font0\n" -#~ msgstr "字体%<PRId64>的宽度不是字体0的两倍\n" +#: ../../../runtime/optwin.vim:441 +msgid "flags to change how spell checking works" +msgstr "更改拼写检查工作方式的标志" -#~ msgid "Font0 width: %<PRId64>\n" -#~ msgstr "字体0的宽度:%<PRId64>\n" +#: ../../../runtime/optwin.vim:444 +msgid "methods used to suggest corrections" +msgstr "用于建议修正的方法" -#~ msgid "" -#~ "Font1 width: %<PRId64>\n" -#~ "\n" -#~ msgstr "" -#~ "字体1的宽度: %<PRId64>\n" -#~ "\n" +#: ../../../runtime/optwin.vim:446 +msgid "amount of memory used by :mkspell before compressing" +msgstr ":mkspell 在压缩前使用的内存量" -#~ msgid "Invalid font specification" -#~ msgstr "指定了无效的字体" +#: ../../../runtime/optwin.vim:451 +msgid "multiple windows" +msgstr "多个窗口" -#~ msgid "&Dismiss" -#~ msgstr "取消(&D)" +#: ../../../runtime/optwin.vim:452 +msgid "0, 1, 2 or 3; when to use a status line for the last window" +msgstr "0、1、2 或 3;何时为最后一个窗口使用状态行" -#~ msgid "no specific match" -#~ msgstr "找不到匹配的项" +#: ../../../runtime/optwin.vim:455 +msgid "alternate format to be used for a status line" +msgstr "用于状态行的替代格式" -#~ msgid "Vim - Font Selector" -#~ msgstr "Vim - 字体选择器" +#: ../../../runtime/optwin.vim:458 +msgid "make all windows the same size when adding/removing windows" +msgstr "当添加/删除窗口时,使所有窗口的大小相同" -#~ msgid "Name:" -#~ msgstr "名称:" +#: ../../../runtime/optwin.vim:460 +msgid "in which direction 'equalalways' works: \"ver\", \"hor\" or \"both\"" +msgstr "'equalalways' 的工作方向:\"ver\", \"hor\" 或者 \"both\"" -#~ msgid "Encoding:" -#~ msgstr "编码:" +#: ../../../runtime/optwin.vim:462 +msgid "minimal number of lines used for the current window" +msgstr "当前窗口的最小行数" -#~ msgid "Font:" -#~ msgstr "字体:" +#: ../../../runtime/optwin.vim:464 +msgid "minimal number of lines used for any window" +msgstr "任何窗口的最小行数" -#~ msgid "Style:" -#~ msgstr "风格:" +#: ../../../runtime/optwin.vim:466 +msgid "keep the height of the window" +msgstr "保持窗口的高度" -#~ msgid "Size:" -#~ msgstr "尺寸:" +#: ../../../runtime/optwin.vim:469 +msgid "keep the width of the window" +msgstr "保持窗口的宽度" -#~ msgid "E256: Hangul automata ERROR" -#~ msgstr "E256: Hangul automata 错误" +#: ../../../runtime/optwin.vim:472 +msgid "minimal number of columns used for the current window" +msgstr "当前窗口的最小列数" -#~ msgid "E563: stat error" -#~ msgstr "E563: stat 错误" +#: ../../../runtime/optwin.vim:474 +msgid "minimal number of columns used for any window" +msgstr "任何窗口的最小列数" -#~ msgid "E625: cannot open cscope database: %s" -#~ msgstr "E625: 无法打开 cscope 数据库: %s" +#: ../../../runtime/optwin.vim:476 +msgid "initial height of the help window" +msgstr "帮助窗口的初始高度" -#~ msgid "E626: cannot get cscope database information" -#~ msgstr "E626: 无法获取 cscope 数据库信息" +#: ../../../runtime/optwin.vim:481 +msgid "default height for the preview window" +msgstr "预览窗口的默认高度" -#~ msgid "E569: maximum number of cscope connections reached" -#~ msgstr "E569: 已达到 cscope 的最大连接数" +#: ../../../runtime/optwin.vim:483 +msgid "identifies the preview window" +msgstr "标识预览窗口" -#~ msgid "" -#~ "???: Sorry, this command is disabled, the MzScheme library could not be " -#~ "loaded." -#~ msgstr "???: 抱歉,此命令不可用,无法加载 MzScheme 库" +#: ../../../runtime/optwin.vim:487 +msgid "don't unload a buffer when no longer shown in a window" +msgstr "当缓冲区不再显示在窗口中时,不要卸载它" -#~ msgid "invalid expression" -#~ msgstr "无效的表达式" +#: ../../../runtime/optwin.vim:489 +msgid "" +"\"useopen\" and/or \"split\"; which window to use when jumping\n" +"to a buffer" +msgstr "当向缓冲区跳转时可使用窗口: \"useopen\" 和/或 \"split\"" -#~ msgid "expressions disabled at compile time" -#~ msgstr "编译时没有启用表达式" +#: ../../../runtime/optwin.vim:491 +msgid "a new window is put below the current one" +msgstr "新窗口放在当前窗口的下面" -#~ msgid "hidden option" -#~ msgstr "隐藏的选项" +#: ../../../runtime/optwin.vim:493 +msgid "determines scroll behavior for split windows" +msgstr "" -#~ msgid "unknown option" -#~ msgstr "未知的选项" +#: ../../../runtime/optwin.vim:495 +msgid "a new window is put right of the current one" +msgstr "新窗口放在当前窗口的右边" -#~ msgid "window index is out of range" -#~ msgstr "窗口索引超出范围" +#: ../../../runtime/optwin.vim:497 +msgid "this window scrolls together with other bound windows" +msgstr "此窗口与其他已绑定的窗口一起滚动" -#~ msgid "couldn't open buffer" -#~ msgstr "无法打开缓冲区" +#: ../../../runtime/optwin.vim:500 +msgid "\"ver\", \"hor\" and/or \"jump\"; list of options for 'scrollbind'" +msgstr "'scrollbind' 的选项列表: \"ver\", \"hor\" 和/或 \"jump\"" -#~ msgid "cannot save undo information" -#~ msgstr "无法保存撤销信息" +#: ../../../runtime/optwin.vim:502 +msgid "this window's cursor moves together with other bound windows" +msgstr "此窗口的光标与其他已绑定的窗口一起移动" -#~ msgid "cannot delete line" -#~ msgstr "无法删除行" +#: ../../../runtime/optwin.vim:506 +msgid "size of a terminal window" +msgstr "终端窗口的大小" -#~ msgid "cannot replace line" -#~ msgstr "无法替换行" +#: ../../../runtime/optwin.vim:509 +msgid "key that precedes Vim commands in a terminal window" +msgstr "终端窗口中 Vim 命令的前导键" -#~ msgid "cannot insert line" -#~ msgstr "无法插入行" +#: ../../../runtime/optwin.vim:515 +msgid "multiple tab pages" +msgstr "多个标签页" -#~ msgid "string cannot contain newlines" -#~ msgstr "字符串不能包含换行(NL)" +#: ../../../runtime/optwin.vim:516 +msgid "0, 1 or 2; when to use a tab pages line" +msgstr "0, 1 或 2; 何时使用标签页行" -#~ msgid "Vim error: ~a" -#~ msgstr "Vim 错误: ~a" +#: ../../../runtime/optwin.vim:518 +msgid "maximum number of tab pages to open for -p and \"tab all\"" +msgstr "-p 和 \"tab all\" 打开的最大标签页数" -#~ msgid "Vim error" -#~ msgstr "Vim 错误" +#: ../../../runtime/optwin.vim:520 +msgid "custom tab pages line" +msgstr "自定义标签页行" -#~ msgid "buffer is invalid" -#~ msgstr "缓冲区无效" +#: ../../../runtime/optwin.vim:523 +msgid "custom tab page label for the GUI" +msgstr "为 GUI 自定义标签页的标签" -#~ msgid "window is invalid" -#~ msgstr "窗口无效" +#: ../../../runtime/optwin.vim:525 +msgid "custom tab page tooltip for the GUI" +msgstr "为 GUI 自定义标签页的工具提示" -#~ msgid "linenr out of range" -#~ msgstr "行号超出范围" +#: ../../../runtime/optwin.vim:530 +msgid "terminal" +msgstr "终端" -#~ msgid "not allowed in the Vim sandbox" -#~ msgstr "不允许在 sandbox 中使用" +#: ../../../runtime/optwin.vim:531 +msgid "minimal number of lines to scroll at a time" +msgstr "一次滚动的最少行数" -#~ msgid "" -#~ "E263: Sorry, this command is disabled, the Python library could not be " -#~ "loaded." -#~ msgstr "E263: 抱歉,此命令不可用,无法加载 Python 库。" +#: ../../../runtime/optwin.vim:534 +msgid "specifies what the cursor looks like in different modes" +msgstr "指定光标在不同模式下的样子" -#~ msgid "E659: Cannot invoke Python recursively" -#~ msgstr "E659: 不能递归调用 Python" +#: ../../../runtime/optwin.vim:539 +msgid "show info in the window title" +msgstr "在窗口标题中显示信息" -#~ msgid "can't delete OutputObject attributes" -#~ msgstr "不能删除 OutputObject 属性" +#: ../../../runtime/optwin.vim:542 +msgid "percentage of 'columns' used for the window title" +msgstr "窗口标题的 'columns' 的百分比" -#~ msgid "softspace must be an integer" -#~ msgstr "softspace 必须是整数" +#: ../../../runtime/optwin.vim:544 +msgid "when not empty, string to be used for the window title" +msgstr "非空时,用于窗口标题的字符串" -#~ msgid "invalid attribute" -#~ msgstr "无效的属性" +#: ../../../runtime/optwin.vim:546 +msgid "string to restore the title to when exiting Vim" +msgstr "退出 Vim 时用于恢复标题的字符串" -#~ msgid "writelines() requires list of strings" -#~ msgstr "writelines() 需要字符串列表作参数" +#: ../../../runtime/optwin.vim:549 +msgid "set the text of the icon for this window" +msgstr "设置此窗口图标的文本" -#~ msgid "E264: Python: Error initialising I/O objects" -#~ msgstr "E264: Python: 初始化 I/O 对象出错" +#: ../../../runtime/optwin.vim:552 +msgid "when not empty, text for the icon of this window" +msgstr "非空时,设置此窗口图标的文本" -#~ msgid "attempt to refer to deleted buffer" -#~ msgstr "试图引用已被删除的缓冲区" +#: ../../../runtime/optwin.vim:557 +msgid "using the mouse" +msgstr "使用鼠标" -#~ msgid "line number out of range" -#~ msgstr "行号超出范围" +#: ../../../runtime/optwin.vim:558 +msgid "list of flags for using the mouse" +msgstr "使用鼠标时的标志列表" -#~ msgid "<buffer object (deleted) at %8lX>" -#~ msgstr "<缓冲区对象(已删除): %8lX>" +#: ../../../runtime/optwin.vim:561 +msgid "the window with the mouse pointer becomes the current one" +msgstr "带有鼠标指针的窗口成为当前窗口" -#~ msgid "invalid mark name" -#~ msgstr "无效的标记名称" +#: ../../../runtime/optwin.vim:563 +msgid "hide the mouse pointer while typing" +msgstr "在输入时隐藏鼠标指针" -#~ msgid "no such buffer" -#~ msgstr "无此缓冲区" +#: ../../../runtime/optwin.vim:566 +msgid "" +"\"extend\", \"popup\" or \"popup_setpos\"; what the right\n" +"mouse button is used for" +msgstr "\"extend\", \"popup\" 或 \"popup_setpos\"; 鼠标右键用于什么" -#~ msgid "attempt to refer to deleted window" -#~ msgstr "试图引用已被删除的窗口" +#: ../../../runtime/optwin.vim:568 +msgid "maximum time in msec to recognize a double-click" +msgstr "识别双击的最大时间(以毫秒计)" -#~ msgid "readonly attribute" -#~ msgstr "只读属性" +#: ../../../runtime/optwin.vim:571 +msgid "what the mouse pointer looks like in different modes" +msgstr "鼠标指针在不同模式下的样子" -#~ msgid "cursor position outside buffer" -#~ msgstr "光标位置在缓冲区外" +#: ../../../runtime/optwin.vim:577 +msgid "GUI" +msgstr "图形用户界面" -#~ msgid "<window object (deleted) at %.8lX>" -#~ msgstr "<窗口对象(已删除): %.8lX>" +#: ../../../runtime/optwin.vim:578 +msgid "list of font names to be used in the GUI" +msgstr "在 GUI 中使用的字体名称列表" -#~ msgid "<window object (unknown) at %.8lX>" -#~ msgstr "<窗口对象(未知): %.8lX>" +#: ../../../runtime/optwin.vim:581 +msgid "pair of fonts to be used, for multibyte editing" +msgstr "用于多字节编辑的字体对" -#~ msgid "<window %d>" -#~ msgstr "<窗口 %d>" +#: ../../../runtime/optwin.vim:584 +msgid "list of font names to be used for double-wide characters" +msgstr "用于双宽度字符的字体名称列表" -#~ msgid "no such window" -#~ msgstr "无此窗口" +#: ../../../runtime/optwin.vim:586 +msgid "list of flags that specify how the GUI works" +msgstr "指定 GUI 工作方式的标志列表" -#~ msgid "" -#~ "E266: Sorry, this command is disabled, the Ruby library could not be " -#~ "loaded." -#~ msgstr "E266: 抱歉,此命令不可用,无法加载 Ruby 库" +#: ../../../runtime/optwin.vim:589 +msgid "\"icons\", \"text\" and/or \"tooltips\"; how to show the toolbar" +msgstr "\"icons\", \"text\" 和/或 \"tooltips\"; 如何显示工具栏" -#~ msgid "E273: unknown longjmp status %d" -#~ msgstr "E273: 未知的 longjmp 状态 %d" +#: ../../../runtime/optwin.vim:592 +msgid "size of toolbar icons" +msgstr "工具栏图标大小" -#~ msgid "Toggle implementation/definition" -#~ msgstr "切换实现/定义" +#: ../../../runtime/optwin.vim:597 +msgid "" +"\"last\", \"buffer\" or \"current\": which directory used for the file " +"browser" +msgstr "\"last\", \"buffer\" 或 \"current\"; 文件浏览器使用哪个目录" -#~ msgid "Show base class of" -#~ msgstr "显示 base class of:" +#: ../../../runtime/optwin.vim:601 +msgid "language to be used for the menus" +msgstr "菜单使用的语言" -#~ msgid "Show overridden member function" -#~ msgstr "显示被覆盖的成员函数" +#: ../../../runtime/optwin.vim:604 +msgid "maximum number of items in one menu" +msgstr "单个菜单中的最大项目数量" -#~ msgid "Retrieve from file" -#~ msgstr "恢复: 从文件" +#: ../../../runtime/optwin.vim:607 +msgid "\"no\", \"yes\" or \"menu\"; how to use the ALT key" +msgstr "\"no\", \"yes\" 或 \"menu\"; 如何使用 ALT 键" -#~ msgid "Retrieve from project" -#~ msgstr "恢复: 从对象" +#: ../../../runtime/optwin.vim:610 +msgid "number of pixel lines to use between characters" +msgstr "字符之间使用的像素行数" -#~ msgid "Retrieve from all projects" -#~ msgstr "恢复: 从所有项目" +#: ../../../runtime/optwin.vim:613 +msgid "delay in milliseconds before a balloon may pop up" +msgstr "延迟指定毫秒后,气泡可能会弹出" -#~ msgid "Retrieve" -#~ msgstr "恢复" +#: ../../../runtime/optwin.vim:616 +msgid "use balloon evaluation in the GUI" +msgstr "在 GUI 中使用气泡求值" -#~ msgid "Show source of" -#~ msgstr "显示源代码: " +#: ../../../runtime/optwin.vim:620 +msgid "use balloon evaluation in the terminal" +msgstr "在终端中使用气泡求值" -#~ msgid "Find symbol" -#~ msgstr "查找 symbol" +#: ../../../runtime/optwin.vim:624 +msgid "expression to show in balloon eval" +msgstr "在气泡求值中显示的表达式" -#~ msgid "Browse class" -#~ msgstr "浏览 class" +#: ../../../runtime/optwin.vim:631 +msgid "printing" +msgstr "打印" -#~ msgid "Show class in hierarchy" -#~ msgstr "显示层次关系的类" +#: ../../../runtime/optwin.vim:654 +msgid "messages and info" +msgstr "消息和信息" -#~ msgid "Show class in restricted hierarchy" -#~ msgstr "显示 restricted 层次关系的 class" +#: ../../../runtime/optwin.vim:655 +msgid "add 's' flag in 'shortmess' (don't show search message)" +msgstr "在 'shortmess' 中添加 's' 标志(不显示搜索消息)" -#~ msgid "Xref refers to" -#~ msgstr "Xref 参考到" +#: ../../../runtime/optwin.vim:657 +msgid "list of flags to make messages shorter" +msgstr "使消息更短的标志列表" -#~ msgid "Xref referred by" -#~ msgstr "Xref 被谁参考:" +#: ../../../runtime/optwin.vim:659 +msgid "show (partial) command keys in the status line" +msgstr "在状态行中显示(不完整的)命令键" -#~ msgid "Xref has a" -#~ msgstr "Xref 有" +#: ../../../runtime/optwin.vim:663 +msgid "display the current mode in the status line" +msgstr "在状态行中显示当前模式" -#~ msgid "Xref used by" -#~ msgstr "Xref 被谁使用:" +#: ../../../runtime/optwin.vim:665 +msgid "show cursor position below each window" +msgstr "在每个窗口下方显示光标的位置" -#~ msgid "Show docu of" -#~ msgstr "显示文件: " +#: ../../../runtime/optwin.vim:670 +msgid "alternate format to be used for the ruler" +msgstr "ruler 的替代格式" -#~ msgid "Generate docu for" -#~ msgstr "产生文件: " +#: ../../../runtime/optwin.vim:673 +msgid "threshold for reporting number of changed lines" +msgstr "报告已更改行数的阈值" -#~ msgid "" -#~ "Cannot connect to SNiFF+. Check environment (sniffemacs must be found in " -#~ "$PATH).\n" -#~ msgstr "" -#~ "不能连接到 SNiFF+。请检查环境变量 ($PATH 里必需可以找到 sniffemacs)\n" +#: ../../../runtime/optwin.vim:675 +msgid "the higher the more messages are given" +msgstr "等级越高,给出的信息越多" -#~ msgid "E274: Sniff: Error during read. Disconnected" -#~ msgstr "E274: Sniff: 读取错误. 取消连接" +#: ../../../runtime/optwin.vim:677 +msgid "file to write messages in" +msgstr "用于写入消息的文件" -#~ msgid "SNiFF+ is currently " -#~ msgstr "SNiFF+ 目前" +#: ../../../runtime/optwin.vim:679 +msgid "pause listings when the screen is full" +msgstr "当屏幕满时暂停显示清单" -#~ msgid "not " -#~ msgstr "未" +#: ../../../runtime/optwin.vim:682 +msgid "start a dialog when a command fails" +msgstr "当命令失败时开启对话框" -#~ msgid "connected" -#~ msgstr "连接中" +#: ../../../runtime/optwin.vim:685 +msgid "ring the bell for error messages" +msgstr "错误信息响铃" -#~ msgid "E275: Unknown SNiFF+ request: %s" -#~ msgstr "E275: 不正确的 SNiff+ 调用: %s" +#: ../../../runtime/optwin.vim:687 +msgid "use a visual bell instead of beeping" +msgstr "使用视觉铃声代替响铃" -#~ msgid "E276: Error connecting to SNiFF+" -#~ msgstr "E276: 连接到 SNiFF+ 失败" +#: ../../../runtime/optwin.vim:689 +msgid "do not ring the bell for these reasons" +msgstr "不要为这些原因响铃" -#~ msgid "E278: SNiFF+ not connected" -#~ msgstr "E278: 未连接到 SNiFF+" +#: ../../../runtime/optwin.vim:692 +msgid "list of preferred languages for finding help" +msgstr "查找帮助的首选语言列表" -#~ msgid "E279: Not a SNiFF+ buffer" -#~ msgstr "E279: 不是 SNiFF+ 的缓冲区" +#: ../../../runtime/optwin.vim:697 +msgid "selecting text" +msgstr "选择文本" -#~ msgid "Sniff: Error during write. Disconnected" -#~ msgstr "Sniff: 写入错误。结束连接" +#: ../../../runtime/optwin.vim:698 +msgid "\"old\", \"inclusive\" or \"exclusive\"; how selecting text behaves" +msgstr "\"old\", \"inclusive\" 或 \"exclusive\"; 如何选择文本" -#~ msgid "invalid buffer number" -#~ msgstr "无效的缓冲区号" +#: ../../../runtime/optwin.vim:700 +msgid "" +"\"mouse\", \"key\" and/or \"cmd\"; when to start Select mode\n" +"instead of Visual mode" +msgstr "\"mouse\", \"key\" 和/或 \"cmd\"; 何时启动选择模式而不是可视模式" -#~ msgid "not implemented yet" -#~ msgstr "尚未实现" +#: ../../../runtime/optwin.vim:703 +msgid "" +"\"unnamed\" to use the * register like unnamed register\n" +"\"autoselect\" to always put selected text on the clipboard" +msgstr "" +"\"unnamed\": 像无名寄存器一样使用 * 寄存器\n" +"\"autoselect\": 将选择的文本始终放在剪贴板上" -#~ msgid "cannot set line(s)" -#~ msgstr "无法设定行" +#: ../../../runtime/optwin.vim:706 +msgid "\"startsel\" and/or \"stopsel\"; what special keys can do" +msgstr "\"startsel\" 和/或 \"stopsel\"; 特殊键可以做" -#~ msgid "mark not set" -#~ msgstr "没有设定标记" +#: ../../../runtime/optwin.vim:710 +msgid "editing text" +msgstr "编辑文本" -#~ msgid "row %d column %d" -#~ msgstr "第 %d 行 第 %d 列" +#: ../../../runtime/optwin.vim:711 +msgid "maximum number of changes that can be undone" +msgstr "可以撤销的最大更改数" -#~ msgid "cannot insert/append line" -#~ msgstr "无法插入/追加行" +#: ../../../runtime/optwin.vim:714 +msgid "automatically save and restore undo history" +msgstr "自动保存和恢复撤销历史" -#~ msgid "unknown flag: " -#~ msgstr "未知的标志: " +#: ../../../runtime/optwin.vim:716 +msgid "list of directories for undo files" +msgstr "撤销文件的目录列表" -#~ msgid "unknown vimOption" -#~ msgstr "未知的 vim 选项" +#: ../../../runtime/optwin.vim:718 +msgid "maximum number lines to save for undo on a buffer reload" +msgstr "缓冲区重新加载时为撤销保存的最大行数" -#~ msgid "keyboard interrupt" -#~ msgstr "键盘中断" +#: ../../../runtime/optwin.vim:720 +msgid "changes have been made and not written to a file" +msgstr "已经做出了修改,但没有被写入文件" -#~ msgid "vim error" -#~ msgstr "vim 错误" +#: ../../../runtime/optwin.vim:723 +msgid "buffer is not to be written" +msgstr "缓冲区不会被写入" -#~ msgid "cannot create buffer/window command: object is being deleted" -#~ msgstr "无法创建缓冲区/窗口命令: 对象将被删除" +#: ../../../runtime/optwin.vim:726 +msgid "changes to the text are possible" +msgstr "可以对文本进行更改" -#~ msgid "" -#~ "cannot register callback command: buffer/window is already being deleted" -#~ msgstr "无法注册回调命令: 缓冲区/窗口已被删除" +#: ../../../runtime/optwin.vim:729 +msgid "line length above which to break a line" +msgstr "超过行长度就断行" -#~ msgid "" -#~ "E280: TCL FATAL ERROR: reflist corrupt!? Please report this to vim-" -#~ "dev@vim.org" -#~ msgstr "E280: TCL 严重错误: reflist 损坏!?请报告给 vim-dev@vim.org" +#: ../../../runtime/optwin.vim:732 +msgid "margin from the right in which to break a line" +msgstr "断行开始的右边距" -#~ msgid "cannot register callback command: buffer/window reference not found" -#~ msgstr "无法注册回调命令: 找不到缓冲区/窗口引用" +#: ../../../runtime/optwin.vim:735 +msgid "specifies what <BS>, CTRL-W, etc. can do in Insert mode" +msgstr "指定 <BS>, CTRL-W 等在插入模式下可以做什么" -#~ msgid "" -#~ "E571: Sorry, this command is disabled: the Tcl library could not be " -#~ "loaded." -#~ msgstr "E571: 抱歉,此命令不可用,无法加载 Tcl 库" +#: ../../../runtime/optwin.vim:737 +msgid "definition of what comment lines look like" +msgstr "定义注释行长什么样" -#~ msgid "" -#~ "E281: TCL ERROR: exit code is not int!? Please report this to vim-dev@vim." -#~ "org" -#~ msgstr "E281: TCL 错误: 退出返回值不是整数!?请报告给 vim-dev@vim.org" +#: ../../../runtime/optwin.vim:740 +msgid "list of flags that tell how automatic formatting works" +msgstr "控制自动格式化如何工作的标志列表" -#~ msgid "E572: exit code %d" -#~ msgstr "E572: 退出返回值 %d" +#: ../../../runtime/optwin.vim:743 +msgid "pattern to recognize a numbered list" +msgstr "识别编号列表的模式" -#~ msgid "cannot get line" -#~ msgstr "无法获取行" +#: ../../../runtime/optwin.vim:747 +msgid "expression used for \"gq\" to format lines" +msgstr "用 \"gq\" 格式化行时使用的表达式" -#~ msgid "Unable to register a command server name" -#~ msgstr "无法注册命令服务器名" +#: ../../../runtime/optwin.vim:752 +msgid "specifies how Insert mode completion works for CTRL-N and CTRL-P" +msgstr "指定使用 CTRL-N 和 CTRL-P 进行插入模式补全的工作方式" -#~ msgid "E248: Failed to send command to the destination program" -#~ msgstr "E248: 无法发送命令到目的程序" +#: ../../../runtime/optwin.vim:755 +msgid "whether to use a popup menu for Insert mode completion" +msgstr "是否在插入模式补全时使用弹出菜单" -#~ msgid "E573: Invalid server id used: %s" -#~ msgstr "E573: 使用了无效的服务器 id: %s" +#: ../../../runtime/optwin.vim:757 +msgid "maximum height of the popup menu" +msgstr "弹出菜单的最大高度" -#~ msgid "E251: VIM instance registry property is badly formed. Deleted!" -#~ msgstr "E251: VIM 实例注册属性有误。已删除!" +#: ../../../runtime/optwin.vim:759 +msgid "minimum width of the popup menu" +msgstr "弹出菜单的最小宽度" -#~ msgid "This Vim was not compiled with the diff feature." -#~ msgstr "此 Vim 编译时没有加入 diff 功能" +#: ../../../runtime/optwin.vim:761 +msgid "user defined function for Insert mode completion" +msgstr "用于插入模式补全的用户定义函数" -#~ msgid "Vim: Error: Failure to start gvim from NetBeans\n" -#~ msgstr "Vim: 错误: 无法从 NetBeans 中启动 gvim\n" +#: ../../../runtime/optwin.vim:764 +msgid "function for filetype-specific Insert mode completion" +msgstr "用于特定文件类型的插入模式补全的函数" -#~ msgid "-register\t\tRegister this gvim for OLE" -#~ msgstr "-register\t\t注册此 gvim 到 OLE" +#: ../../../runtime/optwin.vim:767 +msgid "list of dictionary files for keyword completion" +msgstr "用于关键字补全的字典文件列表" -#~ msgid "-unregister\t\tUnregister gvim for OLE" -#~ msgstr "-unregister\t\t取消 OLE 中的 gvim 注册" +#: ../../../runtime/optwin.vim:770 +msgid "list of thesaurus files for keyword completion" +msgstr "用于关键字补全的同义词字典文件列表" -#~ msgid "-g\t\t\tRun using GUI (like \"gvim\")" -#~ msgstr "-g\t\t\t使用图形界面 (同 \"gvim\")" +#: ../../../runtime/optwin.vim:773 +msgid "function used for thesaurus completion" +msgstr "用于同义词字典补全的函数" -#~ msgid "-f or --nofork\tForeground: Don't fork when starting GUI" -#~ msgstr "-f 或 --nofork\t前台: 启动图形界面时不 fork" +#: ../../../runtime/optwin.vim:777 +msgid "adjust case of a keyword completion match" +msgstr "调整关键字补全匹配的大小写" -#~ msgid "-V[N]\t\tVerbose level" -#~ msgstr "-V[N]\t\tVerbose 等级" +#: ../../../runtime/optwin.vim:781 +msgid "enable entering digraphs with c1 <BS> c2" +msgstr "支持使用 c1 <BS> c2 输入二合字符" -#~ msgid "-f\t\t\tDon't use newcli to open window" -#~ msgstr "-f\t\t\t不使用 newcli 来打开窗口" +#: ../../../runtime/optwin.vim:784 +msgid "the \"~\" command behaves like an operator" +msgstr "\"~\"命令表现得像操作符" -#~ msgid "-dev <device>\t\tUse <device> for I/O" -#~ msgstr "-dev <device>\t\t使用 <device> 进行输入输出" +#: ../../../runtime/optwin.vim:786 +msgid "function called for the \"g@\" operator" +msgstr "\"g@\" 操作符调用的函数" -#~ msgid "-U <gvimrc>\t\tUse <gvimrc> instead of any .gvimrc" -#~ msgstr "-U <gvimrc>\t\t使用 <gvimrc> 替代任何 .gvimrc" +#: ../../../runtime/optwin.vim:788 +msgid "when inserting a bracket, briefly jump to its match" +msgstr "当插入括号时,短暂地跳转到匹配它的括号" -#~ msgid "-x\t\t\tEdit encrypted files" -#~ msgstr "-x\t\t\t编辑加密的文件" +#: ../../../runtime/optwin.vim:790 +msgid "tenth of a second to show a match for 'showmatch'" +msgstr "显示 'showmatch' 的匹配的时长(以十分之一秒计)" -#~ msgid "-display <display>\tConnect vim to this particular X-server" -#~ msgstr "-display <display>\t将 vim 与指定的 X-server 连接" +#: ../../../runtime/optwin.vim:792 +msgid "list of pairs that match for the \"%\" command" +msgstr "\"%\" 命令匹配的对列表" -#~ msgid "-X\t\t\tDo not connect to X server" -#~ msgstr "-X\t\t\t不连接到 X Server" +#: ../../../runtime/optwin.vim:795 +msgid "use two spaces after '.' when joining a line" +msgstr "连接行时,在 '.' 后面添加两个空格" -#~ msgid "--remote <files>\tEdit <files> in a Vim server if possible" -#~ msgstr "--remote <files>\t如有可能,在 Vim 服务器上编辑文件 <files>" +#: ../../../runtime/optwin.vim:797 +msgid "" +"\"alpha\", \"octal\", \"hex\", \"bin\" and/or \"unsigned\"; number formats\n" +"recognized for CTRL-A and CTRL-X commands" +msgstr "" +"\"alpha\", \"octal\", \"hex\", \"bin\" 和/或 \"unsigned\"\n" +"CTRL-A 和 CTRL-X 命令识别的数字的格式" -#~ msgid "--remote-silent <files> Same, don't complain if there is no server" -#~ msgstr "--remote-silent <files> 同上,找不到服务器时不抱怨" +#: ../../../runtime/optwin.vim:802 +msgid "tabs and indenting" +msgstr "Tab 和缩进" -#~ msgid "" -#~ "--remote-wait <files> As --remote but wait for files to have been edited" -#~ msgstr "--remote-wait <files> 同 --remote 但会等待文件完成编辑" +#: ../../../runtime/optwin.vim:803 +msgid "number of spaces a <Tab> in the text stands for" +msgstr "<Tab> 在文本中代表的空格数" -#~ msgid "" -#~ "--remote-wait-silent <files> Same, don't complain if there is no server" -#~ msgstr "--remote-wait-silent <files> 同上,找不到服务器时不抱怨" +#: ../../../runtime/optwin.vim:806 +msgid "number of spaces used for each step of (auto)indent" +msgstr "每步(自动)缩进所使用的空格数" -#~ msgid "--remote-tab <files> As --remote but open tab page for each file" -#~ msgstr "--remote-tab <files> 同 --remote 但对每个文件打开一个标签页" +#: ../../../runtime/optwin.vim:810 +msgid "list of number of spaces a tab counts for" +msgstr "tab 代表的空格数的列表" -#~ msgid "--remote-send <keys>\tSend <keys> to a Vim server and exit" -#~ msgstr "--remote-send <keys>\t送出 <keys> 到 Vim 服务器并退出" +#: ../../../runtime/optwin.vim:813 +msgid "list of number of spaces a soft tabsstop counts for" +msgstr "软制表符代表的空格数的列表" -#~ msgid "" -#~ "--remote-expr <expr>\tEvaluate <expr> in a Vim server and print result" -#~ msgstr "--remote-expr <expr>\t在 Vim 服务器上求 <expr> 的值并打印结果" +#: ../../../runtime/optwin.vim:817 +msgid "a <Tab> in an indent inserts 'shiftwidth' spaces" +msgstr "用 <Tab> 键缩进时插入 'shiftwidth' 个空格" -#~ msgid "--serverlist\t\tList available Vim server names and exit" -#~ msgstr "--serverlist\t\t列出可用的 Vim 服务器名称并退出" +#: ../../../runtime/optwin.vim:819 +msgid "if non-zero, number of spaces to insert for a <Tab>" +msgstr "如果非零,为 <Tab> 插入的空格数" -#~ msgid "--servername <name>\tSend to/become the Vim server <name>" -#~ msgstr "--servername <name>\t发送到或成为 Vim 服务器 <name>" +#: ../../../runtime/optwin.vim:822 +msgid "round to 'shiftwidth' for \"<<\" and \">>\"" +msgstr "用 \"<<\" 和 \">>\" 缩进时,插入 'shiftwidth' 整数倍个空格" -#~ msgid "" -#~ "\n" -#~ "Arguments recognised by gvim (Motif version):\n" -#~ msgstr "" -#~ "\n" -#~ "gvim (Motif 版本) 可识别的参数:\n" +#: ../../../runtime/optwin.vim:824 +msgid "expand <Tab> to spaces in Insert mode" +msgstr "在插入模式下将 <Tab> 展开为空格" -#~ msgid "" -#~ "\n" -#~ "Arguments recognised by gvim (neXtaw version):\n" -#~ msgstr "" -#~ "\n" -#~ "gvim (neXtaw 版本) 可识别的参数:\n" +#: ../../../runtime/optwin.vim:827 +msgid "automatically set the indent of a new line" +msgstr "自动设置新行缩进" -#~ msgid "" -#~ "\n" -#~ "Arguments recognised by gvim (Athena version):\n" -#~ msgstr "" -#~ "\n" -#~ "gvim (Athena 版本) 可识别的参数:\n" +#: ../../../runtime/optwin.vim:831 +msgid "do clever autoindenting" +msgstr "智能自动缩进" -#~ msgid "-display <display>\tRun vim on <display>" -#~ msgstr "-display <display>\t在 <display> 上运行 vim" +#: ../../../runtime/optwin.vim:836 +msgid "enable specific indenting for C code" +msgstr "为 C 代码启用特定的缩进" -#~ msgid "-iconic\t\tStart vim iconified" -#~ msgstr "-iconic\t\t启动后最小化" +#: ../../../runtime/optwin.vim:839 +msgid "options for C-indenting" +msgstr "C 风格缩进的选项" -#~ msgid "-name <name>\t\tUse resource as if vim was <name>" -#~ msgstr "-name <name>\t\t读取 Resource 时把 vim 视为 <name>" +#: ../../../runtime/optwin.vim:842 +msgid "keys that trigger C-indenting in Insert mode" +msgstr "在插入模式下触发 C 风格缩进的键" -#~ msgid "\t\t\t (Unimplemented)\n" -#~ msgstr "\t\t\t (尚未实现)\n" +#: ../../../runtime/optwin.vim:845 +msgid "list of words that cause more C-indent" +msgstr "导致更多 C 风格缩进的单词列表" -#~ msgid "-background <color>\tUse <color> for the background (also: -bg)" -#~ msgstr "-background <color>\t使用 <color> 作为背景色 (也可用 -bg)" +#: ../../../runtime/optwin.vim:848 +msgid "list of scope declaration names used by cino-g" +msgstr "cino-g 使用的作用域声明名称列表" -#~ msgid "-foreground <color>\tUse <color> for normal text (also: -fg)" -#~ msgstr "-foreground <color>\t使用 <color> 作为一般文字颜色 (也可用 -fg)" +#: ../../../runtime/optwin.vim:851 +msgid "expression used to obtain the indent of a line" +msgstr "用于获取一行缩进的表达式" -#~ msgid "-font <font>\t\tUse <font> for normal text (also: -fn)" -#~ msgstr "-font <font>\t使用 <font> 作为一般字体 (也可用 -fn)" +#: ../../../runtime/optwin.vim:854 +msgid "keys that trigger indenting with 'indentexpr' in Insert mode" +msgstr "在插入模式下使用 'indentexpr' 触发缩进的键" -#~ msgid "-boldfont <font>\tUse <font> for bold text" -#~ msgstr "-boldfont <font>\t使用 <font> 作为粗体字体" +#: ../../../runtime/optwin.vim:858 +msgid "copy whitespace for indenting from previous line" +msgstr "为缩进从上一行复制空白字符" -#~ msgid "-italicfont <font>\tUse <font> for italic text" -#~ msgstr "-italicfont <font>\t使用 <font> 作为斜体字体" +#: ../../../runtime/optwin.vim:861 +msgid "preserve kind of whitespace when changing indent" +msgstr "在更改缩进时保留空白类型" -#~ msgid "-geometry <geom>\tUse <geom> for initial geometry (also: -geom)" -#~ msgstr "-geometry <geom>\t使用 <geom> 作为初始位置 (也可用 -geom)" +#: ../../../runtime/optwin.vim:865 +msgid "enable lisp mode" +msgstr "启用 lisp 模式" -#~ msgid "-borderwidth <width>\tUse a border width of <width> (also: -bw)" -#~ msgstr "-borderwidth <width>\t设定边框宽度为 <width> (也可用 -bw)" +#: ../../../runtime/optwin.vim:868 +msgid "words that change how lisp indenting works" +msgstr "改变 lisp 如何缩进的单词" -#~ msgid "" -#~ "-scrollbarwidth <width> Use a scrollbar width of <width> (also: -sw)" -#~ msgstr "-scrollbarwidth <width> 设定滚动条宽度为 <width> (也可用 -sw)" +#: ../../../runtime/optwin.vim:870 +msgid "options for Lisp indenting" +msgstr "Lisp 缩进选项" -#~ msgid "-menuheight <height>\tUse a menu bar height of <height> (also: -mh)" -#~ msgstr "-menuheight <height>\t设定菜单栏高度为 <height> (也可用 -mh)" +#: ../../../runtime/optwin.vim:876 +msgid "folding" +msgstr "折叠" -#~ msgid "-reverse\t\tUse reverse video (also: -rv)" -#~ msgstr "-reverse\t\t使用反显 (也可用 -rv)" +#: ../../../runtime/optwin.vim:877 +msgid "unset to display all folds open" +msgstr "取消设置以打开所有折叠" -#~ msgid "+reverse\t\tDon't use reverse video (also: +rv)" -#~ msgstr "+reverse\t\t不使用反显 (也可用 +rv)" +#: ../../../runtime/optwin.vim:880 +msgid "folds with a level higher than this number will be closed" +msgstr "等级比这个数字高的折叠将被关闭" -#~ msgid "-xrm <resource>\tSet the specified resource" -#~ msgstr "-xrm <resource>\t设定指定的资源" +#: ../../../runtime/optwin.vim:883 +msgid "value for 'foldlevel' when starting to edit a file" +msgstr "开始编辑文件时,'foldlevel' 的值" -#~ msgid "" -#~ "\n" -#~ "Arguments recognised by gvim (RISC OS version):\n" -#~ msgstr "" -#~ "\n" -#~ "gvim (RISC OS 版本) 可识别的参数:\n" +#: ../../../runtime/optwin.vim:885 +msgid "width of the column used to indicate folds" +msgstr "用来指示折叠的列的宽度" -#~ msgid "--columns <number>\tInitial width of window in columns" -#~ msgstr "--columns <number>\t窗口初始宽度" +#: ../../../runtime/optwin.vim:888 +msgid "expression used to display the text of a closed fold" +msgstr "用于显示已关闭折叠的表达式" -#~ msgid "--rows <number>\tInitial height of window in rows" -#~ msgstr "--rows <number>\t窗口初始高度" +#: ../../../runtime/optwin.vim:891 +msgid "set to \"all\" to close a fold when the cursor leaves it" +msgstr "设置为 \"all\" 来在光标离开时关闭折叠" -#~ msgid "" -#~ "\n" -#~ "Arguments recognised by gvim (GTK+ version):\n" -#~ msgstr "" -#~ "\n" -#~ "gvim (GTK+ 版本) 可识别的参数:\n" +#: ../../../runtime/optwin.vim:893 +msgid "specifies for which commands a fold will be opened" +msgstr "指定会打开折叠的命令" -#~ msgid "-display <display>\tRun vim on <display> (also: --display)" -#~ msgstr "-display <display>\t在 <display> 上运行 vim (也可用 --display)" +#: ../../../runtime/optwin.vim:895 +msgid "minimum number of screen lines for a fold to be closed" +msgstr "可关闭折叠所需的最小屏幕行数" -#~ msgid "--role <role>\tSet a unique role to identify the main window" -#~ msgstr "--role <role>\t设置用于区分主窗口的窗口角色名" +#: ../../../runtime/optwin.vim:898 +msgid "template for comments; used to put the marker in" +msgstr "注释的模板,用于放置折叠标记" -#~ msgid "--socketid <xid>\tOpen Vim inside another GTK widget" -#~ msgstr "--socketid <xid>\t在另一个 GTK 部件中打开 Vim" +#: ../../../runtime/optwin.vim:900 +msgid "" +"folding type: \"manual\", \"indent\", \"expr\", \"marker\",\n" +"\"syntax\" or \"diff\"" +msgstr "" +"折叠类型:\"manual\", \"indent\", \"expr\", \"marker\", \n" +"\"syntax\" 或者 \"diff\"" -#~ msgid "-P <parent title>\tOpen Vim inside parent application" -#~ msgstr "-P <parent title>\t在父应用程序中打开 Vim" +#: ../../../runtime/optwin.vim:903 +msgid "expression used when 'foldmethod' is \"expr\"" +msgstr "当 'foldmethod' 为 \"expr\" 时使用的表达式" -#~ msgid "No display" -#~ msgstr "没有 display" +#: ../../../runtime/optwin.vim:906 +msgid "used to ignore lines when 'foldmethod' is \"indent\"" +msgstr "当 'foldmethod' 为 \"indent\" 时用于忽略行" -#~ msgid ": Send failed.\n" -#~ msgstr ": 发送失败。\n" +#: ../../../runtime/optwin.vim:909 +msgid "markers used when 'foldmethod' is \"marker\"" +msgstr "当 'foldmethod' 为 \"marker\" 时所使用的标记" -#~ msgid ": Send failed. Trying to execute locally\n" -#~ msgstr ": 发送失败。尝试本地执行\n" +#: ../../../runtime/optwin.vim:912 +msgid "maximum fold depth for when 'foldmethod' is \"indent\" or \"syntax\"" +msgstr "当 'foldmethod' 为 \"indent\" 或 \"syntax\" 时的最大折叠深度" -#~ msgid "%d of %d edited" -#~ msgstr "%d 中 %d 已编辑" +#: ../../../runtime/optwin.vim:919 +msgid "diff mode" +msgstr "差异模式" -#~ msgid "No display: Send expression failed.\n" -#~ msgstr "没有 display: 发送表达式失败。\n" +#: ../../../runtime/optwin.vim:920 +msgid "use diff mode for the current window" +msgstr "对当前窗口使用差异模式" -#~ msgid ": Send expression failed.\n" -#~ msgstr ": 发送表达式失败。\n" +#: ../../../runtime/optwin.vim:923 +msgid "options for using diff mode" +msgstr "使用差异模式的选项" -#~ msgid "E543: Not a valid codepage" -#~ msgstr "E543: 无效的代码页" +#: ../../../runtime/optwin.vim:925 +msgid "expression used to obtain a diff file" +msgstr "用于获取差异文件的表达式" -#~ msgid "E285: Failed to create input context" -#~ msgstr "E285: 无法创建输入上下文" +#: ../../../runtime/optwin.vim:927 +msgid "expression used to patch a file" +msgstr "用于给文件打补丁的表达式" -#~ msgid "E286: Failed to open input method" -#~ msgstr "E286: 无法打开输入法" +#: ../../../runtime/optwin.vim:932 +msgid "mapping" +msgstr "映射" -#~ msgid "E287: Warning: Could not set destroy callback to IM" -#~ msgstr "E287: 警告: 无法设定输入法的释放回调函数" +#: ../../../runtime/optwin.vim:933 +msgid "maximum depth of mapping" +msgstr "最大映射深度" -#~ msgid "E288: input method doesn't support any style" -#~ msgstr "E288: 输入法不支持任何风格" +#: ../../../runtime/optwin.vim:935 +msgid "allow timing out halfway into a mapping" +msgstr "允许在映射中途超时" -#~ msgid "E289: input method doesn't support my preedit type" -#~ msgstr "E289: 输入法不支持我的预编辑类型" +#: ../../../runtime/optwin.vim:937 +msgid "allow timing out halfway into a key code" +msgstr "允许在键码中途超时" -#~ msgid "E290: over-the-spot style requires fontset" -#~ msgstr "E290: over-the-spot 风格需要 Fontset" +#: ../../../runtime/optwin.vim:939 +msgid "time in msec for 'timeout'" +msgstr "'timeout' 的时间(以毫秒计)" -#~ msgid "E291: Your GTK+ is older than 1.2.3. Status area disabled" -#~ msgstr "E291: 你的 GTK+ 比 1.2.3 旧。状态区不可用。" +#: ../../../runtime/optwin.vim:941 +msgid "time in msec for 'ttimeout'" +msgstr "'ttimeout' 的时间(以毫秒计)" -#~ msgid "E292: Input Method Server is not running" -#~ msgstr "E292: 输入法服务器未运行" +#: ../../../runtime/optwin.vim:945 +msgid "reading and writing files" +msgstr "读写文件" -#~ msgid "" -#~ "\n" -#~ " [not usable with this version of Vim]" -#~ msgstr "" -#~ "\n" -#~ " [不能在该版本的 Vim 上使用]" +#: ../../../runtime/optwin.vim:946 +msgid "enable using settings from modelines when reading a file" +msgstr "读取文件时是否使用 modeline 里的设置" -#~ msgid "Tear off this menu" -#~ msgstr "撕下此菜单" +#: ../../../runtime/optwin.vim:949 +msgid "allow setting expression options from a modeline" +msgstr "允许从 modeline 中设置表达式选项" -#~ msgid "Select Directory dialog" -#~ msgstr "选择目录对话框" +#: ../../../runtime/optwin.vim:951 +msgid "number of lines to check for modelines" +msgstr "为 modeline 而检查的行数" -#~ msgid "Save File dialog" -#~ msgstr "保存文件对话框" +#: ../../../runtime/optwin.vim:953 +msgid "binary file editing" +msgstr "二进制文件编辑" -#~ msgid "Open File dialog" -#~ msgstr "打开文件对话框" +#: ../../../runtime/optwin.vim:956 +msgid "last line in the file has an end-of-line" +msgstr "文件的最后一行有换行符" -#~ msgid "E338: Sorry, no file browser in console mode" -#~ msgstr "E338: 抱歉,控制台模式下没有文件浏览器" +#: ../../../runtime/optwin.vim:959 +msgid "last line in the file followed by CTRL-Z" +msgstr "" -#~ msgid "Vim: preserving files...\n" -#~ msgstr "Vim: 正在保留文件……\n" +#: ../../../runtime/optwin.vim:962 +msgid "fixes missing end-of-line at end of text file" +msgstr "修复文本文件末尾缺少换行符的问题" -#~ msgid "Vim: Finished.\n" -#~ msgstr "Vim: 结束。\n" +#: ../../../runtime/optwin.vim:965 +msgid "prepend a Byte Order Mark to the file" +msgstr "在文件前加上字节顺序标记" -#~ msgid "ERROR: " -#~ msgstr "错误: " +#: ../../../runtime/optwin.vim:968 +msgid "end-of-line format: \"dos\", \"unix\" or \"mac\"" +msgstr "换行符格式: \"dos\", \"unix\" 或 \"mac\"" -#~ msgid "" -#~ "\n" -#~ "[bytes] total alloc-freed %<PRIu64>-%<PRIu64>, in use %<PRIu64>, peak use " -#~ "%<PRIu64>\n" -#~ msgstr "" -#~ "\n" -#~ "[字节] 总共 alloc-free %<PRIu64>-%<PRIu64>,使用中 %<PRIu64>,高峰使用 " -#~ "%<PRIu64>\n" +#: ../../../runtime/optwin.vim:971 +msgid "list of file formats to look for when editing a file" +msgstr "编辑文件时要检查的文件格式列表" -#~ msgid "" -#~ "[calls] total re/malloc()'s %<PRIu64>, total free()'s %<PRIu64>\n" -#~ "\n" -#~ msgstr "" -#~ "[调用] 总共 re/malloc(): %<PRIu64>,总共 free()': %<PRIu64>\n" -#~ "\n" +#: ../../../runtime/optwin.vim:973 +msgid "writing files is allowed" +msgstr "允许写入文件" -#~ msgid "E340: Line is becoming too long" -#~ msgstr "E340: 此行过长" +#: ../../../runtime/optwin.vim:975 +msgid "write a backup file before overwriting a file" +msgstr "覆盖文件前先写入备份文件" -#~ msgid "E341: Internal error: lalloc(%<PRId64>, )" -#~ msgstr "E341: 内部错误: lalloc(%<PRId64>, )" +#: ../../../runtime/optwin.vim:977 +msgid "keep a backup after overwriting a file" +msgstr "覆盖文件后保留备份" -#~ msgid "E547: Illegal mouseshape" -#~ msgstr "E547: 无效的鼠标形状" +#: ../../../runtime/optwin.vim:979 +msgid "patterns that specify for which files a backup is not made" +msgstr "不备份的文件的模式" -#~ msgid "Enter encryption key: " -#~ msgstr "输入密码: " +#: ../../../runtime/optwin.vim:981 +msgid "whether to make the backup as a copy or rename the existing file" +msgstr "通过复制还是重命名已有文件进行备份" -#~ msgid "Enter same key again: " -#~ msgstr "请再输入一次: " +#: ../../../runtime/optwin.vim:984 +msgid "list of directories to put backup files in" +msgstr "存放备份文件的目录列表" -#~ msgid "Keys don't match!" -#~ msgstr "两次密码不匹配!" +#: ../../../runtime/optwin.vim:986 +msgid "file name extension for the backup file" +msgstr "备份文件使用的扩展名" -#~ msgid "Cannot connect to Netbeans #2" -#~ msgstr "无法连接到 Netbeans #2" +#: ../../../runtime/optwin.vim:988 +msgid "automatically write a file when leaving a modified buffer" +msgstr "在离开修改过的缓冲区时自动写入文件" -#~ msgid "Cannot connect to Netbeans" -#~ msgstr "无法连接到 Netbeans" +#: ../../../runtime/optwin.vim:990 +msgid "as 'autowrite', but works with more commands" +msgstr "类似于 'autowrite',但适用于更多命令" -#~ msgid "E668: Wrong access mode for NetBeans connection info file: \"%s\"" -#~ msgstr "E668: NetBeans 连接信息文件中错误的访问模式: \"%s\"" +#: ../../../runtime/optwin.vim:992 +msgid "always write without asking for confirmation" +msgstr "写文件时总是不需要确认" -#~ msgid "read from Netbeans socket" -#~ msgstr "从 Netbeans 套接字读取" +#: ../../../runtime/optwin.vim:994 +msgid "automatically read a file when it was modified outside of Vim" +msgstr "在 Vim 之外修改了文件时,自动读取文件" -#~ msgid "E658: NetBeans connection lost for buffer %<PRId64>" -#~ msgstr "E658: 缓冲区 %<PRId64> 丢失 NetBeans 连接" +#: ../../../runtime/optwin.vim:997 +msgid "keep oldest version of a file; specifies file name extension" +msgstr "保存最旧版本的文件;指定文件扩展名" -#~ msgid "E505: " -#~ msgstr "E505: " +#: ../../../runtime/optwin.vim:999 +msgid "forcibly sync the file to disk after writing it" +msgstr "文件写入后强制同步到磁盘" -#~ msgid "E775: Eval feature not available" -#~ msgstr "E775: 求值功能不可用" +#: ../../../runtime/optwin.vim:1003 +msgid "the swap file" +msgstr "交换文件" -#~ msgid "freeing %<PRId64> lines" -#~ msgstr "释放了 %<PRId64> 行" +#: ../../../runtime/optwin.vim:1004 +msgid "list of directories for the swap file" +msgstr "交换文件的目录列表" -#~ msgid "E530: Cannot change term in GUI" -#~ msgstr "E530: 在图形界面中不能改变终端" +#: ../../../runtime/optwin.vim:1006 +msgid "use a swap file for this buffer" +msgstr "对这个缓冲区使用交换文件" -#~ msgid "E531: Use \":gui\" to start the GUI" -#~ msgstr "E531: 请用 \":gui\" 启动图形界面" +#: ../../../runtime/optwin.vim:1009 +msgid "number of characters typed to cause a swap file update" +msgstr "导致交换文件更新的字符数" -#~ msgid "E617: Cannot be changed in the GTK+ 2 GUI" -#~ msgstr "E617: 在 GTK+ 2 图形界面中不能更改" +#: ../../../runtime/optwin.vim:1011 +msgid "time in msec after which the swap file will be updated" +msgstr "更新交换文件前所需的毫秒数" -#~ msgid "E596: Invalid font(s)" -#~ msgstr "E596: 无效的字体" +# do not translate to avoid writing Chinese in files +#: ../../../runtime/optwin.vim:1015 +msgid "command line editing" +msgstr "" -#~ msgid "E597: can't select fontset" -#~ msgstr "E597: 无法选择 Fontset" +#: ../../../runtime/optwin.vim:1016 +msgid "how many command lines are remembered" +msgstr "记住的命令行数" -#~ msgid "E598: Invalid fontset" -#~ msgstr "E598: 无效的 Fontset" +#: ../../../runtime/optwin.vim:1018 +msgid "key that triggers command-line expansion" +msgstr "触发命令行扩展的键" -#~ msgid "E533: can't select wide font" -#~ msgstr "E533: 无法选择宽字体" +#: ../../../runtime/optwin.vim:1020 +msgid "like 'wildchar' but can also be used in a mapping" +msgstr "类似 'wildchar',但也可以在映射中使用" -#~ msgid "E534: Invalid wide font" -#~ msgstr "E534: 无效的宽字体" +#: ../../../runtime/optwin.vim:1022 +msgid "specifies how command line completion works" +msgstr "指定命令行如何补全" -#~ msgid "E538: No mouse support" -#~ msgstr "E538: 不支持鼠标" +#: ../../../runtime/optwin.vim:1025 +msgid "empty or \"tagfile\" to list file name of matching tags" +msgstr "空或 \"tagfile\" 来列出匹配标签的文件名" -#~ msgid "cannot open " -#~ msgstr "不能打开" +#: ../../../runtime/optwin.vim:1028 +msgid "list of file name extensions that have a lower priority" +msgstr "优先级低的文件扩展名列表" -#~ msgid "VIM: Can't open window!\n" -#~ msgstr "VIM: 不能打开窗口!\n" +#: ../../../runtime/optwin.vim:1031 +msgid "list of file name extensions added when searching for a file" +msgstr "搜索文件时添加的文件扩展名列表" -#~ msgid "Need Amigados version 2.04 or later\n" -#~ msgstr "需要 Amigados 版本 2.04 以上\n" +#: ../../../runtime/optwin.vim:1036 +msgid "list of patterns to ignore files for file name completion" +msgstr "文件名补全所忽略文件的模式列表" -#~ msgid "Need %s version %<PRId64>\n" -#~ msgstr "需要 %s 版本 %<PRId64>\n" +#: ../../../runtime/optwin.vim:1039 +msgid "ignore case when using file names" +msgstr "使用文件名时忽略大小写" -#~ msgid "Cannot open NIL:\n" -#~ msgstr "不能打开 NIL:\n" +#: ../../../runtime/optwin.vim:1041 +msgid "ignore case when completing file names" +msgstr "补全文件名时忽略大小写" -#~ msgid "Cannot create " -#~ msgstr "不能创建 " +#: ../../../runtime/optwin.vim:1044 +msgid "command-line completion shows a list of matches" +msgstr "命令行补全时显示匹配列表" -#~ msgid "Vim exiting with %d\n" -#~ msgstr "Vim 返回值: %d\n" +#: ../../../runtime/optwin.vim:1047 +msgid "key used to open the command-line window" +msgstr "用于打开命令行窗口的键" -#~ msgid "cannot change console mode ?!\n" -#~ msgstr "不能切换主控台(console)模式 !?\n" +#: ../../../runtime/optwin.vim:1049 +msgid "height of the command-line window" +msgstr "命令行窗口的高度" -#~ msgid "mch_get_shellsize: not a console??\n" -#~ msgstr "mch_get_shellsize: 不是主控台(console)??\n" +#: ../../../runtime/optwin.vim:1053 +msgid "executing external commands" +msgstr "执行外部命令" -#~ msgid "E360: Cannot execute shell with -f option" -#~ msgstr "E360: 不能用 -f 选项执行 shell" +#: ../../../runtime/optwin.vim:1054 +msgid "name of the shell program used for external commands" +msgstr "用于外部命令的 shell 程序的名称" -#~ msgid "Cannot execute " -#~ msgstr "不能执行 " +#: ../../../runtime/optwin.vim:1056 +msgid "character(s) to enclose a shell command in" +msgstr "用于包围 shell 命令的字符" -#~ msgid "shell " -#~ msgstr "shell " +#: ../../../runtime/optwin.vim:1058 +msgid "like 'shellquote' but include the redirection" +msgstr "类似 'shellquote',但包含重定向" -#~ msgid " returned\n" -#~ msgstr " 已返回\n" +#: ../../../runtime/optwin.vim:1060 +msgid "characters to escape when 'shellxquote' is (" +msgstr "'shellxquote' 为 ( 时需要转义的字符" -#~ msgid "ANCHOR_BUF_SIZE too small." -#~ msgstr "ANCHOR_BUF_SIZE 太小" +#: ../../../runtime/optwin.vim:1062 +msgid "argument for 'shell' to execute a command" +msgstr "'shell' 执行命令的参数" -#~ msgid "I/O ERROR" -#~ msgstr "I/O 错误" +#: ../../../runtime/optwin.vim:1064 +msgid "used to redirect command output to a file" +msgstr "用于将命令输出重定向到文件" -#~ msgid "Message" -#~ msgstr "消息" +#: ../../../runtime/optwin.vim:1066 +msgid "use a temp file for shell commands instead of using a pipe" +msgstr "对 shell 命令使用临时文件而不是管道" -#~ msgid "'columns' is not 80, cannot execute external commands" -#~ msgstr "'columns' 不是 80, 不能执行外部命令" +#: ../../../runtime/optwin.vim:1068 +msgid "program used for \"=\" command" +msgstr "用于 \"=\" 命令的程序" -#~ msgid "E237: Printer selection failed" -#~ msgstr "E237: 选择打印机失败" +#: ../../../runtime/optwin.vim:1071 +msgid "program used to format lines with \"gq\" command" +msgstr "用 \"gq\" 命令格式化代码时使用的程序" -#~ msgid "to %s on %s" -#~ msgstr "从 %s 到 %s" +#: ../../../runtime/optwin.vim:1073 +msgid "program used for the \"K\" command" +msgstr "用于 \"K\" 命令的程序" -#~ msgid "E613: Unknown printer font: %s" -#~ msgstr "E613: 未知的打印机字体: %s" +#: ../../../runtime/optwin.vim:1075 +msgid "warn when using a shell command and a buffer has changes" +msgstr "当使用 shell 命令并且缓冲区有修改时发出警告" -#~ msgid "E238: Print error: %s" -#~ msgstr "E238: 打印错误: %s" +#: ../../../runtime/optwin.vim:1080 +msgid "running make and jumping to errors (quickfix)" +msgstr "运行 make 并跳到错误(快速修复)" -#~ msgid "Printing '%s'" -#~ msgstr "打印 '%s'" +#: ../../../runtime/optwin.vim:1081 +msgid "name of the file that contains error messages" +msgstr "包含错误消息的文件的名称" -#~ msgid "E244: Illegal charset name \"%s\" in font name \"%s\"" -#~ msgstr "E244: 字符集 \"%s\" 不能对应字体\"%s\"" +#: ../../../runtime/optwin.vim:1083 +msgid "list of formats for error messages" +msgstr "错误消息的格式列表" -#~ msgid "E245: Illegal char '%c' in font name \"%s\"" -#~ msgstr "E245: 不正确的字符 '%c' 出现在字体名称 \"%s\" 内" +#: ../../../runtime/optwin.vim:1086 +msgid "program used for the \":make\" command" +msgstr "用于 \":make\" 命令的程序" -#~ msgid "Vim: Double signal, exiting\n" -#~ msgstr "Vim: 双重信号,退出中\n" +#: ../../../runtime/optwin.vim:1089 +msgid "string used to put the output of \":make\" in the error file" +msgstr "用于将 \":make\" 的输出放在错误文件中的字符串" -#~ msgid "Vim: Caught deadly signal %s\n" -#~ msgstr "Vim: 拦截到致命信号(deadly signal) %s\n" +#: ../../../runtime/optwin.vim:1091 +msgid "name of the errorfile for the 'makeprg' command" +msgstr "'makeprg' 命令的错误文件的名称" -#~ msgid "Vim: Caught deadly signal\n" -#~ msgstr "Vim: 拦截到致命信号(deadly signal)\n" +#: ../../../runtime/optwin.vim:1093 +msgid "program used for the \":grep\" command" +msgstr "用于 \":grep\" 命令的程序" -#~ msgid "Opening the X display took %<PRId64> msec" -#~ msgstr "打开 X display 用时 %<PRId64> 秒" +#: ../../../runtime/optwin.vim:1096 +msgid "list of formats for output of 'grepprg'" +msgstr "'grepprg' 的输出格式列表" + +#: ../../../runtime/optwin.vim:1098 +msgid "encoding of the \":make\" and \":grep\" output" +msgstr "\":make\" 和 \":grep\" 输出的编码" + +#: ../../../runtime/optwin.vim:1105 +msgid "system specific" +msgstr "系统特定" + +#: ../../../runtime/optwin.vim:1106 +msgid "use forward slashes in file names; for Unix-like shells" +msgstr "在文件名中使用正斜杠;用于类 Unix shell" + +#: ../../../runtime/optwin.vim:1108 +msgid "specifies slash/backslash used for completion" +msgstr "指定补全时使用的斜杠/反斜杠" + +#: ../../../runtime/optwin.vim:1113 +msgid "language specific" +msgstr "语言特定" + +#: ../../../runtime/optwin.vim:1114 +msgid "specifies the characters in a file name" +msgstr "指定文件名中的字符" + +#: ../../../runtime/optwin.vim:1116 +msgid "specifies the characters in an identifier" +msgstr "指定标识符中的字符" + +#: ../../../runtime/optwin.vim:1118 +msgid "specifies the characters in a keyword" +msgstr "指定关键字中的字符" + +#: ../../../runtime/optwin.vim:1121 +msgid "specifies printable characters" +msgstr "指定可打印字符" + +#: ../../../runtime/optwin.vim:1124 +msgid "specifies escape characters in a string" +msgstr "指定字符串中的转义字符" + +#: ../../../runtime/optwin.vim:1129 +msgid "display the buffer right-to-left" +msgstr "从右到左显示缓冲区" + +#: ../../../runtime/optwin.vim:1132 +msgid "when to edit the command-line right-to-left" +msgstr "何时从右到左编辑命令行" + +#: ../../../runtime/optwin.vim:1135 +msgid "insert characters backwards" +msgstr "倒序插入字符" + +#: ../../../runtime/optwin.vim:1137 +msgid "allow CTRL-_ in Insert and Command-line mode to toggle 'revins'" +msgstr "在插入和命令行模式下允许 CTRL-_ 切换 'revins'" + +#: ../../../runtime/optwin.vim:1139 +msgid "the ASCII code for the first letter of the Hebrew alphabet" +msgstr "希伯来字母表第一个字母的 ASCII 码" + +#: ../../../runtime/optwin.vim:1141 +msgid "use Hebrew keyboard mapping" +msgstr "使用希伯来语键盘映射" + +#: ../../../runtime/optwin.vim:1143 +msgid "use phonetic Hebrew keyboard mapping" +msgstr "使用希伯来语的语音键盘映射" + +#: ../../../runtime/optwin.vim:1147 +msgid "prepare for editing Arabic text" +msgstr "准备编辑阿拉伯语文本" + +#: ../../../runtime/optwin.vim:1150 +msgid "perform shaping of Arabic characters" +msgstr "阿拉伯语的字形重整" + +#: ../../../runtime/optwin.vim:1152 +msgid "terminal will perform bidi handling" +msgstr "终端支持双向文本" + +#: ../../../runtime/optwin.vim:1156 +msgid "name of a keyboard mapping" +msgstr "键盘映射名称" + +#: ../../../runtime/optwin.vim:1160 +msgid "list of characters that are translated in Normal mode" +msgstr "在普通模式下转换的字符列表" + +#: ../../../runtime/optwin.vim:1162 +msgid "apply 'langmap' to mapped characters" +msgstr "对映射的字符应用 'langmap'" + +#: ../../../runtime/optwin.vim:1166 +msgid "when set never use IM; overrules following IM options" +msgstr "设置时总不使用输入法;覆盖以下输入法选项" + +#: ../../../runtime/optwin.vim:1169 +msgid "in Insert mode: 1: use :lmap; 2: use IM; 0: neither" +msgstr "插入模式:1:使用 :lamp;2:使用输入法;0:都不用" + +#: ../../../runtime/optwin.vim:1172 +msgid "entering a search pattern: 1: use :lmap; 2: use IM; 0: neither" +msgstr "输入搜索模式时:1:使用 :lamp;2:使用输入法;0:都不用" + +#: ../../../runtime/optwin.vim:1176 +msgid "when set always use IM when starting to edit a command line" +msgstr "如果设置,开始编辑命令行时总是使用输入法" + +#: ../../../runtime/optwin.vim:1178 +msgid "function to obtain IME status" +msgstr "获取输入法状态的函数" + +#: ../../../runtime/optwin.vim:1180 +msgid "function to enable/disable IME" +msgstr "启用/禁用输入法的函数" + +#: ../../../runtime/optwin.vim:1185 +msgid "multi-byte characters" +msgstr "多字节字符" + +#: ../../../runtime/optwin.vim:1186 +msgid "character encoding used in Nvim: \"utf-8\"" +msgstr "Nvim 中使用的字符编码:\"utf-8\"" + +#: ../../../runtime/optwin.vim:1188 +msgid "character encoding for the current file" +msgstr "当前文件的字符编码" + +#: ../../../runtime/optwin.vim:1191 +msgid "automatically detected character encodings" +msgstr "自动检测字符编码" + +#: ../../../runtime/optwin.vim:1193 +msgid "expression used for character encoding conversion" +msgstr "用于字符编码转换的表达式" + +#: ../../../runtime/optwin.vim:1195 +msgid "delete combining (composing) characters on their own" +msgstr "删除组合字符本身" + +#: ../../../runtime/optwin.vim:1197 +msgid "maximum number of combining (composing) characters displayed" +msgstr "显示的最大字符组合数" + +#: ../../../runtime/optwin.vim:1200 +msgid "key that activates the X input method" +msgstr "激活 X 输入方法的键" + +#: ../../../runtime/optwin.vim:1203 +msgid "width of ambiguous width characters" +msgstr "宽度有歧义字符的宽度" + +#: ../../../runtime/optwin.vim:1205 +msgid "emoji characters are full width" +msgstr "表情字符视作全宽" + +#: ../../../runtime/optwin.vim:1209 +msgid "various" +msgstr "杂项" + +#: ../../../runtime/optwin.vim:1210 +msgid "" +"when to use virtual editing: \"block\", \"insert\", \"all\"\n" +"and/or \"onemore\"" +msgstr "何时使用虚拟编辑:\"block\", \"insert\", \"all\" 和/或 \"onemore\"" + +#: ../../../runtime/optwin.vim:1212 +msgid "list of autocommand events which are to be ignored" +msgstr "要忽略的自动命令事件列表" + +#: ../../../runtime/optwin.vim:1214 +msgid "load plugin scripts when starting up" +msgstr "启动时加载插件脚本" + +#: ../../../runtime/optwin.vim:1216 +msgid "enable reading .vimrc/.exrc/.gvimrc in the current directory" +msgstr "启用读取在当前目录下的 .vimrc/.exrc/.gvimrc" + +#: ../../../runtime/optwin.vim:1218 +msgid "safer working with script files in the current directory" +msgstr "在当前目录下使用脚本文件时更安全" + +#: ../../../runtime/optwin.vim:1220 +msgid "use the 'g' flag for \":substitute\"" +msgstr "打开 \":substitute\" 的 'g' 标志" + +#: ../../../runtime/optwin.vim:1223 +msgid "allow reading/writing devices" +msgstr "允许读写设备" + +#: ../../../runtime/optwin.vim:1227 +msgid "maximum depth of function calls" +msgstr "函数调用的最大深度" + +#: ../../../runtime/optwin.vim:1231 +msgid "list of words that specifies what to put in a session file" +msgstr "指定放入会话文件的内容的单词列表" + +#: ../../../runtime/optwin.vim:1233 +msgid "list of words that specifies what to save for :mkview" +msgstr "指定 :mkview 保存的内容的单词列表" + +#: ../../../runtime/optwin.vim:1235 +msgid "directory where to store files with :mkview" +msgstr ":mkview 存放文件的目录" + +#: ../../../runtime/optwin.vim:1239 +msgid "list that specifies what to write in the ShaDa file" +msgstr "指定在 ShaDa 文件中写入的内容的列表" + +#: ../../../runtime/optwin.vim:1243 +msgid "what happens with a buffer when it's no longer in a window" +msgstr "当缓冲区不再位于窗口中时,会发生什么" + +#: ../../../runtime/optwin.vim:1246 +msgid "empty, \"nofile\", \"nowrite\", \"quickfix\", etc.: type of buffer" +msgstr "空, \"nofile\", \"nowrite\", \"quickfix\" 等; 缓冲区类型" + +#: ../../../runtime/optwin.vim:1250 +msgid "whether the buffer shows up in the buffer list" +msgstr "缓冲区是否显示在缓冲区列表中" + +#: ../../../runtime/optwin.vim:1253 +msgid "set to \"msg\" to see all error messages" +msgstr "设置为 \"msg\" 以查看所有错误消息" + +#: ../../../runtime/optwin.vim:1255 +msgid "whether to show the signcolumn" +msgstr "是否显示标号列" + +#: ../../../runtime/optwin.vim:1282 +msgid "name of the MzScheme dynamic library" +msgstr "MzScheme 动态库的名字" + +#: ../../../runtime/optwin.vim:1284 +msgid "name of the MzScheme GC dynamic library" +msgstr "MzScheme GC 动态库的名字" + +#: ../../../runtime/optwin.vim:1288 +msgid "whether to use Python 2 or 3" +msgstr "是否使用 Python 2 和 3" + +#~ msgid "\t\t\t (Unimplemented)\n" +#~ msgstr "\t\t\t (尚未实现)\n" #~ msgid "" #~ "\n" -#~ "Vim: Got X error\n" +#~ "\n" +#~ "Arguments:\n" #~ msgstr "" #~ "\n" -#~ "Vim: X 错误\n" - -#~ msgid "Testing the X display failed" -#~ msgstr "测试 X display 失败" - -#~ msgid "Opening the X display timed out" -#~ msgstr "打开 X display 超时" +#~ "\n" +#~ "参数:\n" #~ msgid "" #~ "\n" -#~ "Cannot execute shell sh\n" +#~ " [not usable with this version of Vim]" #~ msgstr "" #~ "\n" -#~ "无法执行 shell sh\n" +#~ " [不能在该版本的 Vim 上使用]" #~ msgid "" #~ "\n" -#~ "Cannot create pipes\n" +#~ " a: Find assignments to this symbol\n" +#~ " c: Find functions calling this function\n" +#~ " d: Find functions called by this function\n" +#~ " e: Find this egrep pattern\n" +#~ " f: Find this file\n" +#~ " g: Find this definition\n" +#~ " i: Find files #including this file\n" +#~ " s: Find this C symbol\n" +#~ " t: Find this text string\n" #~ msgstr "" #~ "\n" -#~ "无法建立管道\n" +#~ " a: 搜索对此符号的赋值\n" +#~ " c: 搜索调用此函数的函数\n" +#~ " d: 搜索此函数调用的函数\n" +#~ " e: 搜索此 egrep 模式\n" +#~ " f: 搜索此文件\n" +#~ " g: 搜索此定义\n" +#~ " i: 搜索包含此文件的文件\n" +#~ " s: 搜索此 C 符号\n" +#~ " t: 搜索此文本字符串\n" #~ msgid "" #~ "\n" -#~ "Cannot fork\n" +#~ " # line" #~ msgstr "" #~ "\n" -#~ "无法 fork\n" +#~ " # 行 " #~ msgid "" #~ "\n" -#~ "Command terminated\n" +#~ " or:" #~ msgstr "" #~ "\n" -#~ "命令已结束\n" +#~ " 或:" -#~ msgid "XSMP lost ICE connection" -#~ msgstr "XSMP 丢失了到 ICE 的连接" - -#~ msgid "Opening the X display failed" -#~ msgstr "打开 X display 失败" +# do not translate to avoid writing Chinese in files +#, fuzzy, c-format +#~ msgid "" +#~ "\n" +#~ "# %s History (newest to oldest):\n" +#~ msgstr "" +#~ "\n" +#~ "# %s 历史记录 (从新到旧):\n" -#~ msgid "XSMP handling save-yourself request" -#~ msgstr "XSMP 处理 save-yourself 请求" +#~ msgid "" +#~ "\n" +#~ "# Buffer list:\n" +#~ msgstr "" +#~ "\n" +#~ "# 缓冲区列表:\n" -#~ msgid "XSMP opening connection" -#~ msgstr "XSMP 打开连接" +#~ msgid "" +#~ "\n" +#~ "# File marks:\n" +#~ msgstr "" +#~ "\n" +#~ "# 文件标记:\n" -#~ msgid "XSMP ICE connection watch failed" -#~ msgstr "XSMP ICE 连接监视失败" +#~ msgid "" +#~ "\n" +#~ "# History of marks within files (newest to oldest):\n" +#~ msgstr "" +#~ "\n" +#~ "# 文件内的标记历史记录 (从新到旧):\n" -#~ msgid "XSMP SmcOpenConnection failed: %s" -#~ msgstr "XSMP SmcOpenConnection 调用失败: %s" +#~ msgid "" +#~ "\n" +#~ "# Jumplist (newest first):\n" +#~ msgstr "" +#~ "\n" +#~ "# 跳转列表 (从新到旧):\n" -#~ msgid "At line" -#~ msgstr "在行号 " +#, c-format +#~ msgid "" +#~ "\n" +#~ "# Last %sSearch Pattern:\n" +#~ "~" +#~ msgstr "" +#~ "\n" +#~ "# 最后 %s搜索模式:\n" +#~ "~" -#~ msgid "Could not load vim32.dll!" -#~ msgstr "无法加载 vim32.dll!" +# do not translate to avoid writing Chinese in files +#, fuzzy +#~ msgid "" +#~ "\n" +#~ "# Last Substitute String:\n" +#~ "$" +#~ msgstr "" +#~ "\n" +#~ "# 最近的替换字符串:\n" +#~ "$" -#~ msgid "VIM Error" -#~ msgstr "VIM 错误" +#~ msgid "" +#~ "\n" +#~ "# Registers:\n" +#~ msgstr "" +#~ "\n" +#~ "# 寄存器:\n" -#~ msgid "Could not fix up function pointers to the DLL!" -#~ msgstr "无法修正到 DLL 的函数指针!" +#~ msgid "" +#~ "\n" +#~ "# global variables:\n" +#~ msgstr "" +#~ "\n" +#~ "# 全局变量:\n" -#~ msgid "shell returned %d" -#~ msgstr "Shell 返回 %d" +#~ msgid "" +#~ "\n" +#~ "--- Registers ---" +#~ msgstr "" +#~ "\n" +#~ "--- 寄存器 ---" -#~ msgid "Vim: Caught %s event\n" -#~ msgstr "Vim: 拦截到 %s 事件\n" +#~ msgid "" +#~ "\n" +#~ "--- Terminal codes ---" +#~ msgstr "" +#~ "\n" +#~ "--- 终端编码 ---" -#~ msgid "close" -#~ msgstr "关闭" +#~ msgid "" +#~ "\n" +#~ "--- Terminal keys ---" +#~ msgstr "" +#~ "\n" +#~ "--- 终端按键 ---" -#~ msgid "logoff" -#~ msgstr "注消" +#~ msgid "" +#~ "\n" +#~ "16-bit MS-DOS version" +#~ msgstr "" +#~ "\n" +#~ "16 位 MS-DOS 版本" -#~ msgid "shutdown" -#~ msgstr "关机" +#~ msgid "" +#~ "\n" +#~ "32-bit MS-DOS version" +#~ msgstr "" +#~ "\n" +#~ "32 位 MS-DOS 版本" -#~ msgid "E371: Command not found" -#~ msgstr "E371: 找不到命令" +#~ msgid "" +#~ "\n" +#~ "Arguments recognised by gvim (Athena version):\n" +#~ msgstr "" +#~ "\n" +#~ "gvim (Athena 版本) 可识别的参数:\n" #~ msgid "" -#~ "VIMRUN.EXE not found in your $PATH.\n" -#~ "External commands will not pause after completion.\n" -#~ "See :help win32-vimrun for more information." +#~ "\n" +#~ "Arguments recognised by gvim (GTK+ version):\n" #~ msgstr "" -#~ "在你的 $PATH 中找不到 VIMRUN.EXE。\n" -#~ "外部命令执行完毕后将不会暂停。\n" -#~ "进一步说明请见 :help win32-vimrun" +#~ "\n" +#~ "gvim (GTK+ 版本) 可识别的参数:\n" -#~ msgid "Vim Warning" -#~ msgstr "Vim 警告" +#~ msgid "" +#~ "\n" +#~ "Arguments recognised by gvim (Motif version):\n" +#~ msgstr "" +#~ "\n" +#~ "gvim (Motif 版本) 可识别的参数:\n" -#~ msgid "Conversion in %s not supported" -#~ msgstr "不支持 %s 中的转换" +#~ msgid "" +#~ "\n" +#~ "Arguments recognised by gvim (RISC OS version):\n" +#~ msgstr "" +#~ "\n" +#~ "gvim (RISC OS 版本) 可识别的参数:\n" -#~ msgid "E396: containedin argument not accepted here" -#~ msgstr "E396: 使用了不正确的参数" +#~ msgid "" +#~ "\n" +#~ "Arguments recognised by gvim (neXtaw version):\n" +#~ msgstr "" +#~ "\n" +#~ "gvim (neXtaw 版本) 可识别的参数:\n" -#~ msgid "E430: Tag file path truncated for %s\n" -#~ msgstr "E430: Tag 文件路径被截断为 %s\n" +#~ msgid "" +#~ "\n" +#~ "Big version " +#~ msgstr "" +#~ "\n" +#~ "大型版本 " -#~ msgid "new shell started\n" -#~ msgstr "启动新 shell\n" +#~ msgid "" +#~ "\n" +#~ "Cannot create pipes\n" +#~ msgstr "" +#~ "\n" +#~ "无法建立管道\n" -#~ msgid "No undo possible; continue anyway" -#~ msgstr "无法撤销;仍然继续" +#~ msgid "" +#~ "\n" +#~ "Cannot execute shell " +#~ msgstr "" +#~ "\n" +#~ "无法执行 shell" -#~ msgid "number changes time" -#~ msgstr " 编号 改变 时间" +#~ msgid "" +#~ "\n" +#~ "Cannot execute shell sh\n" +#~ msgstr "" +#~ "\n" +#~ "无法执行 shell sh\n" #~ msgid "" #~ "\n" -#~ "MS-Windows 16/32-bit GUI version" +#~ "Cannot fork\n" #~ msgstr "" #~ "\n" -#~ "MS-Windows 16/32 位图形界面版本" +#~ "无法 fork\n" #~ msgid "" #~ "\n" -#~ "MS-Windows 32-bit GUI version" +#~ "Command terminated\n" #~ msgstr "" #~ "\n" -#~ "MS-Windows 32 位图形界面版本" +#~ "命令已结束\n" -#~ msgid " in Win32s mode" -#~ msgstr " Win32s 模式" +#, fuzzy +#~ msgid "" +#~ "\n" +#~ "Extra patches: " +#~ msgstr "外部符合:\n" -#~ msgid " with OLE support" -#~ msgstr " 带 OLE 支持" +#~ msgid "" +#~ "\n" +#~ "Huge version " +#~ msgstr "" +#~ "\n" +#~ "巨型版本 " #~ msgid "" #~ "\n" -#~ "MS-Windows 32-bit console version" +#~ "Included patches: " #~ msgstr "" #~ "\n" -#~ "MS-Windows 32 位控制台版本" +#~ "包含补丁: " #~ msgid "" #~ "\n" @@ -7640,17 +9395,24 @@ msgstr "E446: 光标处没有文件名" #~ msgid "" #~ "\n" -#~ "32-bit MS-DOS version" +#~ "MS-Windows 16/32-bit GUI version" #~ msgstr "" #~ "\n" -#~ "32 位 MS-DOS 版本" +#~ "MS-Windows 16/32 位图形界面版本" #~ msgid "" #~ "\n" -#~ "16-bit MS-DOS version" +#~ "MS-Windows 32-bit GUI version" #~ msgstr "" #~ "\n" -#~ "16 位 MS-DOS 版本" +#~ "MS-Windows 32 位图形界面版本" + +#~ msgid "" +#~ "\n" +#~ "MS-Windows 32-bit console version" +#~ msgstr "" +#~ "\n" +#~ "MS-Windows 32 位控制台版本" #~ msgid "" #~ "\n" @@ -7675,6 +9437,13 @@ msgstr "E446: 光标处没有文件名" #~ msgid "" #~ "\n" +#~ "Normal version " +#~ msgstr "" +#~ "\n" +#~ "正常版本 " + +#~ msgid "" +#~ "\n" #~ "RISC OS version" #~ msgstr "" #~ "\n" @@ -7682,73 +9451,463 @@ msgstr "E446: 光标处没有文件名" #~ msgid "" #~ "\n" -#~ "Big version " +#~ "Small version " #~ msgstr "" #~ "\n" -#~ "大型版本 " +#~ "小型版本 " #~ msgid "" #~ "\n" -#~ "Normal version " +#~ "Tiny version " #~ msgstr "" #~ "\n" -#~ "正常版本 " +#~ "微型版本 " #~ msgid "" #~ "\n" -#~ "Small version " +#~ "Vim: Got X error\n" #~ msgstr "" #~ "\n" -#~ "小型版本 " +#~ "Vim: X 错误\n" #~ msgid "" #~ "\n" -#~ "Tiny version " +#~ "[bytes] total alloc-freed %<PRIu64>-%<PRIu64>, in use %<PRIu64>, peak use " +#~ "%<PRIu64>\n" #~ msgstr "" #~ "\n" -#~ "微型版本 " +#~ "[字节] 总共 alloc-free %<PRIu64>-%<PRIu64>,使用中 %<PRIu64>,高峰使用 " +#~ "%<PRIu64>\n" -#~ msgid "with GTK2-GNOME GUI." -#~ msgstr "带 GTK2-GNOME 图形界面。" +#, fuzzy +#~ msgid " for Vim defaults " +#~ msgstr " # pid 数据库名称 prepend path\n" -#~ msgid "with GTK-GNOME GUI." -#~ msgstr "带 GTK-GNOME 图形界面。" +#~ msgid " user exrc file: \"" +#~ msgstr " 用户 exrc 文件: \"" -#~ msgid "with GTK2 GUI." -#~ msgstr "带 GTK2 图形界面。" +#~ msgid " user vimrc file: \"" +#~ msgstr " 用户 vimrc 文件: \"" -#~ msgid "with GTK GUI." -#~ msgstr "带 GTK 图形界面。" +#~ msgid " system menu file: \"" +#~ msgstr " 系统菜单文件: \"" -#~ msgid "with X11-Motif GUI." -#~ msgstr "带 X11-Motif 图形界面。" +#~ msgid " user gvimrc file: \"" +#~ msgstr " 用户 gvimrc 文件: \"" -#~ msgid "with X11-neXtaw GUI." -#~ msgstr "带 X11-neXtaw 图形界面。" +#~ msgid " 2nd user exrc file: \"" +#~ msgstr " 第二用户 exrc 文件: \"" -#~ msgid "with X11-Athena GUI." -#~ msgstr "带 X11-Athena 图形界面。" +#~ msgid " DEBUG BUILD" +#~ msgstr " 调试版本" -#~ msgid "with Photon GUI." -#~ msgstr "带 Photon 图形界面。" +#~ msgid " Features included (+) or not (-):\n" +#~ msgstr " 可使用(+)与不可使用(-)的功能:\n" -#~ msgid "with GUI." -#~ msgstr "带图形界面。" +#, fuzzy +#~ msgid " Quit, or continue with caution.\n" +#~ msgstr " 退出,或小心地继续。\n" -#~ msgid "with Carbon GUI." -#~ msgstr "带 Carbon 图形界面。" +#~ msgid " system gvimrc file: \"" +#~ msgstr " 系统 gvimrc 文件: \"" -#~ msgid "with Cocoa GUI." -#~ msgstr "带 Cocoa 图形界面。" +#~ msgid " # pid database name prepend path\n" +#~ msgstr " # pid 数据库名 prepend path\n" -#~ msgid "with (classic) GUI." -#~ msgstr "带(传统)图形界面。" +#~ msgid " (NOT FOUND)" +#~ msgstr " (找不到)" -#~ msgid " system gvimrc file: \"" -#~ msgstr " 系统 gvimrc 文件: \"" +#~ msgid " (RET/BS: line, SPACE/b: page, d/u: half page, q: quit)" +#~ msgstr " (RET/BS: 向下/向上一行, 空格/b: 一页, d/u: 半页, q: 退出)" -#~ msgid " user gvimrc file: \"" -#~ msgstr " 用户 gvimrc 文件: \"" +#~ msgid " (RET: line, SPACE: page, d: half page, q: quit)" +#~ msgstr " (RET: 向下一行, 空白键: 一页, d: 半页, q: 退出)" + +#~ msgid " (lang)" +#~ msgstr " (语言)" + +#~ msgid " 2nd user vimrc file: \"" +#~ msgstr " 第二用户 vimrc 文件: \"" + +#~ msgid " 3rd user vimrc file: \"" +#~ msgstr " 第三用户 vimrc 文件: \"" + +#~ msgid " BLOCK" +#~ msgstr " 块" + +#~ msgid " LINE" +#~ msgstr " 行" + +#~ msgid " in Win32s mode" +#~ msgstr " Win32s 模式" + +#~ msgid " on 1 line" +#~ msgstr "共 1 行" + +#~ msgid " returned\n" +#~ msgstr " 已返回\n" + +#~ msgid " vim [arguments] " +#~ msgstr " vim [参数] " + +#~ msgid " with OLE support" +#~ msgstr " 带 OLE 支持" + +#~ msgid "\"\n" +#~ msgstr "\"\n" + +# do not translate to avoid writing Chinese in files +#, fuzzy, c-format +#~ msgid "# This viminfo file was generated by Vim %s.\n" +#~ msgstr "# 这个 viminfo 文件是由 Vim %s 生成的。\n" + +# do not translate to avoid writing Chinese in files +#, fuzzy +#~ msgid "# Value of 'encoding' when this file was written\n" +#~ msgstr "# 'encoding' 在此文件建立时的值\n" + +# do not translate to avoid writing Chinese in files +#, fuzzy +#~ msgid "" +#~ "# You may edit it if you're careful!\n" +#~ "\n" +#~ msgstr "" +#~ "# 如果要自行修改请特别小心!\n" +#~ "\n" + +#, fuzzy, c-format +#~ msgid "%-5s: %s%*s (Usage: %s)" +#~ msgstr "%-5s: %-30s (用法: %s)" + +#~ msgid "%2d %-5ld %-34s <none>\n" +#~ msgstr "%2d %-5ld %-34s <无>\n" + +#~ msgid "%<%f%h%m%=Page %N" +#~ msgstr "%<%f%h%m%=页 %N" + +#, c-format +#~ msgid "%<PRId64> characters" +#~ msgstr "%<PRId64> 个字符" + +#, c-format +#~ msgid "%<PRId64> fewer lines" +#~ msgstr "少了 %<PRId64> 行" + +#, c-format +#~ msgid "%<PRId64> lines %sed 1 time" +#~ msgstr "%<PRId64> 行 %s 了 1 次" + +#, c-format +#~ msgid "%<PRId64> lines moved" +#~ msgstr "移动了 %<PRId64> 行" + +#, c-format +#~ msgid "%<PRId64> more lines" +#~ msgstr "多了 %<PRId64> 行" + +#, c-format +#~ msgid "%d files to edit\n" +#~ msgstr "还有 %d 个文件等待编辑\n" + +#~ msgid "%d of %d edited" +#~ msgstr "%d 中 %d 已编辑" + +# bad to translate +#, c-format +#~ msgid "%sviminfo: %s in line: " +#~ msgstr "%sviminfo: %s 位于行: " + +#~ msgid "&Cancel" +#~ msgstr "取消(&C)" + +#~ msgid "&Dismiss" +#~ msgstr "取消(&D)" + +#~ msgid "&Filter" +#~ msgstr "过滤(&F)" + +#~ msgid "&Help" +#~ msgstr "帮助(&H)" + +#~ msgid "&OK" +#~ msgstr "确定(&O)" + +#~ msgid "" +#~ "&OK\n" +#~ "&Cancel" +#~ msgstr "" +#~ "确定(&O)\n" +#~ "取消(&C)" + +#~ msgid "" +#~ "&OK\n" +#~ "&Load File" +#~ msgstr "" +#~ "确定(&O)\n" +#~ "加载文件(&L)" + +#~ msgid "&Replace" +#~ msgstr "替换(&R)" + +#~ msgid "&Undo" +#~ msgstr "撤销(&U)" + +#~ msgid "' not known. Available builtin terminals are:" +#~ msgstr "' 未知。可用的内建终端有:" + +#, c-format +#~ msgid "(NFA) COULD NOT OPEN %s !" +#~ msgstr "(NFA) 不能打开 %s !" + +#~ msgid "+\t\t\tStart at end of file" +#~ msgstr "+\t\t\t启动后跳到文件末尾" + +#~ msgid "+<lnum>\t\tStart at line <lnum>" +#~ msgstr "+<lnum>\t\t启动后跳到第 <lnum> 行" + +#~ msgid "+reverse\t\tDon't use reverse video (also: +rv)" +#~ msgstr "+reverse\t\t不使用反显 (也可用 +rv)" + +#~ msgid "--columns <number>\tInitial width of window in columns" +#~ msgstr "--columns <number>\t窗口初始宽度" + +#~ msgid "--help\t\tShow Gnome arguments" +#~ msgstr "--help\t\t显示 Gnome 相关参数" + +#~ msgid "--literal\t\tDon't expand wildcards" +#~ msgstr "--literal\t\t不扩展通配符" + +#~ msgid "--remote <files>\tEdit <files> in a Vim server if possible" +#~ msgstr "--remote <files>\t如有可能,在 Vim 服务器上编辑文件 <files>" + +#~ msgid "" +#~ "--remote-expr <expr>\tEvaluate <expr> in a Vim server and print result" +#~ msgstr "--remote-expr <expr>\t在 Vim 服务器上求 <expr> 的值并打印结果" + +#~ msgid "--remote-send <keys>\tSend <keys> to a Vim server and exit" +#~ msgstr "--remote-send <keys>\t送出 <keys> 到 Vim 服务器并退出" + +#~ msgid "--remote-silent <files> Same, don't complain if there is no server" +#~ msgstr "--remote-silent <files> 同上,找不到服务器时不抱怨" + +#~ msgid "--remote-tab <files> As --remote but open tab page for each file" +#~ msgstr "--remote-tab <files> 同 --remote 但对每个文件打开一个标签页" + +#~ msgid "" +#~ "--remote-wait <files> As --remote but wait for files to have been edited" +#~ msgstr "--remote-wait <files> 同 --remote 但会等待文件完成编辑" + +#~ msgid "" +#~ "--remote-wait-silent <files> Same, don't complain if there is no server" +#~ msgstr "--remote-wait-silent <files> 同上,找不到服务器时不抱怨" + +#~ msgid "--role <role>\tSet a unique role to identify the main window" +#~ msgstr "--role <role>\t设置用于区分主窗口的窗口角色名" + +#~ msgid "--rows <number>\tInitial height of window in rows" +#~ msgstr "--rows <number>\t窗口初始高度" + +#~ msgid "--serverlist\t\tList available Vim server names and exit" +#~ msgstr "--serverlist\t\t列出可用的 Vim 服务器名称并退出" + +#~ msgid "--servername <name>\tSend to/become the Vim server <name>" +#~ msgstr "--servername <name>\t发送到或成为 Vim 服务器 <name>" + +#~ msgid "--socketid <xid>\tOpen Vim inside another GTK widget" +#~ msgstr "--socketid <xid>\t在另一个 GTK 部件中打开 Vim" + +#~ msgid "-A\t\t\tstart in Arabic mode" +#~ msgstr "-A\t\t\t以 Arabic 模式启动" + +#~ msgid "-C\t\t\tCompatible with Vi: 'compatible'" +#~ msgstr "-C\t\t\t兼容传统的 Vi: 'compatible'" + +#~ msgid "-D\t\t\tDebugging mode" +#~ msgstr "-D\t\t\t调试模式" + +#~ msgid "-F\t\t\tStart in Farsi mode" +#~ msgstr "-F\t\t\t以 Farsi 模式启动" + +#~ msgid "-H\t\t\tStart in Hebrew mode" +#~ msgstr "-H\t\t\t以 Hebrew 模式启动" + +#~ msgid "-L\t\t\tSame as -r" +#~ msgstr "-L\t\t\t同 -r" + +#~ msgid "-N\t\t\tNot fully Vi compatible: 'nocompatible'" +#~ msgstr "-N\t\t\t不完全兼容传统的 Vi: 'nocompatible'" + +#~ msgid "-O[N]\t\tLike -o but split vertically" +#~ msgstr "-O[N]\t\t同 -o 但垂直分割" + +#~ msgid "-P <parent title>\tOpen Vim inside parent application" +#~ msgstr "-P <parent title>\t在父应用程序中打开 Vim" + +#~ msgid "-R\t\t\tReadonly mode (like \"view\")" +#~ msgstr "-R\t\t\t只读模式 (同 \"view\")" + +#~ msgid "-T <terminal>\tSet terminal type to <terminal>" +#~ msgstr "-T <terminal>\t设定终端类型为 <terminal>" + +#~ msgid "-U <gvimrc>\t\tUse <gvimrc> instead of any .gvimrc" +#~ msgstr "-U <gvimrc>\t\t使用 <gvimrc> 替代任何 .gvimrc" + +#~ msgid "-V[N]\t\tVerbose level" +#~ msgstr "-V[N]\t\tVerbose 等级" + +#~ msgid "-V[N][fname]\t\tBe verbose [level N] [log messages to fname]" +#~ msgstr "-V[N][fname]\t\t详细 [level N] [log messages to fname]" + +#~ msgid "-W <scriptout>\tWrite all typed commands to file <scriptout>" +#~ msgstr "-W <scriptout>\t将所有输入的命令写入到文件 <scriptout>" + +#~ msgid "-X\t\t\tDo not connect to X server" +#~ msgstr "-X\t\t\t不连接到 X Server" + +#~ msgid "-b\t\t\tBinary mode" +#~ msgstr "-b\t\t\t二进制模式" + +#~ msgid "-background <color>\tUse <color> for the background (also: -bg)" +#~ msgstr "-background <color>\t使用 <color> 作为背景色 (也可用 -bg)" + +#~ msgid "-boldfont <font>\tUse <font> for bold text" +#~ msgstr "-boldfont <font>\t使用 <font> 作为粗体字体" + +#~ msgid "-borderwidth <width>\tUse a border width of <width> (also: -bw)" +#~ msgstr "-borderwidth <width>\t设定边框宽度为 <width> (也可用 -bw)" + +#~ msgid "-d\t\t\tDiff mode (like \"vimdiff\")" +#~ msgstr "-d\t\t\tDiff 模式 (同 \"vimdiff\")" + +#~ msgid "-dev <device>\t\tUse <device> for I/O" +#~ msgstr "-dev <device>\t\t使用 <device> 进行输入输出" + +#~ msgid "-display <display>\tConnect vim to this particular X-server" +#~ msgstr "-display <display>\t将 vim 与指定的 X-server 连接" + +#~ msgid "-display <display>\tRun vim on <display>" +#~ msgstr "-display <display>\t在 <display> 上运行 vim" + +#~ msgid "-display <display>\tRun vim on <display> (also: --display)" +#~ msgstr "-display <display>\t在 <display> 上运行 vim (也可用 --display)" + +#~ msgid "-e\t\t\tEx mode (like \"ex\")" +#~ msgstr "-e\t\t\tEx 模式 (同 \"ex\")" + +#~ msgid "-f\t\t\tDon't use newcli to open window" +#~ msgstr "-f\t\t\t不使用 newcli 来打开窗口" + +#~ msgid "-f or --nofork\tForeground: Don't fork when starting GUI" +#~ msgstr "-f 或 --nofork\t前台: 启动图形界面时不 fork" + +#~ msgid "-font <font>\t\tUse <font> for normal text (also: -fn)" +#~ msgstr "-font <font>\t使用 <font> 作为一般字体 (也可用 -fn)" + +#~ msgid "-foreground <color>\tUse <color> for normal text (also: -fg)" +#~ msgstr "-foreground <color>\t使用 <color> 作为一般文字颜色 (也可用 -fg)" + +#~ msgid "-g\t\t\tRun using GUI (like \"gvim\")" +#~ msgstr "-g\t\t\t使用图形界面 (同 \"gvim\")" + +#~ msgid "-geometry <geom>\tUse <geom> for initial geometry (also: -geom)" +#~ msgstr "-geometry <geom>\t使用 <geom> 作为初始位置 (也可用 -geom)" + +#~ msgid "-h or --help\tPrint Help (this message) and exit" +#~ msgstr "-h 或 --help\t打印帮助(本信息)并退出" + +#~ msgid "-i <viminfo>\t\tUse <viminfo> instead of .viminfo" +#~ msgstr "-i <viminfo>\t\t使用 <viminfo> 取代 .viminfo" + +#~ msgid "-iconic\t\tStart vim iconified" +#~ msgstr "-iconic\t\t启动后最小化" + +#~ msgid "-italicfont <font>\tUse <font> for italic text" +#~ msgstr "-italicfont <font>\t使用 <font> 作为斜体字体" + +#~ msgid "-menuheight <height>\tUse a menu bar height of <height> (also: -mh)" +#~ msgstr "-menuheight <height>\t设定菜单栏高度为 <height> (也可用 -mh)" + +#~ msgid "-name <name>\t\tUse resource as if vim was <name>" +#~ msgstr "-name <name>\t\t读取 Resource 时把 vim 视为 <name>" + +#~ msgid "-r\t\t\tList swap files and exit" +#~ msgstr "-r\t\t\t列出交换文件并退出" + +#~ msgid "-r (with file name)\tRecover crashed session" +#~ msgstr "-r (跟文件名)\t\t恢复崩溃的会话" + +#~ msgid "-register\t\tRegister this gvim for OLE" +#~ msgstr "-register\t\t注册此 gvim 到 OLE" + +#~ msgid "-reverse\t\tUse reverse video (also: -rv)" +#~ msgstr "-reverse\t\t使用反显 (也可用 -rv)" + +#~ msgid "-s\t\t\tSilent (batch) mode (only for \"ex\")" +#~ msgstr "-s\t\t\t安静(批处理)模式 (只能与 \"ex\" 一起使用)" + +#~ msgid "" +#~ "-scrollbarwidth <width> Use a scrollbar width of <width> (also: -sw)" +#~ msgstr "-scrollbarwidth <width> 设定滚动条宽度为 <width> (也可用 -sw)" + +#~ msgid "-u <vimrc>\t\tUse <vimrc> instead of any .vimrc" +#~ msgstr "-u <vimrc>\t\t使用 <vimrc> 替代任何 .vimrc" + +#~ msgid "-unregister\t\tUnregister gvim for OLE" +#~ msgstr "-unregister\t\t取消 OLE 中的 gvim 注册" + +#~ msgid "-v\t\t\tVi mode (like \"vi\")" +#~ msgstr "-v\t\t\tVi 模式 (同 \"vi\")" + +#~ msgid "-w <scriptout>\tAppend all typed commands to file <scriptout>" +#~ msgstr "-w <scriptout>\t将所有输入的命令追加到文件 <scriptout>" + +#~ msgid "-x\t\t\tEdit encrypted files" +#~ msgstr "-x\t\t\t编辑加密的文件" + +#~ msgid "-xrm <resource>\tSet the specified resource" +#~ msgstr "-xrm <resource>\t设定指定的资源" + +#~ msgid "-y\t\t\tEasy mode (like \"evim\", modeless)" +#~ msgstr "-y\t\t\t容易模式 (同 \"evim\",无模式)" + +#~ msgid "1 buffer deleted" +#~ msgstr "删除了 1 个缓冲区" + +#~ msgid "1 buffer unloaded" +#~ msgstr "释放了 1 个缓冲区" + +#~ msgid "1 buffer wiped out" +#~ msgstr "清除了 1 个缓冲区" + +#, c-format +#~ msgid "1 line %sed %d times" +#~ msgstr "1 行 %s 了 %d 次" + +#, c-format +#~ msgid "1 line %sed 1 time" +#~ msgstr "1 行 %s 了 1 次" + +#, c-format +#~ msgid "1 line --%d%%--" +#~ msgstr "1 行 --%d%%--" + +#~ msgid "1 line changed" +#~ msgstr "改变了 1 行" + +#~ msgid "1 line indented " +#~ msgstr "缩进了 1 行 " + +#~ msgid "1 line yanked" +#~ msgstr "复制了 1 行" + +#~ msgid "1 match" +#~ msgstr "1 个匹配," + +#~ msgid "1 more file to edit. Quit anyway?" +#~ msgstr "还有 1 个文件未编辑。确实要退出吗?" + +#~ msgid "1 substitution" +#~ msgstr "1 次替换," #~ msgid "2nd user gvimrc file: \"" #~ msgstr "第二用户 gvimrc 文件: \"" @@ -7756,182 +9915,1439 @@ msgstr "E446: 光标处没有文件名" #~ msgid "3rd user gvimrc file: \"" #~ msgstr "第三用户 gvimrc 文件: \"" -#~ msgid " system menu file: \"" -#~ msgstr " 系统菜单文件: \"" +#~ msgid ": Send expression failed.\n" +#~ msgstr ": 发送表达式失败。\n" + +#~ msgid ": Send failed.\n" +#~ msgstr ": 发送失败。\n" + +#~ msgid ": Send failed. Trying to execute locally\n" +#~ msgstr ": 发送失败。尝试本地执行\n" + +#~ msgid "<buffer object (deleted) at %8lX>" +#~ msgstr "<缓冲区对象(已删除): %8lX>" + +#~ msgid "<cannot open> " +#~ msgstr "<无法打开>" + +#~ msgid "<window %d>" +#~ msgstr "<窗口 %d>" + +#~ msgid "<window object (deleted) at %.8lX>" +#~ msgstr "<窗口对象(已删除): %.8lX>" + +#~ msgid "<window object (unknown) at %.8lX>" +#~ msgstr "<窗口对象(未知): %.8lX>" + +#~ msgid "" +#~ "???: Sorry, this command is disabled, the MzScheme library could not be " +#~ "loaded." +#~ msgstr "???: 抱歉,此命令不可用,无法加载 MzScheme 库" + +#~ msgid "ANCHOR_BUF_SIZE too small." +#~ msgstr "ANCHOR_BUF_SIZE 太小" + +#~ msgid "Add a new database" +#~ msgstr "添加一个新的数据库" + +#, c-format +#~ msgid "Added cscope database %s" +#~ msgstr "添加了 cscope 数据库 %s" + +#~ msgid "Affix flags ignored when PFXPOSTPONE used in %s line %d: %s" +#~ msgstr "%s 第 %d 行,使用 PFXPOSTPONE 时附加标志被忽略: %s" + +#~ msgid "All cscope databases reset" +#~ msgstr "所有 cscope 数据库已被重置" + +#~ msgid "Append File" +#~ msgstr "追加文件" + +#~ msgid "At line" +#~ msgstr "在行号 " + +#~ msgid "Binary tag search" +#~ msgstr "二进制查找(Binary search) 标签(Tags)" + +#~ msgid "Browse class" +#~ msgstr "浏览 class" + +#, c-format +#~ msgid "Calling shell to execute: \"%s\"" +#~ msgstr "调用 shell 执行: \"%s\"" + +#~ msgid "Cancel" +#~ msgstr "取消" + +#~ msgid "Cannot connect to Netbeans" +#~ msgstr "无法连接到 Netbeans" + +#~ msgid "Cannot connect to Netbeans #2" +#~ msgstr "无法连接到 Netbeans #2" + +#~ msgid "" +#~ "Cannot connect to SNiFF+. Check environment (sniffemacs must be found in " +#~ "$PATH).\n" +#~ msgstr "" +#~ "不能连接到 SNiFF+。请检查环境变量 ($PATH 里必需可以找到 sniffemacs)\n" + +#~ msgid "Cannot create " +#~ msgstr "不能创建 " + +#~ msgid "Cannot execute " +#~ msgstr "不能执行 " + +#~ msgid "Cannot open NIL:\n" +#~ msgstr "不能打开 NIL:\n" + +#~ msgid "Close tab" +#~ msgstr "关闭标签" + +#~ msgid "Compilation: " +#~ msgstr "编译方式: " #~ msgid "Compiler: " #~ msgstr "编译器: " -#~ msgid "menu Help->Orphans for information " -#~ msgstr "菜单 帮助->孤儿 查看说明 " +#~ msgid "Conversion in %s not supported" +#~ msgstr "不支持 %s 中的转换" -#~ msgid "Running modeless, typed text is inserted" -#~ msgstr "无模式运行,输入文字即插入" +#~ msgid "Could not fix up function pointers to the DLL!" +#~ msgstr "无法修正到 DLL 的函数指针!" -#~ msgid "menu Edit->Global Settings->Toggle Insert Mode " -#~ msgstr "菜单 编辑->全局设定->开/关插入模式 " +#~ msgid "Could not load vim32.dll!" +#~ msgstr "无法加载 vim32.dll!" -#, fuzzy -#~ msgid " for two modes " -#~ msgstr " # pid 数据库名称 prepend path\n" +#~ msgid "" +#~ "Could not open temporary log file for writing, displaying on stderr ... " +#~ msgstr "无法打开临时日志文件进行写入,显示在 stderr 中..." -#, fuzzy -#~ msgid " for Vim defaults " -#~ msgstr " # pid 数据库名称 prepend path\n" +#, c-format +#~ msgid "Cscope tag: %s" +#~ msgstr "Cscope tag: %s" -#~ msgid "WARNING: Windows 95/98/ME detected" -#~ msgstr "警告: 检测到 Windows 95/98/ME" +#~ msgid "Diff with Vim" +#~ msgstr "用 Vim 比较(diff)" -#~ msgid "type :help windows95<Enter> for info on this" -#~ msgstr "输入 :help windows95<Enter> 查看相关说明 " +#~ msgid "Direction" +#~ msgstr "方向" + +#~ msgid "Directories" +#~ msgstr "目录" + +#~ msgid "Directory\t*.nothing\n" +#~ msgstr "目录\t*.nothing\n" + +#~ msgid "Down" +#~ msgstr "向下" + +#~ msgid "E106: Unknown variable: \"%s\"" +#~ msgstr "E106: 未定义的变量: \"%s\"" + +#~ msgid "E130: Undefined function: %s" +#~ msgstr "E130: 函数 %s 尚未定义" + +#~ msgid "E136: viminfo: Too many errors, skipping rest of file" +#~ msgstr "E136: viminfo: 错误过多,忽略文件的剩余部分" + +#, c-format +#~ msgid "E138: Can't write viminfo file %s!" +#~ msgstr "E138: 无法写入 viminfo 文件 %s!" + +#~ msgid "E14: Invalid address" +#~ msgstr "E14: 无效的地址" + +#~ msgid "E172: Only one file name allowed" +#~ msgstr "E172: 只允许一个文件名" + +#~ msgid "E173: 1 more file to edit" +#~ msgstr "E173: 还有 1 个文件未编辑" + +#~ msgid "E188: Obtaining window position not implemented for this platform" +#~ msgstr "E188: 在此平台上不能获得窗口位置" + +#~ msgid "E195: Cannot open viminfo file for reading" +#~ msgstr "E195: 无法打开并读取 viminfo 文件" + +#~ msgid "E196: No digraphs in this version" +#~ msgstr "E196: 此版本无复合字符(digraph)" + +#~ msgid "E198: cmd_pchar beyond the command length" +#~ msgstr "E198: cmd_pchar 超过命令长度" + +#~ msgid "E229: Cannot start the GUI" +#~ msgstr "E229: 无法启动图形界面" + +#~ msgid "E230: Cannot read from \"%s\"" +#~ msgstr "E230: 无法读取文件 \"%s\"" + +#~ msgid "E231: 'guifontwide' invalid" +#~ msgstr "E231: 无效的 'guifontwide'" + +#~ msgid "E232: Cannot create BalloonEval with both message and callback" +#~ msgstr "E232: 不能同时使用消息和回调函数来创建 BalloonEval" + +#~ msgid "E233: cannot open display" +#~ msgstr "E233: 无法打开 display" + +#~ msgid "E234: Unknown fontset: %s" +#~ msgstr "E234: 未知的 Fontset: %s" + +#~ msgid "E237: Printer selection failed" +#~ msgstr "E237: 选择打印机失败" + +#~ msgid "E238: Print error: %s" +#~ msgstr "E238: 打印错误: %s" + +#~ msgid "E240: No connection to Vim server" +#~ msgstr "E240: 没有到 Vim 服务器的连接" + +#~ msgid "E243: Argument not supported: \"-%s\"; Use the OLE version." +#~ msgstr "E243: 不支持的参数: \"-%s\";请使用 OLE 版本。" + +#~ msgid "E244: Illegal charset name \"%s\" in font name \"%s\"" +#~ msgstr "E244: 字符集 \"%s\" 不能对应字体\"%s\"" + +#~ msgid "E245: Illegal char '%c' in font name \"%s\"" +#~ msgstr "E245: 不正确的字符 '%c' 出现在字体名称 \"%s\" 内" + +#~ msgid "E247: no registered server named \"%s\"" +#~ msgstr "E247: 没有名叫 \"%s\" 的已注册的服务器" + +#~ msgid "E248: Failed to send command to the destination program" +#~ msgstr "E248: 无法发送命令到目的程序" + +#~ msgid "E249: couldn't read VIM instance registry property" +#~ msgstr "E249: 不能读取 VIM 的 注册表属性" + +#~ msgid "E250: Fonts for the following charsets are missing in fontset %s:" +#~ msgstr "E250: Fontset %s 缺少下列字符集的字体:" + +#~ msgid "E251: VIM instance registry property is badly formed. Deleted!" +#~ msgstr "E251: VIM 实例注册属性有误。已删除!" + +#~ msgid "E252: Fontset name: %s" +#~ msgstr "E252: Fontset 名称: %s" + +#~ msgid "E253: Fontset name: %s\n" +#~ msgstr "E253: Fontset 名称: %s\n" + +#~ msgid "E256: Hangul automata ERROR" +#~ msgstr "E256: Hangul automata 错误" + +#~ msgid "E257: cstag: tag not found" +#~ msgstr "E257: cstag: 找不到 tag" + +#~ msgid "E258: Unable to send to client" +#~ msgstr "E258: 无法发送到客户端" + +#, c-format +#~ msgid "E259: no matches found for cscope query %s of %s" +#~ msgstr "E259: cscope 查询 %s %s 没有找到匹配的结果" + +#~ msgid "E260: cscope connection not found" +#~ msgstr "E260: 找不到 cscope 连接" + +#, c-format +#~ msgid "E261: cscope connection %s not found" +#~ msgstr "E261: 找不到 cscope 连接 %s" + +#, c-format +#~ msgid "E262: error reading cscope connection %<PRId64>" +#~ msgstr "E262: 读取 cscope 连接 %<PRId64> 出错" + +#~ msgid "" +#~ "E263: Sorry, this command is disabled, the Python library could not be " +#~ "loaded." +#~ msgstr "E263: 抱歉,此命令不可用,无法加载 Python 库。" + +#~ msgid "E264: Python: Error initialising I/O objects" +#~ msgstr "E264: Python: 初始化 I/O 对象出错" + +#~ msgid "" +#~ "E266: Sorry, this command is disabled, the Ruby library could not be " +#~ "loaded." +#~ msgstr "E266: 抱歉,此命令不可用,无法加载 Ruby 库" + +#~ msgid "E26: Hebrew cannot be used: Not enabled at compile time\n" +#~ msgstr "E26: 无法使用 Hebrew: 编译时没有启用\n" + +#~ msgid "E273: unknown longjmp status %d" +#~ msgstr "E273: 未知的 longjmp 状态 %d" + +#~ msgid "E274: Sniff: Error during read. Disconnected" +#~ msgstr "E274: Sniff: 读取错误. 取消连接" + +#~ msgid "E275: Unknown SNiFF+ request: %s" +#~ msgstr "E275: 不正确的 SNiff+ 调用: %s" + +#~ msgid "E276: Error connecting to SNiFF+" +#~ msgstr "E276: 连接到 SNiFF+ 失败" + +#~ msgid "E277: Unable to read a server reply" +#~ msgstr "E277: 无法读取服务器响应" + +#~ msgid "E278: SNiFF+ not connected" +#~ msgstr "E278: 未连接到 SNiFF+" + +#~ msgid "E279: Not a SNiFF+ buffer" +#~ msgstr "E279: 不是 SNiFF+ 的缓冲区" + +#~ msgid "E27: Farsi cannot be used: Not enabled at compile time\n" +#~ msgstr "E27: 无法使用 Farsi: 编译时没有启用\n" + +#~ msgid "" +#~ "E280: TCL FATAL ERROR: reflist corrupt!? Please report this to vim-" +#~ "dev@vim.org" +#~ msgstr "E280: TCL 严重错误: reflist 损坏!?请报告给 vim-dev@vim.org" + +#~ msgid "" +#~ "E281: TCL ERROR: exit code is not int!? Please report this to vim-dev@vim." +#~ "org" +#~ msgstr "E281: TCL 错误: 退出返回值不是整数!?请报告给 vim-dev@vim.org" + +#~ msgid "E285: Failed to create input context" +#~ msgstr "E285: 无法创建输入上下文" + +#~ msgid "E287: Warning: Could not set destroy callback to IM" +#~ msgstr "E287: 警告: 无法设定输入法的释放回调函数" + +#~ msgid "E288: input method doesn't support any style" +#~ msgstr "E288: 输入法不支持任何风格" + +#~ msgid "E289: input method doesn't support my preedit type" +#~ msgstr "E289: 输入法不支持我的预编辑类型" + +#~ msgid "E290: over-the-spot style requires fontset" +#~ msgstr "E290: over-the-spot 风格需要 Fontset" + +#~ msgid "E291: Your GTK+ is older than 1.2.3. Status area disabled" +#~ msgstr "E291: 你的 GTK+ 比 1.2.3 旧。状态区不可用。" + +#~ msgid "E292: Input Method Server is not running" +#~ msgstr "E292: 输入法服务器未运行" + +#~ msgid "E338: Sorry, no file browser in console mode" +#~ msgstr "E338: 抱歉,控制台模式下没有文件浏览器" + +#~ msgid "E339: Pattern too long" +#~ msgstr "E339: 模式太长" + +#~ msgid "E341: Internal error: lalloc(%<PRId64>, )" +#~ msgstr "E341: 内部错误: lalloc(%<PRId64>, )" + +#~ msgid "E360: Cannot execute shell with -f option" +#~ msgstr "E360: 不能用 -f 选项执行 shell" + +#~ msgid "E361: Crash intercepted; regexp too complex?" +#~ msgstr "E361: 不能执行; regular expression 太复杂?" + +#~ msgid "E363: pattern caused out-of-stack error" +#~ msgstr "E363: regular expression 造成堆栈用光的错误" #~ msgid "E370: Could not load library %s" #~ msgstr "E370: 无法加载库 %s" +#~ msgid "E371: Command not found" +#~ msgstr "E371: 找不到命令" + +#~ msgid "E396: containedin argument not accepted here" +#~ msgstr "E396: 使用了不正确的参数" + +#, c-format +#~ msgid "E422: terminal code too long: %s" +#~ msgstr "E422: 终端编码太长: %s" + +#~ msgid "E430: Tag file path truncated for %s\n" +#~ msgstr "E430: Tag 文件路径被截断为 %s\n" + +#, c-format +#~ msgid "E436: No \"%s\" entry in termcap" +#~ msgstr "E436: termcap 中没有 \"%s\" 项" + +#~ msgid "E437: terminal capability \"cm\" required" +#~ msgstr "E437: 终端需要能力 \"cm\"" + +#~ msgid "E448: Could not load library function %s" +#~ msgstr "E448: 无法加载库函数 %s" + +#~ msgid "E449: Invalid expression received" +#~ msgstr "E449: 收到无效的表达式" + +#~ msgid "E460: The resource fork would be lost (add ! to override)" +#~ msgstr "E460: Resource fork 会丢失 (请加 ! 强制执行)" + +#, c-format +#~ msgid "E469: invalid cscopequickfix flag %c for %c" +#~ msgstr "E469: cscopequickfix 标志 %c 对 %c 无效" + +#~ msgid "E505: " +#~ msgstr "E505: " + +#~ msgid "E506: Can't write to backup file (add ! to override)" +#~ msgstr "E506: 无法写入备份文件 (请加 ! 强制执行)" + +#~ msgid "E507: Close error for backup file (add ! to override)" +#~ msgstr "E507: 关闭备份文件出错 (请加 ! 强制执行)" + +#~ msgid "E508: Can't read file for backup (add ! to override)" +#~ msgstr "E508: 无法读取文件以供备份 (请加 ! 强制执行)" + +#~ msgid "E50: Too many \\z(" +#~ msgstr "E50: 太多 \\z(" + +#, fuzzy, c-format #~ msgid "" -#~ "Sorry, this command is disabled: the Perl library could not be loaded." -#~ msgstr "抱歉,此命令不可用: 无法加载 Perl 库。" +#~ "E513: write error, conversion failed in line %<PRId64> (make 'fenc' empty " +#~ "to override)" +#~ msgstr "E513: 写入错误,转换失败 (请将 'fenc' 置空以强制执行)" -#~ msgid "Edit with &multiple Vims" -#~ msgstr "用多个 Vim 编辑(&M)" +#, c-format +#~ msgid "E51: Too many %s(" +#~ msgstr "E51: 太多 %s(" -#~ msgid "Edit with single &Vim" -#~ msgstr "用单个 Vim 编辑(&V)" +#~ msgid "E522: Not found in termcap" +#~ msgstr "E522: Termcap 里面找不到" -#~ msgid "Diff with Vim" -#~ msgstr "用 Vim 比较(diff)" +#~ msgid "E52: Unmatched \\z(" +#~ msgstr "E52: 不匹配的 \\z(" + +#~ msgid "E530: Cannot change term in GUI" +#~ msgstr "E530: 在图形界面中不能改变终端" + +#~ msgid "E531: Use \":gui\" to start the GUI" +#~ msgstr "E531: 请用 \":gui\" 启动图形界面" + +#~ msgid "E533: can't select wide font" +#~ msgstr "E533: 无法选择宽字体" + +#~ msgid "E538: No mouse support" +#~ msgstr "E538: 不支持鼠标" + +#~ msgid "E541: too many items" +#~ msgstr "E541: 项目过多" + +#~ msgid "E543: Not a valid codepage" +#~ msgstr "E543: 无效的代码页" + +#~ msgid "E547: Illegal mouseshape" +#~ msgstr "E547: 无效的鼠标形状" + +#~ msgid "E557: Cannot open termcap file" +#~ msgstr "E557: 无法打开 termcap 文件" + +#~ msgid "E558: Terminal entry not found in terminfo" +#~ msgstr "E558: 在 terminfo 中找不到终端项" + +#~ msgid "E559: Terminal entry not found in termcap" +#~ msgstr "E559: 在 termcap 中找不到终端项" + +#, c-format +#~ msgid "E560: Usage: cs[cope] %s" +#~ msgstr "E560: 用法: cs[cope] %s" + +#~ msgid "E561: unknown cscope search type" +#~ msgstr "E561: 未知的 cscope 查找类型" + +#~ msgid "E562: Usage: cstag <ident>" +#~ msgstr "E562: 用法: cstag <ident>" + +#~ msgid "E563: stat error" +#~ msgstr "E563: stat 错误" + +#, c-format +#~ msgid "E563: stat(%s) error: %d" +#~ msgstr "E563: stat(%s) 错误: %d" + +#, c-format +#~ msgid "E564: %s is not a directory or a valid cscope database" +#~ msgstr "E564: %s 不是目录或有效的 cscope 数据库" + +#~ msgid "E566: Could not create cscope pipes" +#~ msgstr "E566: 无法创建 cscope 管道" + +#~ msgid "E567: no cscope connections" +#~ msgstr "E567: 没有 cscope 连接" + +#~ msgid "E568: duplicate cscope database not added" +#~ msgstr "E568: 重复的 cscope 数据库未被加入" + +#~ msgid "E569: maximum number of cscope connections reached" +#~ msgstr "E569: 已达到 cscope 的最大连接数" + +#~ msgid "E570: fatal error in cs_manage_matches" +#~ msgstr "E570: cs_manage_matches 严重错误" + +#~ msgid "" +#~ "E571: Sorry, this command is disabled: the Tcl library could not be " +#~ "loaded." +#~ msgstr "E571: 抱歉,此命令不可用,无法加载 Tcl 库" + +#~ msgid "E572: exit code %d" +#~ msgstr "E572: 退出返回值 %d" + +#~ msgid "E573: Invalid server id used: %s" +#~ msgstr "E573: 使用了无效的服务器 id: %s" + +#, c-format +#~ msgid "E574: Unknown register type %d" +#~ msgstr "E574: 未知的寄存器类型 %d" + +#~ msgid "E596: Invalid font(s)" +#~ msgstr "E596: 无效的字体" + +#~ msgid "E597: can't select fontset" +#~ msgstr "E597: 无法选择 Fontset" + +#~ msgid "E599: Value of 'imactivatekey' is invalid" +#~ msgstr "E599: 'imactivatekey' 的值无效" + +#, c-format +#~ msgid "E59: invalid character after %s@" +#~ msgstr "E59: %s@ 后面有无效的字符" + +#, c-format +#~ msgid "E609: Cscope error: %s" +#~ msgstr "E609: Cscope 错误: %s" + +#, c-format +#~ msgid "E60: Too many complex %s{...}s" +#~ msgstr "E60: 太多复杂的 %s{...}s" + +#~ msgid "E613: Unknown printer font: %s" +#~ msgstr "E613: 未知的打印机字体: %s" + +#~ msgid "E614: vim_SelFile: can't return to current directory" +#~ msgstr "E614: vim_SelFile: 无法返回当前目录" + +#~ msgid "E615: vim_SelFile: can't get current directory" +#~ msgstr "E615: vim_SelFile: 无法获取当前目录" + +#~ msgid "E616: vim_SelFile: can't get font %s" +#~ msgstr "E616: vim_SelFile: 无法获取字体 %s" + +#~ msgid "E617: Cannot be changed in the GTK+ 2 GUI" +#~ msgstr "E617: 在 GTK+ 2 图形界面中不能更改" + +#, c-format +#~ msgid "E61: Nested %s*" +#~ msgstr "E61: 嵌套的 %s*" + +#~ msgid "E622: Could not fork for cscope" +#~ msgstr "E622: 无法对 cscope 进行 fork" + +#~ msgid "E625: cannot open cscope database: %s" +#~ msgstr "E625: 无法打开 cscope 数据库: %s" + +#~ msgid "E626: cannot get cscope database information" +#~ msgstr "E626: 无法获取 cscope 数据库信息" + +#, c-format +#~ msgid "E62: Nested %s%c" +#~ msgstr "E62: 嵌套的 %s%c" + +#~ msgid "E63: invalid use of \\_" +#~ msgstr "E63: 不正确地使用 \\_" + +#, c-format +#~ msgid "E64: %s%c follows nothing" +#~ msgstr "E64: %s%c 前面无内容" + +#~ msgid "E658: NetBeans connection lost for buffer %<PRId64>" +#~ msgstr "E658: 缓冲区 %<PRId64> 丢失 NetBeans 连接" + +#~ msgid "E65: Illegal back reference" +#~ msgstr "E65: 无效的回引" + +#~ msgid "E665: Cannot start GUI, no valid font found" +#~ msgstr "E665: 无法启动图形界面,找不到有效的字体" + +#~ msgid "E668: Wrong access mode for NetBeans connection info file: \"%s\"" +#~ msgstr "E668: NetBeans 连接信息文件中错误的访问模式: \"%s\"" + +#~ msgid "E672: Unable to open window inside MDI application" +#~ msgstr "E672: 无法在 MDI 应用程序中打开窗口" + +#, c-format +#~ msgid "E678: Invalid character after %s%%[dxouU]" +#~ msgstr "E678: %s%%[dxouU] 后面有无效的字符" + +#~ msgid "E679: recursive loop loading syncolor.vim" +#~ msgstr "E679: 加载 syncolor.vim 时出现嵌套循环" + +#~ msgid "E68: Invalid character after \\z" +#~ msgstr "E68: \\z 后面有无效的字符" + +#~ msgid "E693: Can only compare Funcref with Funcref" +#~ msgstr "E693: 只能比较 Funcref 和 Funcref" + +#, c-format +#~ msgid "E706: Variable type mismatch for: %s" +#~ msgstr "E706: 变量类型不匹配: %s" + +#~ msgid "E724: variable nested too deep for displaying" +#~ msgstr "E724: 变量嵌套过深无法显示" + +#~ msgid "E729: using Funcref as a String" +#~ msgstr "E729: 将函数当做字符串使用" + +#~ msgid "E744: NetBeans does not allow changes in read-only files" +#~ msgstr "E744: NetBeans 不允许改变只读文件" + +#~ msgid "" +#~ "E747: Cannot change directory, buffer is modified (add ! to override)" +#~ msgstr "E747: 不能改变目录,缓冲区已修改 (请加 ! 强制执行)" + +#~ msgid "E761: Format error in affix file FOL, LOW or UPP" +#~ msgstr "E761: 附加文件 FOL、LOW 或 UPP 中格式错误" + +#~ msgid "E762: Character in FOL, LOW or UPP is out of range" +#~ msgstr "E762: FOL、LOW 或 UPP 中字符超出范围" + +#~ msgid "E775: Eval feature not available" +#~ msgstr "E775: 求值功能不可用" + +#~ msgid "E800: Arabic cannot be used: Not enabled at compile time\n" +#~ msgstr "E800: 无法使用 Arabic: 编译时没有启用\n" + +#~ msgid "E839: Completion function changed window" +#~ msgstr "E839: 补全函数更改了窗口" + +#~ msgid "E865: (NFA) Regexp end encountered prematurely" +#~ msgstr "E865: (NFA) 过早地遇到了正则表达式的结尾" + +#, c-format +#~ msgid "E867: (NFA) Unknown operator '\\%%%c'" +#~ msgstr "E867: (NFA) 未知的操作符 '\\%%%c'" + +#, c-format +#~ msgid "E867: (NFA) Unknown operator '\\z%c'" +#~ msgstr "E867: (NFA) 未知的操作符 '\\z%c'" + +#, c-format +#~ msgid "E869: (NFA) Unknown operator '\\@%c'" +#~ msgstr "E869: (NFA) 未知的操作符 '\\@%c'" + +#~ msgid "E870: (NFA regexp) Error reading repetition limits" +#~ msgstr "E870: (NFA regexp) 读取重复限制时出错" + +#~ msgid "E871: (NFA regexp) Can't have a multi follow a multi !" +#~ msgstr "E871: (NFA regexp) 不能多个跟多个!" + +#~ msgid "E872: (NFA regexp) Too many '('" +#~ msgstr "E872: (NFA regexp) 太多 '('" + +#~ msgid "E873: (NFA regexp) proper termination error" +#~ msgstr "E873: (NFA regexp) 未适当终止" + +#~ msgid "E874: (NFA) Could not pop the stack !" +#~ msgstr "E874: (NFA) 无法出栈!" + +#~ msgid "" +#~ "E875: (NFA regexp) (While converting from postfix to NFA), too many " +#~ "states left on stack" +#~ msgstr "E875: (NFA regexp) (从后缀转换到 NFA 时),栈上遗留了太多状态" + +#~ msgid "E876: (NFA regexp) Not enough space to store the whole NFA " +#~ msgstr "E876: (NFA regexp) 没有足够的空间存储整个NFA " + +#, c-format +#~ msgid "E877: (NFA regexp) Invalid character class: %<PRId64>" +#~ msgstr "E877: (NFA regexp) 不可用的字符类: %<PRId64>" + +#, fuzzy +#~ msgid "E879: (NFA regexp) Too many \\z(" +#~ msgstr "E50: 太多 \\z(" + +#~ msgid "ERROR: " +#~ msgstr "错误: " + +#~ msgid "Edit File" +#~ msgstr "编辑文件" + +#~ msgid "Edit File in new window" +#~ msgstr "在新窗口编辑文件" #~ msgid "Edit with &Vim" #~ msgstr "用 Vim 编辑(&V)" +#~ msgid "Edit with &multiple Vims" +#~ msgstr "用多个 Vim 编辑(&M)" + #~ msgid "Edit with existing Vim - " #~ msgstr "用当前的 Vim 编辑 - " +#~ msgid "Edit with single &Vim" +#~ msgstr "用单个 Vim 编辑(&V)" + #~ msgid "Edits the selected file(s) with Vim" #~ msgstr "用 Vim 编辑选中的文件" +#~ msgid "Encoding:" +#~ msgstr "编码:" + +#~ msgid "Enter encryption key: " +#~ msgstr "输入密码: " + +#~ msgid "Enter nr of choice (<CR> to abort): " +#~ msgstr "输入 nr 或选择 (<CR> 退出): " + +#~ msgid "Enter same key again: " +#~ msgstr "请再输入一次: " + #~ msgid "Error creating process: Check if gvim is in your path!" #~ msgstr "创建进程失败: 请检查 gvim 是否在路径中!" -#~ msgid "gvimext.dll error" -#~ msgstr "gvimext.dll 错误" +# do not translate to avoid writing Chinese in files +#, fuzzy +#~ msgid "Expression" +#~ msgstr "表达式" + +#~ msgid "External submatches:\n" +#~ msgstr "外部符合:\n" + +#~ msgid "Files" +#~ msgstr "文件" + +#~ msgid "Filter" +#~ msgstr "过滤器" + +#~ msgid "Find & Replace (use '\\\\' to find a '\\')" +#~ msgstr "查找和替换字符串 (使用 '\\\\' 来查找 '\\')" + +#~ msgid "Find &Next" +#~ msgstr "查找下一个(&N)" + +#~ msgid "Find Next" +#~ msgstr "查找下一个" + +#~ msgid "Find string (use '\\\\' to find a '\\')" +#~ msgstr "查找字符串 (使用 '\\\\' 来查找 '\\')" + +#~ msgid "Find symbol" +#~ msgstr "查找 symbol" + +#~ msgid "Find what:" +#~ msgstr "查找内容:" + +#~ msgid "Font '%s' is not fixed-width" +#~ msgstr "'%s' 不是固定宽度的字体" + +#~ msgid "Font Selection" +#~ msgstr "选择字体" + +#~ msgid "Font%<PRId64> width is not twice that of font0\n" +#~ msgstr "字体%<PRId64>的宽度不是字体0的两倍\n" + +#~ msgid "Font0 width: %<PRId64>\n" +#~ msgstr "字体0的宽度:%<PRId64>\n" + +#~ msgid "Font0: %s\n" +#~ msgstr "字体0: %s\n" + +#~ msgid "" +#~ "Font1 width: %<PRId64>\n" +#~ "\n" +#~ msgstr "" +#~ "字体1的宽度: %<PRId64>\n" +#~ "\n" + +#~ msgid "Font1: %s\n" +#~ msgstr "字体1: %s\n" + +#~ msgid "Font:" +#~ msgstr "字体:" + +#~ msgid "Generate docu for" +#~ msgstr "产生文件: " + +#~ msgid "Hit ENTER to continue" +#~ msgstr "请按 ENTER 继续" + +#~ msgid "I/O ERROR" +#~ msgstr "I/O 错误" + +#~ msgid "Ignoring long line in tags file" +#~ msgstr "忽略较长的行在 tags 文件中" + +#~ msgid "Illegal register name" +#~ msgstr "无效的寄存器名" + +#~ msgid "Illegal starting char" +#~ msgstr "无效的启动字符" + +# do not translate to avoid writing Chinese in files +#, fuzzy +#~ msgid "Input Line" +#~ msgstr "输入行" + +#~ msgid "Input _Methods" +#~ msgstr "输入法(_M)" + +#~ msgid "Invalid argument for" +#~ msgstr "无效的参数" + +#~ msgid "Invalid font specification" +#~ msgstr "指定了无效的字体" + +#~ msgid "Keys don't match!" +#~ msgstr "两次密码不匹配!" + +#~ msgid "Kill a connection" +#~ msgstr "结束一个连接" + +#~ msgid "Linear tag search" +#~ msgstr "线性查找标签 (Tags)" + +#~ msgid "Linking: " +#~ msgstr "链接方式: " + +#~ msgid "Match case" +#~ msgstr "匹配大小写" + +#~ msgid "Match whole word only" +#~ msgstr "匹配完整的词" + +#~ msgid "Message" +#~ msgstr "消息" + +#~ msgid "Missing '>'" +#~ msgstr "缺少 '>'" + +#, c-format +#~ msgid "Missing FOL/LOW/UPP line in %s" +#~ msgstr "%s 中缺少 FOL/LOW/UPP 行" + +#~ msgid "Modified by " +#~ msgstr "修改者 " + +#~ msgid "Name:" +#~ msgstr "名称:" + +#~ msgid "Need %s version %<PRId64>\n" +#~ msgstr "需要 %s 版本 %<PRId64>\n" + +#~ msgid "Need Amigados version 2.04 or later\n" +#~ msgstr "需要 Amigados 版本 2.04 以上\n" + +#~ msgid "NetBeans disallows writes of unmodified buffers" +#~ msgstr "NetBeans 不允许未修改的缓冲区写入" + +#~ msgid "New tab" +#~ msgstr "新建标签" + +#~ msgid "No display: Send expression failed.\n" +#~ msgstr "没有 display: 发送表达式失败。\n" + +#~ msgid "No match at cursor, finding next" +#~ msgstr "在光标处没有匹配,查找下一个" + +#~ msgid "No undo possible; continue anyway" +#~ msgstr "无法撤销;仍然继续" + +#~ msgid "Not Used" +#~ msgstr "未使用" + +#~ msgid "Nvim: Reading from stdin...\n" +#~ msgstr "Vim: 从标准输入读取...\n" + +#~ msgid "OK" +#~ msgstr "确定" + +#~ msgid "Open File dialog" +#~ msgstr "打开文件对话框" + +#~ msgid "Open Tab..." +#~ msgstr "打开标签..." + +#~ msgid "Open tab..." +#~ msgstr "打开标签..." + +#~ msgid "Opening the X display failed" +#~ msgstr "打开 X display 失败" + +#~ msgid "Opening the X display timed out" +#~ msgstr "打开 X display 超时" + +#~ msgid "Opening the X display took %<PRId64> msec" +#~ msgstr "打开 X display 用时 %<PRId64> 秒" + +#~ msgid "Partial writes disallowed for NetBeans buffers" +#~ msgstr "NetBeans 不允许缓冲区部分写入" #~ msgid "Path length too long!" #~ msgstr "路径太长!" -#~ msgid "E234: Unknown fontset: %s" -#~ msgstr "E234: 未知的 Fontset: %s" +#~ msgid "Pathname:" +#~ msgstr "路径:" -#~ msgid "E235: Unknown font: %s" -#~ msgstr "E235: 未知的字体: %s" +#~ msgid "Query for a pattern" +#~ msgstr "查询一个模式" -#~ msgid "E236: Font \"%s\" is not fixed-width" -#~ msgstr "E236: 字体 \"%s\" 不是等宽字体" +#~ msgid "Reading from stdin..." +#~ msgstr "从标准输入读取..." -#~ msgid "E448: Could not load library function %s" -#~ msgstr "E448: 无法加载库函数 %s" +#~ msgid "Reinit all connections" +#~ msgstr "重置所有连接" -#~ msgid "E26: Hebrew cannot be used: Not enabled at compile time\n" -#~ msgstr "E26: 无法使用 Hebrew: 编译时没有启用\n" +#~ msgid "Replace" +#~ msgstr "替换" -#~ msgid "E27: Farsi cannot be used: Not enabled at compile time\n" -#~ msgstr "E27: 无法使用 Farsi: 编译时没有启用\n" +#~ msgid "Replace &All" +#~ msgstr "全部替换(&A)" -#~ msgid "E800: Arabic cannot be used: Not enabled at compile time\n" -#~ msgstr "E800: 无法使用 Arabic: 编译时没有启用\n" +#~ msgid "Replace All" +#~ msgstr "全部替换" -#~ msgid "E247: no registered server named \"%s\"" -#~ msgstr "E247: 没有名叫 \"%s\" 的已注册的服务器" +#~ msgid "Replace with:" +#~ msgstr "替换为:" -#~ msgid "E233: cannot open display" -#~ msgstr "E233: 无法打开 display" +#~ msgid "Retrieve" +#~ msgstr "恢复" -#~ msgid "E449: Invalid expression received" -#~ msgstr "E449: 收到无效的表达式" +#~ msgid "Retrieve from all projects" +#~ msgstr "恢复: 从所有项目" -#~ msgid "E744: NetBeans does not allow changes in read-only files" -#~ msgstr "E744: NetBeans 不允许改变只读文件" +#~ msgid "Retrieve from file" +#~ msgstr "恢复: 从文件" -#~ msgid "Affix flags ignored when PFXPOSTPONE used in %s line %d: %s" -#~ msgstr "%s 第 %d 行,使用 PFXPOSTPONE 时附加标志被忽略: %s" +#~ msgid "Retrieve from project" +#~ msgstr "恢复: 从对象" -#~ msgid "[No file]" -#~ msgstr "[未命名]" +#~ msgid "Run Macro" +#~ msgstr "执行宏" + +#~ msgid "Running in Vi compatible mode" +#~ msgstr "运行于 Vi 兼容模式" + +#~ msgid "Running modeless, typed text is inserted" +#~ msgstr "无模式运行,输入文字即插入" + +#~ msgid "SNiFF+ is currently " +#~ msgstr "SNiFF+ 目前" + +#~ msgid "Save As" +#~ msgstr "另存为" + +#~ msgid "Save File dialog" +#~ msgstr "保存文件对话框" + +#~ msgid "Save Redirection" +#~ msgstr "保存重定向" + +#~ msgid "Save Session" +#~ msgstr "保存会话" + +#~ msgid "Save Setup" +#~ msgstr "保存设定" + +#~ msgid "Save View" +#~ msgstr "保存视图" + +#~ msgid "Scrollbar Widget: Could not get geometry of thumb pixmap." +#~ msgstr "滚动条部件: 无法获取滑块图像的几何大小" + +# do not translate to avoid writing Chinese in files +#, fuzzy +#~ msgid "Search String" +#~ msgstr "查找字符串" + +#~ msgid "Select Directory dialog" +#~ msgstr "选择目录对话框" + +#~ msgid "Show base class of" +#~ msgstr "显示 base class of:" + +#~ msgid "Show class in hierarchy" +#~ msgstr "显示层次关系的类" + +#~ msgid "Show class in restricted hierarchy" +#~ msgstr "显示 restricted 层次关系的 class" + +#~ msgid "Show connections" +#~ msgstr "显示连接" + +#~ msgid "Show docu of" +#~ msgstr "显示文件: " + +#~ msgid "Show overridden member function" +#~ msgstr "显示被覆盖的成员函数" + +#~ msgid "Show source of" +#~ msgstr "显示源代码: " + +#~ msgid "Show this message" +#~ msgstr "显示此信息" + +#~ msgid "Size:" +#~ msgstr "尺寸:" + +#~ msgid "Sniff: Error during write. Disconnected" +#~ msgstr "Sniff: 写入错误。结束连接" + +#~ msgid "" +#~ "Sorry, this command is disabled: the Perl library could not be loaded." +#~ msgstr "抱歉,此命令不可用: 无法加载 Perl 库。" + +#~ msgid "Source Vim script" +#~ msgstr "执行 Vim 脚本" + +#~ msgid "Style:" +#~ msgstr "风格:" + +#, fuzzy +#~ msgid "Substitute " +#~ msgstr "1 次替换," + +#~ msgid "Swap file already exists!" +#~ msgstr "交换文件已存在!" + +#~ msgid "Tear off this menu" +#~ msgstr "撕下此菜单" + +#~ msgid "Testing the X display failed" +#~ msgstr "测试 X display 失败" + +#~ msgid "Thanks for flying Vim" +#~ msgstr "感谢您选择 Vim" + +#~ msgid "This Vim was not compiled with the diff feature." +#~ msgstr "此 Vim 编译时没有加入 diff 功能" + +#~ msgid "This cscope command does not support splitting the window.\n" +#~ msgstr "这个 cscope 命令不支持分割窗口。\n" + +#~ msgid "Toggle implementation/definition" +#~ msgstr "切换实现/定义" + +#, fuzzy +#~ msgid "Unable to get option value" +#~ msgstr "选项参数后的内容无效" + +#~ msgid "Unable to register a command server name" +#~ msgstr "无法注册命令服务器名" + +#~ msgid "Up" +#~ msgstr "向上" + +#~ msgid "Used CUT_BUFFER0 instead of empty selection" +#~ msgstr "使用 CUT_BUFFER0 来取代空选择" + +#~ msgid "VIM - Search and Replace..." +#~ msgstr "VIM - 查找和替换..." + +#~ msgid "VIM - Search..." +#~ msgstr "VIM - 查找..." + +#~ msgid "VIM - Vi IMproved" +#~ msgstr "VIM - Vi IMproved" + +#~ msgid "VIM Error" +#~ msgstr "VIM 错误" + +#~ msgid "VIM: Can't open window!\n" +#~ msgstr "VIM: 不能打开窗口!\n" + +#~ msgid "" +#~ "VIMRUN.EXE not found in your $PATH.\n" +#~ "External commands will not pause after completion.\n" +#~ "See :help win32-vimrun for more information." +#~ msgstr "" +#~ "在你的 $PATH 中找不到 VIMRUN.EXE。\n" +#~ "外部命令执行完毕后将不会暂停。\n" +#~ "进一步说明请见 :help win32-vimrun" + +#~ msgid "Vim - Font Selector" +#~ msgstr "Vim - 字体选择器" + +#~ msgid "" +#~ "Vim E458: Cannot allocate colormap entry, some colors may be incorrect" +#~ msgstr "Vim E458: 无法分配颜色表项,某些颜色可能不正确" + +#~ msgid "Vim Warning" +#~ msgstr "Vim 警告" + +#~ msgid "Vim dialog" +#~ msgstr "Vim 对话框" + +#~ msgid "Vim dialog..." +#~ msgstr "Vim 对话框..." + +#~ msgid "Vim error" +#~ msgstr "Vim 错误" + +#~ msgid "Vim error: ~a" +#~ msgstr "Vim 错误: ~a" + +#~ msgid "Vim exiting with %d\n" +#~ msgstr "Vim 返回值: %d\n" + +#~ msgid "Vim: Caught %s event\n" +#~ msgstr "Vim: 拦截到 %s 事件\n" + +#~ msgid "Vim: Caught deadly signal\n" +#~ msgstr "Vim: 拦截到致命信号(deadly signal)\n" + +#~ msgid "Vim: Caught deadly signal %s\n" +#~ msgstr "Vim: 拦截到致命信号(deadly signal) %s\n" + +#~ msgid "Vim: Double signal, exiting\n" +#~ msgstr "Vim: 双重信号,退出中\n" + +#~ msgid "Vim: Error: Failure to start gvim from NetBeans\n" +#~ msgstr "Vim: 错误: 无法从 NetBeans 中启动 gvim\n" + +#~ msgid "Vim: Finished.\n" +#~ msgstr "Vim: 结束。\n" + +#~ msgid "Vim: Main window unexpectedly destroyed\n" +#~ msgstr "Vim: 主窗口被意外地摧毁\n" + +#~ msgid "Vim: Received \"die\" request from session manager\n" +#~ msgstr "Vim: 从会话管理器收到 \"die\" 请求\n" + +#~ msgid "Vim: Warning: Input is not from a terminal\n" +#~ msgstr "Vim: 警告: 输入不是来自终端(键盘)\n" + +#~ msgid "Vim: Warning: Output is not to a terminal\n" +#~ msgstr "Vim: 警告: 输出不是到终端(屏幕)\n" + +#~ msgid "Vim: preserving files...\n" +#~ msgstr "Vim: 正在保留文件……\n" + +#~ msgid "WARNING: Windows 95/98/ME detected" +#~ msgstr "警告: 检测到 Windows 95/98/ME" + +#~ msgid "Warning: terminal cannot highlight" +#~ msgstr "警告: 你的终端不能显示高亮" + +#~ msgid "Window position: X %d, Y %d" +#~ msgstr "窗口位置: X %d, Y %d" + +#~ msgid "XSMP ICE connection watch failed" +#~ msgstr "XSMP ICE 连接监视失败" + +#~ msgid "XSMP handling save-yourself request" +#~ msgstr "XSMP 处理 save-yourself 请求" + +#~ msgid "XSMP lost ICE connection" +#~ msgstr "XSMP 丢失了到 ICE 的连接" + +#~ msgid "XSMP opening connection" +#~ msgstr "XSMP 打开连接" + +#~ msgid "Xref has a" +#~ msgstr "Xref 有" + +#~ msgid "Xref referred by" +#~ msgstr "Xref 被谁参考:" + +#~ msgid "Xref refers to" +#~ msgstr "Xref 参考到" + +#~ msgid "Xref used by" +#~ msgstr "Xref 被谁使用:" + +#~ msgid "Zero count" +#~ msgstr "计数为零" #~ msgid "[Error List]" #~ msgstr "[错误列表]" -#~ msgid "E106: Unknown variable: \"%s\"" -#~ msgstr "E106: 未定义的变量: \"%s\"" +#~ msgid "[NL found]" +#~ msgstr "[找到 NL]" -#~ msgid "function " -#~ msgstr "函数 " +#~ msgid "[New file]" +#~ msgstr "[新文件]" -#~ msgid "E130: Undefined function: %s" -#~ msgstr "E130: 函数 %s 尚未定义" +#~ msgid "[No file]" +#~ msgstr "[未命名]" -#~ msgid "Run Macro" -#~ msgstr "执行宏" +#~ msgid "" +#~ "[calls] total re/malloc()'s %<PRIu64>, total free()'s %<PRIu64>\n" +#~ "\n" +#~ msgstr "" +#~ "[调用] 总共 re/malloc(): %<PRIu64>,总共 free()': %<PRIu64>\n" +#~ "\n" -#~ msgid "E242: Color name not recognized: %s" -#~ msgstr "E242: %s 为不能识别的颜色名称" +#~ msgid "[crypted]" +#~ msgstr "[已加密]" -#~ msgid "error reading cscope connection %d" -#~ msgstr "读取 cscope 连接 %d 时错误" +#~ msgid "[fifo/socket]" +#~ msgstr "[fifo/socket]" -#~ msgid "E260: cscope connection not found" -#~ msgstr "E260: 找不到 cscope 连接" +#~ msgid "[string too long]" +#~ msgstr "[字符串太长]" -#~ msgid "cscope connection closed" -#~ msgstr "cscope 连接已关闭" +#~ msgid "attempt to refer to deleted buffer" +#~ msgstr "试图引用已被删除的缓冲区" + +#~ msgid "attempt to refer to deleted window" +#~ msgstr "试图引用已被删除的窗口" + +#~ msgid "block of 1 line yanked" +#~ msgstr "复制了 1 行的块" + +#~ msgid "buffer is invalid" +#~ msgstr "缓冲区无效" + +#~ msgid "by Bram Moolenaar et al." +#~ msgstr "维护人 Bram Moolenaar 等" + +#~ msgid "can't delete OutputObject attributes" +#~ msgstr "不能删除 OutputObject 属性" + +#~ msgid "cannot change console mode ?!\n" +#~ msgstr "不能切换主控台(console)模式 !?\n" + +#~ msgid "cannot create buffer/window command: object is being deleted" +#~ msgstr "无法创建缓冲区/窗口命令: 对象将被删除" + +#~ msgid "cannot delete line" +#~ msgstr "无法删除行" + +#~ msgid "cannot insert line" +#~ msgstr "无法插入行" + +#~ msgid "cannot insert/append line" +#~ msgstr "无法插入/追加行" + +#~ msgid "cannot open " +#~ msgstr "不能打开" + +#~ msgid "" +#~ "cannot register callback command: buffer/window is already being deleted" +#~ msgstr "无法注册回调命令: 缓冲区/窗口已被删除" + +#~ msgid "cannot register callback command: buffer/window reference not found" +#~ msgstr "无法注册回调命令: 找不到缓冲区/窗口引用" + +#~ msgid "cannot replace line" +#~ msgstr "无法替换行" + +#~ msgid "cannot set line(s)" +#~ msgstr "无法设定行" + +#~ msgid "cannot yank; delete anyway" +#~ msgstr "无法复制;改为删除" + +#~ msgid "close" +#~ msgstr "关闭" + +#~ msgid "connected" +#~ msgstr "连接中" #~ msgid "couldn't malloc\n" #~ msgstr "不能使用 malloc\n" -#~ msgid "%2d %-5ld %-34s <none>\n" -#~ msgstr "%2d %-5ld %-34s <无>\n" +#~ msgid "couldn't open buffer" +#~ msgstr "无法打开缓冲区" -#~ msgid "E249: couldn't read VIM instance registry property" -#~ msgstr "E249: 不能读取 VIM 的 注册表属性" +#~ msgid "cs_create_connection exec failed" +#~ msgstr "cs_create_connection 执行失败" -#~ msgid "\"\n" -#~ msgstr "\"\n" +#, fuzzy +#~ msgid "cs_create_connection setpgid failed" +#~ msgstr "cs_create_connection 执行失败" -#~ msgid "--help\t\tShow Gnome arguments" -#~ msgstr "--help\t\t显示 Gnome 相关参数" +#~ msgid "cs_create_connection: fdopen for fr_fp failed" +#~ msgstr "cs_create_connection: fdopen fr_fp 失败" -#~ msgid "[string too long]" -#~ msgstr "[字符串太长]" +#~ msgid "cs_create_connection: fdopen for to_fp failed" +#~ msgstr "cs_create_connection: fdopen to_fp 失败" -#~ msgid "Hit ENTER to continue" -#~ msgstr "请按 ENTER 继续" +#~ msgid "cscope commands:\n" +#~ msgstr "cscope 命令:\n" -#~ msgid " (RET/BS: line, SPACE/b: page, d/u: half page, q: quit)" -#~ msgstr " (RET/BS: 向下/向上一行, 空格/b: 一页, d/u: 半页, q: 退出)" +#, c-format +#~ msgid "cscope connection %s closed" +#~ msgstr "cscope 连接 %s 已关闭" -#~ msgid " (RET: line, SPACE: page, d: half page, q: quit)" -#~ msgstr " (RET: 向下一行, 空白键: 一页, d: 半页, q: 退出)" +#~ msgid "cursor position outside buffer" +#~ msgstr "光标位置在缓冲区外" -#~ msgid "E361: Crash intercepted; regexp too complex?" -#~ msgstr "E361: 不能执行; regular expression 太复杂?" +#~ msgid "defaulting to '" +#~ msgstr "默认值为: '" -#~ msgid "E363: pattern caused out-of-stack error" -#~ msgstr "E363: regular expression 造成堆栈用光的错误" +#~ msgid "error reading cscope connection %d" +#~ msgstr "读取 cscope 连接 %d 时错误" -#~ msgid " BLOCK" -#~ msgstr " 块" +#~ msgid "filename / context / line\n" +#~ msgstr "文件名 / 上下文 / 行\n" -#~ msgid " LINE" -#~ msgstr " 行" +#~ msgid "freeing %<PRId64> lines" +#~ msgstr "释放了 %<PRId64> 行" -#~ msgid "Enter nr of choice (<CR> to abort): " -#~ msgstr "输入 nr 或选择 (<CR> 退出): " +#~ msgid "gvimext.dll error" +#~ msgstr "gvimext.dll 错误" -#~ msgid "Linear tag search" -#~ msgstr "线性查找标签 (Tags)" +#~ msgid "hidden option" +#~ msgstr "隐藏的选项" -#~ msgid "Binary tag search" -#~ msgstr "二进制查找(Binary search) 标签(Tags)" +#~ msgid "internal error: unknown option type" +#~ msgstr "内部错误:未知的选项类型" + +#~ msgid "invalid attribute" +#~ msgstr "无效的属性" + +#~ msgid "invalid buffer number" +#~ msgstr "无效的缓冲区号" + +#~ msgid "invalid expression" +#~ msgstr "无效的表达式" + +#~ msgid "invalid mark name" +#~ msgstr "无效的标记名称" + +#~ msgid "keyboard interrupt" +#~ msgstr "键盘中断" + +#~ msgid "linenr out of range" +#~ msgstr "行号超出范围" + +#~ msgid "logoff" +#~ msgstr "注消" + +#~ msgid "mark not set" +#~ msgstr "没有设定标记" + +#~ msgid "mch_get_shellsize: not a console??\n" +#~ msgstr "mch_get_shellsize: 不是主控台(console)??\n" + +#~ msgid "menu Edit->Global Settings->Toggle Insert Mode " +#~ msgstr "菜单 编辑->全局设定->开/关插入模式 " + +#~ msgid "menu Help->Orphans for information " +#~ msgstr "菜单 帮助->孤儿 查看说明 " + +#~ msgid "new shell started\n" +#~ msgstr "启动新 shell\n" + +#~ msgid "no cscope connections\n" +#~ msgstr "没有 cscope 连接\n" + +#~ msgid "no specific match" +#~ msgstr "找不到匹配的项" + +#~ msgid "no such buffer" +#~ msgstr "无此缓冲区" + +#~ msgid "no such window" +#~ msgstr "无此窗口" + +#~ msgid "not " +#~ msgstr "未" + +#~ msgid "not allowed in the Vim sandbox" +#~ msgstr "不允许在 sandbox 中使用" + +#~ msgid "not implemented yet" +#~ msgstr "尚未实现" + +#~ msgid "number changes time" +#~ msgstr " 编号 改变 时间" + +#~ msgid "read from Netbeans socket" +#~ msgstr "从 Netbeans 套接字读取" + +#~ msgid "readonly attribute" +#~ msgstr "只读属性" + +#~ msgid "row %d column %d" +#~ msgstr "第 %d 行 第 %d 列" + +#~ msgid "shell " +#~ msgstr "shell " + +#~ msgid "shell returned %d" +#~ msgstr "Shell 返回 %d" + +#~ msgid "shutdown" +#~ msgstr "关机" + +#~ msgid "softspace must be an integer" +#~ msgstr "softspace 必须是整数" + +#~ msgid "string cannot contain newlines" +#~ msgstr "字符串不能包含换行(NL)" + +#~ msgid "to %s on %s" +#~ msgstr "从 %s 到 %s" + +#~ msgid "type :help cp-default<Enter> for info on this" +#~ msgstr "输入 :help cp-default<Enter> 查看相关说明 " + +#~ msgid "type :help windows95<Enter> for info on this" +#~ msgstr "输入 :help windows95<Enter> 查看相关说明 " + +#~ msgid "type :help<Enter> or <F1> for on-line help" +#~ msgstr "输入 :help<Enter> 或 <F1> 查看在线帮助 " + +#~ msgid "type :set nocp<Enter> for Vim defaults" +#~ msgstr "输入 :set nocp<Enter> 恢复默认的 Vim " + +#~ msgid "unknown flag: " +#~ msgstr "未知的标志: " + +#~ msgid "unknown option" +#~ msgstr "未知的选项" + +#~ msgid "unknown vimOption" +#~ msgstr "未知的 vim 选项" + +#~ msgid "version " +#~ msgstr "版本 " + +#~ msgid "vim error" +#~ msgstr "vim 错误" + +#~ msgid "window index is out of range" +#~ msgstr "窗口索引超出范围" + +#~ msgid "window is invalid" +#~ msgstr "窗口无效" + +#~ msgid "with (classic) GUI." +#~ msgstr "带(传统)图形界面。" #~ msgid "with BeOS GUI." #~ msgstr "使用 BeOS 图形界面。" + +#~ msgid "with Carbon GUI." +#~ msgstr "带 Carbon 图形界面。" + +#~ msgid "with Cocoa GUI." +#~ msgstr "带 Cocoa 图形界面。" + +#~ msgid "with GTK GUI." +#~ msgstr "带 GTK 图形界面。" + +#~ msgid "with GTK-GNOME GUI." +#~ msgstr "带 GTK-GNOME 图形界面。" + +#~ msgid "with GTK2 GUI." +#~ msgstr "带 GTK2 图形界面。" + +#~ msgid "with GTK2-GNOME GUI." +#~ msgstr "带 GTK2-GNOME 图形界面。" + +#~ msgid "with GUI." +#~ msgstr "带图形界面。" + +#~ msgid "with Photon GUI." +#~ msgstr "带 Photon 图形界面。" + +#~ msgid "with X11-Athena GUI." +#~ msgstr "带 X11-Athena 图形界面。" + +#~ msgid "with X11-Motif GUI." +#~ msgstr "带 X11-Motif 图形界面。" + +#~ msgid "with X11-neXtaw GUI." +#~ msgstr "带 X11-neXtaw 图形界面。" + +#~ msgid "without GUI." +#~ msgstr "无图形界面。" + +#~ msgid "writelines() requires list of strings" +#~ msgstr "writelines() 需要字符串列表作参数" diff --git a/src/nvim/po/zh_TW.UTF-8.po b/src/nvim/po/zh_TW.UTF-8.po index e2fb2d39d4..cba95e2af2 100644 --- a/src/nvim/po/zh_TW.UTF-8.po +++ b/src/nvim/po/zh_TW.UTF-8.po @@ -59,19 +59,19 @@ msgstr "無法傳送回應訊息" #: ../api/private/helpers.c:204 msgid "internal error: unknown option type" -msgstr "" +msgstr "內部錯誤: 未知的選項類型" #: ../buffer.c:92 msgid "[Location List]" -msgstr "" +msgstr "[Location 列表]" #: ../buffer.c:93 msgid "[Quickfix List]" -msgstr "" +msgstr "[Quickfix 列表]" #: ../buffer.c:94 msgid "E855: Autocommands caused command to abort" -msgstr "" +msgstr "E855: 自動命令導致命令被停止" #: ../buffer.c:135 msgid "E82: Cannot allocate any buffer, exiting..." @@ -349,7 +349,7 @@ msgstr "E103: 緩衝區 \"%s\" 不是在 diff 模式" #: ../diff.c:2193 msgid "E787: Buffer changed unexpectedly" -msgstr "" +msgstr "E787: 意外地改變了緩衝區" #: ../digraph.c:1598 msgid "E104: Escape not allowed in digraph" @@ -365,7 +365,7 @@ msgstr "E105: 使用 :loadkeymap " #: ../digraph.c:1821 msgid "E791: Empty keymap entry" -msgstr "" +msgstr "E791: 空的鍵位映射項" #: ../edit.c:82 msgid " Keyword completion (^N^P)" @@ -434,11 +434,11 @@ msgstr "已到段落結尾" #: ../edit.c:101 msgid "E839: Completion function changed window" -msgstr "" +msgstr "E839: 補全函式更改了窗口" #: ../edit.c:102 msgid "E840: Completion function deleted text" -msgstr "" +msgstr "E840: 補全函式刪除了文本" #: ../edit.c:1847 msgid "'dictionary' option is empty" @@ -556,7 +556,7 @@ msgstr "E118: 函式 %s 的引數過多" #: ../eval.c:148 #, c-format msgid "E716: Key not present in Dictionary: %s" -msgstr "" +msgstr "E716: 鍵在字典中不存在: %s" #: ../eval.c:150 #, c-format @@ -581,7 +581,7 @@ msgstr "E360: 不能用 -f 選項執行 shell" #: ../eval.c:154 #, c-format msgid "E734: Wrong variable type for %s=" -msgstr "" +msgstr "E734: 錯誤的變數類型: %s=" #: ../eval.c:155 #, fuzzy, c-format @@ -595,19 +595,19 @@ msgstr "E461: 不合法的變數名稱: %s" #: ../eval.c:157 msgid "E806: using Float as a String" -msgstr "" +msgstr "E806: 使用浮點數作為字串" #: ../eval.c:1830 msgid "E687: Less targets than List items" -msgstr "" +msgstr "E687: 目標比列表項數少" #: ../eval.c:1834 msgid "E688: More targets than List items" -msgstr "" +msgstr "E688: 目標比列表項數多" #: ../eval.c:1906 msgid "Double ; in list of variables" -msgstr "" +msgstr "變數列表出現兩個 ;" #: ../eval.c:2078 #, fuzzy, c-format @@ -616,23 +616,23 @@ msgstr "E138: 無法寫入 viminfo 檔案 %s !" #: ../eval.c:2391 msgid "E689: Can only index a List or Dictionary" -msgstr "" +msgstr "E689: 只能索引一個列表或者字典" #: ../eval.c:2396 msgid "E708: [:] must come last" -msgstr "" +msgstr "E708: [:] 必須在最後" #: ../eval.c:2439 msgid "E709: [:] requires a List value" -msgstr "" +msgstr "E709: [:] 需要一個列表值" #: ../eval.c:2674 msgid "E710: List value has more items than target" -msgstr "" +msgstr "E710: 列表值的項比目標多" #: ../eval.c:2678 msgid "E711: List value has not enough items" -msgstr "" +msgstr "E711: 列表值沒有足夠多的項" #: ../eval.c:2867 #, fuzzy @@ -651,7 +651,7 @@ msgstr "E108: 無此變數: \"%s\"" #: ../eval.c:3333 msgid "E743: variable nested too deep for (un)lock" -msgstr "" +msgstr "E743: (un)lock 的變數嵌套過深" #: ../eval.c:3630 msgid "E109: Missing ':' after '?'" @@ -659,7 +659,7 @@ msgstr "E109: '?' 後缺少 ':'" #: ../eval.c:3893 msgid "E691: Can only compare List with List" -msgstr "" +msgstr "E691: 只能比較列表和列表" #: ../eval.c:3895 #, fuzzy @@ -668,7 +668,7 @@ msgstr "E449: 收到不正確的運算式" #: ../eval.c:3915 msgid "E735: Can only compare Dictionary with Dictionary" -msgstr "" +msgstr "E735: 只能比較字典和字典" #: ../eval.c:3917 #, fuzzy @@ -677,7 +677,7 @@ msgstr "E116: 函式 %s 的引數不正確" #: ../eval.c:3932 msgid "E693: Can only compare Funcref with Funcref" -msgstr "" +msgstr "E693: 只能比較Funcref 和 Funcref" #: ../eval.c:3934 #, fuzzy @@ -736,7 +736,7 @@ msgstr "E242: 找不到顏色: %s" #: ../eval.c:6499 #, c-format msgid "E721: Duplicate key in Dictionary: \"%s\"" -msgstr "" +msgstr "E721: Dictionary 中出現重複的鍵: \"%s\"" #: ../eval.c:6517 #, fuzzy, c-format @@ -781,7 +781,7 @@ msgstr "E120: <SID> 不能在 script 本文外使用: %s" #: ../eval.c:7391 #, c-format msgid "E725: Calling dict function without Dictionary: %s" -msgstr "" +msgstr "E725: 調用字典函式但是沒有字典: %s" #: ../eval.c:7453 #, fuzzy @@ -814,16 +814,15 @@ msgstr "E227: %s 的 mapping 已經存在" #: ../eval.c:8692 msgid "extend() argument" -msgstr "" +msgstr "extend() 參數" #: ../eval.c:8915 -#, fuzzy msgid "map() argument" -msgstr "vim [參數] " +msgstr "map() 參數" #: ../eval.c:8916 msgid "filter() argument" -msgstr "" +msgstr "filter() 參數" #: ../eval.c:9229 #, c-format @@ -857,19 +856,19 @@ msgstr "E596: 不正確的字型" #: ../eval.c:11980 msgid "E726: Stride is zero" -msgstr "" +msgstr "E726: 步長為零" #: ../eval.c:11982 msgid "E727: Start past end" -msgstr "" +msgstr "E727: 起始值在終止值後" #: ../eval.c:12024 ../eval.c:15297 msgid "<empty>" -msgstr "" +msgstr "<空>" #: ../eval.c:12282 msgid "remove() argument" -msgstr "" +msgstr "remove() 參數" #: ../eval.c:12466 msgid "E655: Too many symbolic links (cycle?)" @@ -877,11 +876,11 @@ msgstr "E655: 太多層的符號鏈結(symlink) (循環?)" #: ../eval.c:12593 msgid "reverse() argument" -msgstr "" +msgstr "reverse() 參數" #: ../eval.c:13721 msgid "sort() argument" -msgstr "" +msgstr "sort() 參數" #: ../eval.c:13721 #, fuzzy @@ -895,7 +894,7 @@ msgstr "E237: 無法選擇此印表機" #: ../eval.c:13806 msgid "E882: Uniq compare function failed" -msgstr "" +msgstr "E882: Uniq 比較函式失敗" #: ../eval.c:14085 msgid "(Invalid)" @@ -908,32 +907,31 @@ msgstr "E208: 寫入檔案 \"%s\" 錯誤" #: ../eval.c:16159 msgid "E805: Using a Float as a Number" -msgstr "" +msgstr "E805: 將浮點數當做數字使用" #: ../eval.c:16162 msgid "E703: Using a Funcref as a Number" -msgstr "" +msgstr "E703: 將函式當做數字使用" #: ../eval.c:16170 msgid "E745: Using a List as a Number" -msgstr "" +msgstr "E745: 將列表當做數字使用" #: ../eval.c:16173 msgid "E728: Using a Dictionary as a Number" -msgstr "" +msgstr "E728: 將字典當做數字使用" #: ../eval.c:16259 msgid "E729: using Funcref as a String" -msgstr "" +msgstr "E729: 將函式當做字串使用" #: ../eval.c:16262 -#, fuzzy msgid "E730: using List as a String" -msgstr "E374: 格式化字串裡少了 ]" +msgstr "E730: 將列表當做字串使用" #: ../eval.c:16265 msgid "E731: using Dictionary as a String" -msgstr "" +msgstr "E731: 將字典當做字串使用" #: ../eval.c:16619 #, fuzzy, c-format @@ -953,12 +951,12 @@ msgstr "E128: 函式名稱第一個字母必須大寫: %s" #: ../eval.c:16732 #, c-format msgid "E705: Variable name conflicts with existing function: %s" -msgstr "" +msgstr "E705: 變數名與已有函式名衝突: %s" #: ../eval.c:16763 #, c-format msgid "E741: Value is locked: %s" -msgstr "" +msgstr "E741: 值已鎖定: %s" #: ../eval.c:16764 ../eval.c:16769 ../message.c:1839 msgid "Unknown" @@ -971,7 +969,7 @@ msgstr "E284: 不能設定 IC 數值" #: ../eval.c:16838 msgid "E698: variable nested too deep for making a copy" -msgstr "" +msgstr "E698: 變數嵌套過深無法複製" #: ../eval.c:17249 #, c-format @@ -1216,7 +1214,7 @@ msgstr "要覆寫已存在的檔案 \"%.*s\"?" #: ../ex_cmds.c:2317 #, c-format msgid "Swap file \"%s\" exists, overwrite anyway?" -msgstr "" +msgstr "交換文件 \"%s\" 已存在,確實需要覆蓋嗎?" #: ../ex_cmds.c:2326 #, fuzzy, c-format @@ -1456,7 +1454,7 @@ msgstr "%3d %s %s 第 %<PRId64> 行 " #: ../ex_cmds2.c:942 msgid "E750: First use \":profile start {fname}\"" -msgstr "" +msgstr "E750: 請先使用 :profile start <fname>" #: ../ex_cmds2.c:1269 #, fuzzy, c-format @@ -1555,7 +1553,7 @@ msgstr "vim [參數] " #: ../ex_cmds2.c:2771 msgid "environment variable" -msgstr "" +msgstr "環境變數" #: ../ex_cmds2.c:2773 #, fuzzy @@ -2047,7 +2045,7 @@ msgstr "E199: 已刪除掉作用中的視窗或暫存區" #: ../file_search.c:203 msgid "E854: path too long for completion" -msgstr "" +msgstr "E854: 補全用的路徑太長了" #: ../file_search.c:446 #, c-format @@ -2099,11 +2097,11 @@ msgstr "[未命名]" #: ../fileio.c:511 msgid "[New DIRECTORY]" -msgstr "" +msgstr "[新目錄]" #: ../fileio.c:529 ../fileio.c:532 msgid "[File too big]" -msgstr "" +msgstr "[文件太大]" #: ../fileio.c:534 msgid "[Permission Denied]" @@ -2265,7 +2263,7 @@ msgstr "E513: 無法寫入 -- 轉換失敗" msgid "" "E513: write error, conversion failed in line %<PRId64> (make 'fenc' empty to " "override)" -msgstr "" +msgstr "E513: 寫入錯誤,轉換失敗 (請將 'fenc' 置空以強制執行)" #: ../fileio.c:3448 msgid "E514: write error (file system full?)" @@ -2720,7 +2718,7 @@ msgstr "E49: 錯誤的捲動大小" #: ../globals.h:1021 msgid "E901: Job table is full" -msgstr "" +msgstr "E901: 任務表已經滿" #: ../globals.h:1024 #, c-format @@ -2877,7 +2875,7 @@ msgstr "E42: 沒有錯誤" #: ../globals.h:1067 msgid "E776: No location list" -msgstr "" +msgstr "E776: 沒有位置列表" #: ../globals.h:1068 msgid "E43: Damaged match string" @@ -2992,7 +2990,7 @@ msgstr "E473: 內部錯誤" #: ../globals.h:1104 msgid "E363: pattern uses more memory than 'maxmempattern'" -msgstr "" +msgstr "E363: 表達式的內存超出 'maxmempattern'" #: ../globals.h:1105 #, fuzzy @@ -3026,127 +3024,6 @@ msgstr "已搜尋到檔案開頭;再從結尾繼續搜尋" msgid "search hit BOTTOM, continuing at TOP" msgstr "已搜尋到檔案結尾;再從開頭繼續搜尋" -#: ../hardcopy.c:240 -msgid "E550: Missing colon" -msgstr "E550: 缺少 colon" - -#: ../hardcopy.c:252 -msgid "E551: Illegal component" -msgstr "E551: 不正確的模式" - -#: ../hardcopy.c:259 -msgid "E552: digit expected" -msgstr "E552: 應該要有數字" - -#: ../hardcopy.c:473 -#, c-format -msgid "Page %d" -msgstr "第 %d 頁" - -#: ../hardcopy.c:597 -msgid "No text to be printed" -msgstr "沒有要列印的文字" - -#: ../hardcopy.c:668 -#, c-format -msgid "Printing page %d (%d%%)" -msgstr "列印中: 第 %d 頁 (%d%%)" - -#: ../hardcopy.c:680 -#, c-format -msgid " Copy %d of %d" -msgstr "複製 %d / %d" - -#: ../hardcopy.c:733 -#, c-format -msgid "Printed: %s" -msgstr "已列印: %s" - -#: ../hardcopy.c:740 -msgid "Printing aborted" -msgstr "已取消列印" - -#: ../hardcopy.c:1365 -msgid "E455: Error writing to PostScript output file" -msgstr "E455: 無法寫入 PostScript 輸出檔" - -#: ../hardcopy.c:1747 -#, c-format -msgid "E624: Can't open file \"%s\"" -msgstr "E624: 無法開啟檔案 \"%s\"" - -#: ../hardcopy.c:1756 ../hardcopy.c:2470 -#, c-format -msgid "E457: Can't read PostScript resource file \"%s\"" -msgstr "E457: 無法讀取 PostScript 資源檔 \"%s\"" - -#: ../hardcopy.c:1772 -#, c-format -msgid "E618: file \"%s\" is not a PostScript resource file" -msgstr "E618: 檔案 \"%s\" 不是 PostScript 資源檔 " - -#: ../hardcopy.c:1788 ../hardcopy.c:1805 ../hardcopy.c:1844 -#, c-format -msgid "E619: file \"%s\" is not a supported PostScript resource file" -msgstr "E619: 不支援 PostScript 資源檔 \"%s\"" - -#: ../hardcopy.c:1856 -#, c-format -msgid "E621: \"%s\" resource file has wrong version" -msgstr "E621: \"%s\" 資源檔版本錯誤" - -#: ../hardcopy.c:2225 -msgid "E673: Incompatible multi-byte encoding and character set." -msgstr "" - -#: ../hardcopy.c:2238 -msgid "E674: printmbcharset cannot be empty with multi-byte encoding." -msgstr "" - -#: ../hardcopy.c:2254 -msgid "E675: No default font specified for multi-byte printing." -msgstr "" - -#: ../hardcopy.c:2426 -msgid "E324: Can't open PostScript output file" -msgstr "E324: 無法開啟 PostScript 輸出檔" - -#: ../hardcopy.c:2458 -#, c-format -msgid "E456: Can't open file \"%s\"" -msgstr "E456: 無法開啟檔案 \"%s\"" - -#: ../hardcopy.c:2583 -msgid "E456: Can't find PostScript resource file \"prolog.ps\"" -msgstr "E456: 無法讀取 PostScript 資源檔 \"prolog.ps\"" - -#: ../hardcopy.c:2593 -#, fuzzy -msgid "E456: Can't find PostScript resource file \"cidfont.ps\"" -msgstr "E456: 無法讀取 PostScript 資源檔 \"%s.ps\"" - -#: ../hardcopy.c:2622 ../hardcopy.c:2639 ../hardcopy.c:2665 -#, c-format -msgid "E456: Can't find PostScript resource file \"%s.ps\"" -msgstr "E456: 無法讀取 PostScript 資源檔 \"%s.ps\"" - -#: ../hardcopy.c:2654 -#, fuzzy, c-format -msgid "E620: Unable to convert to print encoding \"%s\"" -msgstr "E620:無法轉換至 \"%s\" 字元編碼" - -#: ../hardcopy.c:2877 -msgid "Sending to printer..." -msgstr "傳送資料到印表機..." - -#: ../hardcopy.c:2881 -msgid "E365: Failed to print PostScript file" -msgstr "E365: 無法列印 PostScript 檔案" - -#: ../hardcopy.c:2883 -msgid "Print job sent." -msgstr "已送出列印工作。" - #: ../if_cscope.c:85 msgid "Add a new database" msgstr "新增資料庫" @@ -3267,6 +3144,7 @@ msgstr "%-5s: %-30s (用法: %s)" #: ../if_cscope.c:1155 msgid "" "\n" +" a: Find assignments to this symbol\n" " c: Find functions calling this function\n" " d: Find functions called by this function\n" " e: Find this egrep pattern\n" @@ -3276,6 +3154,16 @@ msgid "" " s: Find this C symbol\n" " t: Find this text string\n" msgstr "" +"\n" +" a: 搜索對此符號的賦值\n" +" c: 搜索調用此函式的函式\n" +" d: 搜索此函式調用的函式\n" +" e: 搜索此 egrep 模式\n" +" f: 搜索此文件\n" +" g: 搜索此定義\n" +" i: 搜索包含此文件的文件\n" +" s: 搜索此 C 符号\n" +" t: 搜索此文本字串\n" #: ../if_cscope.c:1226 msgid "E568: duplicate cscope database not added" @@ -3509,7 +3397,7 @@ msgstr "-N\t\t\t'nocompatible' 不完全與傳統 Vi 相容,可使用 Vim 加 #: ../main.c:2215 msgid "-V[N][fname]\t\tBe verbose [level N] [log messages to fname]" -msgstr "" +msgstr "-V[N][fname]\t\t詳細 [level N] [log messages to fname]" #: ../main.c:2216 msgid "-D\t\t\tDebugging mode" @@ -3602,7 +3490,7 @@ msgstr "-W <scriptout>\t對檔案 <scriptout> 寫入所有輸入的命令" #: ../main.c:2240 msgid "--startuptime <file>\tWrite startup timing messages to <file>" -msgstr "" +msgstr "--startuptime <file>\t將啟動時間寫入到文件 <file>" #: ../main.c:2242 msgid "-i <viminfo>\t\tUse <viminfo> instead of .viminfo" @@ -3794,7 +3682,7 @@ msgstr "" #: ../memline.c:945 msgid " has been damaged (page size is smaller than minimum value).\n" -msgstr "" +msgstr "已损坏(頁面大小小於最小值)。\n" #: ../memline.c:974 #, c-format @@ -4082,7 +3970,7 @@ msgstr "E317: 指標區塊 id 錯 2" #: ../memline.c:3070 #, c-format msgid "E773: Symlink loop for \"%s\"" -msgstr "" +msgstr "E773: \"%s\" 符號鏈接出現循環" #: ../memline.c:3221 msgid "E325: ATTENTION" @@ -4231,7 +4119,7 @@ msgstr "E329: 沒有那樣的選單" #. Only a mnemonic or accelerator is not valid. #: ../menu.c:329 msgid "E792: Empty menu name" -msgstr "" +msgstr "E792: 空的菜單名稱" #: ../menu.c:340 msgid "E330: Menu path must not lead to a sub-menu" @@ -4312,7 +4200,7 @@ msgstr "-- 尚有 --" #: ../message.c:2398 msgid " SPACE/d/j: screen/page/line down, b/u/k: up, q: quit " -msgstr "" +msgstr " 空格/d/j: 屏幕/頁/行 下翻,b/u/k: 上翻,q: 退出 " #: ../message.c:3021 ../message.c:3031 msgid "Question" @@ -4357,7 +4245,7 @@ msgstr "E116: 函式 %s 的引數不正確" #: ../message.c:3119 msgid "E807: Expected Float argument for printf()" -msgstr "" +msgstr "E807: 期盼浮點數作為printf()參數" #: ../message.c:3873 #, fuzzy @@ -4370,11 +4258,11 @@ msgstr "W10: 注意: 你正在修改一個唯讀檔" #: ../misc1.c:2537 msgid "Type number and <Enter> or click with mouse (empty cancels): " -msgstr "" +msgstr "請輸入數字並<Enter>或點擊鼠標(空白取消): " #: ../misc1.c:2539 msgid "Type number and <Enter> (empty cancels): " -msgstr "" +msgstr "請選擇數字並(<Enter> 取消): " #: ../misc1.c:2585 msgid "1 more line" @@ -4400,7 +4288,7 @@ msgstr " (已中斷)" #: ../misc1.c:2635 msgid "Beep!" -msgstr "" +msgstr "Beep!" #: ../misc2.c:738 #, c-format @@ -4617,7 +4505,7 @@ msgstr "E520: 不能在 Modeline 裡出現" #: ../option.c:2815 msgid "E846: Key code not set" -msgstr "" +msgstr "E846: 未設置鍵位代碼" #: ../option.c:2924 msgid "E521: Number required after =" @@ -4642,11 +4530,11 @@ msgstr "E589: 'backupext' 跟 'patchmode' 是一樣的" #: ../option.c:3964 msgid "E834: Conflicts with value of 'listchars'" -msgstr "" +msgstr "E834: 與'listchars'中的值發生衝突" #: ../option.c:3966 msgid "E835: Conflicts with value of 'fillchars'" -msgstr "" +msgstr "E835: 與'fillchars'中的值發生衝突" #: ../option.c:4163 msgid "E524: Missing colon" @@ -4884,7 +4772,7 @@ msgstr "E382: 無法寫入,'buftype' 選項已設定" #: ../quickfix.c:2812 msgid "E683: File name missing or invalid pattern" -msgstr "" +msgstr "E683: 缺少文件名或模式無效" #: ../quickfix.c:2911 #, fuzzy, c-format @@ -5022,25 +4910,26 @@ msgid "" "E864: \\%#= can only be followed by 0, 1, or 2. The automatic engine will be " "used " msgstr "" +"E864: \\%#= 後面只能是0,1,或者2。自動引擎將會被使用" #: ../regexp_nfa.c:239 msgid "E865: (NFA) Regexp end encountered prematurely" -msgstr "" +msgstr "E865: (NFA) 過早的遇到了正則表達式的結尾" #: ../regexp_nfa.c:240 #, c-format msgid "E866: (NFA regexp) Misplaced %c" -msgstr "" +msgstr "E866: (NFA regexp) %c 放錯了位置" #: ../regexp_nfa.c:242 #, c-format msgid "E877: (NFA regexp) Invalid character class: %<PRId64>" -msgstr "" +msgstr "E877: (NFA regexp) 不可用的字元類: %<PRId64>" #: ../regexp_nfa.c:1261 #, c-format msgid "E867: (NFA) Unknown operator '\\z%c'" -msgstr "" +msgstr "E867: (NFA) 未知的操作符 '\\z%c'" #: ../regexp_nfa.c:1387 #, c-format @@ -5050,21 +4939,21 @@ msgstr "" #: ../regexp_nfa.c:1802 #, c-format msgid "E869: (NFA) Unknown operator '\\@%c'" -msgstr "" +msgstr "E869: (NFA) 未知的操作符 '\\%%%c'" #: ../regexp_nfa.c:1831 msgid "E870: (NFA regexp) Error reading repetition limits" -msgstr "" +msgstr "E870: (NFA regexp) 读取重复限制时出错" #. Can't have a multi follow a multi. #: ../regexp_nfa.c:1895 msgid "E871: (NFA regexp) Can't have a multi follow a multi !" -msgstr "" +msgstr "E871: (NFA regexp) 不能多个跟多个!" #. Too many `(' #: ../regexp_nfa.c:2037 msgid "E872: (NFA regexp) Too many '('" -msgstr "" +msgstr "E872: (NFA regexp) 太多 '('" #: ../regexp_nfa.c:2042 #, fuzzy @@ -5073,31 +4962,32 @@ msgstr "E50: 太多 \\z(" #: ../regexp_nfa.c:2066 msgid "E873: (NFA regexp) proper termination error" -msgstr "" +msgstr "E873: (NFA regexp) 未適當終止" #: ../regexp_nfa.c:2599 msgid "E874: (NFA) Could not pop the stack !" -msgstr "" +msgstr "E874: (NFA) 無法出棧!" #: ../regexp_nfa.c:3298 msgid "" "E875: (NFA regexp) (While converting from postfix to NFA), too many states " "left on stack" -msgstr "" +msgstr "E875: (NFA regexp) (從後綴轉到 NFA 时),棧上遺留了太多狀態" #: ../regexp_nfa.c:3302 msgid "E876: (NFA regexp) Not enough space to store the whole NFA " -msgstr "" +msgstr "E876: (NFA regexp) 沒有足夠的空間存儲NFA " #: ../regexp_nfa.c:4571 ../regexp_nfa.c:4869 msgid "" "Could not open temporary log file for writing, displaying on stderr ... " msgstr "" +"無法打開臨時日志文件進行寫入,顯示在stderr中..." #: ../regexp_nfa.c:4840 #, c-format msgid "(NFA) COULD NOT OPEN %s !" -msgstr "" +msgstr "(NFA) 不能打开 %s !" #: ../regexp_nfa.c:6049 #, fuzzy @@ -5270,17 +5160,17 @@ msgstr "E297: 暫存檔寫入錯誤" #: ../spell.c:952 msgid "E758: Truncated spell file" -msgstr "" +msgstr "E758: 已截斷的拼寫文件" #: ../spell.c:953 #, c-format msgid "Trailing text in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,多餘的後續文本: %s" #: ../spell.c:954 #, c-format msgid "Affix name too long in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,附加項名字太長: %s" #: ../spell.c:955 #, fuzzy @@ -5289,20 +5179,20 @@ msgstr "E431: Tag 檔 \"%s\" 格式錯誤" #: ../spell.c:957 msgid "E762: Character in FOL, LOW or UPP is out of range" -msgstr "" +msgstr "E762: FOL、LOW 或 UPP 中字元超出範圍" #: ../spell.c:958 msgid "Compressing word tree..." -msgstr "" +msgstr "壓縮單詞樹……" #: ../spell.c:1951 msgid "E756: Spell checking is not enabled" -msgstr "" +msgstr "E756: 拼寫檢查未啟用" #: ../spell.c:2249 #, c-format msgid "Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\"" -msgstr "" +msgstr "警告: 找不到單詞列表 \"%s.%s.spl\" or \"%s.ascii.spl\"" #: ../spell.c:2473 #, fuzzy, c-format @@ -5316,11 +5206,11 @@ msgstr "E307: %s 看起來不像是 Vim 暫存檔" #: ../spell.c:2501 msgid "E771: Old spell file, needs to be updated" -msgstr "" +msgstr "E771: 舊的拼寫文件,需要更新" #: ../spell.c:2504 msgid "E772: Spell file is for newer version of Vim" -msgstr "" +msgstr "E772: 為更高版本的 Vim 所使用的拼寫文件" #: ../spell.c:2602 #, fuzzy @@ -5340,66 +5230,68 @@ msgstr "搜尋 tag 檔案 \"%s\"" #: ../spell.c:4589 ../spell.c:5635 ../spell.c:6140 #, c-format msgid "Conversion failure for word in %s line %d: %s" -msgstr "" +msgstr "單詞 %s 轉換失敗,第 %d 行: %s" #: ../spell.c:4630 ../spell.c:6170 #, c-format msgid "Conversion in %s not supported: from %s to %s" -msgstr "" +msgstr "不支持 %s 中的轉換: 从 %s 到 %s" #: ../spell.c:4642 #, c-format msgid "Invalid value for FLAG in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,FLAG 的值无效: %s" #: ../spell.c:4655 #, c-format msgid "FLAG after using flags in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,在使用標志後出現 FLAG: %s" #: ../spell.c:4723 #, c-format msgid "" "Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line " "%d" -msgstr "" +msgstr "在 PFX 項之後定義 COMPOUNDFORBIDFLAG (%s 第%d行)可能會給出的錯誤結果" +"%d" #: ../spell.c:4731 #, c-format msgid "" "Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line " "%d" -msgstr "" +msgstr "在 PFX 項之後定義 COMPOUNDFORBIDFLAG (%s 第%d行)可能會給出的錯誤結果" +"%d" #: ../spell.c:4747 #, c-format msgid "Wrong COMPOUNDRULES value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 COMPOUNDMIN 值: %s" #: ../spell.c:4771 #, c-format msgid "Wrong COMPOUNDWORDMAX value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 COMPOUNDWORDMAX 值: %s" #: ../spell.c:4777 #, c-format msgid "Wrong COMPOUNDMIN value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 COMPOUNDMIN 值: %s" #: ../spell.c:4783 #, c-format msgid "Wrong COMPOUNDSYLMAX value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 COMPOUNDSYLMAX 值: %s" #: ../spell.c:4795 #, c-format msgid "Wrong CHECKCOMPOUNDPATTERN value in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,错误的 CHECKCOMPOUNDPATTERN 值: %s" #: ../spell.c:4847 #, c-format msgid "Different combining flag in continued affix block in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,在連續的附加塊種出現不同的組合標誌: %s" #: ../spell.c:4850 #, fuzzy, c-format @@ -5412,45 +5304,47 @@ msgid "" "Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in %s " "line %d: %s" msgstr "" +"%s 第 %d 行,附加項被 BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST 使" +"用: %s" #: ../spell.c:4893 #, c-format msgid "Expected Y or N in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,此處需要 Y 或 N: %s" #: ../spell.c:4968 #, c-format msgid "Broken condition in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,錯誤的條件: %s" #: ../spell.c:5091 #, c-format msgid "Expected REP(SAL) count in %s line %d" -msgstr "" +msgstr "%s 第 %d 行,此處需要 REP(SAL) 計數" #: ../spell.c:5120 #, c-format msgid "Expected MAP count in %s line %d" -msgstr "" +msgstr "%s 第 %d 行,此處需要 MAP 計數" #: ../spell.c:5132 #, c-format msgid "Duplicate character in MAP in %s line %d" -msgstr "" +msgstr "%s 第 %d 行,MAP 中存在重複的字元" #: ../spell.c:5176 #, c-format msgid "Unrecognized or duplicate item in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,無法識別或重複的項: %s" #: ../spell.c:5197 #, c-format msgid "Missing FOL/LOW/UPP line in %s" -msgstr "" +msgstr "%s 中缺少 FOL/LOW/UPP 行" #: ../spell.c:5220 msgid "COMPOUNDSYLMAX used without SYLLABLE" -msgstr "" +msgstr "在没有 SYLLABLE 的情況下使用了 COMPOUNDSYLMAX" #: ../spell.c:5236 #, fuzzy @@ -5464,32 +5358,32 @@ msgstr "太多編輯參數" #: ../spell.c:5240 msgid "Too many postponed prefixes and/or compound flags" -msgstr "" +msgstr "太多延遲前綴和/或組合標誌" #: ../spell.c:5250 #, c-format msgid "Missing SOFO%s line in %s" -msgstr "" +msgstr "%s 中缺少 SOFO%s 行" #: ../spell.c:5253 #, c-format msgid "Both SAL and SOFO lines in %s" -msgstr "" +msgstr "%s 同時出現 SAL 和 SOFO 行" #: ../spell.c:5331 #, c-format msgid "Flag is not a number in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,標誌不是數字: %s" #: ../spell.c:5334 #, c-format msgid "Illegal flag in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,無效的標誌: %s" #: ../spell.c:5493 ../spell.c:5501 #, c-format msgid "%s value differs from what is used in another .aff file" -msgstr "" +msgstr "%s 的值與另一個 .aff 文件中使用的值不相同" #: ../spell.c:5602 #, fuzzy, c-format @@ -5499,12 +5393,12 @@ msgstr "掃瞄字典: %s" #: ../spell.c:5611 #, c-format msgid "E760: No word count in %s" -msgstr "" +msgstr "E760: %s 中没有單詞計數" #: ../spell.c:5669 #, c-format msgid "line %6d, word %6d - %s" -msgstr "" +msgstr "第 %6d 行,第 %6d 个單詞 - %s" #: ../spell.c:5691 #, fuzzy, c-format @@ -5514,17 +5408,17 @@ msgstr "每一行都找不到: %s" #: ../spell.c:5694 #, c-format msgid "First duplicate word in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,首次出現重複的單詞: %s" #: ../spell.c:5746 #, c-format msgid "%d duplicate word(s) in %s" -msgstr "" +msgstr "存在 %d 个重複的單詞,在 %s 中" #: ../spell.c:5748 #, c-format msgid "Ignored %d word(s) with non-ASCII characters in %s" -msgstr "" +msgstr "忽略了含有非 ASCII 字元的 %d 个單詞,在 %s 中" #: ../spell.c:6115 #, fuzzy, c-format @@ -5534,42 +5428,42 @@ msgstr "從標準輸入讀取..." #: ../spell.c:6155 #, c-format msgid "Duplicate /encoding= line ignored in %s line %d: %s" -msgstr "" +msgstr "%s 第 %ld 行,重复的 /encoding= 行已被忽略: %s" #: ../spell.c:6159 #, c-format msgid "/encoding= line after word ignored in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,单词后的 /encoding= 行已被忽略: %s" #: ../spell.c:6180 #, c-format msgid "Duplicate /regions= line ignored in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,重复的 /regions= 行已被忽略: %s" #: ../spell.c:6185 #, c-format msgid "Too many regions in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,太多區域: %s" #: ../spell.c:6198 #, c-format msgid "/ line ignored in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,/ 行已被忽略: %s" #: ../spell.c:6224 #, fuzzy, c-format msgid "Invalid region nr in %s line %d: %s" -msgstr "E573: 不正確的伺服器 id : %s" +msgstr "%s 第 %d 行,無效的區域號: %s" #: ../spell.c:6230 #, c-format msgid "Unrecognized flags in %s line %d: %s" -msgstr "" +msgstr "%s 第 %d 行,不可識別的標誌: %s" #: ../spell.c:6257 #, c-format msgid "Ignored %d words with non-ASCII characters" -msgstr "" +msgstr "忽略了含有非 ASCII 字元的 %d 个單詞" #: ../spell.c:6656 #, c-format @@ -5578,23 +5472,23 @@ msgstr "" #: ../spell.c:7340 msgid "Reading back spell file..." -msgstr "" +msgstr "读取拼寫文件……" #. Go through the trie of good words, soundfold each word and add it to #. the soundfold trie. #: ../spell.c:7357 msgid "Performing soundfolding..." -msgstr "" +msgstr "正在 soundfolding……" #: ../spell.c:7368 #, c-format msgid "Number of words after soundfolding: %<PRId64>" -msgstr "" +msgstr "soundfolding 后的單詞数: %<PRId64>" #: ../spell.c:7476 #, c-format msgid "Total number of words: %d" -msgstr "" +msgstr "單詞总数: %d" #: ../spell.c:7655 #, fuzzy, c-format @@ -5604,11 +5498,11 @@ msgstr "寫入 viminfo 檔案 \"%s\" 中" #: ../spell.c:7707 ../spell.c:7927 #, c-format msgid "Estimated runtime memory use: %d bytes" -msgstr "" +msgstr "估計運行時的內存用量: %d 位元" #: ../spell.c:7820 msgid "E751: Output file name must not have region name" -msgstr "" +msgstr "E751: 輸出文件不能含有區域名" #: ../spell.c:7822 #, fuzzy @@ -5622,7 +5516,7 @@ msgstr "E15: 不正確的運算式: %s" #: ../spell.c:7907 msgid "Warning: both compounding and NOBREAK specified" -msgstr "" +msgstr "警告: 同時指定了 compounding 和 NOBREAK" #: ../spell.c:7920 #, fuzzy, c-format @@ -5631,30 +5525,30 @@ msgstr "寫入 viminfo 檔案 \"%s\" 中" #: ../spell.c:7925 msgid "Done!" -msgstr "" +msgstr "完成!" #: ../spell.c:8034 #, c-format msgid "E765: 'spellfile' does not have %<PRId64> entries" -msgstr "" +msgstr "E765: 'spellfile' 没有 %<PRId64> 項" #: ../spell.c:8074 #, c-format msgid "Word '%.*s' removed from %s" -msgstr "" +msgstr "从 %s 中删除了單詞" #: ../spell.c:8117 #, c-format msgid "Word '%.*s' added to %s" -msgstr "" +msgstr "向 %s 中添加了單詞" #: ../spell.c:8381 msgid "E763: Word characters differ between spell files" -msgstr "" +msgstr "E763: 拼寫文件之間的字元不相同" #: ../spell.c:8684 msgid "Sorry, no suggestions" -msgstr "" +msgstr "抱歉,没有建议" #: ../spell.c:8687 #, fuzzy, c-format @@ -5671,7 +5565,7 @@ msgstr "將變動存儲至 \"%.*s\"?" #: ../spell.c:8737 #, c-format msgid " < \"%.*s\"" -msgstr "" +msgstr " < \"%.*s\"" #: ../spell.c:8882 #, fuzzy @@ -5691,28 +5585,28 @@ msgstr "E307: %s 看起來不像是 Vim 暫存檔" #: ../spell.c:9282 #, c-format msgid "E779: Old .sug file, needs to be updated: %s" -msgstr "" +msgstr "E779: 舊的.sug 文件,需要更新: %s" #: ../spell.c:9286 #, c-format msgid "E780: .sug file is for newer version of Vim: %s" -msgstr "" +msgstr "E780: .sug 文件適用於較新的 Vim 版本: %s" #: ../spell.c:9295 #, c-format msgid "E781: .sug file doesn't match .spl file: %s" -msgstr "" +msgstr "E781: .sug 文件不能匹配 .spl 文件: %s" #: ../spell.c:9305 #, fuzzy, c-format msgid "E782: error while reading .sug file: %s" -msgstr "E47: 讀取錯誤檔案失敗" +msgstr "E782: 當讀取.sug 文件時錯誤" #. This should have been checked when generating the .spl #. file. #: ../spell.c:11575 msgid "E783: duplicate char in MAP entry" -msgstr "" +msgstr "E783: MAP 條目中有重複的字元" #: ../syntax.c:266 msgid "No Syntax items defined for this buffer" @@ -5894,10 +5788,11 @@ msgstr "E410: 不正確的 :syntax 子命令: %s" msgid "" " TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN" msgstr "" +" 總 計 計 數 匹 配 最 慢 的 平 均 名 字 模 式" #: ../syntax.c:6146 msgid "E679: recursive loop loading syncolor.vim" -msgstr "" +msgstr "E679: 加載 syncolor.vim 时出現嵌套循環" #: ../syntax.c:6256 #, c-format @@ -5969,13 +5864,13 @@ msgstr "E424: 使用了過多相異的高亮度屬性" msgid "E669: Unprintable character in group name" msgstr "E669: 群組名稱中有無法列印的字元" -#: ../syntax.c:7434 -msgid "W18: Invalid character in group name" -msgstr "W18: 群組名稱中有不正確的字元" +#: ../highlight_group.c:1756 +msgid "E5248: Invalid character in group name" +msgstr "E5248: 群組名稱中有不正確的字元" #: ../syntax.c:7448 msgid "E849: Too many highlight and syntax groups" -msgstr "" +msgstr "E849: 高亮和語法組過多" #: ../tag.c:104 msgid "E555: at bottom of tag stack" @@ -6083,7 +5978,7 @@ msgstr "E435: 找不到 tag, 用猜的!" #: ../tag.c:2797 #, c-format msgid "Duplicate field name: %s" -msgstr "" +msgstr "重複的字段名: %s" #: ../term.c:1442 msgid "' not known. Available builtin terminals are:" @@ -6131,35 +6026,35 @@ msgstr "Vim: 讀取輸入錯誤,離開中...\n" #. * file in a way it becomes shorter. #: ../undo.c:379 msgid "E881: Line count changed unexpectedly" -msgstr "" +msgstr "E881: 行數意外地改變了" #: ../undo.c:627 -#, fuzzy, c-format +#, c-format msgid "E828: Cannot open undo file for writing: %s" -msgstr "E212: 無法以寫入模式開啟" +msgstr "E828: 無法打開撤銷文件去寫入" #: ../undo.c:717 #, c-format msgid "E825: Corrupted undo file (%s): %s" -msgstr "" +msgstr "E825: 已損壞的撤銷文件 (%s): %s" #: ../undo.c:1039 msgid "Cannot write undo file in any directory in 'undodir'" -msgstr "" +msgstr "不能寫入撤銷文件到 'undodir' 中的任何文件夾" #: ../undo.c:1074 #, c-format msgid "Will not overwrite with undo file, cannot read: %s" -msgstr "" +msgstr "不能寫入撤銷文件,不可讀取: %s" #: ../undo.c:1092 #, c-format msgid "Will not overwrite, this is not an undo file: %s" -msgstr "" +msgstr "不會覆蓋,這不是撤銷文件: %s" #: ../undo.c:1108 msgid "Skipping undo file write, nothing to undo" -msgstr "" +msgstr "跳過撤消文件寫入,沒有可撤消的內容" #: ../undo.c:1121 #, fuzzy, c-format @@ -6174,7 +6069,7 @@ msgstr "E297: 暫存檔寫入錯誤" #: ../undo.c:1280 #, c-format msgid "Not reading undo file, owner differs: %s" -msgstr "" +msgstr "不能讀取撤銷文件,擁有者不同: %s" #: ../undo.c:1292 #, fuzzy, c-format @@ -6198,7 +6093,7 @@ msgstr "E484: 無法開啟檔案 %s" #: ../undo.c:1328 msgid "File contents changed, cannot use undo info" -msgstr "" +msgstr "文件內容已經改變,不能使用撤銷信息" #: ../undo.c:1497 #, fuzzy, c-format @@ -6207,11 +6102,11 @@ msgstr "結束執行 %s" #: ../undo.c:1586 ../undo.c:1812 msgid "Already at oldest change" -msgstr "" +msgstr "已經在最早的改變" #: ../undo.c:1597 ../undo.c:1814 msgid "Already at newest change" -msgstr "" +msgstr "已經在最新的改變" #: ../undo.c:1806 #, fuzzy, c-format @@ -6259,11 +6154,11 @@ msgstr "%<PRId64> 行 %s 過 %d 次" #: ../undo.c:2228 msgid "before" -msgstr "" +msgstr "之前" #: ../undo.c:2228 msgid "after" -msgstr "" +msgstr "之後" #: ../undo.c:2325 #, fuzzy @@ -6272,7 +6167,7 @@ msgstr "沒有這個 mapping 對應" #: ../undo.c:2330 msgid "number changes when saved" -msgstr "" +msgstr " 編號 改變 時間 保存" #: ../undo.c:2360 #, fuzzy, c-format diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index a4afe97ac8..245ce87865 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -6,27 +6,35 @@ /// Popup menu (PUM) #include <assert.h> -#include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <string.h> +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds.h" +#include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/insexpand.h" +#include "nvim/keycodes.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/menu.h" +#include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/popupmenu.h" -#include "nvim/search.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" @@ -70,19 +78,19 @@ static void pum_compute_size(void) for (int i = 0; i < pum_size; i++) { int w; if (pum_array[i].pum_text != NULL) { - w = vim_strsize((char *)pum_array[i].pum_text); + w = vim_strsize(pum_array[i].pum_text); if (pum_base_width < w) { pum_base_width = w; } } if (pum_array[i].pum_kind != NULL) { - w = vim_strsize((char *)pum_array[i].pum_kind) + 1; + w = vim_strsize(pum_array[i].pum_kind) + 1; if (pum_kind_width < w) { pum_kind_width = w; } } if (pum_array[i].pum_extra != NULL) { - w = vim_strsize((char *)pum_array[i].pum_extra) + 1; + w = vim_strsize(pum_array[i].pum_extra) + 1; if (pum_extra_width < w) { pum_extra_width = w; } @@ -156,19 +164,18 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (pum_external) { if (array_changed) { Arena arena = ARENA_EMPTY; - arena_start(&arena, &ui_ext_fixblk); Array arr = arena_array(&arena, (size_t)size); for (int i = 0; i < size; i++) { Array item = arena_array(&arena, 4); - ADD_C(item, STRING_OBJ(cstr_as_string((char *)array[i].pum_text))); - ADD_C(item, STRING_OBJ(cstr_as_string((char *)array[i].pum_kind))); - ADD_C(item, STRING_OBJ(cstr_as_string((char *)array[i].pum_extra))); - ADD_C(item, STRING_OBJ(cstr_as_string((char *)array[i].pum_info))); + ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_text))); + ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_kind))); + ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_extra))); + ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_info))); ADD_C(arr, ARRAY_OBJ(item)); } ui_call_popupmenu_show(arr, selected, pum_win_row, cursor_col, pum_anchor_grid); - arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); + arena_mem_free(arena_finish(&arena)); } else { ui_call_popupmenu_select(selected); return; @@ -211,11 +218,16 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i // pum above "pum_win_row" pum_above = true; - // Leave two lines of context if possible - if (curwin->w_wrow - curwin->w_cline_row >= 2) { - context_lines = 2; + if (State == MODE_CMDLINE) { + // for cmdline pum, no need for context lines + context_lines = 0; } else { - context_lines = curwin->w_wrow - curwin->w_cline_row; + // Leave two lines of context if possible + if (curwin->w_wrow - curwin->w_cline_row >= 2) { + context_lines = 2; + } else { + context_lines = curwin->w_wrow - curwin->w_cline_row; + } } if (pum_win_row >= size + context_lines) { @@ -234,13 +246,17 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i // pum below "pum_win_row" pum_above = false; - // Leave two lines of context if possible - validate_cheight(); - if (curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow >= 3) { - context_lines = 3; + if (State == MODE_CMDLINE) { + // for cmdline pum, no need for context lines + context_lines = 0; } else { - context_lines = curwin->w_cline_row - + curwin->w_cline_height - curwin->w_wrow; + // Leave two lines of context if possible + validate_cheight(); + if (curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow >= 3) { + context_lines = 3; + } else { + context_lines = curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow; + } } pum_row = pum_win_row + context_lines; @@ -265,12 +281,15 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i pum_row = above_row; pum_height = pum_win_row - above_row; } + + pum_array = array; + // Set "pum_size" before returning so that pum_set_event_info() gets the correct size. + pum_size = size; + if (pum_external) { return; } - pum_array = array; - pum_size = size; pum_compute_size(); int max_width = pum_base_width; @@ -398,8 +417,8 @@ void pum_redraw(void) int attr; int i; int idx; - char_u *s; - char_u *p = NULL; + char *s; + char *p = NULL; int totwidth, width, w; int thumb_pos = 0; int thumb_height = 1; @@ -508,24 +527,24 @@ void pum_redraw(void) if (s == NULL) { s = p; } - w = ptr2cells((char *)p); + w = ptr2cells(p); if ((*p == NUL) || (*p == TAB) || (totwidth + w > pum_width)) { // Display the text that fits or comes before a Tab. // First convert it to printable characters. - char_u *st; - char_u saved = *p; + char *st; + char saved = *p; if (saved != NUL) { *p = NUL; } - st = (char_u *)transstr((const char *)s, true); + st = transstr(s, true); if (saved != NUL) { *p = saved; } if (pum_rl) { - char *rt = (char *)reverse_text(st); + char *rt = reverse_text(st); char *rt_start = rt; int size = vim_strsize(rt); @@ -543,8 +562,7 @@ void pum_redraw(void) size++; } } - grid_puts_len(&pum_grid, (char_u *)rt, (int)STRLEN(rt), row, - grid_col - size + 1, attr); + grid_puts_len(&pum_grid, rt, (int)strlen(rt), row, grid_col - size + 1, attr); xfree(rt_start); xfree(st); grid_col -= width; @@ -561,11 +579,11 @@ void pum_redraw(void) // Display two spaces for a Tab. if (pum_rl) { - grid_puts_len(&pum_grid, (char_u *)" ", 2, row, grid_col - 1, + grid_puts_len(&pum_grid, " ", 2, row, grid_col - 1, attr); grid_col -= 2; } else { - grid_puts_len(&pum_grid, (char_u *)" ", 2, row, grid_col, attr); + grid_puts_len(&pum_grid, " ", 2, row, grid_col, attr); grid_col += 2; } totwidth += 2; @@ -704,7 +722,7 @@ static bool pum_set_selected(int n, int repeat) if ((pum_array[pum_selected].pum_info != NULL) && (Rows > 10) && (repeat <= 1) - && (vim_strchr((char *)p_cot, 'p') != NULL)) { + && (vim_strchr(p_cot, 'p') != NULL)) { win_T *curwin_save = curwin; tabpage_T *curtab_save = curtab; int res = OK; @@ -744,29 +762,28 @@ static bool pum_set_selected(int n, int repeat) if (res == OK) { // Edit a new, empty buffer. Set options for a "wipeout" // buffer. - set_option_value("swf", 0L, NULL, OPT_LOCAL); - set_option_value("bl", 0L, NULL, OPT_LOCAL); - set_option_value("bt", 0L, "nofile", OPT_LOCAL); - set_option_value("bh", 0L, "wipe", OPT_LOCAL); - set_option_value("diff", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("bl", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("bt", 0L, "nofile", OPT_LOCAL); + set_option_value_give_err("bh", 0L, "wipe", OPT_LOCAL); + set_option_value_give_err("diff", 0L, NULL, OPT_LOCAL); } } if (res == OK) { - char_u *p, *e; + char *p, *e; linenr_T lnum = 0; for (p = pum_array[pum_selected].pum_info; *p != NUL;) { - e = (char_u *)vim_strchr((char *)p, '\n'); + e = vim_strchr(p, '\n'); if (e == NULL) { - ml_append(lnum++, (char *)p, 0, false); + ml_append(lnum++, p, 0, false); break; - } else { - *e = NUL; - ml_append(lnum++, (char *)p, (int)(e - p + 1), false); - *e = '\n'; - p = e + 1; } + *e = NUL; + ml_append(lnum++, p, (int)(e - p + 1), false); + *e = '\n'; + p = e + 1; } // Increase the height of the preview window to show the @@ -802,12 +819,12 @@ static bool pum_set_selected(int n, int repeat) // Return cursor to where we were validate_cursor(); - redraw_later(curwin, SOME_VALID); + redraw_later(curwin, UPD_SOME_VALID); // When the preview window was resized we need to // update the view on the buffer. Only go back to // the window when needed, otherwise it will always be - // redraw. + // redrawn. if (resized) { no_u_sync++; win_enter(curwin_save, true); @@ -820,7 +837,7 @@ static bool pum_set_selected(int n, int repeat) // TODO(bfredl): can simplify, get rid of the flag munging? // or at least eliminate extra redraw before win_enter()? pum_is_visible = false; - update_screen(0); + update_screen(); pum_is_visible = true; if (!resized && win_valid(curwin_save)) { @@ -832,7 +849,7 @@ static bool pum_set_selected(int n, int repeat) // May need to update the screen again when there are // autocommands involved. pum_is_visible = false; - update_screen(0); + update_screen(); pum_is_visible = true; } } @@ -870,6 +887,7 @@ void pum_check_clear(void) grid_free(&pum_grid); } pum_is_drawn = false; + pum_external = false; } } @@ -903,6 +921,17 @@ void pum_recompose(void) ui_comp_compose_grid(&pum_grid); } +void pum_ext_select_item(int item, bool insert, bool finish) +{ + if (!pum_visible() || item < -1 || item >= pum_size) { + return; + } + pum_want.active = true; + pum_want.item = item; + pum_want.insert = insert; + pum_want.finish = finish; +} + /// Gets the height of the menu. /// /// @return the height of the popup menu, the number of entries visible. @@ -1032,10 +1061,16 @@ void pum_show_popupmenu(vimmenu_T *menu) pumitem_T *array = (pumitem_T *)xcalloc((size_t)pum_size, sizeof(pumitem_T)); for (vimmenu_T *mp = menu->children; mp != NULL; mp = mp->next) { + char *s = NULL; + // Make a copy of the text, the menu may be redefined in a callback. if (menu_is_separator(mp->dname)) { - array[idx++].pum_text = (char_u *)""; + s = ""; } else if (mp->modes & mp->enabled & mode) { - array[idx++].pum_text = (char_u *)mp->dname; + s = mp->dname; + } + if (s != NULL) { + s = xstrdup(s); + array[idx++].pum_text = s; } } @@ -1047,6 +1082,10 @@ void pum_show_popupmenu(vimmenu_T *menu) pum_selected = -1; pum_first = 0; + if (!p_mousemev) { + // Pretend 'mousemoveevent' is set. + ui_call_option_set(STATIC_CSTR_AS_STRING("mousemoveevent"), BOOLEAN_OBJ(true)); + } for (;;) { pum_is_visible = true; @@ -1102,8 +1141,14 @@ void pum_show_popupmenu(vimmenu_T *menu) } } + for (idx = 0; idx < pum_size; idx++) { + xfree(array[idx].pum_text); + } xfree(array); pum_undisplay(true); + if (!p_mousemev) { + ui_call_option_set(STATIC_CSTR_AS_STRING("mousemoveevent"), BOOLEAN_OBJ(false)); + } } void pum_make_popup(const char *path_name, int use_mouse_pos) diff --git a/src/nvim/popupmenu.h b/src/nvim/popupmenu.h index 851ad31486..08b791c509 100644 --- a/src/nvim/popupmenu.h +++ b/src/nvim/popupmenu.h @@ -1,6 +1,8 @@ #ifndef NVIM_POPUPMENU_H #define NVIM_POPUPMENU_H +#include <stdbool.h> + #include "nvim/grid_defs.h" #include "nvim/macros.h" #include "nvim/types.h" @@ -8,14 +10,22 @@ /// Used for popup menu items. typedef struct { - char_u *pum_text; // main menu text - char_u *pum_kind; // extra kind text (may be truncated) - char_u *pum_extra; // extra menu text (may be truncated) - char_u *pum_info; // extra info + char *pum_text; // main menu text + char *pum_kind; // extra kind text (may be truncated) + char *pum_extra; // extra menu text (may be truncated) + char *pum_info; // extra info } pumitem_T; EXTERN ScreenGrid pum_grid INIT(= SCREEN_GRID_INIT); +/// state for pum_ext_select_item. +EXTERN struct { + bool active; + int item; + bool insert; + bool finish; +} pum_want; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "popupmenu.h.generated.h" #endif diff --git a/src/nvim/profile.c b/src/nvim/profile.c index d4f3756f4d..fd024f2d38 100644 --- a/src/nvim/profile.c +++ b/src/nvim/profile.c @@ -3,20 +3,34 @@ #include <assert.h> #include <math.h> +#include <stdbool.h> +#include <stdint.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> -#include "nvim/assert.h" +#include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/debugger.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/fileio.h" -#include "nvim/func_attr.h" -#include "nvim/globals.h" // for the global `time_fd` (startuptime) +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" +#include "nvim/keycodes.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/os/os.h" #include "nvim/os/time.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/runtime.h" +#include "nvim/types.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -279,35 +293,35 @@ void ex_profile(exarg_T *eap) char *e; int len; - e = (char *)skiptowhite((char_u *)eap->arg); + e = skiptowhite(eap->arg); len = (int)(e - eap->arg); e = skipwhite(e); - if (len == 5 && STRNCMP(eap->arg, "start", 5) == 0 && *e != NUL) { + if (len == 5 && strncmp(eap->arg, "start", 5) == 0 && *e != NUL) { xfree(profile_fname); - profile_fname = (char *)expand_env_save_opt((char_u *)e, true); + profile_fname = expand_env_save_opt(e, true); do_profiling = PROF_YES; profile_set_wait(profile_zero()); set_vim_var_nr(VV_PROFILING, 1L); } else if (do_profiling == PROF_NONE) { emsg(_("E750: First use \":profile start {fname}\"")); - } else if (STRCMP(eap->arg, "stop") == 0) { + } else if (strcmp(eap->arg, "stop") == 0) { profile_dump(); do_profiling = PROF_NONE; set_vim_var_nr(VV_PROFILING, 0L); profile_reset(); - } else if (STRCMP(eap->arg, "pause") == 0) { + } else if (strcmp(eap->arg, "pause") == 0) { if (do_profiling == PROF_YES) { pause_time = profile_start(); } do_profiling = PROF_PAUSED; - } else if (STRCMP(eap->arg, "continue") == 0) { + } else if (strcmp(eap->arg, "continue") == 0) { if (do_profiling == PROF_PAUSED) { pause_time = profile_end(pause_time); profile_set_wait(profile_add(profile_get_wait(), pause_time)); } do_profiling = PROF_YES; - } else if (STRCMP(eap->arg, "dump") == 0) { + } else if (strcmp(eap->arg, "dump") == 0) { profile_dump(); } else { // The rest is similar to ":breakadd". @@ -340,7 +354,6 @@ char *get_profile_name(expand_T *xp, int idx) switch (pexpand_what) { case PEXP_SUBCMD: return pexpand_cmds[idx]; - // case PEXP_FUNC: TODO default: return NULL; } @@ -354,18 +367,22 @@ void set_context_in_profile_cmd(expand_T *xp, const char *arg) pexpand_what = PEXP_SUBCMD; xp->xp_pattern = (char *)arg; - char_u *const end_subcmd = skiptowhite((const char_u *)arg); + char *const end_subcmd = skiptowhite(arg); if (*end_subcmd == NUL) { return; } - if ((const char *)end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) { + if ((end_subcmd - arg == 5 && strncmp(arg, "start", 5) == 0) + || (end_subcmd - arg == 4 && strncmp(arg, "file", 4) == 0)) { xp->xp_context = EXPAND_FILES; - xp->xp_pattern = skipwhite((char *)end_subcmd); + xp->xp_pattern = skipwhite(end_subcmd); + return; + } else if (end_subcmd - arg == 4 && strncmp(arg, "func", 4) == 0) { + xp->xp_context = EXPAND_USER_FUNC; + xp->xp_pattern = skipwhite(end_subcmd); return; } - // TODO(tarruda): expand function names after "func" xp->xp_context = EXPAND_NOTHING; } @@ -398,7 +415,7 @@ bool prof_def_func(void) /// Print the count and times for one function or function line. /// /// @param prefer_self when equal print only self time -static void prof_func_line(FILE *fd, int count, proftime_T *total, proftime_T *self, +static void prof_func_line(FILE *fd, int count, const proftime_T *total, const proftime_T *self, bool prefer_self) { if (count > 0) { @@ -430,7 +447,7 @@ static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, fp = sorttab[i]; prof_func_line(fd, fp->uf_tm_count, &fp->uf_tm_total, &fp->uf_tm_self, prefer_self); - if (fp->uf_name[0] == K_SPECIAL) { + if ((uint8_t)fp->uf_name[0] == K_SPECIAL) { fprintf(fd, " <SNR>%s()\n", fp->uf_name + 3); } else { fprintf(fd, " %s()\n", fp->uf_name); @@ -601,7 +618,7 @@ static void func_dump_profile(FILE *fd) if (fp->uf_prof_initialized) { sorttab[st_len++] = fp; - if (fp->uf_name[0] == K_SPECIAL) { + if ((uint8_t)fp->uf_name[0] == K_SPECIAL) { fprintf(fd, "FUNCTION <SNR>%s()\n", fp->uf_name + 3); } else { fprintf(fd, "FUNCTION %s()\n", fp->uf_name); @@ -612,7 +629,7 @@ static void func_dump_profile(FILE *fd) .script_ctx = fp->uf_script_ctx, .channel_id = 0, }; - char *p = (char *)get_scriptname(last_set, &should_free); + char *p = get_scriptname(last_set, &should_free); fprintf(fd, " Defined: %s:%" PRIdLINENR "\n", p, fp->uf_script_ctx.sc_lnum); if (should_free) { @@ -684,19 +701,19 @@ void script_prof_save(proftime_T *tm) } /// Count time spent in children after invoking another script or function. -void script_prof_restore(proftime_T *tm) +void script_prof_restore(const proftime_T *tm) { - scriptitem_T *si; + if (!SCRIPT_ID_VALID(current_sctx.sc_sid)) { + return; + } - if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) { - si = &SCRIPT_ITEM(current_sctx.sc_sid); - if (si->sn_prof_on && --si->sn_pr_nest == 0) { - si->sn_pr_child = profile_end(si->sn_pr_child); - // don't count wait time - si->sn_pr_child = profile_sub_wait(*tm, si->sn_pr_child); - si->sn_pr_children = profile_add(si->sn_pr_children, si->sn_pr_child); - si->sn_prl_children = profile_add(si->sn_prl_children, si->sn_pr_child); - } + scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid); + if (si->sn_prof_on && --si->sn_pr_nest == 0) { + si->sn_pr_child = profile_end(si->sn_pr_child); + // don't count wait time + si->sn_pr_child = profile_sub_wait(*tm, si->sn_pr_child); + si->sn_pr_children = profile_add(si->sn_pr_children, si->sn_pr_child); + si->sn_prl_children = profile_add(si->sn_prl_children, si->sn_pr_child); } } @@ -721,7 +738,7 @@ static void script_dump_profile(FILE *fd) fprintf(fd, "\n"); fprintf(fd, "count total (s) self (s)\n"); - sfd = os_fopen((char *)si->sn_name, "r"); + sfd = os_fopen(si->sn_name, "r"); if (sfd == NULL) { fprintf(fd, "Cannot open file!\n"); } else { @@ -770,17 +787,17 @@ static void script_dump_profile(FILE *fd) /// Dump the profiling info. void profile_dump(void) { - FILE *fd; + if (profile_fname == NULL) { + return; + } - if (profile_fname != NULL) { - fd = os_fopen(profile_fname, "w"); - if (fd == NULL) { - semsg(_(e_notopen), profile_fname); - } else { - script_dump_profile(fd); - func_dump_profile(fd); - fclose(fd); - } + FILE *fd = os_fopen(profile_fname, "w"); + if (fd == NULL) { + semsg(_(e_notopen), profile_fname); + } else { + script_dump_profile(fd); + func_dump_profile(fd); + fclose(fd); } } diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 17fbbe17b8..5518fdfa51 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -4,30 +4,44 @@ // quickfix.c: functions for quickfix mode, using a file with error messages #include <assert.h> +#include <errno.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include <time.h> -#include "nvim/api/private/helpers.h" #include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/eval/window.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/help.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" +#include "nvim/memfile_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" @@ -35,14 +49,16 @@ #include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/search.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -67,9 +83,9 @@ struct qfline_S { char *qf_module; ///< module name for this error char *qf_pattern; ///< search pattern for the error char *qf_text; ///< description of the error - char qf_viscol; ///< set to TRUE if qf_col and qf_end_col is + char qf_viscol; ///< set to true if qf_col and qf_end_col is // screen column - char qf_cleared; ///< set to TRUE if line has been deleted + char qf_cleared; ///< set to true if line has been deleted char qf_type; ///< type of the error (mostly 'E'); 1 for :helpgrep char qf_valid; ///< valid error message detected }; @@ -100,7 +116,7 @@ typedef struct qf_list_S { qfline_T *qf_ptr; ///< pointer to the current error int qf_count; ///< number of errors (0 means empty list) int qf_index; ///< current index in the error list - int qf_nonevalid; ///< TRUE if not a single valid entry found + int qf_nonevalid; ///< true if not a single valid entry found char *qf_title; ///< title derived from the command that created ///< the error list or set by setqflist typval_T *qf_ctx; ///< context set by setqflist/setloclist @@ -176,6 +192,7 @@ enum { QF_NOMEM = 3, QF_IGNORE_LINE = 4, QF_MULTISCAN = 5, + QF_ABORT = 6, }; /// State information used to parse lines and add entries to a quickfix/location @@ -336,8 +353,7 @@ static const size_t LINE_MAXLEN = 4096; static struct fmtpattern { char convchar; char *pattern; -} fmt_pat[FMT_PATTERNS] = -{ +} fmt_pat[FMT_PATTERNS] = { { 'f', ".\\+" }, // only used when at end { 'n', "\\d\\+" }, // 1 { 'l', "\\d\\+" }, // 2 @@ -368,9 +384,9 @@ static char *efmpat_to_regpat(const char *efmpat, char *regpat, efm_T *efminfo, return NULL; } if ((idx && idx < FMT_PATTERN_R - && vim_strchr("DXOPQ", efminfo->prefix) != NULL) + && vim_strchr("DXOPQ", (uint8_t)efminfo->prefix) != NULL) || (idx == FMT_PATTERN_R - && vim_strchr("OPQ", efminfo->prefix) == NULL)) { + && vim_strchr("OPQ", (uint8_t)efminfo->prefix) == NULL)) { semsg(_("E373: Unexpected %%%c in format string"), *efmpat); return NULL; } @@ -452,10 +468,10 @@ static char *scanf_fmt_to_regpat(const char **pefmp, const char *efm, int len, c static const char *efm_analyze_prefix(const char *efmp, efm_T *efminfo) FUNC_ATTR_NONNULL_ALL { - if (vim_strchr("+-", *efmp) != NULL) { + if (vim_strchr("+-", (uint8_t)(*efmp)) != NULL) { efminfo->flags = *efmp++; } - if (vim_strchr("DXAEWINCZGOPQ", *efmp) != NULL) { + if (vim_strchr("DXAEWINCZGOPQ", (uint8_t)(*efmp)) != NULL) { efminfo->prefix = *efmp; } else { semsg(_("E376: Invalid %%%c in format string prefix"), *efmp); @@ -494,7 +510,7 @@ static int efm_to_regpat(const char *efm, int len, efm_T *fmt_ptr, char *regpat) if (ptr == NULL) { return FAIL; } - } else if (vim_strchr("%\\.^$~[", *efmp) != NULL) { + } else if (vim_strchr("%\\.^$~[", (uint8_t)(*efmp)) != NULL) { *ptr++ = *efmp; // regexp magic characters } else if (*efmp == '#') { *ptr++ = '*'; @@ -514,7 +530,7 @@ static int efm_to_regpat(const char *efm, int len, efm_T *fmt_ptr, char *regpat) } else { // copy normal character if (*efmp == '\\' && efmp + 1 < efm + len) { efmp++; - } else if (vim_strchr(".*^$~[", *efmp) != NULL) { + } else if (vim_strchr(".*^$~[", (uint8_t)(*efmp)) != NULL) { *ptr++ = '\\'; // escape regexp atoms } if (*efmp) { @@ -548,9 +564,9 @@ static void free_efm_list(efm_T **efm_first) /// a regular expression pattern. static size_t efm_regpat_bufsz(char *efm) { - size_t sz = (FMT_PATTERNS * 3) + (STRLEN(efm) << 2); + size_t sz = (FMT_PATTERNS * 3) + (strlen(efm) << 2); for (int i = FMT_PATTERNS - 1; i >= 0;) { - sz += STRLEN(fmt_pat[i--].pattern); + sz += strlen(fmt_pat[i--].pattern); } #ifdef BACKSLASH_IN_FILENAME sz += 12; // "%f" can become twelve chars longer (see efm_to_regpat) @@ -562,7 +578,7 @@ static size_t efm_regpat_bufsz(char *efm) } /// Return the length of a 'errorformat' option part (separated by ","). -static int efm_option_part_len(char *efm) +static int efm_option_part_len(const char *efm) { int len; @@ -607,7 +623,7 @@ static efm_T *parse_efm_option(char *efm) goto parse_efm_error; } // Advance to next part - efm = (char *)skip_to_option_part((char_u *)efm + len); // skip comma and spaces + efm = skip_to_option_part(efm + len); // skip comma and spaces } if (fmt_first == NULL) { // nothing found @@ -652,12 +668,12 @@ static int qf_get_next_str_line(qfstate_T *state) } char *p = vim_strchr(p_str, '\n'); - size_t len = (p != NULL) ? (size_t)(p - p_str) + 1 : STRLEN(p_str); + size_t len = (p != NULL) ? (size_t)(p - p_str) + 1 : strlen(p_str); if (len > IOSIZE - 2) { state->linebuf = qf_grow_linebuf(state, len); } else { - state->linebuf = (char *)IObuff; + state->linebuf = IObuff; state->linelen = len; } memcpy(state->linebuf, p_str, state->linelen); @@ -688,16 +704,16 @@ static int qf_get_next_list_line(qfstate_T *state) return QF_END_OF_INPUT; } - size_t len = STRLEN(TV_LIST_ITEM_TV(p_li)->vval.v_string); + size_t len = strlen(TV_LIST_ITEM_TV(p_li)->vval.v_string); if (len > IOSIZE - 2) { state->linebuf = qf_grow_linebuf(state, len); } else { - state->linebuf = (char *)IObuff; + state->linebuf = IObuff; state->linelen = len; } - STRLCPY(state->linebuf, TV_LIST_ITEM_TV(p_li)->vval.v_string, - state->linelen + 1); + xstrlcpy(state->linebuf, TV_LIST_ITEM_TV(p_li)->vval.v_string, + state->linelen + 1); state->p_li = TV_LIST_ITEM_NEXT(state->p_list, p_li); return QF_OK; @@ -710,17 +726,17 @@ static int qf_get_next_buf_line(qfstate_T *state) if (state->buflnum > state->lnumlast) { return QF_END_OF_INPUT; } - char *p_buf = (char *)ml_get_buf(state->buf, state->buflnum, false); + char *p_buf = ml_get_buf(state->buf, state->buflnum, false); state->buflnum += 1; - size_t len = STRLEN(p_buf); + size_t len = strlen(p_buf); if (len > IOSIZE - 2) { state->linebuf = qf_grow_linebuf(state, len); } else { - state->linebuf = (char *)IObuff; + state->linebuf = IObuff; state->linelen = len; } - STRLCPY(state->linebuf, p_buf, state->linelen + 1); + xstrlcpy(state->linebuf, p_buf, state->linelen + 1); return QF_OK; } @@ -730,7 +746,7 @@ static int qf_get_next_file_line(qfstate_T *state) { retry: errno = 0; - if (fgets((char *)IObuff, IOSIZE, state->fd) == NULL) { + if (fgets(IObuff, IOSIZE, state->fd) == NULL) { if (errno == EINTR) { goto retry; } @@ -738,7 +754,7 @@ retry: } bool discard = false; - state->linelen = STRLEN(IObuff); + state->linelen = strlen(IObuff); if (state->linelen == IOSIZE - 1 && !(IObuff[state->linelen - 1] == '\n')) { // The current line exceeds IObuff, continue reading using growbuf @@ -761,7 +777,7 @@ retry: } break; } - state->linelen = STRLEN(state->growbuf + growbuflen); + state->linelen = strlen(state->growbuf + growbuflen); growbuflen += state->linelen; if (state->growbuf[growbuflen - 1] == '\n') { break; @@ -780,13 +796,13 @@ retry: // The current line is longer than LINE_MAXLEN, continue reading but // discard everything until EOL or EOF is reached. errno = 0; - if (fgets((char *)IObuff, IOSIZE, state->fd) == NULL) { + if (fgets(IObuff, IOSIZE, state->fd) == NULL) { if (errno == EINTR) { continue; } break; } - if (STRLEN(IObuff) < IOSIZE - 1 || IObuff[IOSIZE - 2] == '\n') { + if (strlen(IObuff) < IOSIZE - 1 || IObuff[IOSIZE - 2] == '\n') { break; } } @@ -794,15 +810,15 @@ retry: state->linebuf = state->growbuf; state->linelen = growbuflen; } else { - state->linebuf = (char *)IObuff; + state->linebuf = IObuff; } // Convert a line if it contains a non-ASCII character - if (state->vc.vc_type != CONV_NONE && has_non_ascii((char_u *)state->linebuf)) { - char *line = (char *)string_convert(&state->vc, (char_u *)state->linebuf, &state->linelen); + if (state->vc.vc_type != CONV_NONE && has_non_ascii(state->linebuf)) { + char *line = string_convert(&state->vc, state->linebuf, &state->linelen); if (line != NULL) { if (state->linelen < IOSIZE) { - STRLCPY(state->linebuf, line, state->linelen + 1); + xstrlcpy(state->linebuf, line, state->linelen + 1); xfree(line); } else { xfree(state->growbuf); @@ -852,7 +868,7 @@ static int qf_get_nextline(qfstate_T *state) #endif } - remove_bom((char_u *)state->linebuf); + remove_bom(state->linebuf); return QF_OK; } @@ -909,7 +925,7 @@ restofline: // match or no match. fields->valid = true; for (; fmt_ptr != NULL; fmt_ptr = fmt_ptr->next) { - idx = (char_u)fmt_ptr->prefix; + idx = (uint8_t)fmt_ptr->prefix; status = qf_parse_get_fields(linebuf, linelen, fmt_ptr, fields, qfl->qf_multiline, qfl->qf_multiscan, &tail); @@ -1000,7 +1016,7 @@ static int qf_setup_state(qfstate_T *pstate, char *restrict enc, const char *res { pstate->vc.vc_type = CONV_NONE; if (enc != NULL && *enc != NUL) { - convert_setup(&pstate->vc, (char_u *)enc, p_enc); + convert_setup(&pstate->vc, enc, p_enc); } if (efile != NULL @@ -1058,6 +1074,7 @@ static int qf_init_ext(qf_info_T *qi, int qf_idx, const char *restrict efile, bu { qfstate_T state = { 0 }; qffields_T fields = { 0 }; + qfline_T *old_last = NULL; static efm_T *fmt_first = NULL; static char *last_efm = NULL; int retval = -1; // default: return error flag @@ -1071,7 +1088,6 @@ static int qf_init_ext(qf_info_T *qi, int qf_idx, const char *restrict efile, bu } qf_list_T *qfl; - qfline_T *old_last = NULL; bool adding = false; if (newlist || qf_idx == qi->qf_listcount) { // make place for a new list @@ -1091,14 +1107,14 @@ static int qf_init_ext(qf_info_T *qi, int qf_idx, const char *restrict efile, bu // Use the local value of 'errorformat' if it's set. if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) { - efm = (char *)buf->b_p_efm; + efm = buf->b_p_efm; } else { efm = errorformat; } // If the errorformat didn't change between calls, then reuse the previously // parsed values. - if (last_efm == NULL || (STRCMP(last_efm, efm) != 0)) { + if (last_efm == NULL || (strcmp(last_efm, efm) != 0)) { // free the previously parsed data XFREE_CLEAR(last_efm); free_efm_list(&fmt_first); @@ -1174,13 +1190,15 @@ static void qf_store_title(qf_list_T *qfl, const char *title) { XFREE_CLEAR(qfl->qf_title); - if (title != NULL) { - size_t len = STRLEN(title) + 1; - char *p = xmallocz(len); - - qfl->qf_title = p; - STRLCPY(p, title, len + 1); + if (title == NULL) { + return; } + + size_t len = strlen(title) + 1; + char *p = xmallocz(len); + + qfl->qf_title = p; + xstrlcpy(p, title, len + 1); } /// The title of a quickfix/location list is set, by default, to the command @@ -1242,15 +1260,15 @@ static int qf_parse_fmt_f(regmatch_T *rmp, int midx, qffields_T *fields, int pre } // Expand ~/file and $HOME/file to full path. - char c = (char)(*rmp->endp[midx]); + char c = *rmp->endp[midx]; *rmp->endp[midx] = NUL; - expand_env(rmp->startp[midx], (char_u *)fields->namebuf, CMDBUFFSIZE); - *rmp->endp[midx] = (char_u)c; + expand_env(rmp->startp[midx], fields->namebuf, CMDBUFFSIZE); + *rmp->endp[midx] = c; // For separate filename patterns (%O, %P and %Q), the specified file // should exist. if (vim_strchr("OPQ", prefix) != NULL - && !os_path_exists((char_u *)fields->namebuf)) { + && !os_path_exists(fields->namebuf)) { return QF_FAIL; } @@ -1264,7 +1282,7 @@ static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields) if (rmp->startp[midx] == NULL) { return QF_FAIL; } - fields->enr = (int)atol((char *)rmp->startp[midx]); + fields->enr = (int)atol(rmp->startp[midx]); return QF_OK; } @@ -1275,7 +1293,7 @@ static int qf_parse_fmt_l(regmatch_T *rmp, int midx, qffields_T *fields) if (rmp->startp[midx] == NULL) { return QF_FAIL; } - fields->lnum = (linenr_T)atol((char *)rmp->startp[midx]); + fields->lnum = (linenr_T)atol(rmp->startp[midx]); return QF_OK; } @@ -1286,7 +1304,7 @@ static int qf_parse_fmt_e(regmatch_T *rmp, int midx, qffields_T *fields) if (rmp->startp[midx] == NULL) { return QF_FAIL; } - fields->end_lnum = (linenr_T)atol((char *)rmp->startp[midx]); + fields->end_lnum = (linenr_T)atol(rmp->startp[midx]); return QF_OK; } @@ -1297,7 +1315,7 @@ static int qf_parse_fmt_c(regmatch_T *rmp, int midx, qffields_T *fields) if (rmp->startp[midx] == NULL) { return QF_FAIL; } - fields->col = (int)atol((char *)rmp->startp[midx]); + fields->col = (int)atol(rmp->startp[midx]); return QF_OK; } @@ -1308,7 +1326,7 @@ static int qf_parse_fmt_k(regmatch_T *rmp, int midx, qffields_T *fields) if (rmp->startp[midx] == NULL) { return QF_FAIL; } - fields->end_col = (int)atol((char *)rmp->startp[midx]); + fields->end_col = (int)atol(rmp->startp[midx]); return QF_OK; } @@ -1319,13 +1337,13 @@ static int qf_parse_fmt_t(regmatch_T *rmp, int midx, qffields_T *fields) if (rmp->startp[midx] == NULL) { return QF_FAIL; } - fields->type = (char)(*rmp->startp[midx]); + fields->type = *rmp->startp[midx]; return QF_OK; } -/// Parse the match for '%+' format pattern. The whole matching line is included -/// in the error string. Return the matched line in "fields->errmsg". -static void qf_parse_fmt_plus(const char *linebuf, size_t linelen, qffields_T *fields) +/// Copy a non-error line into the error string. Return the matched line in +/// "fields->errmsg". +static int copy_nonerror_line(const char *linebuf, size_t linelen, qffields_T *fields) FUNC_ATTR_NONNULL_ALL { if (linelen >= fields->errmsglen) { @@ -1333,7 +1351,10 @@ static void qf_parse_fmt_plus(const char *linebuf, size_t linelen, qffields_T *f fields->errmsg = xrealloc(fields->errmsg, linelen + 1); fields->errmsglen = linelen + 1; } - STRLCPY(fields->errmsg, linebuf, linelen + 1); + // copy whole line to error message + xstrlcpy(fields->errmsg, linebuf, linelen + 1); + + return QF_OK; } /// Parse the match for error message ('%m') pattern in regmatch. @@ -1349,7 +1370,7 @@ static int qf_parse_fmt_m(regmatch_T *rmp, int midx, qffields_T *fields) fields->errmsg = xrealloc(fields->errmsg, len + 1); fields->errmsglen = len + 1; } - STRLCPY(fields->errmsg, rmp->startp[midx], len + 1); + xstrlcpy(fields->errmsg, rmp->startp[midx], len + 1); return QF_OK; } @@ -1360,7 +1381,7 @@ static int qf_parse_fmt_r(regmatch_T *rmp, int midx, char **tail) if (rmp->startp[midx] == NULL) { return QF_FAIL; } - *tail = (char *)rmp->startp[midx]; + *tail = rmp->startp[midx]; return QF_OK; } @@ -1372,7 +1393,7 @@ static int qf_parse_fmt_p(regmatch_T *rmp, int midx, qffields_T *fields) return QF_FAIL; } fields->col = 0; - for (char *match_ptr = (char *)rmp->startp[midx]; (char_u *)match_ptr != rmp->endp[midx]; + for (char *match_ptr = rmp->startp[midx]; match_ptr != rmp->endp[midx]; match_ptr++) { fields->col++; if (*match_ptr == TAB) { @@ -1392,7 +1413,7 @@ static int qf_parse_fmt_v(regmatch_T *rmp, int midx, qffields_T *fields) if (rmp->startp[midx] == NULL) { return QF_FAIL; } - fields->col = (int)atol((char *)rmp->startp[midx]); + fields->col = (int)atol(rmp->startp[midx]); fields->use_viscol = true; return QF_OK; } @@ -1409,7 +1430,7 @@ static int qf_parse_fmt_s(regmatch_T *rmp, int midx, qffields_T *fields) len = CMDBUFFSIZE - 5; } STRCPY(fields->pattern, "^\\V"); - STRLCAT(fields->pattern, rmp->startp[midx], len + 4); + xstrlcat(fields->pattern, rmp->startp[midx], len + 4); fields->pattern[len + 3] = '\\'; fields->pattern[len + 4] = '$'; fields->pattern[len + 5] = NUL; @@ -1424,11 +1445,11 @@ static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields) return QF_FAIL; } size_t len = (size_t)(rmp->endp[midx] - rmp->startp[midx]); - size_t dsize = STRLEN(fields->module) + len + 1; + size_t dsize = strlen(fields->module) + len + 1; if (dsize > CMDBUFFSIZE) { dsize = CMDBUFFSIZE; } - STRLCAT(fields->module, rmp->startp[midx], dsize); + xstrlcat(fields->module, rmp->startp[midx], dsize); return QF_OK; } @@ -1464,7 +1485,7 @@ static int qf_parse_match(char *linebuf, size_t linelen, efm_T *fmt_ptr, regmatc if ((idx == 'C' || idx == 'Z') && !qf_multiline) { return QF_FAIL; } - if (vim_strchr("EWIN", idx) != NULL) { + if (vim_strchr("EWIN", (uint8_t)idx) != NULL) { fields->type = idx; } else { fields->type = 0; @@ -1480,7 +1501,7 @@ static int qf_parse_match(char *linebuf, size_t linelen, efm_T *fmt_ptr, regmatc status = qf_parse_fmt_f(regmatch, midx, fields, idx); } else if (i == FMT_PATTERN_M) { if (fmt_ptr->flags == '+' && !qf_multiscan) { // %+ - qf_parse_fmt_plus(linebuf, linelen, fields); + status = copy_nonerror_line(linebuf, linelen, fields); } else if (midx > 0) { // %m status = qf_parse_fmt_m(regmatch, midx, fields); } @@ -1505,7 +1526,7 @@ static int qf_parse_match(char *linebuf, size_t linelen, efm_T *fmt_ptr, regmatc static int qf_parse_get_fields(char *linebuf, size_t linelen, efm_T *fmt_ptr, qffields_T *fields, int qf_multiline, int qf_multiscan, char **tail) { - if (qf_multiscan && vim_strchr("OPQ", fmt_ptr->prefix) == NULL) { + if (qf_multiscan && vim_strchr("OPQ", (uint8_t)fmt_ptr->prefix) == NULL) { return QF_FAIL; } @@ -1564,7 +1585,7 @@ static int qf_parse_dir_pfx(int idx, qffields_T *fields, qf_list_T *qfl) static int qf_parse_file_pfx(int idx, qffields_T *fields, qf_list_T *qfl, char *tail) { fields->valid = false; - if (*fields->namebuf == NUL || os_path_exists((char_u *)fields->namebuf)) { + if (*fields->namebuf == NUL || os_path_exists(fields->namebuf)) { if (*fields->namebuf && idx == 'P') { qfl->qf_currfile = qf_push_dir(fields->namebuf, &qfl->qf_file_stack, true); } else if (idx == 'Q') { @@ -1588,15 +1609,8 @@ static int qf_parse_line_nomatch(char *linebuf, size_t linelen, qffields_T *fiel fields->namebuf[0] = NUL; // no match found, remove file name fields->lnum = 0; // don't jump to this line fields->valid = false; - if (linelen >= fields->errmsglen) { - // linelen + null terminator - fields->errmsg = xrealloc(fields->errmsg, linelen + 1); - fields->errmsglen = linelen + 1; - } - // copy whole line to error message - STRLCPY(fields->errmsg, linebuf, linelen + 1); - return QF_OK; + return copy_nonerror_line(linebuf, linelen, fields); } /// Parse multi-line error format prefixes (%C and %Z) @@ -1609,8 +1623,8 @@ static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields) return QF_FAIL; } if (*fields->errmsg) { - size_t textlen = STRLEN(qfprev->qf_text); - size_t errlen = STRLEN(fields->errmsg); + size_t textlen = strlen(qfprev->qf_text); + size_t errlen = strlen(fields->errmsg); qfprev->qf_text = xrealloc(qfprev->qf_text, textlen + errlen + 2); qfprev->qf_text[textlen] = '\n'; STRCPY(qfprev->qf_text + textlen + 1, fields->errmsg); @@ -1672,14 +1686,16 @@ int qf_stack_get_bufnr(void) static void wipe_qf_buffer(qf_info_T *qi) FUNC_ATTR_NONNULL_ALL { - if (qi->qf_bufnr != INVALID_QFBUFNR) { - buf_T *const qfbuf = buflist_findnr(qi->qf_bufnr); - if (qfbuf != NULL && qfbuf->b_nwindows == 0) { - // If the quickfix buffer is not loaded in any window, then - // wipe the buffer. - close_buffer(NULL, qfbuf, DOBUF_WIPE, false, false); - qi->qf_bufnr = INVALID_QFBUFNR; - } + if (qi->qf_bufnr == INVALID_QFBUFNR) { + return; + } + + buf_T *const qfbuf = buflist_findnr(qi->qf_bufnr); + if (qfbuf != NULL && qfbuf->b_nwindows == 0) { + // If the quickfix buffer is not loaded in any window, then + // wipe the buffer. + close_buffer(NULL, qfbuf, DOBUF_WIPE, false, false); + qi->qf_bufnr = INVALID_QFBUFNR; } } @@ -1790,7 +1806,7 @@ void check_quickfix_busy(void) /// @param type type character /// @param valid valid entry /// -/// @returns QF_OK or QF_FAIL. +/// @return QF_OK on success or QF_FAIL on failure. static int qf_add_entry(qf_list_T *qfl, char *dir, char *fname, char *module, int bufnum, char *mesg, linenr_T lnum, linenr_T end_lnum, int col, int end_col, char vis_col, char *pattern, int nr, char type, char valid) @@ -1891,7 +1907,7 @@ static qf_info_T *ll_get_or_alloc_list(win_T *wp) /// Get the quickfix/location list stack to use for the specified Ex command. /// For a location list command, returns the stack for the current window. If /// the location list is not found, then returns NULL and prints an error -/// message if 'print_emsg' is TRUE. +/// message if 'print_emsg' is true. static qf_info_T *qf_cmd_get_stack(exarg_T *eap, int print_emsg) { qf_info_T *qi = &ql_info; @@ -2063,13 +2079,13 @@ static int qf_get_fnum(qf_list_T *qfl, char *directory, char *fname) } slash_adjust(fname); #endif - if (directory != NULL && !vim_isAbsName((char_u *)fname)) { + if (directory != NULL && !vim_isAbsName(fname)) { ptr = concat_fnames(directory, fname, true); // Here we check if the file really exists. // This should normally be true, but if make works without // "leaving directory"-messages we might have missed a // directory change. - if (!os_path_exists((char_u *)ptr)) { + if (!os_path_exists(ptr)) { xfree(ptr); directory = qf_guess_filepath(qfl, fname); if (directory) { @@ -2085,7 +2101,7 @@ static int qf_get_fnum(qf_list_T *qfl, char *directory, char *fname) } if (qf_last_bufname != NULL - && STRCMP(bufname, qf_last_bufname) == 0 + && strcmp(bufname, qf_last_bufname) == 0 && bufref_valid(&qf_last_bufref)) { buf = qf_last_bufref.br_buf; xfree(ptr); @@ -2116,7 +2132,7 @@ static char *qf_push_dir(char *dirbuf, struct dir_stack_T **stackptr, bool is_fi *stackptr = ds_new; // store directory on the stack - if (vim_isAbsName((char_u *)dirbuf) + if (vim_isAbsName(dirbuf) || (*stackptr)->next == NULL || is_file_stack) { (*stackptr)->dirname = xstrdup(dirbuf); @@ -2129,7 +2145,7 @@ static char *qf_push_dir(char *dirbuf, struct dir_stack_T **stackptr, bool is_fi while (ds_new) { xfree((*stackptr)->dirname); (*stackptr)->dirname = concat_fnames(ds_new->dirname, dirbuf, true); - if (os_isdir((char_u *)(*stackptr)->dirname)) { + if (os_isdir((*stackptr)->dirname)) { break; } @@ -2153,12 +2169,11 @@ static char *qf_push_dir(char *dirbuf, struct dir_stack_T **stackptr, bool is_fi if ((*stackptr)->dirname != NULL) { return (*stackptr)->dirname; - } else { - ds_ptr = *stackptr; - *stackptr = (*stackptr)->next; - xfree(ds_ptr); - return NULL; } + ds_ptr = *stackptr; + *stackptr = (*stackptr)->next; + xfree(ds_ptr); + return NULL; } // pop dirbuf from the directory stack and return previous directory or NULL if @@ -2223,7 +2238,7 @@ static char *qf_guess_filepath(qf_list_T *qfl, char *filename) xfree(fullname); fullname = concat_fnames(ds_ptr->dirname, filename, true); - if (os_path_exists((char_u *)fullname)) { + if (os_path_exists(fullname)) { break; } @@ -2511,7 +2526,7 @@ static win_T *qf_find_win_with_normal_buf(void) } // Go to a window in any tabpage containing the specified file. Returns true -// if successfully jumped to the window. Otherwise returns FALSE. +// if successfully jumped to the window. Otherwise returns false. static bool qf_goto_tabwin_with_file(int fnum) { FOR_ALL_TAB_WINDOWS(tp, wp) { @@ -2686,6 +2701,10 @@ static int qf_jump_to_usable_window(int qf_fnum, bool newwin, int *opened_window } /// Edit the selected file or help file. +/// @return OK if successfully edited the file. +/// FAIL on failing to open the buffer. +/// QF_ABORT if the quickfix/location list was freed by an autocmd +/// when opening the buffer. static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int prev_winid, int *opened_window) { @@ -2702,11 +2721,10 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int if (!can_abandon(curbuf, forceit)) { no_write_message(); return FAIL; - } else { - retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, - ECMD_HIDE + ECMD_SET_HELP, - prev_winid == curwin->handle ? curwin : NULL); } + retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, + ECMD_HIDE + ECMD_SET_HELP, + prev_winid == curwin->handle ? curwin : NULL); } else { retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, GETF_SETMARK | GETF_SWITCH, forceit); @@ -2719,13 +2737,13 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int if (wp == NULL && curwin->w_llist != qi) { emsg(_("E924: Current window was closed")); *opened_window = false; - return NOTDONE; + return QF_ABORT; } } if (qfl_type == QFLT_QUICKFIX && !qflist_valid(NULL, save_qfid)) { emsg(_(e_current_quickfix_list_was_changed)); - return NOTDONE; + return QF_ABORT; } if (old_qf_curlist != qi->qf_curlist // -V560 @@ -2736,7 +2754,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int } else { emsg(_(e_current_location_list_was_changed)); } - return NOTDONE; + return QF_ABORT; } return retval; @@ -2771,7 +2789,7 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char qf_viscol, char // Move the cursor to the first line in the buffer pos_T save_cursor = curwin->w_cursor; curwin->w_cursor.lnum = 0; - if (!do_search(NULL, '/', '/', (char_u *)qf_pattern, (long)1, SEARCH_KEEP, NULL)) { + if (!do_search(NULL, '/', '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) { curwin->w_cursor = save_cursor; } } @@ -2784,15 +2802,18 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf // Update the screen before showing the message, unless the screen // scrolled up. if (!msg_scrolled) { - update_topline_redraw(); + update_topline(curwin); + if (must_redraw) { + update_screen(); + } } - snprintf((char *)IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index, + snprintf(IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index, qf_get_curlist(qi)->qf_count, qf_ptr->qf_cleared ? _(" (line deleted)") : "", qf_types(qf_ptr->qf_type, qf_ptr->qf_nr)); // Add the message, skipping leading whitespace and newlines. - int len = (int)STRLEN(IObuff); - qf_fmt_text(skipwhite(qf_ptr->qf_text), (char *)IObuff + len, IOSIZE - len); + int len = (int)strlen(IObuff); + qf_fmt_text(skipwhite(qf_ptr->qf_text), IObuff + len, IOSIZE - len); // Output the message. Overwrite to avoid scrolling when the 'O' // flag is present in 'shortmess'; But when not jumping, print the @@ -2804,16 +2825,17 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf msg_scroll = false; } msg_ext_set_kind("quickfix"); - msg_attr_keep((char *)IObuff, 0, true, false); + msg_attr_keep(IObuff, 0, true, false); msg_scroll = (int)i; } /// Find a usable window for opening a file from the quickfix/location list. If /// a window is not found then open a new window. If 'newwin' is true, then open /// a new window. -/// Returns OK if successfully jumped or opened a window. Returns FAIL if not -/// able to jump/open a window. Returns NOTDONE if a file is not associated -/// with the entry. +/// @return OK if successfully jumped or opened a window. +/// FAIL if not able to jump/open a window. +/// NOTDONE if a file is not associated with the entry. +/// QF_ABORT if the quickfix/location list was modified by an autocmd. static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int *opened_window) { qf_list_T *qfl = qf_get_curlist(qi); @@ -2835,7 +2857,7 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int } else { emsg(_(e_current_location_list_was_changed)); } - return FAIL; + return QF_ABORT; } // If currently in the quickfix window, find another window to show the @@ -2860,7 +2882,7 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int } else { emsg(_(e_current_location_list_was_changed)); } - return FAIL; + return QF_ABORT; } return OK; @@ -2869,9 +2891,9 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int /// Edit a selected file from the quickfix/location list and jump to a /// particular line/column, adjust the folds and display a message about the /// jump. -/// Returns OK on success and FAIL on failing to open the file/buffer. Returns -/// NOTDONE if the quickfix/location list is freed by an autocmd when opening -/// the file. +/// @return OK on success and FAIL on failing to open the file/buffer. +/// QF_ABORT if the quickfix/location list is freed by an autocmd when opening +/// the file. static int qf_jump_to_buffer(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, int forceit, int prev_winid, int *opened_window, int openfold, int print_message) { @@ -2923,7 +2945,7 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit) // If 'newwin' is true, then open the file in a new window. static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, bool newwin) { - char *old_swb = (char *)p_swb; + char *old_swb = p_swb; unsigned old_swb_flags = swb_flags; const bool old_KeyTyped = KeyTyped; // getting file may reset it @@ -2932,7 +2954,7 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo } if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -2965,14 +2987,19 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo if (retval == FAIL) { goto failed; } + if (retval == QF_ABORT) { + qi = NULL; + qf_ptr = NULL; + goto theend; + } if (retval == NOTDONE) { goto theend; } retval = qf_jump_to_buffer(qi, qf_index, qf_ptr, forceit, prev_winid, &opened_window, old_KeyTyped, print_message); - if (retval == NOTDONE) { - // Quickfix/location list is freed by an autocmd + if (retval == QF_ABORT) { + // Quickfix/location list was modified by an autocmd qi = NULL; qf_ptr = NULL; } @@ -2994,10 +3021,10 @@ theend: qfl->qf_ptr = qf_ptr; qfl->qf_index = qf_index; } - if (p_swb != (char_u *)old_swb && p_swb == empty_option) { + if (p_swb != old_swb && p_swb == empty_option) { // Restore old 'switchbuf' value, but not when an autocommand or // modeline has changed the value. - p_swb = (char_u *)old_swb; + p_swb = old_swb; swb_flags = old_swb_flags; } decr_quickfix_busy(); @@ -3016,7 +3043,7 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) { char *fname = NULL; if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { - vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", qf_idx, qfp->qf_module); + vim_snprintf(IObuff, IOSIZE, "%2d %s", qf_idx, qfp->qf_module); } else { buf_T *buf; if (qfp->qf_fnum != 0 @@ -3027,9 +3054,9 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) } } if (fname == NULL) { - snprintf((char *)IObuff, IOSIZE, "%2d", qf_idx); + snprintf(IObuff, IOSIZE, "%2d", qf_idx); } else { - vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", qf_idx, fname); + vim_snprintf(IObuff, IOSIZE, "%2d %s", qf_idx, fname); } } @@ -3038,16 +3065,16 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) // text of the entry. bool filter_entry = true; if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { - filter_entry &= message_filtered((char_u *)qfp->qf_module); + filter_entry &= message_filtered(qfp->qf_module); } if (filter_entry && fname != NULL) { - filter_entry &= message_filtered((char_u *)fname); + filter_entry &= message_filtered(fname); } if (filter_entry && qfp->qf_pattern != NULL) { - filter_entry &= message_filtered((char_u *)qfp->qf_pattern); + filter_entry &= message_filtered(qfp->qf_pattern); } if (filter_entry) { - filter_entry &= message_filtered((char_u *)qfp->qf_text); + filter_entry &= message_filtered(qfp->qf_text); } if (filter_entry) { return; @@ -3062,21 +3089,21 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) if (qfp->qf_lnum == 0) { IObuff[0] = NUL; } else { - qf_range_text(qfp, (char *)IObuff, IOSIZE); + qf_range_text(qfp, IObuff, IOSIZE); } - vim_snprintf((char *)IObuff + STRLEN(IObuff), IOSIZE, "%s", qf_types(qfp->qf_type, qfp->qf_nr)); + vim_snprintf(IObuff + strlen(IObuff), IOSIZE, "%s", qf_types(qfp->qf_type, qfp->qf_nr)); msg_puts_attr((const char *)IObuff, qfLineAttr); msg_puts_attr(":", qfSepAttr); if (qfp->qf_pattern != NULL) { - qf_fmt_text(qfp->qf_pattern, (char *)IObuff, IOSIZE); + qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE); msg_puts((const char *)IObuff); msg_puts_attr(":", qfSepAttr); } msg_puts(" "); - char_u *tbuf = IObuff; + char *tbuf = IObuff; size_t tbuflen = IOSIZE; - size_t len = STRLEN(qfp->qf_text) + 3; + size_t len = strlen(qfp->qf_text) + 3; if (len > IOSIZE) { tbuf = xmalloc(len); @@ -3088,14 +3115,12 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel) // with ^^^^. qf_fmt_text((fname != NULL || qfp->qf_lnum != 0) ? skipwhite(qfp->qf_text) : qfp->qf_text, - (char *)tbuf, (int)tbuflen); + tbuf, (int)tbuflen); msg_prt_line(tbuf, false); if (tbuf != IObuff) { xfree(tbuf); } - - ui_flush(); // show one line at a time } // ":clist": list all errors @@ -3111,7 +3136,7 @@ void qf_list(exarg_T *eap) } if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -3147,15 +3172,15 @@ void qf_list(exarg_T *eap) // Get the attributes for the different quickfix highlight items. Note // that this depends on syntax items defined in the qf.vim syntax file - qfFileAttr = syn_name2attr((char_u *)"qfFileName"); + qfFileAttr = syn_name2attr("qfFileName"); if (qfFileAttr == 0) { qfFileAttr = HL_ATTR(HLF_D); } - qfSepAttr = syn_name2attr((char_u *)"qfSeparator"); + qfSepAttr = syn_name2attr("qfSeparator"); if (qfSepAttr == 0) { qfSepAttr = HL_ATTR(HLF_D); } - qfLineAttr = syn_name2attr((char_u *)"qfLineNr"); + qfLineAttr = syn_name2attr("qfLineNr"); if (qfLineAttr == 0) { qfLineAttr = HL_ATTR(HLF_N); } @@ -3180,7 +3205,7 @@ static void qf_fmt_text(const char *restrict text, char *restrict buf, int bufsi int i; const char *p = (char *)text; - for (i = 0; *p != NUL && i < bufsize - 1; ++i) { + for (i = 0; *p != NUL && i < bufsize - 1; i++) { if (*p == '\n') { buf[i] = ' '; while (*++p != NUL) { @@ -3200,18 +3225,18 @@ static void qf_fmt_text(const char *restrict text, char *restrict buf, int bufsi static void qf_range_text(const qfline_T *qfp, char *buf, int bufsize) { vim_snprintf(buf, (size_t)bufsize, "%" PRIdLINENR, qfp->qf_lnum); - int len = (int)STRLEN(buf); + int len = (int)strlen(buf); if (qfp->qf_end_lnum > 0 && qfp->qf_lnum != qfp->qf_end_lnum) { vim_snprintf(buf + len, (size_t)(bufsize - len), "-%" PRIdLINENR, qfp->qf_end_lnum); - len += (int)STRLEN(buf + len); + len += (int)strlen(buf + len); } if (qfp->qf_col > 0) { vim_snprintf(buf + len, (size_t)(bufsize - len), " col %d", qfp->qf_col); - len += (int)STRLEN(buf + len); + len += (int)strlen(buf + len); if (qfp->qf_end_col > 0 && qfp->qf_col != qfp->qf_end_col) { vim_snprintf(buf + len, (size_t)(bufsize - len), "-%d", qfp->qf_end_col); - len += (int)STRLEN(buf + len); + len += (int)strlen(buf + len); } } buf[len] = NUL; @@ -3232,13 +3257,13 @@ static void qf_msg(qf_info_T *qi, int which, char *lead) count); if (title != NULL) { - size_t len = STRLEN(buf); + size_t len = strlen(buf); if (len < 34) { memset(buf + len, ' ', 34 - len); buf[34] = NUL; } - STRLCAT(buf, title, IOSIZE); + xstrlcat(buf, title, IOSIZE); } trunc_string(buf, buf, Columns - 1, IOSIZE); msg(buf); @@ -3263,13 +3288,13 @@ void qf_age(exarg_T *eap) emsg(_("E380: At bottom of quickfix stack")); break; } - --qi->qf_curlist; + qi->qf_curlist--; } else { if (qi->qf_curlist >= qi->qf_listcount - 1) { emsg(_("E381: At top of quickfix stack")); break; } - ++qi->qf_curlist; + qi->qf_curlist++; } } qf_msg(qi, qi->qf_curlist, ""); @@ -3393,7 +3418,7 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount, found_one = true; if (qfp->qf_lnum >= line1 && qfp->qf_lnum <= line2) { if (amount == MAXLNUM) { - qfp->qf_cleared = TRUE; + qfp->qf_cleared = true; } else { qfp->qf_lnum += amount; } @@ -3460,14 +3485,12 @@ void qf_view_result(bool split) { qf_info_T *qi = &ql_info; - if (!bt_quickfix(curbuf)) { - return; - } if (IS_LL_WINDOW(curwin)) { qi = GET_LOC_LIST(curwin); } + if (qf_list_empty(qf_get_curlist(qi))) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -3558,12 +3581,12 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, bool vertsp static void qf_set_cwindow_options(void) { // switch off 'swapfile' - set_option_value("swf", 0L, NULL, OPT_LOCAL); - set_option_value("bt", 0L, "quickfix", OPT_LOCAL); - set_option_value("bh", 0L, "hide", OPT_LOCAL); + set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL); + set_option_value_give_err("bt", 0L, "quickfix", OPT_LOCAL); + set_option_value_give_err("bh", 0L, "hide", OPT_LOCAL); RESET_BINDING(curwin); curwin->w_p_diff = false; - set_option_value("fdm", 0L, "manual", OPT_LOCAL); + set_option_value_give_err("fdm", 0L, "manual", OPT_LOCAL); } // Open a new quickfix or location list window, load the quickfix buffer and @@ -3710,7 +3733,7 @@ static void qf_win_goto(win_T *win, linenr_T lnum) curwin->w_cursor.coladd = 0; curwin->w_curswant = 0; update_topline(curwin); // scroll to show the line - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); curwin->w_redr_status = true; // update ruler curwin = old_curwin; curbuf = curwin->w_buffer; @@ -3747,7 +3770,7 @@ linenr_T qf_current_entry(win_T *wp) } /// Update the cursor position in the quickfix window to the current error. -/// Return TRUE if there is a quickfix window. +/// Return true if there is a quickfix window. /// /// @param old_qf_index previous qf_index or zero static bool qf_win_pos_update(qf_info_T *qi, int old_qf_index) @@ -3831,10 +3854,11 @@ static buf_T *qf_find_buf(qf_info_T *qi) } /// Process the 'quickfixtextfunc' option value. -/// @return OK or FAIL -int qf_process_qftf_option(void) +void qf_process_qftf_option(char **errmsg) { - return option_set_callback_func(p_qftf, &qftf_cb); + if (option_set_callback_func(p_qftf, &qftf_cb) == FAIL) { + *errmsg = e_invarg; + } } /// Update the w:quickfix_title variable in the quickfix/location list window in @@ -3859,48 +3883,61 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last) { // Check if a buffer for the quickfix list exists. Update it. buf_T *buf = qf_find_buf(qi); - if (buf != NULL) { - linenr_T old_line_count = buf->b_ml.ml_line_count; - int qf_winid = 0; - - win_T *win; - if (IS_LL_STACK(qi)) { - if (curwin->w_llist == qi) { - win = curwin; - } else { - win = qf_find_win_with_loclist(qi); - if (win == NULL) { - return; - } + if (buf == NULL) { + return; + } + + linenr_T old_line_count = buf->b_ml.ml_line_count; + int qf_winid = 0; + + win_T *win; + if (IS_LL_STACK(qi)) { + if (curwin->w_llist == qi) { + win = curwin; + } else { + // Find the file window (non-quickfix) with this location list + win = qf_find_win_with_loclist(qi); + if (win == NULL) { + // File window is not found. Find the location list window. + win = qf_find_win(qi); + } + if (win == NULL) { + return; } - qf_winid = (int)win->handle; } + qf_winid = (int)win->handle; + } - aco_save_T aco; + // autocommands may cause trouble + incr_quickfix_busy(); - if (old_last == NULL) { - // set curwin/curbuf to buf and save a few things - aucmd_prepbuf(&aco, buf); - } + aco_save_T aco; - qf_update_win_titlevar(qi); + if (old_last == NULL) { + // set curwin/curbuf to buf and save a few things + aucmd_prepbuf(&aco, buf); + } - qf_fill_buffer(qf_get_curlist(qi), buf, old_last, qf_winid); - buf_inc_changedtick(buf); + qf_update_win_titlevar(qi); - if (old_last == NULL) { - (void)qf_win_pos_update(qi, 0); + qf_fill_buffer(qf_get_curlist(qi), buf, old_last, qf_winid); + buf_inc_changedtick(buf); - // restore curwin/curbuf and a few other things - aucmd_restbuf(&aco); - } + if (old_last == NULL) { + (void)qf_win_pos_update(qi, 0); - // Only redraw when added lines are visible. This avoids flickering when - // the added lines are not visible. - if ((win = qf_find_win(qi)) != NULL && old_line_count < win->w_botline) { - redraw_buf_later(buf, NOT_VALID); - } + // restore curwin/curbuf and a few other things + aucmd_restbuf(&aco); } + + // Only redraw when added lines are visible. This avoids flickering when + // the added lines are not visible. + if ((win = qf_find_win(qi)) != NULL && old_line_count < win->w_botline) { + redraw_buf_later(buf, UPD_NOT_VALID); + } + + // always called after incr_quickfix_busy() + decr_quickfix_busy(); } // Add an error line to the quickfix buffer. @@ -3911,33 +3948,33 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli // If the 'quickfixtextfunc' function returned a non-empty custom string // for this entry, then use it. if (qftf_str != NULL && *qftf_str != NUL) { - STRLCPY(IObuff, qftf_str, IOSIZE); + xstrlcpy(IObuff, qftf_str, IOSIZE); } else { buf_T *errbuf; int len; if (qfp->qf_module != NULL) { - STRLCPY(IObuff, qfp->qf_module, IOSIZE); - len = (int)STRLEN(IObuff); + xstrlcpy(IObuff, qfp->qf_module, IOSIZE); + len = (int)strlen(IObuff); } else if (qfp->qf_fnum != 0 && (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL && errbuf->b_fname != NULL) { if (qfp->qf_type == 1) { // :helpgrep - STRLCPY(IObuff, path_tail(errbuf->b_fname), IOSIZE); + xstrlcpy(IObuff, path_tail(errbuf->b_fname), IOSIZE); } else { // Shorten the file name if not done already. // For optimization, do this only for the first entry in a // buffer. if (first_bufline && (errbuf->b_sfname == NULL - || path_is_absolute((char_u *)errbuf->b_sfname))) { + || path_is_absolute(errbuf->b_sfname))) { if (*dirname == NUL) { - os_dirname((char_u *)dirname, MAXPATHL); + os_dirname(dirname, MAXPATHL); } - shorten_buf_fname(errbuf, (char_u *)dirname, false); + shorten_buf_fname(errbuf, dirname, false); } - STRLCPY(IObuff, errbuf->b_fname, IOSIZE); + xstrlcpy(IObuff, errbuf->b_fname, IOSIZE); } - len = (int)STRLEN(IObuff); + len = (int)strlen(IObuff); } else { len = 0; } @@ -3945,15 +3982,15 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli IObuff[len++] = '|'; } if (qfp->qf_lnum > 0) { - qf_range_text(qfp, (char *)IObuff + len, IOSIZE - len); - len += (int)STRLEN(IObuff + len); + qf_range_text(qfp, IObuff + len, IOSIZE - len); + len += (int)strlen(IObuff + len); - snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%s", qf_types(qfp->qf_type, - qfp->qf_nr)); - len += (int)STRLEN(IObuff + len); + snprintf(IObuff + len, (size_t)(IOSIZE - len), "%s", qf_types(qfp->qf_type, + qfp->qf_nr)); + len += (int)strlen(IObuff + len); } else if (qfp->qf_pattern != NULL) { - qf_fmt_text(qfp->qf_pattern, (char *)IObuff + len, IOSIZE - len); - len += (int)STRLEN(IObuff + len); + qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len); + len += (int)strlen(IObuff + len); } if (len < IOSIZE - 2) { IObuff[len++] = '|'; @@ -3964,11 +4001,11 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli // For an unrecognized line keep the indent, the compiler may // mark a word with ^^^^. qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text, - (char *)IObuff + len, IOSIZE - len); + IObuff + len, IOSIZE - len); } if (ml_append_buf(buf, lnum, IObuff, - (colnr_T)STRLEN(IObuff) + 1, false) == FAIL) { + (colnr_T)strlen(IObuff) + 1, false) == FAIL) { return FAIL; } return OK; @@ -3980,6 +4017,12 @@ static list_T *call_qftf_func(qf_list_T *qfl, int qf_winid, long start_idx, long { Callback *cb = &qftf_cb; list_T *qftf_list = NULL; + static bool recursive = false; + + if (recursive) { + return NULL; // this doesn't work properly recursively + } + recursive = true; // If 'quickfixtextfunc' is set, then use the user-supplied function to get // the text to display. Use the local value of 'quickfixtextfunc' if it is @@ -4013,6 +4056,7 @@ static list_T *call_qftf_func(qf_list_T *qfl, int qf_winid, long start_idx, long tv_dict_unref(dict); } + recursive = false; return qftf_list; } @@ -4039,7 +4083,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q } // Check if there is anything to display - if (qfl != NULL) { + if (qfl != NULL && qfl->qf_start != NULL) { char dirname[MAXPATHL]; *dirname = NUL; @@ -4108,7 +4152,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q // resembles reading a file into a buffer, it's more logical when using // autocommands. curbuf->b_ro_locked++; - set_option_value("ft", 0L, "qf", OPT_LOCAL); + set_option_value_give_err("ft", 0L, "qf", OPT_LOCAL); curbuf->b_p_ma = false; keep_filetype = true; // don't detect 'filetype' @@ -4118,7 +4162,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q curbuf->b_ro_locked--; // make sure it will be redrawn - redraw_curbuf_later(NOT_VALID); + redraw_curbuf_later(UPD_NOT_VALID); } // Restore KeyTyped, setting 'filetype' may reset it. @@ -4151,14 +4195,16 @@ static int qf_id2nr(const qf_info_T *const qi, const unsigned qfid) static int qf_restore_list(qf_info_T *qi, unsigned save_qfid) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - if (qf_get_curlist(qi)->qf_id != save_qfid) { - const int curlist = qf_id2nr(qi, save_qfid); - if (curlist < 0) { - // list is not present - return FAIL; - } - qi->qf_curlist = curlist; + if (qf_get_curlist(qi)->qf_id == save_qfid) { + return OK; + } + + const int curlist = qf_id2nr(qi, save_qfid); + if (curlist < 0) { + // list is not present + return FAIL; } + qi->qf_curlist = curlist; return OK; } @@ -4175,15 +4221,14 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit) } } -// Return TRUE when using ":vimgrep" for ":grep". +// Return true when using ":vimgrep" for ":grep". int grep_internal(cmdidx_T cmdidx) { return (cmdidx == CMD_grep || cmdidx == CMD_lgrep || cmdidx == CMD_grepadd || cmdidx == CMD_lgrepadd) - && STRCMP("internal", - *curbuf->b_p_gp == NUL ? p_gp : curbuf->b_p_gp) == 0; + && strcmp("internal", *curbuf->b_p_gp == NUL ? p_gp : curbuf->b_p_gp) == 0; } // Return the make/grep autocmd name. @@ -4213,16 +4258,16 @@ static char *make_get_auname(cmdidx_T cmdidx) static char *make_get_fullcmd(const char *makecmd, const char *fname) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { - size_t len = STRLEN(p_shq) * 2 + STRLEN(makecmd) + 1; + size_t len = strlen(p_shq) * 2 + strlen(makecmd) + 1; if (*p_sp != NUL) { - len += STRLEN(p_sp) + STRLEN(fname) + 3; + len += strlen(p_sp) + strlen(fname) + 3; } char *const cmd = xmalloc(len); - snprintf(cmd, len, "%s%s%s", (char *)p_shq, (char *)makecmd, (char *)p_shq); + snprintf(cmd, len, "%s%s%s", p_shq, (char *)makecmd, p_shq); // If 'shellpipe' empty: don't redirect to 'errorfile'. if (*p_sp != NUL) { - append_redir(cmd, len, (char *)p_sp, (char *)fname); + append_redir(cmd, len, p_sp, (char *)fname); } // Display the fully formed command. Output a newline if there's something @@ -4241,7 +4286,7 @@ static char *make_get_fullcmd(const char *makecmd, const char *fname) // Used for ":make", ":lmake", ":grep", ":lgrep", ":grepadd", and ":lgrepadd" void ex_make(exarg_T *eap) { - char *enc = (*curbuf->b_p_menc != NUL) ? (char *)curbuf->b_p_menc : (char *)p_menc; + char *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; // Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". if (grep_internal(eap->cmdidx)) { @@ -4275,10 +4320,17 @@ void ex_make(exarg_T *eap) incr_quickfix_busy(); - int res = qf_init(wp, fname, (eap->cmdidx != CMD_make - && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, - (eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd), - qf_cmdtitle(*eap->cmdlinep), enc); + char *errorformat = p_efm; + bool newlist = true; + + if (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) { + errorformat = p_gefm; + } + if (eap->cmdidx == CMD_grepadd || eap->cmdidx == CMD_lgrepadd) { + newlist = false; + } + + int res = qf_init(wp, fname, errorformat, newlist, qf_cmdtitle(*eap->cmdlinep), enc); qf_info_T *qi = &ql_info; if (wp != NULL) { @@ -4319,7 +4371,7 @@ static char *get_mef_name(void) static int off = 0; if (*p_mef == NUL) { - name = (char *)vim_tempname(); + name = vim_tempname(); if (name == NULL) { emsg(_(e_notmp)); } @@ -4328,7 +4380,7 @@ static char *get_mef_name(void) char *p; - for (p = p_mef; *p; ++p) { + for (p = p_mef; *p; p++) { if (p[0] == '#' && p[1] == '#') { break; } @@ -4345,9 +4397,9 @@ static char *get_mef_name(void) } else { off += 19; } - name = xmalloc(STRLEN(p_mef) + 30); + name = xmalloc(strlen(p_mef) + 30); STRCPY(name, p_mef); - snprintf(name + (p - p_mef), STRLEN(name), "%d%d", start, off); + snprintf(name + (p - p_mef), strlen(name), "%d%d", start, off); STRCAT(name, p + 2); // Don't accept a symbolic link, it's a security risk. FileInfo file_info; @@ -4832,7 +4884,7 @@ static void qf_get_nth_below_entry(qfline_T *entry_arg, linenr_T n, bool linewis } /// Get the nth quickfix entry above the specified entry. Searches backwards in -/// the list. If linewise is TRUE, then treat multiple entries on a single line +/// the list. If linewise is true, then treat multiple entries on a single line /// as one. static void qf_get_nth_above_entry(qfline_T *entry, linenr_T n, bool linewise, int *errornr) FUNC_ATTR_NONNULL_ALL @@ -4898,7 +4950,7 @@ void ex_cbelow(exarg_T *eap) || eap->cmdidx == CMD_cafter) ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY; if (!(curbuf->b_has_qf_entry & buf_has_flag)) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -4910,7 +4962,7 @@ void ex_cbelow(exarg_T *eap) qf_list_T *qfl = qf_get_curlist(qi); // check if the list has valid errors if (!qf_list_has_valid_entries(qfl)) { - emsg(_(e_quickfix)); + emsg(_(e_no_errors)); return; } @@ -4981,7 +5033,7 @@ void ex_cfile(exarg_T *eap) set_string_option_direct("ef", -1, eap->arg, OPT_FREE, 0); } - char *enc = (*curbuf->b_p_menc != NUL) ? (char *)curbuf->b_p_menc : (char *)p_menc; + char *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; if (is_loclist_cmd(eap->cmdidx)) { wp = curwin; @@ -4997,8 +5049,8 @@ void ex_cfile(exarg_T *eap) // first error. // :caddfile adds to an existing quickfix list. If there is no // quickfix list then a new list is created. - int res = qf_init(wp, (char *)p_ef, p_efm, (eap->cmdidx != CMD_caddfile - && eap->cmdidx != CMD_laddfile), + int res = qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile + && eap->cmdidx != CMD_laddfile), qf_cmdtitle(*eap->cmdlinep), enc); if (wp != NULL) { qi = GET_LOC_LIST(wp); @@ -5062,7 +5114,7 @@ static void vgr_init_regmatch(regmmatch_T *regmatch, char *s) emsg(_(e_noprevre)); return; } - regmatch->regprog = vim_regcomp((char *)last_search_pat(), RE_MAGIC); + regmatch->regprog = vim_regcomp(last_search_pat(), RE_MAGIC); } else { regmatch->regprog = vim_regcomp(s, RE_MAGIC); } @@ -5075,7 +5127,7 @@ static void vgr_init_regmatch(regmmatch_T *regmatch, char *s) static void vgr_display_fname(char *fname) { msg_start(); - char *p = (char *)msg_strtrunc((char_u *)fname, true); + char *p = msg_strtrunc(fname, true); if (p == NULL) { msg_outtrans(fname); } else { @@ -5120,11 +5172,10 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid, char *titl // An autocmd has freed the location list emsg(_(e_current_location_list_was_changed)); return false; - } else { - // Quickfix list is not found, create a new one. - qf_new_list(qi, title); - return true; } + // Quickfix list is not found, create a new one. + qf_new_list(qi, title); + return true; } if (qf_restore_list(qi, qfid) == FAIL) { return false; @@ -5140,6 +5191,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5, 6) { bool found_match = false; + const size_t pat_len = strlen(spat); for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) { colnr_T col = 0; @@ -5154,7 +5206,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp fname, NULL, duplicate_name ? 0 : buf->b_fnum, - (char *)ml_get_buf(buf, regmatch->startpos[0].lnum + lnum, false), + ml_get_buf(buf, regmatch->startpos[0].lnum + lnum, false), regmatch->startpos[0].lnum + lnum, regmatch->endpos[0].lnum + lnum, regmatch->startpos[0].col + 1, @@ -5176,20 +5228,18 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp break; } col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col); - if (col > (colnr_T)STRLEN(ml_get_buf(buf, lnum, false))) { + if (col > (colnr_T)strlen(ml_get_buf(buf, lnum, false))) { break; } } } else { - const size_t pat_len = STRLEN(spat); - char *const str = (char *)ml_get_buf(buf, lnum, false); + char *const str = ml_get_buf(buf, lnum, false); int score; uint32_t matches[MAX_FUZZY_MATCHES]; const size_t sz = sizeof(matches) / sizeof(matches[0]); // Fuzzy string match - while (fuzzy_match((char_u *)str + col, (char_u *)spat, false, &score, matches, - (int)sz) > 0) { + while (fuzzy_match(str + col, spat, false, &score, matches, (int)sz) > 0) { // Pass the buffer number so that it gets used even for a // dummy buffer, unless duplicate_name is set, then the // buffer will be wiped out below. @@ -5220,7 +5270,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp break; } col = (colnr_T)matches[pat_len - 1] + col + 1; - if (col > (colnr_T)STRLEN(str)) { + if (col > (colnr_T)strlen(str)) { break; } } @@ -5236,7 +5286,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp /// Jump to the first match and update the directory. static void vgr_jump_to_match(qf_info_T *qi, int forceit, bool *redraw_for_dummy, - buf_T *first_match_buf, char *target_dir) + buf_T *first_match_buf, char *target_dir) // NOLINT(readability-non-const-parameter) { buf_T *buf = curbuf; qf_jump(qi, 0, 0, forceit); @@ -5262,8 +5312,8 @@ static bool existing_swapfile(const buf_T *buf) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { - const char *const fname = (char *)buf->b_ml.ml_mfp->mf_fname; - const size_t len = STRLEN(fname); + const char *const fname = buf->b_ml.ml_mfp->mf_fname; + const size_t len = strlen(fname); return fname[len - 1] != 'p' || fname[len - 2] != 'w'; } @@ -5305,10 +5355,8 @@ static int vgr_process_args(exarg_T *eap, vgr_args_T *args) } // Parse the list of arguments, wildcards have already been expanded. - if (get_arglist_exp((char_u *)p, &args->fcount, &args->fnames, true) == FAIL) { - return FAIL; - } - if (args->fcount == 0) { + if (get_arglist_exp(p, &args->fcount, &args->fnames, true) == FAIL + || args->fcount == 0) { emsg(_(e_nomatch)); return FAIL; } @@ -5330,11 +5378,11 @@ static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, boo // Remember the current directory, because a BufRead autocommand that does // ":lcd %:p:h" changes the meaning of short path names. - os_dirname((char_u *)dirname_start, MAXPATHL); + os_dirname(dirname_start, MAXPATHL); time_t seconds = (time_t)0; for (int fi = 0; fi < cmd_args->fcount && !got_int && cmd_args->tomatch > 0; fi++) { - char *fname = (char *)path_try_shorten_fname((char_u *)cmd_args->fnames[fi]); + char *fname = path_try_shorten_fname(cmd_args->fnames[fi]); if (time(NULL) > seconds) { // Display the file name every second or so, show the user we are // working on it. @@ -5419,7 +5467,7 @@ static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, boo // directory we jumped to below. if (buf == *first_match_buf && *target_dir == NULL - && STRCMP(dirname_start, dirname_now) != 0) { + && strcmp(dirname_start, dirname_now) != 0) { *target_dir = xstrdup(dirname_now); } @@ -5429,7 +5477,7 @@ static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, boo // options! aco_save_T aco; aucmd_prepbuf(&aco, buf); - apply_autocmds(EVENT_FILETYPE, (char *)buf->b_p_ft, buf->b_fname, true, buf); + apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, true, buf); do_modelines(OPT_NOWIN); aucmd_restbuf(&aco); } @@ -5541,8 +5589,8 @@ static void restore_start_dir(char *dirname_start) { char *dirname_now = xmalloc(MAXPATHL); - os_dirname((char_u *)dirname_now, MAXPATHL); - if (STRCMP(dirname_start, dirname_now) != 0) { + os_dirname(dirname_now, MAXPATHL); + if (strcmp(dirname_start, dirname_now) != 0) { // If the directory has changed, change it back by building up an // appropriate ex command and executing it. exarg_T ea = { @@ -5609,7 +5657,7 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin if (readfile_result == OK && !got_int && !(curbuf->b_flags & BF_NEW)) { - failed = FALSE; + failed = false; if (curbuf != newbuf) { // Bloody autocommands changed the buffer! Can happen when // using netrw and editing a remote file. Use the current @@ -5622,6 +5670,7 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin // Restore curwin/curbuf and a few other things. aucmd_restbuf(&aco); + if (newbuf_to_wipe.br_buf != NULL && bufref_valid(&newbuf_to_wipe)) { wipe_buffer(newbuf_to_wipe.br_buf, false); } @@ -5634,7 +5683,7 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin // When autocommands/'autochdir' option changed directory: go back. // Let the caller know what the resulting dir was first, in case it is // important. - os_dirname((char_u *)resulting_dir, MAXPATHL); + os_dirname(resulting_dir, MAXPATHL); restore_start_dir(dirname_start); if (!bufref_valid(&newbufref)) { @@ -5677,7 +5726,7 @@ static void wipe_dummy_buffer(buf_T *buf, char *dirname_start) cleanup_T cs; // Reset the error/interrupt/exception state here so that aborting() - // returns FALSE when wiping out the buffer. Otherwise it doesn't + // returns false when wiping out the buffer. Otherwise it doesn't // work when got_int is set. enter_cleanup(&cs); @@ -5696,12 +5745,14 @@ static void wipe_dummy_buffer(buf_T *buf, char *dirname_start) // 'autochdir' option have changed it. static void unload_dummy_buffer(buf_T *buf, char *dirname_start) { - if (curbuf != buf) { // safety check - close_buffer(NULL, buf, DOBUF_UNLOAD, false, true); - - // When autocommands/'autochdir' option changed directory: go back. - restore_start_dir(dirname_start); + if (curbuf == buf) { // safety check + return; } + + close_buffer(NULL, buf, DOBUF_UNLOAD, false, true); + + // When autocommands/'autochdir' option changed directory: go back. + restore_start_dir(dirname_start); } /// Copy the specified quickfix entry items into a new dict and append the dict @@ -5826,32 +5877,34 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict) int status = FAIL; // Only a List value is supported - if (di->di_tv.v_type == VAR_LIST && di->di_tv.vval.v_list != NULL) { - char *errorformat = p_efm; - dictitem_T *efm_di; - // If errorformat is supplied then use it, otherwise use the 'efm' - // option setting - if ((efm_di = tv_dict_find(what, S_LEN("efm"))) != NULL) { - if (efm_di->di_tv.v_type != VAR_STRING - || efm_di->di_tv.vval.v_string == NULL) { - return FAIL; - } - errorformat = efm_di->di_tv.vval.v_string; - } - - list_T *l = tv_list_alloc(kListLenMayKnow); - qf_info_T *const qi = qf_alloc_stack(QFLT_INTERNAL); + if (di->di_tv.v_type != VAR_LIST || di->di_tv.vval.v_list == NULL) { + return FAIL; + } - if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat, - true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { - (void)get_errorlist(qi, NULL, 0, 0, l); - qf_free(&qi->qf_lists[0]); + char *errorformat = p_efm; + dictitem_T *efm_di; + // If errorformat is supplied then use it, otherwise use the 'efm' + // option setting + if ((efm_di = tv_dict_find(what, S_LEN("efm"))) != NULL) { + if (efm_di->di_tv.v_type != VAR_STRING + || efm_di->di_tv.vval.v_string == NULL) { + return FAIL; } - xfree(qi); + errorformat = efm_di->di_tv.vval.v_string; + } + + list_T *l = tv_list_alloc(kListLenMayKnow); + qf_info_T *const qi = qf_alloc_stack(QFLT_INTERNAL); - tv_dict_add_list(retdict, S_LEN("items"), l); - status = OK; + if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat, + true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) { + (void)get_errorlist(qi, NULL, 0, 0, l); + qf_free(&qi->qf_lists[0]); } + xfree(qi); + + tv_dict_add_list(retdict, S_LEN("items"), l); + status = OK; return status; } @@ -6500,7 +6553,7 @@ static int qf_setprop_curidx(qf_info_T *qi, qf_list_T *qfl, const dictitem_T *di // If the specified index is '$', then use the last entry if (di->di_tv.v_type == VAR_STRING && di->di_tv.vval.v_string != NULL - && STRCMP(di->di_tv.vval.v_string, "$") == 0) { + && strcmp(di->di_tv.vval.v_string, "$") == 0) { newidx = qfl->qf_count; } else { // Otherwise use the specified index @@ -6668,7 +6721,8 @@ int set_errorlist(win_T *wp, list_T *list, int action, char *title, dict_T *what return retval; } -/// Mark the context as in use for all the lists in a quickfix stack. +/// Mark the quickfix context and callback function as in use for all the lists +/// in a quickfix stack. static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) { bool abort = false; @@ -6679,6 +6733,9 @@ static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) && ctx->v_type != VAR_STRING && ctx->v_type != VAR_FLOAT) { abort = set_ref_in_item(ctx, copyID, NULL, NULL); } + + Callback *cb = &qi->qf_lists[i].qf_qftf_cb; + abort = abort || set_ref_in_callback(cb, copyID, NULL, NULL); } return abort; @@ -6693,6 +6750,11 @@ bool set_ref_in_quickfix(int copyID) return abort; } + abort = set_ref_in_callback(&qftf_cb, copyID, NULL, NULL); + if (abort) { + return abort; + } + FOR_ALL_TAB_WINDOWS(tp, win) { if (win->w_llist != NULL) { abort = mark_quickfix_ctx(win->w_llist, copyID); @@ -6806,8 +6868,8 @@ void ex_cbuffer(exarg_T *eap) char *qf_title = qf_cmdtitle(*eap->cmdlinep); if (buf->b_sfname) { - vim_snprintf((char *)IObuff, IOSIZE, "%s (%s)", qf_title, buf->b_sfname); - qf_title = (char *)IObuff; + vim_snprintf(IObuff, IOSIZE, "%s (%s)", qf_title, buf->b_sfname); + qf_title = IObuff; } incr_quickfix_busy(); @@ -6885,43 +6947,45 @@ void ex_cexpr(exarg_T *eap) // Evaluate the expression. When the result is a string or a list we can // use it to fill the errorlist. typval_T tv; - if (eval0(eap->arg, &tv, &eap->nextcmd, true) != FAIL) { - if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) - || tv.v_type == VAR_LIST) { - incr_quickfix_busy(); - int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm, - (eap->cmdidx != CMD_caddexpr - && eap->cmdidx != CMD_laddexpr), - (linenr_T)0, (linenr_T)0, - qf_cmdtitle(*eap->cmdlinep), NULL); - if (qf_stack_empty(qi)) { - decr_quickfix_busy(); - goto cleanup; - } - if (res >= 0) { - qf_list_changed(qf_get_curlist(qi)); - } - // Remember the current quickfix list identifier, so that we can - // check for autocommands changing the current quickfix list. - unsigned save_qfid = qf_get_curlist(qi)->qf_id; - if (au_name != NULL) { - apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf); - } - // Jump to the first error for a new list and if autocmds didn't - // free the list. - if (res > 0 - && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr) - && qflist_valid(wp, save_qfid)) { - // display the first error - qf_jump_first(qi, save_qfid, eap->forceit); - } + if (eval0(eap->arg, &tv, &eap->nextcmd, true) == FAIL) { + return; + } + + if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) + || tv.v_type == VAR_LIST) { + incr_quickfix_busy(); + int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm, + (eap->cmdidx != CMD_caddexpr + && eap->cmdidx != CMD_laddexpr), + (linenr_T)0, (linenr_T)0, + qf_cmdtitle(*eap->cmdlinep), NULL); + if (qf_stack_empty(qi)) { decr_quickfix_busy(); - } else { - emsg(_("E777: String or List expected")); + goto cleanup; } -cleanup: - tv_clear(&tv); + if (res >= 0) { + qf_list_changed(qf_get_curlist(qi)); + } + // Remember the current quickfix list identifier, so that we can + // check for autocommands changing the current quickfix list. + unsigned save_qfid = qf_get_curlist(qi)->qf_id; + if (au_name != NULL) { + apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, true, curbuf); + } + // Jump to the first error for a new list and if autocmds didn't + // free the list. + if (res > 0 + && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr) + && qflist_valid(wp, save_qfid)) { + // display the first error + qf_jump_first(qi, save_qfid, eap->forceit); + } + decr_quickfix_busy(); + } else { + emsg(_("E777: String or List expected")); } +cleanup: + tv_clear(&tv); } // Get the location list for ":lhelpgrep" @@ -6952,10 +7016,10 @@ static void hgr_search_file(qf_list_T *qfl, char *fname, regmatch_T *p_regmatch) linenr_T lnum = 1; while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { - char *line = (char *)IObuff; + char *line = IObuff; if (vim_regexec(p_regmatch, line, (colnr_T)0)) { - int l = (int)STRLEN(line); + int l = (int)strlen(line); // remove trailing CR, LF, spaces, etc. while (l > 0 && line[l - 1] <= ' ') { @@ -6970,8 +7034,8 @@ static void hgr_search_file(qf_list_T *qfl, char *fname, regmatch_T *p_regmatch) line, lnum, 0, - (int)(p_regmatch->startp[0] - (char_u *)line) + 1, // col - (int)(p_regmatch->endp[0] - (char_u *)line) + (int)(p_regmatch->startp[0] - line) + 1, // col + (int)(p_regmatch->endp[0] - line) + 1, // end_col false, // vis_col NULL, // search pattern @@ -6980,13 +7044,13 @@ static void hgr_search_file(qf_list_T *qfl, char *fname, regmatch_T *p_regmatch) true) // valid == QF_FAIL) { got_int = true; - if ((char_u *)line != IObuff) { + if (line != IObuff) { xfree(line); } break; } } - if ((char_u *)line != IObuff) { + if (line != IObuff) { xfree(line); } lnum++; @@ -7012,9 +7076,9 @@ static void hgr_search_files_in_dir(qf_list_T *qfl, char *dirname, regmatch_T *p for (int fi = 0; fi < fcount && !got_int; fi++) { // Skip files for a different language. if (lang != NULL - && STRNICMP(lang, fnames[fi] + STRLEN(fnames[fi]) - 3, 2) != 0 + && STRNICMP(lang, fnames[fi] + strlen(fnames[fi]) - 3, 2) != 0 && !(STRNICMP(lang, "en", 2) == 0 - && STRNICMP("txt", fnames[fi] + STRLEN(fnames[fi]) - 3, 3) + && STRNICMP("txt", fnames[fi] + strlen(fnames[fi]) - 3, 3) == 0)) { continue; } @@ -7033,11 +7097,11 @@ static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, const char FUNC_ATTR_NONNULL_ARG(1, 2) { // Go through all directories in 'runtimepath' - char *p = (char *)p_rtp; + char *p = p_rtp; while (*p != NUL && !got_int) { - copy_option_part(&p, (char *)NameBuff, MAXPATHL, ","); + copy_option_part(&p, NameBuff, MAXPATHL, ","); - hgr_search_files_in_dir(qfl, (char *)NameBuff, p_regmatch, (char *)lang); + hgr_search_files_in_dir(qfl, NameBuff, p_regmatch, (char *)lang); } } @@ -7045,7 +7109,7 @@ static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, const char void ex_helpgrep(exarg_T *eap) { qf_info_T *qi = &ql_info; - char *au_name = NULL; + char *au_name = NULL; switch (eap->cmdidx) { case CMD_helpgrep: @@ -7062,9 +7126,11 @@ void ex_helpgrep(exarg_T *eap) } } + bool updated = false; // Make 'cpoptions' empty, the 'l' flag should not be used here. char *const save_cpo = p_cpo; - p_cpo = (char *)empty_option; + const bool save_cpo_allocated = is_option_allocated("cpo"); + p_cpo = empty_option; bool new_qi = false; if (is_loclist_cmd(eap->cmdidx)) { @@ -7092,14 +7158,26 @@ void ex_helpgrep(exarg_T *eap) qfl->qf_ptr = qfl->qf_start; qfl->qf_index = 1; qf_list_changed(qfl); - qf_update_buffer(qi, NULL); + updated = true; } - if ((char_u *)p_cpo == empty_option) { + if (p_cpo == empty_option) { p_cpo = save_cpo; } else { - // Darn, some plugin changed the value. - free_string_option((char_u *)save_cpo); + // Darn, some plugin changed the value. If it's still empty it was + // changed and restored, need to restore in the complicated way. + if (*p_cpo == NUL) { + set_option_value_give_err("cpo", 0L, save_cpo, 0); + } + if (save_cpo_allocated) { + free_string_option(save_cpo); + } + } + + if (updated) { + // This may open a window and source scripts, do this after 'cpo' was + // restored. + qf_update_buffer(qi, NULL); } if (au_name != NULL) { @@ -7128,7 +7206,9 @@ void ex_helpgrep(exarg_T *eap) if (new_qi) { ll_free_all(&qi); } - } else if (curwin->w_llist == NULL) { + } else if (curwin->w_llist == NULL && new_qi) { + // current window didn't have a location list associated with it + // before. Associate the new location list now. curwin->w_llist = qi; } } @@ -7158,14 +7238,14 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T * } /// "getloclist()" function -void f_getloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getloclist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { win_T *wp = find_win_by_nr_or_id(&argvars[0]); get_qf_loc_list(false, wp, &argvars[1], rettv); } /// "getqflist()" functions -void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_getqflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { get_qf_loc_list(true, NULL, &argvars[0], rettv); } @@ -7252,7 +7332,7 @@ skip_args: } /// "setloclist()" function -void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_setloclist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = -1; @@ -7263,7 +7343,7 @@ void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "setqflist()" function -void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_setqflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { set_qf_ll_list(NULL, argvars, rettv); } diff --git a/src/nvim/rbuffer.c b/src/nvim/rbuffer.c index 2f718e9c2e..1088dd3778 100644 --- a/src/nvim/rbuffer.c +++ b/src/nvim/rbuffer.c @@ -2,15 +2,16 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <stdbool.h> #include <stddef.h> #include <string.h> +#include "nvim/macros.h" #include "nvim/memory.h" #include "nvim/rbuffer.h" -#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "rbuffer.c.generated.h" +# include "rbuffer.c.generated.h" // IWYU pragma: export #endif /// Creates a new `RBuffer` instance. @@ -164,7 +165,8 @@ void rbuffer_consumed_compact(RBuffer *buf, size_t count) assert(buf->read_ptr <= buf->write_ptr); rbuffer_consumed(buf, count); if (buf->read_ptr > buf->start_ptr) { - assert((size_t)(buf->read_ptr - buf->write_ptr) == buf->size); + assert((size_t)(buf->write_ptr - buf->read_ptr) == buf->size + || buf->write_ptr == buf->start_ptr); memmove(buf->start_ptr, buf->read_ptr, buf->size); buf->read_ptr = buf->start_ptr; buf->write_ptr = buf->read_ptr + buf->size; diff --git a/src/nvim/rbuffer.h b/src/nvim/rbuffer.h index 3ebbc9d82c..63d5119004 100644 --- a/src/nvim/rbuffer.h +++ b/src/nvim/rbuffer.h @@ -17,6 +17,8 @@ #include <stddef.h> #include <stdint.h> +struct rbuffer; + // Macros that simplify working with the read/write pointers directly by hiding // ring buffer wrap logic. Some examples: // diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index b7ec4bf94e..122f3e2020 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -1,9 +1,7 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * Handling of regular expressions: vim_regcomp(), vim_regexec(), vim_regsub() - */ +// Handling of regular expressions: vim_regcomp(), vim_regexec(), vim_regsub() // By default: do not create debugging logs or files related to regular // expressions, even when compiling with -DDEBUG. @@ -15,21 +13,34 @@ #include <inttypes.h> #include <stdbool.h> #include <string.h> +#include <sys/types.h> #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/userfunc.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/os/input.h" #include "nvim/plines.h" -#include "nvim/profile.h" +#include "nvim/pos.h" #include "nvim/regexp.h" +#include "nvim/regexp_defs.h" #include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/undo_defs.h" #include "nvim/vim.h" #ifdef REGEXP_DEBUG @@ -41,21 +52,17 @@ # define BT_REGEXP_DEBUG_LOG_NAME "bt_regexp_debug.log" #endif -/* - * Magic characters have a special meaning, they don't match literally. - * Magic characters are negative. This separates them from literal characters - * (possibly multi-byte). Only ASCII characters can be Magic. - */ +// Magic characters have a special meaning, they don't match literally. +// Magic characters are negative. This separates them from literal characters +// (possibly multi-byte). Only ASCII characters can be Magic. #define Magic(x) ((int)(x) - 256) #define un_Magic(x) ((x) + 256) #define is_Magic(x) ((x) < 0) -/* - * We should define ftpr as a pointer to a function returning a pointer to - * a function returning a pointer to a function ... - * This is impossible, so we declare a pointer to a function returning a - * pointer to a function returning void. This should work for all compilers. - */ +// We should define ftpr as a pointer to a function returning a pointer to +// a function returning a pointer to a function ... +// This is impossible, so we declare a pointer to a function returning a +// pointer to a function returning void. This should work for all compilers. typedef void (*(*fptr_T)(int *, int))(void); static int no_Magic(int x) @@ -80,7 +87,7 @@ static int toggle_Magic(int x) #define REGMAGIC 0234 // Utility definitions. -#define UCHARAT(p) ((int)(*(char_u *)(p))) +#define UCHARAT(p) ((int)(*(uint8_t *)(p))) // Used for an error (down from) vim_regcomp(): give the error message, set // rc_did_emsg and return NULL @@ -97,20 +104,24 @@ static int toggle_Magic(int x) #define MAX_LIMIT (32767L << 16L) -static char_u e_missingbracket[] = N_("E769: Missing ] after %s["); -static char_u e_reverse_range[] = N_("E944: Reverse range in character class"); -static char_u e_large_class[] = N_("E945: Range too large in character class"); -static char_u e_unmatchedpp[] = N_("E53: Unmatched %s%%("); -static char_u e_unmatchedp[] = N_("E54: Unmatched %s("); -static char_u e_unmatchedpar[] = N_("E55: Unmatched %s)"); -static char_u e_z_not_allowed[] = N_("E66: \\z( not allowed here"); -static char_u e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here"); -static char_u e_missing_sb[] = N_("E69: Missing ] after %s%%["); -static char_u e_empty_sb[] = N_("E70: Empty %s%%[]"); -static char_u e_recursive[] = N_("E956: Cannot use pattern recursively"); -static char_u e_regexp_number_after_dot_pos_search[] +static char e_missingbracket[] = N_("E769: Missing ] after %s["); +static char e_reverse_range[] = N_("E944: Reverse range in character class"); +static char e_large_class[] = N_("E945: Range too large in character class"); +static char e_unmatchedpp[] = N_("E53: Unmatched %s%%("); +static char e_unmatchedp[] = N_("E54: Unmatched %s("); +static char e_unmatchedpar[] = N_("E55: Unmatched %s)"); +static char e_z_not_allowed[] = N_("E66: \\z( not allowed here"); +static char e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here"); +static char e_missing_sb[] = N_("E69: Missing ] after %s%%["); +static char e_empty_sb[] = N_("E70: Empty %s%%[]"); +static char e_recursive[] = N_("E956: Cannot use pattern recursively"); +static char e_regexp_number_after_dot_pos_search_chr[] = N_("E1204: No Number allowed after .: '\\%%%c'"); -static char_u e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep"); +static char e_nfa_regexp_missing_value_in_chr[] + = N_("E1273: (NFA regexp) missing value in '\\%%%c'"); +static char e_atom_engine_must_be_at_start_of_pattern[] + = N_("E1281: Atom '\\%%#=%c' must be at the start of the pattern"); +static char e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep"); #define NOT_MULTI 0 #define MULTI_ONE 1 @@ -137,30 +148,26 @@ static int re_multi_type(int c) return NOT_MULTI; } -static char_u *reg_prev_sub = NULL; - -/* - * REGEXP_INRANGE contains all characters which are always special in a [] - * range after '\'. - * REGEXP_ABBR contains all characters which act as abbreviations after '\'. - * These are: - * \n - New line (NL). - * \r - Carriage Return (CR). - * \t - Tab (TAB). - * \e - Escape (ESC). - * \b - Backspace (Ctrl_H). - * \d - Character code in decimal, eg \d123 - * \o - Character code in octal, eg \o80 - * \x - Character code in hex, eg \x4a - * \u - Multibyte character code, eg \u20ac - * \U - Long multibyte character code, eg \U12345678 - */ +static char *reg_prev_sub = NULL; + +// REGEXP_INRANGE contains all characters which are always special in a [] +// range after '\'. +// REGEXP_ABBR contains all characters which act as abbreviations after '\'. +// These are: +// \n - New line (NL). +// \r - Carriage Return (CR). +// \t - Tab (TAB). +// \e - Escape (ESC). +// \b - Backspace (Ctrl_H). +// \d - Character code in decimal, eg \d123 +// \o - Character code in octal, eg \o80 +// \x - Character code in hex, eg \x4a +// \u - Multibyte character code, eg \u20ac +// \U - Long multibyte character code, eg \U12345678 static char REGEXP_INRANGE[] = "]^-n\\"; static char REGEXP_ABBR[] = "nrtebdoxuU"; -/* - * Translate '\x' to its control character, except "\n", which is Magic. - */ +// Translate '\x' to its control character, except "\n", which is Magic. static int backslash_trans(int c) { switch (c) { @@ -181,8 +188,7 @@ static int backslash_trans(int c) /// recognized. Otherwise "pp" is advanced to after the item. static int get_char_class(char **pp) { - static const char *(class_names[]) = - { + static const char *(class_names[]) = { "alnum:]", #define CLASS_ALNUM 0 "alpha:]", @@ -227,8 +233,8 @@ static int get_char_class(char **pp) if ((*pp)[1] == ':') { for (i = 0; i < (int)ARRAY_SIZE(class_names); i++) { - if (STRNCMP(*pp + 2, class_names[i], STRLEN(class_names[i])) == 0) { - *pp += STRLEN(class_names[i]) + 2; + if (strncmp(*pp + 2, class_names[i], strlen(class_names[i])) == 0) { + *pp += strlen(class_names[i]) + 2; return i; } } @@ -236,11 +242,9 @@ static int get_char_class(char **pp) return CLASS_NONE; } -/* - * Specific version of character class functions. - * Using a table to keep this fast. - */ -static short class_tab[256]; +// Specific version of character class functions. +// Using a table to keep this fast. +static int16_t class_tab[256]; #define RI_DIGIT 0x01 #define RI_HEX 0x02 @@ -312,24 +316,18 @@ static int re_has_z; ///< \z item detected static unsigned regflags; ///< RF_ flags for prog static int had_eol; ///< true when EOL found by vim_regcomp() -static int reg_magic; // magicness of the pattern: -#define MAGIC_NONE 1 // "\V" very unmagic -#define MAGIC_OFF 2 // "\M" or 'magic' off -#define MAGIC_ON 3 // "\m" or 'magic' -#define MAGIC_ALL 4 // "\v" very magic +static magic_T reg_magic; ///< magicness of the pattern static int reg_string; // matching with a string instead of a buffer // line static int reg_strict; // "[abc" is illegal -/* - * META contains all characters that may be magic, except '^' and '$'. - */ +// META contains all characters that may be magic, except '^' and '$'. // uncrustify:off // META[] is used often enough to justify turning it into a table. -static char_u META_flags[] = { +static uint8_t META_flags[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // % & ( ) * + . @@ -363,7 +361,7 @@ static int nextchr; // used for ungetchr() #define REG_NPAREN 3 // \%(\) typedef struct { - char_u *regparse; + char *regparse; int prevchr_len; int curchr; int prevchr; @@ -388,21 +386,19 @@ int re_multiline(const regprog_T *prog) return prog->regflags & RF_HASNL; } -/* - * Check for an equivalence class name "[=a=]". "pp" points to the '['. - * Returns a character representing the class. Zero means that no item was - * recognized. Otherwise "pp" is advanced to after the item. - */ +// Check for an equivalence class name "[=a=]". "pp" points to the '['. +// Returns a character representing the class. Zero means that no item was +// recognized. Otherwise "pp" is advanced to after the item. static int get_equi_class(char **pp) { int c; int l = 1; - char_u *p = (char_u *)(*pp); + char *p = *pp; if (p[1] == '=' && p[2] != NUL) { - l = utfc_ptr2len((char *)p + 2); + l = utfc_ptr2len(p + 2); if (p[l + 2] == '=' && p[l + 3] == ']') { - c = utf_ptr2char((char *)p + 2); + c = utf_ptr2char(p + 2); *pp += l + 4; return c; } @@ -410,22 +406,20 @@ static int get_equi_class(char **pp) return 0; } -/* - * Check for a collating element "[.a.]". "pp" points to the '['. - * Returns a character. Zero means that no item was recognized. Otherwise - * "pp" is advanced to after the item. - * Currently only single characters are recognized! - */ +// Check for a collating element "[.a.]". "pp" points to the '['. +// Returns a character. Zero means that no item was recognized. Otherwise +// "pp" is advanced to after the item. +// Currently only single characters are recognized! static int get_coll_element(char **pp) { int c; int l = 1; - char_u *p = (char_u *)(*pp); + char *p = *pp; if (p[0] != NUL && p[1] == '.' && p[2] != NUL) { - l = utfc_ptr2len((char *)p + 2); + l = utfc_ptr2len(p + 2); if (p[l + 2] == '.' && p[l + 3] == ']') { - c = utf_ptr2char((char *)p + 2); + c = utf_ptr2char(p + 2); *pp += l + 4; return c; } @@ -443,7 +437,7 @@ static void get_cpo_flags(void) /// Skip over a "[]" range. /// "p" must point to the character after the '['. /// The returned pointer is on the matching ']', or the terminating NUL. -static char_u *skip_anyof(char *p) +static char *skip_anyof(char *p) { int l; @@ -462,9 +456,9 @@ static char_u *skip_anyof(char *p) MB_PTR_ADV(p); } } else if (*p == '\\' - && (vim_strchr(REGEXP_INRANGE, p[1]) != NULL + && (vim_strchr(REGEXP_INRANGE, (uint8_t)p[1]) != NULL || (!reg_cpo_lit - && vim_strchr(REGEXP_ABBR, p[1]) != NULL))) { + && vim_strchr(REGEXP_ABBR, (uint8_t)p[1]) != NULL))) { p += 2; } else if (*p == '[') { if (get_char_class(&p) == CLASS_NONE @@ -478,20 +472,42 @@ static char_u *skip_anyof(char *p) } } - return (char_u *)p; + return p; } /// Skip past regular expression. -/// Stop at end of "startp" or where "dirc" is found ('/', '?', etc). +/// Stop at end of "startp" or where "delim" is found ('/', '?', etc). /// Take care of characters with a backslash in front of it. /// Skip strings inside [ and ]. +char *skip_regexp(char *startp, int delim, int magic) +{ + return skip_regexp_ex(startp, delim, magic, NULL, NULL, NULL); +} + +/// Call skip_regexp() and when the delimiter does not match give an error and +/// return NULL. +char *skip_regexp_err(char *startp, int delim, int magic) +{ + char *p = skip_regexp(startp, delim, magic); + + if (*p != delim) { + semsg(_("E654: missing delimiter after search pattern: %s"), startp); + return NULL; + } + return p; +} + +/// skip_regexp() with extra arguments: /// When "newp" is not NULL and "dirc" is '?', make an allocated copy of the /// expression and change "\?" to "?". If "*newp" is not NULL the expression /// is changed in-place. -char_u *skip_regexp(char_u *startp, int dirc, int magic, char **newp) +/// If a "\?" is changed to "?" then "dropped" is incremented, unless NULL. +/// If "magic_val" is not NULL, returns the effective magicness of the pattern +char *skip_regexp_ex(char *startp, int dirc, int magic, char **newp, int *dropped, + magic_T *magic_val) { - int mymagic; - char_u *p = startp; + magic_T mymagic; + char *p = startp; if (magic) { mymagic = MAGIC_ON; @@ -506,7 +522,7 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char **newp) } if ((p[0] == '[' && mymagic >= MAGIC_ON) || (p[0] == '\\' && p[1] == '[' && mymagic <= MAGIC_OFF)) { - p = skip_anyof((char *)p + 1); + p = skip_anyof(p + 1); if (p[0] == NUL) { break; } @@ -514,8 +530,11 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char **newp) if (dirc == '?' && newp != NULL && p[1] == '?') { // change "\?" to "?", make a copy first. if (*newp == NULL) { - *newp = (char *)vim_strsave(startp); - p = (char_u *)(*newp) + (p - startp); + *newp = xstrdup(startp); + p = *newp + (p - startp); + } + if (dropped != NULL) { + (*dropped)++; } STRMOVE(p, p + 1); } else { @@ -528,6 +547,9 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char **newp) } } } + if (magic_val != NULL) { + *magic_val = mymagic; + } return p; } @@ -536,25 +558,21 @@ static int prevchr_len; // byte length of previous char static int at_start; // True when on the first character static int prev_at_start; // True when on the second character -/* - * Start parsing at "str". - */ -static void initchr(char_u *str) +// Start parsing at "str". +static void initchr(char *str) { - regparse = (char *)str; + regparse = str; prevchr_len = 0; curchr = prevprevchr = prevchr = nextchr = -1; at_start = true; prev_at_start = false; } -/* - * Save the current parse state, so that it can be restored and parsing - * starts in the same state again. - */ +// Save the current parse state, so that it can be restored and parsing +// starts in the same state again. static void save_parse_state(parse_state_T *ps) { - ps->regparse = (char_u *)regparse; + ps->regparse = regparse; ps->prevchr_len = prevchr_len; ps->curchr = curchr; ps->prevchr = prevchr; @@ -565,12 +583,10 @@ static void save_parse_state(parse_state_T *ps) ps->regnpar = regnpar; } -/* - * Restore a previously saved parse state. - */ +// Restore a previously saved parse state. static void restore_parse_state(parse_state_T *ps) { - regparse = (char *)ps->regparse; + regparse = ps->regparse; prevchr_len = ps->prevchr_len; curchr = ps->curchr; prevchr = ps->prevchr; @@ -581,9 +597,7 @@ static void restore_parse_state(parse_state_T *ps) regnpar = ps->regnpar; } -/* - * Get the next character without advancing. - */ +// Get the next character without advancing. static int peekchr(void) { static int after_slash = false; @@ -663,7 +677,7 @@ static int peekchr(void) // '$' is only magic as the very last char and if it's in front of // either "\|", "\)", "\&", or "\n" if (reg_magic >= MAGIC_OFF) { - char_u *p = (char_u *)regparse + 1; + uint8_t *p = (uint8_t *)regparse + 1; bool is_magic_all = (reg_magic == MAGIC_ALL); // ignore \c \C \m \M \v \V and \Z after '$' @@ -710,9 +724,7 @@ static int peekchr(void) after_slash--; curchr = toggle_Magic(curchr); } else if (vim_strchr(REGEXP_ABBR, c)) { - /* - * Handle abbreviations, like "\t" for TAB -- webb - */ + // Handle abbreviations, like "\t" for TAB -- webb curchr = backslash_trans(c); } else if (reg_magic == MAGIC_NONE && (c == '$' || c == '^')) { curchr = toggle_Magic(c); @@ -731,9 +743,7 @@ static int peekchr(void) return curchr; } -/* - * Eat one lexed character. Do this in a way that we can undo it. - */ +// Eat one lexed character. Do this in a way that we can undo it. static void skipchr(void) { // peekchr() eats a backslash, do the same here @@ -755,10 +765,8 @@ static void skipchr(void) nextchr = -1; } -/* - * Skip a character while keeping the value of prev_at_start for at_start. - * prevchr and prevprevchr are also kept. - */ +// Skip a character while keeping the value of prev_at_start for at_start. +// prevchr and prevprevchr are also kept. static void skipchr_keepstart(void) { int as = prev_at_start; @@ -771,10 +779,8 @@ static void skipchr_keepstart(void) prevprevchr = prpr; } -/* - * Get the next character from the pattern. We know about magic and such, so - * therefore we need a lexical analyzer. - */ +// Get the next character from the pattern. We know about magic and such, so +// therefore we need a lexical analyzer. static int getchr(void) { int chr = peekchr(); @@ -783,9 +789,7 @@ static int getchr(void) return chr; } -/* - * put character back. Works only once! - */ +// put character back. Works only once! static void ungetchr(void) { nextchr = curchr; @@ -799,15 +803,13 @@ static void ungetchr(void) regparse -= prevchr_len; } -/* - * Get and return the value of the hex string at the current position. - * Return -1 if there is no valid hex number. - * The position is updated: - * blahblah\%x20asdf - * before-^ ^-after - * The parameter controls the maximum number of input characters. This will be - * 2 when reading a \%x20 sequence and 4 when reading a \%u20AC sequence. - */ +// Get and return the value of the hex string at the current position. +// Return -1 if there is no valid hex number. +// The position is updated: +// blahblah\%x20asdf +// before-^ ^-after +// The parameter controls the maximum number of input characters. This will be +// 2 when reading a \%x20 sequence and 4 when reading a \%u20AC sequence. static int64_t gethexchrs(int maxinputlen) { int64_t nr = 0; @@ -830,10 +832,8 @@ static int64_t gethexchrs(int maxinputlen) return nr; } -/* - * Get and return the value of the decimal string immediately after the - * current position. Return -1 for invalid. Consumes all digits. - */ +// Get and return the value of the decimal string immediately after the +// current position. Return -1 for invalid. Consumes all digits. static int64_t getdecchrs(void) { int64_t nr = 0; @@ -857,14 +857,12 @@ static int64_t getdecchrs(void) return nr; } -/* - * get and return the value of the octal string immediately after the current - * position. Return -1 for invalid, or 0-255 for valid. Smart enough to handle - * numbers > 377 correctly (for example, 400 is treated as 40) and doesn't - * treat 8 or 9 as recognised characters. Position is updated: - * blahblah\%o210asdf - * before-^ ^-after - */ +// get and return the value of the octal string immediately after the current +// position. Return -1 for invalid, or 0-255 for valid. Smart enough to handle +// numbers > 377 correctly (for example, 400 is treated as 40) and doesn't +// treat 8 or 9 as recognised characters. Position is updated: +// blahblah\%o210asdf +// before-^ ^-after static int64_t getoctchrs(void) { int64_t nr = 0; @@ -887,16 +885,14 @@ static int64_t getoctchrs(void) return nr; } -/* - * read_limits - Read two integers to be taken as a minimum and maximum. - * If the first character is '-', then the range is reversed. - * Should end with 'end'. If minval is missing, zero is default, if maxval is - * missing, a very big number is the default. - */ +// read_limits - Read two integers to be taken as a minimum and maximum. +// If the first character is '-', then the range is reversed. +// Should end with 'end'. If minval is missing, zero is default, if maxval is +// missing, a very big number is the default. static int read_limits(long *minval, long *maxval) { int reverse = false; - char_u *first_char; + char *first_char; long tmp; if (*regparse == '-') { @@ -904,7 +900,7 @@ static int read_limits(long *minval, long *maxval) regparse++; reverse = true; } - first_char = (char_u *)regparse; + first_char = regparse; *minval = getdigits_long(®parse, false, 0); if (*regparse == ',') { // There is a comma. if (ascii_isdigit(*++regparse)) { @@ -924,10 +920,8 @@ static int read_limits(long *minval, long *maxval) EMSG2_RET_FAIL(_("E554: Syntax error in %s{...}"), reg_magic == MAGIC_ALL); } - /* - * Reverse the range if there was a '-', or make sure it is in the right - * order otherwise. - */ + // Reverse the range if there was a '-', or make sure it is in the right + // order otherwise. if ((!reverse && *minval > *maxval) || (reverse && *minval < *maxval)) { tmp = *minval; *minval = *maxval; @@ -937,18 +931,14 @@ static int read_limits(long *minval, long *maxval) return OK; } -/* - * vim_regexec and friends - */ +// vim_regexec and friends -/* - * Global work variables for vim_regexec(). - */ +// Global work variables for vim_regexec(). // Sometimes need to save a copy of a line. Since alloc()/free() is very // slow, we keep one allocated piece of memory and only re-allocate it when // it's too small. It's freed in bt_regexec_both() when finished. -static char_u *reg_tofree = NULL; +static uint8_t *reg_tofree = NULL; static unsigned reg_tofreelen; // Structure used to store the execution state of the regex engine. @@ -969,10 +959,12 @@ static unsigned reg_tofreelen; typedef struct { regmatch_T *reg_match; regmmatch_T *reg_mmatch; - char_u **reg_startp; - char_u **reg_endp; + + uint8_t **reg_startp; + uint8_t **reg_endp; lpos_T *reg_startpos; lpos_T *reg_endpos; + win_T *reg_win; buf_T *reg_buf; linenr_T reg_firstlnum; @@ -981,8 +973,8 @@ typedef struct { // The current match-position is remembered with these variables: linenr_T lnum; ///< line number, relative to first line - char_u *line; ///< start of current line - char_u *input; ///< current input, points into "line" + uint8_t *line; ///< start of current line + uint8_t *input; ///< current input, points into "line" int need_clear_subexpr; ///< subexpressions still need to be cleared int need_clear_zsubexpr; ///< extmatch subexpressions still need to be @@ -1026,10 +1018,8 @@ static bool reg_iswordc(int c) return vim_iswordc_buf(c, rex.reg_buf); } -/* - * Get pointer to the line "lnum", which is relative to "reg_firstlnum". - */ -static char_u *reg_getline(linenr_T lnum) +// Get pointer to the line "lnum", which is relative to "reg_firstlnum". +static char *reg_getline(linenr_T lnum) { // when looking behind for a match/no-match lnum is negative. But we // can't go before line 1 @@ -1038,22 +1028,20 @@ static char_u *reg_getline(linenr_T lnum) } if (lnum > rex.reg_maxline) { // Must have matched the "\n" in the last line. - return (char_u *)""; + return ""; } return ml_get_buf(rex.reg_buf, rex.reg_firstlnum + lnum, false); } -static char_u *reg_startzp[NSUBEXP]; // Workspace to mark beginning -static char_u *reg_endzp[NSUBEXP]; // and end of \z(...\) matches +static uint8_t *reg_startzp[NSUBEXP]; // Workspace to mark beginning +static uint8_t *reg_endzp[NSUBEXP]; // and end of \z(...\) matches static lpos_T reg_startzpos[NSUBEXP]; // idem, beginning pos static lpos_T reg_endzpos[NSUBEXP]; // idem, end pos // true if using multi-line regexp. #define REG_MULTI (rex.reg_match == NULL) -/* - * Create a new extmatch and mark it as referenced once. - */ +// Create a new extmatch and mark it as referenced once. static reg_extmatch_T *make_extmatch(void) FUNC_ATTR_NONNULL_RET { @@ -1062,9 +1050,7 @@ static reg_extmatch_T *make_extmatch(void) return em; } -/* - * Add a reference to an extmatch. - */ +// Add a reference to an extmatch. reg_extmatch_T *ref_extmatch(reg_extmatch_T *em) { if (em != NULL) { @@ -1073,10 +1059,8 @@ reg_extmatch_T *ref_extmatch(reg_extmatch_T *em) return em; } -/* - * Remove a reference to an extmatch. If there are no references left, free - * the info. - */ +// Remove a reference to an extmatch. If there are no references left, free +// the info. void unref_extmatch(reg_extmatch_T *em) { int i; @@ -1093,7 +1077,8 @@ void unref_extmatch(reg_extmatch_T *em) static int reg_prev_class(void) { if (rex.input > rex.line) { - return mb_get_class_tab(rex.input - 1 - utf_head_off(rex.line, rex.input - 1), + return mb_get_class_tab((char *)rex.input - 1 - + utf_head_off((char *)rex.line, (char *)rex.input - 1), rex.reg_buf->b_chartab); } return -1; @@ -1111,8 +1096,8 @@ static bool reg_match_visual(void) colnr_T start2, end2; colnr_T curswant; - // Check if the buffer is the current buffer. - if (rex.reg_buf != curbuf || VIsual.lnum == 0) { + // Check if the buffer is the current buffer and not using a string. + if (rex.reg_buf != curbuf || VIsual.lnum == 0 || !REG_MULTI) { return false; } @@ -1162,10 +1147,10 @@ static bool reg_match_visual(void) } // getvvcol() flushes rex.line, need to get it again - rex.line = reg_getline(rex.lnum); + rex.line = (uint8_t *)reg_getline(rex.lnum); rex.input = rex.line + col; - unsigned int cols_u = win_linetabsize(wp, rex.line, col); + unsigned int cols_u = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col); assert(cols_u <= MAXCOL); colnr_T cols = (colnr_T)cols_u; if (cols < start || cols > end - (*p_sel == 'e')) { @@ -1175,10 +1160,8 @@ static bool reg_match_visual(void) return true; } -/* - * Check the regexp program for its magic number. - * Return true if it's wrong. - */ +// Check the regexp program for its magic number. +// Return true if it's wrong. static int prog_magic_wrong(void) { regprog_T *prog; @@ -1196,62 +1179,62 @@ static int prog_magic_wrong(void) return false; } -/* - * Cleanup the subexpressions, if this wasn't done yet. - * This construction is used to clear the subexpressions only when they are - * used (to increase speed). - */ +// Cleanup the subexpressions, if this wasn't done yet. +// This construction is used to clear the subexpressions only when they are +// used (to increase speed). static void cleanup_subexpr(void) { - if (rex.need_clear_subexpr) { - if (REG_MULTI) { - // Use 0xff to set lnum to -1 - memset(rex.reg_startpos, 0xff, sizeof(lpos_T) * NSUBEXP); - memset(rex.reg_endpos, 0xff, sizeof(lpos_T) * NSUBEXP); - } else { - memset(rex.reg_startp, 0, sizeof(char_u *) * NSUBEXP); - memset(rex.reg_endp, 0, sizeof(char_u *) * NSUBEXP); - } - rex.need_clear_subexpr = false; + if (!rex.need_clear_subexpr) { + return; + } + + if (REG_MULTI) { + // Use 0xff to set lnum to -1 + memset(rex.reg_startpos, 0xff, sizeof(lpos_T) * NSUBEXP); + memset(rex.reg_endpos, 0xff, sizeof(lpos_T) * NSUBEXP); + } else { + memset(rex.reg_startp, 0, sizeof(char *) * NSUBEXP); + memset(rex.reg_endp, 0, sizeof(char *) * NSUBEXP); } + rex.need_clear_subexpr = false; } static void cleanup_zsubexpr(void) { - if (rex.need_clear_zsubexpr) { - if (REG_MULTI) { - // Use 0xff to set lnum to -1 - memset(reg_startzpos, 0xff, sizeof(lpos_T) * NSUBEXP); - memset(reg_endzpos, 0xff, sizeof(lpos_T) * NSUBEXP); - } else { - memset(reg_startzp, 0, sizeof(char_u *) * NSUBEXP); - memset(reg_endzp, 0, sizeof(char_u *) * NSUBEXP); - } - rex.need_clear_zsubexpr = false; + if (!rex.need_clear_zsubexpr) { + return; + } + + if (REG_MULTI) { + // Use 0xff to set lnum to -1 + memset(reg_startzpos, 0xff, sizeof(lpos_T) * NSUBEXP); + memset(reg_endzpos, 0xff, sizeof(lpos_T) * NSUBEXP); + } else { + memset(reg_startzp, 0, sizeof(char *) * NSUBEXP); + memset(reg_endzp, 0, sizeof(char *) * NSUBEXP); } + rex.need_clear_zsubexpr = false; } // Advance rex.lnum, rex.line and rex.input to the next line. static void reg_nextline(void) { - rex.line = reg_getline(++rex.lnum); + rex.line = (uint8_t *)reg_getline(++rex.lnum); rex.input = rex.line; fast_breakcheck(); } -/* - * Check whether a backreference matches. - * Returns RA_FAIL, RA_NOMATCH or RA_MATCH. - * If "bytelen" is not NULL, it is set to the byte length of the match in the - * last line. - */ +// Check whether a backreference matches. +// Returns RA_FAIL, RA_NOMATCH or RA_MATCH. +// If "bytelen" is not NULL, it is set to the byte length of the match in the +// last line. static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T end_lnum, colnr_T end_col, int *bytelen) { linenr_T clnum = start_lnum; colnr_T ccol = start_col; int len; - char_u *p; + char *p; if (bytelen != NULL) { *bytelen = 0; @@ -1260,7 +1243,7 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e // Since getting one line may invalidate the other, need to make copy. // Slow! if (rex.line != reg_tofree) { - len = (int)STRLEN(rex.line); + len = (int)strlen((char *)rex.line); if (reg_tofree == NULL || len >= (int)reg_tofreelen) { len += 50; // get some extra xfree(reg_tofree); @@ -1279,10 +1262,10 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e if (clnum == end_lnum) { len = end_col - ccol; } else { - len = (int)STRLEN(p + ccol); + len = (int)strlen(p + ccol); } - if (cstrncmp(p + ccol, rex.input, &len) != 0) { + if (cstrncmp(p + ccol, (char *)rex.input, &len) != 0) { return RA_NOMATCH; // doesn't match } if (bytelen != NULL) { @@ -1328,8 +1311,7 @@ typedef struct { } decomp_T; // 0xfb20 - 0xfb4f -static decomp_T decomp_table[0xfb4f - 0xfb20 + 1] = -{ +static decomp_T decomp_table[0xfb4f - 0xfb20 + 1] = { { 0x5e2, 0, 0 }, // 0xfb20 alt ayin { 0x5d0, 0, 0 }, // 0xfb21 alt alef { 0x5d3, 0, 0 }, // 0xfb22 alt dalet @@ -1395,23 +1377,23 @@ static void mb_decompose(int c, int *c1, int *c2, int *c3) } } -// Compare two strings, ignore case if rex.reg_ic set. -// Return 0 if strings match, non-zero otherwise. -// Correct the length "*n" when composing characters are ignored. -static int cstrncmp(char_u *s1, char_u *s2, int *n) +/// Compare two strings, ignore case if rex.reg_ic set. +/// Return 0 if strings match, non-zero otherwise. +/// Correct the length "*n" when composing characters are ignored. +static int cstrncmp(char *s1, char *s2, int *n) { int result; if (!rex.reg_ic) { - result = STRNCMP(s1, s2, *n); + result = strncmp(s1, s2, (size_t)(*n)); } else { assert(*n >= 0); - result = mb_strnicmp(s1, s2, (size_t)*n); + result = mb_strnicmp(s1, s2, (size_t)(*n)); } // if it failed and it's utf8 and we want to combineignore: if (result != 0 && rex.reg_icombine) { - char_u *str1, *str2; + char *str1, *str2; int c1, c2, c11, c12; int junk; @@ -1421,12 +1403,12 @@ static int cstrncmp(char_u *s1, char_u *s2, int *n) str2 = s2; c1 = c2 = 0; while ((int)(str1 - s1) < *n) { - c1 = mb_ptr2char_adv((const char_u **)&str1); - c2 = mb_ptr2char_adv((const char_u **)&str2); + c1 = mb_ptr2char_adv((const char **)&str1); + c2 = mb_ptr2char_adv((const char **)&str2); - /* decompose the character if necessary, into 'base' characters - * because I don't care about Arabic, I will hard-code the Hebrew - * which I *do* care about! So sue me... */ + // decompose the character if necessary, into 'base' characters + // because I don't care about Arabic, I will hard-code the Hebrew + // which I *do* care about! So sue me... if (c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2))) { // decomposition necessary? mb_decompose(c1, &c11, &junk, &junk); @@ -1456,21 +1438,21 @@ static int cstrncmp(char_u *s1, char_u *s2, int *n) /// @param c character to find in @a s /// /// @return NULL if no match, otherwise pointer to the position in @a s -static inline char_u *cstrchr(const char_u *const s, const int c) +static inline char *cstrchr(const char *const s, const int c) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE { if (!rex.reg_ic) { - return (char_u *)vim_strchr((char *)s, c); + return vim_strchr(s, c); } // Use folded case for UTF-8, slow! For ASCII use libc strpbrk which is // expected to be highly optimized. if (c > 0x80) { const int folded_c = utf_fold(c); - for (const char_u *p = s; *p != NUL; p += utfc_ptr2len((char *)p)) { - if (utf_fold(utf_ptr2char((char *)p)) == folded_c) { - return (char_u *)p; + for (const char *p = s; *p != NUL; p += utfc_ptr2len(p)) { + if (utf_fold(utf_ptr2char(p)) == folded_c) { + return (char *)p; } } return NULL; @@ -1482,11 +1464,11 @@ static inline char_u *cstrchr(const char_u *const s, const int c) } else if (ASCII_ISLOWER(c)) { cc = TOUPPER_ASC(c); } else { - return (char_u *)vim_strchr((char *)s, c); + return vim_strchr(s, c); } char tofind[] = { (char)c, (char)cc, NUL }; - return (char_u *)strpbrk((const char *)s, tofind); + return strpbrk(s, tofind); } //////////////////////////////////////////////////////////////// @@ -1523,32 +1505,30 @@ static fptr_T do_Lower(int *d, int c) return (fptr_T)do_Lower; } -/* - * regtilde(): Replace tildes in the pattern by the old pattern. - * - * Short explanation of the tilde: It stands for the previous replacement - * pattern. If that previous pattern also contains a ~ we should go back a - * step further... But we insert the previous pattern into the current one - * and remember that. - * This still does not handle the case where "magic" changes. So require the - * user to keep his hands off of "magic". - * - * The tildes are parsed once before the first call to vim_regsub(). - */ -char_u *regtilde(char_u *source, int magic, bool preview) -{ - char_u *newsub = source; - char_u *tmpsub; - char_u *p; +/// regtilde(): Replace tildes in the pattern by the old pattern. +/// +/// Short explanation of the tilde: It stands for the previous replacement +/// pattern. If that previous pattern also contains a ~ we should go back a +/// step further... But we insert the previous pattern into the current one +/// and remember that. +/// This still does not handle the case where "magic" changes. So require the +/// user to keep his hands off of "magic". +/// +/// The tildes are parsed once before the first call to vim_regsub(). +char *regtilde(char *source, int magic, bool preview) +{ + char *newsub = source; + char *tmpsub; + char *p; int len; int prevlen; - for (p = newsub; *p; ++p) { + for (p = newsub; *p; p++) { if ((*p == '~' && magic) || (*p == '\\' && *(p + 1) == '~' && !magic)) { if (reg_prev_sub != NULL) { // length = len(newsub) - 1 + len(prev_sub) + 1 - prevlen = (int)STRLEN(reg_prev_sub); - tmpsub = xmalloc(STRLEN(newsub) + (size_t)prevlen); + prevlen = (int)strlen(reg_prev_sub); + tmpsub = xmalloc(strlen(newsub) + (size_t)prevlen); // copy prefix len = (int)(p - newsub); // not including ~ memmove(tmpsub, newsub, (size_t)len); @@ -1575,7 +1555,7 @@ char_u *regtilde(char_u *source, int magic, bool preview) if (*p == '\\' && p[1]) { // skip escaped characters p++; } - p += utfc_ptr2len((char *)p) - 1; + p += utfc_ptr2len(p) - 1; } } @@ -1584,7 +1564,7 @@ char_u *regtilde(char_u *source, int magic, bool preview) // Store a copy of newsub in reg_prev_sub. It is always allocated, // because recursive calls may make the returned string invalid. xfree(reg_prev_sub); - reg_prev_sub = vim_strsave(newsub); + reg_prev_sub = xstrdup(newsub); } return newsub; @@ -1607,12 +1587,12 @@ static regsubmatch_T rsm; // can only be used when can_f_submatch is true /// Put the submatches in "argv[argskip]" which is a list passed into /// call_func() by vim_regsub_both(). -static int fill_submatch_list(int argc FUNC_ATTR_UNUSED, typval_T *argv, int argskip, int argcount) +static int fill_submatch_list(int argc FUNC_ATTR_UNUSED, typval_T *argv, int argskip, ufunc_T *fp) FUNC_ATTR_NONNULL_ALL { typval_T *listarg = argv + argskip; - if (argcount == argskip) { + if (!fp->uf_varargs && fp->uf_args.ga_len <= argskip) { // called function doesn't take a submatches argument return argskip; } @@ -1623,14 +1603,14 @@ static int fill_submatch_list(int argc FUNC_ATTR_UNUSED, typval_T *argv, int arg // There are always 10 list items in staticList10_T. listitem_T *li = tv_list_first(listarg->vval.v_list); for (int i = 0; i < 10; i++) { - char_u *s = rsm.sm_match->startp[i]; + char *s = rsm.sm_match->startp[i]; if (s == NULL || rsm.sm_match->endp[i] == NULL) { s = NULL; } else { - s = vim_strnsave(s, (size_t)(rsm.sm_match->endp[i] - s)); + s = xstrnsave(s, (size_t)(rsm.sm_match->endp[i] - s)); } TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; - TV_LIST_ITEM_TV(li)->vval.v_string = (char *)s; + TV_LIST_ITEM_TV(li)->vval.v_string = s; li = TV_LIST_ITEM_NEXT(argv->vval.v_list, li); } return argskip + 1; @@ -1660,8 +1640,7 @@ static void clear_submatch_list(staticList10_T *sl) /// references invalid! /// /// Returns the size of the replacement, including terminating NUL. -int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, int destlen, - int flags) +int vim_regsub(regmatch_T *rmp, char *source, typval_T *expr, char *dest, int destlen, int flags) { regexec_T rex_save; bool rex_in_use_save = rex_in_use; @@ -1687,7 +1666,7 @@ int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, in return result; } -int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, int destlen, +int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char *source, char *dest, int destlen, int flags) { regexec_T rex_save; @@ -1717,7 +1696,7 @@ int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *de // When nesting more than a couple levels it's probably a mistake. #define MAX_REGSUB_NESTING 4 -static char_u *eval_result[MAX_REGSUB_NESTING] = { NULL, NULL, NULL, NULL }; +static char *eval_result[MAX_REGSUB_NESTING] = { NULL, NULL, NULL, NULL }; #if defined(EXITFREE) void free_resub_eval_result(void) @@ -1728,11 +1707,11 @@ void free_resub_eval_result(void) } #endif -static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int destlen, int flags) +static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen, int flags) { - char_u *src; - char_u *dst; - char_u *s; + char *src; + char *dst; + char *s; int c; int cc; int no = -1; @@ -1769,7 +1748,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des if (copy) { if (eval_result[nested] != NULL) { STRCPY(dest, eval_result[nested]); - dst += STRLEN(eval_result[nested]); + dst += strlen(eval_result[nested]); XFREE_CLEAR(eval_result[nested]); } } else { @@ -1805,17 +1784,17 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des argv[0].v_type = VAR_LIST; argv[0].vval.v_list = &matchList.sl_list; funcexe_T funcexe = FUNCEXE_INIT; - funcexe.argv_func = fill_submatch_list; - funcexe.evaluate = true; + funcexe.fe_argv_func = fill_submatch_list; + funcexe.fe_evaluate = true; if (expr->v_type == VAR_FUNC) { - s = (char_u *)expr->vval.v_string; - call_func((char *)s, -1, &rettv, 1, argv, &funcexe); + s = expr->vval.v_string; + call_func(s, -1, &rettv, 1, argv, &funcexe); } else if (expr->v_type == VAR_PARTIAL) { partial_T *partial = expr->vval.v_partial; - s = (char_u *)partial_name(partial); - funcexe.partial = partial; - call_func((char *)s, -1, &rettv, 1, argv, &funcexe); + s = partial_name(partial); + funcexe.fe_partial = partial; + call_func(s, -1, &rettv, 1, argv, &funcexe); } if (tv_list_len(&matchList.sl_list) > 0) { // fill_submatch_list() was called. @@ -1826,14 +1805,14 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des eval_result[nested] = NULL; } else { char buf[NUMBUFLEN]; - eval_result[nested] = (char_u *)tv_get_string_buf_chk(&rettv, buf); + eval_result[nested] = (char *)tv_get_string_buf_chk(&rettv, buf); if (eval_result[nested] != NULL) { - eval_result[nested] = vim_strsave(eval_result[nested]); + eval_result[nested] = xstrdup(eval_result[nested]); } } tv_clear(&rettv); } else { - eval_result[nested] = (char_u *)eval_to_string((char *)source + 2, NULL, true); + eval_result[nested] = eval_to_string(source + 2, NULL, true); } nesting--; @@ -1848,12 +1827,11 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des *s = CAR; } else if (*s == '\\' && s[1] != NUL) { s++; - /* Change NL to CR here too, so that this works: - * :s/abc\\\ndef/\="aaa\\\nbbb"/ on text: - * abc\ - * def - * Not when called from vim_regexec_nl(). - */ + // Change NL to CR here too, so that this works: + // :s/abc\\\ndef/\="aaa\\\nbbb"/ on text: + // abc{backslash} + // def + // Not when called from vim_regexec_nl(). if (*s == NL && !rsm.sm_line_lbr) { *s = CAR; } @@ -1862,12 +1840,12 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des } if (had_backslash && (flags & REGSUB_BACKSLASH)) { // Backslashes will be consumed, need to double them. - s = vim_strsave_escaped(eval_result[nested], (char_u *)"\\"); + s = vim_strsave_escaped(eval_result[nested], "\\"); xfree(eval_result[nested]); eval_result[nested] = s; } - dst += STRLEN(eval_result[nested]); + dst += strlen(eval_result[nested]); } can_f_submatch = prev_can_f_submatch; @@ -1876,7 +1854,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des } } } else { - while ((c = *src++) != NUL) { + while ((c = (uint8_t)(*src++)) != NUL) { if (c == '&' && (flags & REGSUB_MAGIC)) { no = 0; } else if (c == '\\' && *src != NUL) { @@ -1885,7 +1863,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des no = 0; } else if ('0' <= *src && *src <= '9') { no = *src++ - '0'; - } else if (vim_strchr("uUlLeE", *src)) { + } else if (vim_strchr("uUlLeE", (uint8_t)(*src))) { switch (*src++) { case 'u': func_one = (fptr_T)do_upper; @@ -1914,7 +1892,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des iemsg("vim_regsub_both(): not enough space"); return 0; } - *dst++ = (char_u)c; + *dst++ = (char)c; *dst++ = *src++; *dst++ = *src++; } else { @@ -1951,10 +1929,10 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des } dst++; } - c = *src++; + c = (uint8_t)(*src++); } } else { - c = utf_ptr2char((char *)src - 1); + c = utf_ptr2char(src - 1); } // Write to buffer, if copy is set. if (func_one != NULL) { @@ -1966,7 +1944,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des cc = c; } - int totlen = utfc_ptr2len((char *)src - 1); + int totlen = utfc_ptr2len(src - 1); int charlen = utf_char2len(cc); if (copy) { @@ -1974,10 +1952,10 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des iemsg("vim_regsub_both(): not enough space"); return 0; } - utf_char2bytes(cc, (char *)dst); + utf_char2bytes(cc, dst); } dst += charlen - 1; - int clen = utf_ptr2len((char *)src - 1); + int clen = utf_ptr2len(src - 1); // If the character length is shorter than "totlen", there // are composing characters; copy them as-is. @@ -2004,7 +1982,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des len = rex.reg_mmatch->endpos[no].col - rex.reg_mmatch->startpos[no].col; } else { - len = (int)STRLEN(s); + len = (int)strlen(s); } } } else { @@ -2034,7 +2012,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des if (rex.reg_mmatch->endpos[no].lnum == clnum) { len = rex.reg_mmatch->endpos[no].col; } else { - len = (int)STRLEN(s); + len = (int)strlen(s); } } else { break; @@ -2060,7 +2038,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des } dst += 2; } else { - c = utf_ptr2char((char *)s); + c = utf_ptr2char(s); if (func_one != (fptr_T)NULL) { // Turbo C complains without the typecast @@ -2078,7 +2056,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des // Copy composing characters separately, one // at a time. - l = utf_ptr2len((char *)s) - 1; + l = utf_ptr2len(s) - 1; s += l; len -= l; @@ -2088,7 +2066,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int des iemsg("vim_regsub_both(): not enough space"); return 0; } - utf_char2bytes(cc, (char *)dst); + utf_char2bytes(cc, dst); } dst += charlen - 1; } @@ -2112,14 +2090,12 @@ exit: return (int)((dst - dest) + 1); } -/* - * Call reg_getline() with the line numbers from the submatch. If a - * substitute() was used the reg_maxline and other values have been - * overwritten. - */ -static char_u *reg_getline_submatch(linenr_T lnum) +/// Call reg_getline() with the line numbers from the submatch. If a +/// substitute() was used the reg_maxline and other values have been +/// overwritten. +static char *reg_getline_submatch(linenr_T lnum) { - char_u *s; + char *s; linenr_T save_first = rex.reg_firstlnum; linenr_T save_max = rex.reg_maxline; @@ -2133,15 +2109,14 @@ static char_u *reg_getline_submatch(linenr_T lnum) return s; } -/* - * Used for the submatch() function: get the string from the n'th submatch in - * allocated memory. - * Returns NULL when not in a ":s" command and for a non-existing submatch. - */ -char_u *reg_submatch(int no) +/// Used for the submatch() function: get the string from the n'th submatch in +/// allocated memory. +/// +/// @return NULL when not in a ":s" command and for a non-existing submatch. +char *reg_submatch(int no) { - char_u *retval = NULL; - char_u *s; + char *retval = NULL; + char *s; int round; linenr_T lnum; @@ -2152,10 +2127,8 @@ char_u *reg_submatch(int no) if (rsm.sm_match == NULL) { ssize_t len; - /* - * First round: compute the length and allocate memory. - * Second round: copy the text. - */ + // First round: compute the length and allocate memory. + // Second round: copy the text. for (round = 1; round <= 2; round++) { lnum = rsm.sm_mmatch->startpos[no].lnum; if (lnum < 0 || rsm.sm_mmatch->endpos[no].lnum < 0) { @@ -2171,13 +2144,13 @@ char_u *reg_submatch(int no) // Within one line: take form start to end col. len = rsm.sm_mmatch->endpos[no].col - rsm.sm_mmatch->startpos[no].col; if (round == 2) { - STRLCPY(retval, s, len + 1); + xstrlcpy(retval, s, (size_t)len + 1); } len++; } else { // Multiple lines: take start line from start col, middle // lines completely and end line up to end col. - len = (ssize_t)STRLEN(s); + len = (ssize_t)strlen(s); if (round == 2) { STRCPY(retval, s); retval[len] = '\n'; @@ -2189,15 +2162,16 @@ char_u *reg_submatch(int no) if (round == 2) { STRCPY(retval + len, s); } - len += (ssize_t)STRLEN(s); + len += (ssize_t)strlen(s); if (round == 2) { retval[len] = '\n'; } len++; } if (round == 2) { - STRNCPY(retval + len, reg_getline_submatch(lnum), - rsm.sm_mmatch->endpos[no].col); + strncpy(retval + len, // NOLINT(runtime/printf) + reg_getline_submatch(lnum), + (size_t)rsm.sm_mmatch->endpos[no].col); } len += rsm.sm_mmatch->endpos[no].col; if (round == 2) { @@ -2215,7 +2189,7 @@ char_u *reg_submatch(int no) if (s == NULL || rsm.sm_match->endp[no] == NULL) { retval = NULL; } else { - retval = vim_strnsave(s, (size_t)(rsm.sm_match->endp[no] - s)); + retval = xstrnsave(s, (size_t)(rsm.sm_match->endp[no] - s)); } } @@ -2250,16 +2224,16 @@ list_T *reg_submatch_list(int no) list = tv_list_alloc(elnum - slnum + 1); - s = (const char *)reg_getline_submatch(slnum) + scol; + s = reg_getline_submatch(slnum) + scol; if (slnum == elnum) { tv_list_append_string(list, s, ecol - scol); } else { tv_list_append_string(list, s, -1); for (int i = 1; i < elnum - slnum; i++) { - s = (const char *)reg_getline_submatch(slnum + i); + s = reg_getline_submatch(slnum + i); tv_list_append_string(list, s, -1); } - s = (const char *)reg_getline_submatch(elnum); + s = reg_getline_submatch(elnum); tv_list_append_string(list, s, ecol); } } else { @@ -2275,22 +2249,39 @@ list_T *reg_submatch_list(int no) return list; } +/// Initialize the values used for matching against multiple lines +/// +/// @param win window in which to search or NULL +/// @param buf buffer in which to search +/// @param lnum nr of line to start looking for match +static void init_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum) +{ + rex.reg_match = NULL; + rex.reg_mmatch = rmp; + rex.reg_buf = buf; + rex.reg_win = win; + rex.reg_firstlnum = lnum; + rex.reg_maxline = rex.reg_buf->b_ml.ml_line_count - lnum; + rex.reg_line_lbr = false; + rex.reg_ic = rmp->rmm_ic; + rex.reg_icombine = false; + rex.reg_maxcol = rmp->rmm_maxcol; +} + // XXX Do not allow headers generator to catch definitions from regexp_nfa.c #ifndef DO_NOT_DEFINE_EMPTY_ATTRIBUTES # include "nvim/regexp_bt.c" # include "nvim/regexp_nfa.c" #endif -static regengine_T bt_regengine = -{ +static regengine_T bt_regengine = { bt_regcomp, bt_regfree, bt_regexec_nl, bt_regexec_multi, }; -static regengine_T nfa_regengine = -{ +static regengine_T nfa_regengine = { nfa_regcomp, nfa_regfree, nfa_regexec_nl, @@ -2302,28 +2293,26 @@ static regengine_T nfa_regengine = static int regexp_engine = 0; #ifdef REGEXP_DEBUG -static char_u regname[][30] = { +static uint8_t regname[][30] = { "AUTOMATIC Regexp Engine", "BACKTRACKING Regexp Engine", "NFA Regexp Engine" }; #endif -/* - * Compile a regular expression into internal code. - * Returns the program in allocated memory. - * Use vim_regfree() to free the memory. - * Returns NULL for an error. - */ +// Compile a regular expression into internal code. +// Returns the program in allocated memory. +// Use vim_regfree() to free the memory. +// Returns NULL for an error. regprog_T *vim_regcomp(char *expr_arg, int re_flags) { regprog_T *prog = NULL; - char_u *expr = (char_u *)expr_arg; + char *expr = expr_arg; regexp_engine = (int)p_re; // Check for prefix "\%#=", that sets the regexp engine - if (STRNCMP(expr, "\\%#=", 4) == 0) { + if (strncmp(expr, "\\%#=", 4) == 0) { int newengine = expr[4] - '0'; if (newengine == AUTOMATIC_ENGINE @@ -2353,10 +2342,10 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags) // const int called_emsg_before = called_emsg; if (regexp_engine != BACKTRACKING_ENGINE) { - prog = nfa_regengine.regcomp(expr, + prog = nfa_regengine.regcomp((uint8_t *)expr, re_flags + (regexp_engine == AUTOMATIC_ENGINE ? RE_AUTO : 0)); } else { - prog = bt_regengine.regcomp(expr, re_flags); + prog = bt_regengine.regcomp((uint8_t *)expr, re_flags); } // Check for error compiling regexp with initial engine. @@ -2381,7 +2370,7 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags) if (regexp_engine == AUTOMATIC_ENGINE && called_emsg == called_emsg_before) { regexp_engine = BACKTRACKING_ENGINE; report_re_switch(expr); - prog = bt_regengine.regcomp(expr, re_flags); + prog = bt_regengine.regcomp((uint8_t *)expr, re_flags); } } @@ -2395,9 +2384,7 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags) return prog; } -/* - * Free a compiled regexp program, returned by vim_regcomp(). - */ +// Free a compiled regexp program, returned by vim_regcomp(). void vim_regfree(regprog_T *prog) { if (prog != NULL) { @@ -2416,12 +2403,12 @@ void free_regexp_stuff(void) #endif -static void report_re_switch(char_u *pat) +static void report_re_switch(char *pat) { if (p_verbose > 0) { verbose_enter(); msg_puts(_("Switching to backtracking RE engine for pattern: ")); - msg_puts((char *)pat); + msg_puts(pat); verbose_leave(); } } @@ -2438,7 +2425,7 @@ static void report_re_switch(char_u *pat) /// @param nl /// /// @return true if there is a match, false if not. -static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, bool nl) +static bool vim_regexec_string(regmatch_T *rmp, char *line, colnr_T col, bool nl) { regexec_T rex_save; bool rex_in_use_save = rex_in_use; @@ -2461,7 +2448,7 @@ static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, bool rex.reg_startpos = NULL; rex.reg_endpos = NULL; - int result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl); + int result = rmp->regprog->engine->regexec_nl(rmp, (uint8_t *)line, col, nl); rmp->regprog->re_in_use = false; // NFA engine aborted because it's very slow, use backtracking engine instead. @@ -2469,15 +2456,15 @@ static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, bool && result == NFA_TOO_EXPENSIVE) { int save_p_re = (int)p_re; int re_flags = (int)rmp->regprog->re_flags; - char_u *pat = vim_strsave(((nfa_regprog_T *)rmp->regprog)->pattern); + char *pat = xstrdup(((nfa_regprog_T *)rmp->regprog)->pattern); p_re = BACKTRACKING_ENGINE; vim_regfree(rmp->regprog); report_re_switch(pat); - rmp->regprog = vim_regcomp((char *)pat, re_flags); + rmp->regprog = vim_regcomp(pat, re_flags); if (rmp->regprog != NULL) { rmp->regprog->re_in_use = true; - result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl); + result = rmp->regprog->engine->regexec_nl(rmp, (uint8_t *)line, col, nl); rmp->regprog->re_in_use = false; } @@ -2495,7 +2482,7 @@ static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, bool // Note: "*prog" may be freed and changed. // Return true if there is a match, false if not. -bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line, colnr_T col) +bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char *line, colnr_T col) { regmatch_T regmatch = { .regprog = *prog, .rm_ic = ignore_case }; bool r = vim_regexec_string(®match, line, col, false); @@ -2507,13 +2494,13 @@ bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line, colnr_T // Return true if there is a match, false if not. bool vim_regexec(regmatch_T *rmp, char *line, colnr_T col) { - return vim_regexec_string(rmp, (char_u *)line, col, false); + return vim_regexec_string(rmp, line, col, false); } // Like vim_regexec(), but consider a "\n" in "line" to be a line break. // Note: "rmp->regprog" may be freed and changed. // Return true if there is a match, false if not. -bool vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col) +bool vim_regexec_nl(regmatch_T *rmp, char *line, colnr_T col) { return vim_regexec_string(rmp, line, col, true); } @@ -2560,7 +2547,7 @@ long vim_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, && result == NFA_TOO_EXPENSIVE) { int save_p_re = (int)p_re; int re_flags = (int)rmp->regprog->re_flags; - char_u *pat = vim_strsave(((nfa_regprog_T *)rmp->regprog)->pattern); + char *pat = xstrdup(((nfa_regprog_T *)rmp->regprog)->pattern); p_re = BACKTRACKING_ENGINE; regprog_T *prev_prog = rmp->regprog; @@ -2569,7 +2556,7 @@ long vim_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, // checking for \z misuse was already done when compiling for NFA, // allow all here reg_do_extmatch = REX_ALL; - rmp->regprog = vim_regcomp((char *)pat, re_flags); + rmp->regprog = vim_regcomp(pat, re_flags); reg_do_extmatch = 0; if (rmp->regprog == NULL) { diff --git a/src/nvim/regexp.h b/src/nvim/regexp.h index 085f78af54..dcc58fa34c 100644 --- a/src/nvim/regexp.h +++ b/src/nvim/regexp.h @@ -17,10 +17,11 @@ #define REX_ALL (REX_SET | REX_USE) // regexp.c +// uncrustify:off #ifdef INCLUDE_GENERATED_DECLARATIONS # include "regexp.h.generated.h" - # include "regexp_bt.h.generated.h" #endif +// uncrustify:on #endif // NVIM_REGEXP_H diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c index 769d2ceeef..1b32447d77 100644 --- a/src/nvim/regexp_bt.c +++ b/src/nvim/regexp_bt.c @@ -1,137 +1,130 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * - * Backtracking regular expression implementation. - * - * This file is included in "regexp.c". - * - * NOTICE: - * - * This is NOT the original regular expression code as written by Henry - * Spencer. This code has been modified specifically for use with the VIM - * editor, and should not be used separately from Vim. If you want a good - * regular expression library, get the original code. The copyright notice - * that follows is from the original. - * - * END NOTICE - * - * Copyright (c) 1986 by University of Toronto. - * Written by Henry Spencer. Not derived from licensed software. - * - * Permission is granted to anyone to use this software for any - * purpose on any computer system, and to redistribute it freely, - * subject to the following restrictions: - * - * 1. The author is not responsible for the consequences of use of - * this software, no matter how awful, even if they arise - * from defects in it. - * - * 2. The origin of this software must not be misrepresented, either - * by explicit claim or by omission. - * - * 3. Altered versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * - * Beware that some of this code is subtly aware of the way operator - * precedence is structured in regular expressions. Serious changes in - * regular-expression syntax might require a total rethink. - * - * Changes have been made by Tony Andrews, Olaf 'Rhialto' Seibert, Robert - * Webb, Ciaran McCreesh and Bram Moolenaar. - * Named character class support added by Walter Briscoe (1998 Jul 01) - */ - -/* - * The "internal use only" fields in regexp_defs.h are present to pass info from - * compile to execute that permits the execute phase to run lots faster on - * simple cases. They are: - * - * regstart char that must begin a match; NUL if none obvious; Can be a - * multi-byte character. - * reganch is the match anchored (at beginning-of-line only)? - * regmust string (pointer into program) that match must include, or NULL - * regmlen length of regmust string - * regflags RF_ values or'ed together - * - * Regstart and reganch permit very fast decisions on suitable starting points - * for a match, cutting down the work a lot. Regmust permits fast rejection - * of lines that cannot possibly match. The regmust tests are costly enough - * that vim_regcomp() supplies a regmust only if the r.e. contains something - * potentially expensive (at present, the only such thing detected is * or + - * at the start of the r.e., which can involve a lot of backup). Regmlen is - * supplied because the test in vim_regexec() needs it and vim_regcomp() is - * computing it anyway. - */ - -/* - * Structure for regexp "program". This is essentially a linear encoding - * of a nondeterministic finite-state machine (aka syntax charts or - * "railroad normal form" in parsing technology). Each node is an opcode - * plus a "next" pointer, possibly plus an operand. "Next" pointers of - * all nodes except BRANCH and BRACES_COMPLEX implement concatenation; a "next" - * pointer with a BRANCH on both ends of it is connecting two alternatives. - * (Here we have one of the subtle syntax dependencies: an individual BRANCH - * (as opposed to a collection of them) is never concatenated with anything - * because of operator precedence). The "next" pointer of a BRACES_COMPLEX - * node points to the node after the stuff to be repeated. - * The operand of some types of node is a literal string; for others, it is a - * node leading into a sub-FSM. In particular, the operand of a BRANCH node - * is the first node of the branch. - * (NB this is *not* a tree structure: the tail of the branch connects to the - * thing following the set of BRANCHes.) - * - * pattern is coded like: - * - * +-----------------+ - * | V - * <aa>\|<bb> BRANCH <aa> BRANCH <bb> --> END - * | ^ | ^ - * +------+ +----------+ - * - * - * +------------------+ - * V | - * <aa>* BRANCH BRANCH <aa> --> BACK BRANCH --> NOTHING --> END - * | | ^ ^ - * | +---------------+ | - * +---------------------------------------------+ - * - * - * +----------------------+ - * V | - * <aa>\+ BRANCH <aa> --> BRANCH --> BACK BRANCH --> NOTHING --> END - * | | ^ ^ - * | +-----------+ | - * +--------------------------------------------------+ - * - * - * +-------------------------+ - * V | - * <aa>\{} BRANCH BRACE_LIMITS --> BRACE_COMPLEX <aa> --> BACK END - * | | ^ - * | +----------------+ - * +-----------------------------------------------+ - * - * - * <aa>\@!<bb> BRANCH NOMATCH <aa> --> END <bb> --> END - * | | ^ ^ - * | +----------------+ | - * +--------------------------------+ - * - * +---------+ - * | V - * \z[abc] BRANCH BRANCH a BRANCH b BRANCH c BRANCH NOTHING --> END - * | | | | ^ ^ - * | | | +-----+ | - * | | +----------------+ | - * | +---------------------------+ | - * +------------------------------------------------------+ - * - * They all start with a BRANCH for "\|" alternatives, even when there is only - * one alternative. - */ +// Backtracking regular expression implementation. +// +// This file is included in "regexp.c". +// +// NOTICE: +// +// This is NOT the original regular expression code as written by Henry +// Spencer. This code has been modified specifically for use with the VIM +// editor, and should not be used separately from Vim. If you want a good +// regular expression library, get the original code. The copyright notice +// that follows is from the original. +// +// END NOTICE +// +// Copyright (c) 1986 by University of Toronto. +// Written by Henry Spencer. Not derived from licensed software. +// +// Permission is granted to anyone to use this software for any +// purpose on any computer system, and to redistribute it freely, +// subject to the following restrictions: +// +// 1. The author is not responsible for the consequences of use of +// this software, no matter how awful, even if they arise +// from defects in it. +// +// 2. The origin of this software must not be misrepresented, either +// by explicit claim or by omission. +// +// 3. Altered versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// Beware that some of this code is subtly aware of the way operator +// precedence is structured in regular expressions. Serious changes in +// regular-expression syntax might require a total rethink. +// +// Changes have been made by Tony Andrews, Olaf 'Rhialto' Seibert, Robert +// Webb, Ciaran McCreesh and Bram Moolenaar. +// Named character class support added by Walter Briscoe (1998 Jul 01) + +// The "internal use only" fields in regexp_defs.h are present to pass info from +// compile to execute that permits the execute phase to run lots faster on +// simple cases. They are: +// +// regstart char that must begin a match; NUL if none obvious; Can be a +// multi-byte character. +// reganch is the match anchored (at beginning-of-line only)? +// regmust string (pointer into program) that match must include, or NULL +// regmlen length of regmust string +// regflags RF_ values or'ed together +// +// Regstart and reganch permit very fast decisions on suitable starting points +// for a match, cutting down the work a lot. Regmust permits fast rejection +// of lines that cannot possibly match. The regmust tests are costly enough +// that vim_regcomp() supplies a regmust only if the r.e. contains something +// potentially expensive (at present, the only such thing detected is * or + +// at the start of the r.e., which can involve a lot of backup). Regmlen is +// supplied because the test in vim_regexec() needs it and vim_regcomp() is +// computing it anyway. + +// Structure for regexp "program". This is essentially a linear encoding +// of a nondeterministic finite-state machine (aka syntax charts or +// "railroad normal form" in parsing technology). Each node is an opcode +// plus a "next" pointer, possibly plus an operand. "Next" pointers of +// all nodes except BRANCH and BRACES_COMPLEX implement concatenation; a "next" +// pointer with a BRANCH on both ends of it is connecting two alternatives. +// (Here we have one of the subtle syntax dependencies: an individual BRANCH +// (as opposed to a collection of them) is never concatenated with anything +// because of operator precedence). The "next" pointer of a BRACES_COMPLEX +// node points to the node after the stuff to be repeated. +// The operand of some types of node is a literal string; for others, it is a +// node leading into a sub-FSM. In particular, the operand of a BRANCH node +// is the first node of the branch. +// (NB this is *not* a tree structure: the tail of the branch connects to the +// thing following the set of BRANCHes.) +// +// pattern is coded like: +// +// +-----------------+ +// | V +// <aa>\|<bb> BRANCH <aa> BRANCH <bb> --> END +// | ^ | ^ +// +------+ +----------+ +// +// +// +------------------+ +// V | +// <aa>* BRANCH BRANCH <aa> --> BACK BRANCH --> NOTHING --> END +// | | ^ ^ +// | +---------------+ | +// +---------------------------------------------+ +// +// +// +----------------------+ +// V | +// <aa>\+ BRANCH <aa> --> BRANCH --> BACK BRANCH --> NOTHING --> END +// | | ^ ^ +// | +-----------+ | +// +--------------------------------------------------+ +// +// +// +-------------------------+ +// V | +// <aa>\{} BRANCH BRACE_LIMITS --> BRACE_COMPLEX <aa> --> BACK END +// | | ^ +// | +----------------+ +// +-----------------------------------------------+ +// +// +// <aa>\@!<bb> BRANCH NOMATCH <aa> --> END <bb> --> END +// | | ^ ^ +// | +----------------+ | +// +--------------------------------+ +// +// +---------+ +// | V +// \z[abc] BRANCH BRANCH a BRANCH b BRANCH c BRANCH NOTHING --> END +// | | | | ^ ^ +// | | | +-----+ | +// | | +----------------+ | +// | +---------------------------+ | +// +------------------------------------------------------+ +// +// They all start with a BRANCH for "\|" alternatives, even when there is only +// one alternative. #include <assert.h> #include <inttypes.h> @@ -139,11 +132,10 @@ #include <string.h> #include "nvim/garray.h" +#include "nvim/profile.h" #include "nvim/regexp.h" -/* - * The opcodes are: - */ +// The opcodes are: // definition number opnd? meaning #define END 0 // End of program or NOMATCH operand. @@ -240,9 +232,7 @@ #define RE_VISUAL 208 // Match Visual area #define RE_COMPOSING 209 // any composing characters -/* - * Flags to be passed up and down. - */ +// Flags to be passed up and down. #define HASWIDTH 0x1 // Known never to match null string. #define SIMPLE 0x2 // Simple enough to be STAR/PLUS operand. #define SPSTART 0x4 // Starts with * or +. @@ -252,17 +242,17 @@ static int prevchr_len; ///< byte length of previous char static int num_complex_braces; ///< Complex \{...} count -static char_u *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE +static uint8_t *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE static long regsize; ///< Code size. static int reg_toolong; ///< true when offset out of range -static char_u had_endbrace[NSUBEXP]; ///< flags, true if end of () found +static uint8_t had_endbrace[NSUBEXP]; ///< flags, true if end of () found static long brace_min[10]; ///< Minimums for complex brace repeats static long brace_max[10]; ///< Maximums for complex brace repeats static int brace_count[10]; ///< Current counts for complex brace repeats static int one_exactly = false; ///< only do one char for EXACTLY // When making changes to classchars also change nfa_classcodes. -static char_u *classchars = (char_u *)".iIkKfFpPsSdDxXoOwWhHaAlLuU"; +static uint8_t *classchars = (uint8_t *)".iIkKfFpPsSdDxXoOwWhHaAlLuU"; static int classcodes[] = { ANY, IDENT, SIDENT, KWORD, SKWORD, FNAME, SFNAME, PRINT, SPRINT, @@ -273,11 +263,9 @@ static int classcodes[] = { UPPER, NUPPER }; -/* - * When regcode is set to this value, code is not emitted and size is computed - * instead. - */ -#define JUST_CALC_SIZE ((char_u *)-1) +// When regcode is set to this value, code is not emitted and size is computed +// instead. +#define JUST_CALC_SIZE ((uint8_t *)-1) // Values for rs_state in regitem_T. typedef enum regstate_E { @@ -297,14 +285,12 @@ typedef enum regstate_E { RS_STAR_SHORT, // STAR/PLUS/BRACE_SIMPLE shortest match } regstate_T; -/* - * Structure used to save the current input state, when it needs to be - * restored after trying a match. Used by reg_save() and reg_restore(). - * Also stores the length of "backpos". - */ +// Structure used to save the current input state, when it needs to be +// restored after trying a match. Used by reg_save() and reg_restore(). +// Also stores the length of "backpos". typedef struct { union { - char_u *ptr; // rex.input pointer, for single-line regexp + uint8_t *ptr; // rex.input pointer, for single-line regexp lpos_T pos; // rex.input pos, for multi-line regexp } rs_u; int rs_len; @@ -313,7 +299,7 @@ typedef struct { // struct to save start/end pointer/position in for \(\) typedef struct { union { - char_u *ptr; + uint8_t *ptr; lpos_T pos; } se_u; } save_se_T; @@ -327,16 +313,14 @@ typedef struct regbehind_S { save_se_T save_end[NSUBEXP]; } regbehind_T; -/* - * When there are alternatives a regstate_T is put on the regstack to remember - * what we are doing. - * Before it may be another type of item, depending on rs_state, to remember - * more things. - */ +// When there are alternatives a regstate_T is put on the regstack to remember +// what we are doing. +// Before it may be another type of item, depending on rs_state, to remember +// more things. typedef struct regitem_S { regstate_T rs_state; // what we are doing, one of RS_ above int16_t rs_no; // submatch nr or BEHIND/NOBEHIND - char_u *rs_scan; // current node in program + uint8_t *rs_scan; // current node in program union { save_se_T sesave; regsave_T regsave; @@ -355,73 +339,67 @@ typedef struct regstar_S { // used to store input position when a BACK was encountered, so that we now if // we made any progress since the last time. typedef struct backpos_S { - char_u *bp_scan; // "scan" where BACK was encountered + uint8_t *bp_scan; // "scan" where BACK was encountered regsave_T bp_pos; // last input position } backpos_T; -/* - * "regstack" and "backpos" are used by regmatch(). They are kept over calls - * to avoid invoking malloc() and free() often. - * "regstack" is a stack with regitem_T items, sometimes preceded by regstar_T - * or regbehind_T. - * "backpos_T" is a table with backpos_T for BACK - */ +// "regstack" and "backpos" are used by regmatch(). They are kept over calls +// to avoid invoking malloc() and free() often. +// "regstack" is a stack with regitem_T items, sometimes preceded by regstar_T +// or regbehind_T. +// "backpos_T" is a table with backpos_T for BACK static garray_T regstack = GA_EMPTY_INIT_VALUE; static garray_T backpos = GA_EMPTY_INIT_VALUE; static regsave_T behind_pos; -/* - * Both for regstack and backpos tables we use the following strategy of - * allocation (to reduce malloc/free calls): - * - Initial size is fairly small. - * - When needed, the tables are grown bigger (8 times at first, double after - * that). - * - After executing the match we free the memory only if the array has grown. - * Thus the memory is kept allocated when it's at the initial size. - * This makes it fast while not keeping a lot of memory allocated. - * A three times speed increase was observed when using many simple patterns. - */ +// Both for regstack and backpos tables we use the following strategy of +// allocation (to reduce malloc/free calls): +// - Initial size is fairly small. +// - When needed, the tables are grown bigger (8 times at first, double after +// that). +// - After executing the match we free the memory only if the array has grown. +// Thus the memory is kept allocated when it's at the initial size. +// This makes it fast while not keeping a lot of memory allocated. +// A three times speed increase was observed when using many simple patterns. #define REGSTACK_INITIAL 2048 #define BACKPOS_INITIAL 64 -/* - * Opcode notes: - * - * BRANCH The set of branches constituting a single choice are hooked - * together with their "next" pointers, since precedence prevents - * anything being concatenated to any individual branch. The - * "next" pointer of the last BRANCH in a choice points to the - * thing following the whole choice. This is also where the - * final "next" pointer of each individual branch points; each - * branch starts with the operand node of a BRANCH node. - * - * BACK Normal "next" pointers all implicitly point forward; BACK - * exists to make loop structures possible. - * - * STAR,PLUS '=', and complex '*' and '+', are implemented as circular - * BRANCH structures using BACK. Simple cases (one character - * per match) are implemented with STAR and PLUS for speed - * and to minimize recursive plunges. - * - * BRACE_LIMITS This is always followed by a BRACE_SIMPLE or BRACE_COMPLEX - * node, and defines the min and max limits to be used for that - * node. - * - * MOPEN,MCLOSE ...are numbered at compile time. - * ZOPEN,ZCLOSE ...ditto - */ - -/* - * A node is one char of opcode followed by two chars of "next" pointer. - * "Next" pointers are stored as two 8-bit bytes, high order first. The - * value is a positive offset from the opcode of the node containing it. - * An operand, if any, simply follows the node. (Note that much of the - * code generation knows about this implicit relationship.) - * - * Using two bytes for the "next" pointer is vast overkill for most things, - * but allows patterns to get big without disasters. - */ +// Opcode notes: +// +// BRANCH The set of branches constituting a single choice are hooked +// together with their "next" pointers, since precedence prevents +// anything being concatenated to any individual branch. The +// "next" pointer of the last BRANCH in a choice points to the +// thing following the whole choice. This is also where the +// final "next" pointer of each individual branch points; each +// branch starts with the operand node of a BRANCH node. +// +// BACK Normal "next" pointers all implicitly point forward; BACK +// exists to make loop structures possible. +// +// STAR,PLUS '=', and complex '*' and '+', are implemented as circular +// BRANCH structures using BACK. Simple cases (one character +// per match) are implemented with STAR and PLUS for speed +// and to minimize recursive plunges. +// +// BRACE_LIMITS This is always followed by a BRACE_SIMPLE or BRACE_COMPLEX +// node, and defines the min and max limits to be used for that +// node. +// +// MOPEN,MCLOSE ...are numbered at compile time. +// ZOPEN,ZCLOSE ...ditto +/// +// +// +// A node is one char of opcode followed by two chars of "next" pointer. +// "Next" pointers are stored as two 8-bit bytes, high order first. The +// value is a positive offset from the opcode of the node containing it. +// An operand, if any, simply follows the node. (Note that much of the +// code generation knows about this implicit relationship.) +// +// Using two bytes for the "next" pointer is vast overkill for most things, +// but allows patterns to get big without disasters. #define OP(p) ((int)(*(p))) #define NEXT(p) (((*((p) + 1) & 0377) << 8) + (*((p) + 2) & 0377)) #define OPERAND(p) ((p) + 3) @@ -433,14 +411,14 @@ static regsave_T behind_pos; // Obtain a second single-byte operand stored after a four bytes operand. #define OPERAND_CMP(p) (p)[7] -static char_u *reg(int paren, int *flagp); +static uint8_t *reg(int paren, int *flagp); #ifdef BT_REGEXP_DUMP -static void regdump(char_u *, bt_regprog_T *); +static void regdump(uint8_t *, bt_regprog_T *); #endif #ifdef REGEXP_DEBUG -static char_u *regprop(char_u *); +static uint8_t *regprop(uint8_t *); static int regnarrate = 0; #endif @@ -449,12 +427,10 @@ static int regnarrate = 0; # include "regexp_bt.c.generated.h" #endif -/* - * Setup to parse the regexp. Used once to get the length and once to do it. - */ -static void regcomp_start(char_u *expr, int re_flags) // see vim_regcomp() +// Setup to parse the regexp. Used once to get the length and once to do it. +static void regcomp_start(uint8_t *expr, int re_flags) // see vim_regcomp() { - initchr(expr); + initchr((char *)expr); if (re_flags & RE_MAGIC) { reg_magic = MAGIC_ON; } else { @@ -484,21 +460,17 @@ static bool use_multibytecode(int c) || utf_iscomposing(c)); } -/* - * Emit (if appropriate) a byte of code - */ +// Emit (if appropriate) a byte of code static void regc(int b) { if (regcode == JUST_CALC_SIZE) { regsize++; } else { - *regcode++ = (char_u)b; + *regcode++ = (uint8_t)b; } } -/* - * Emit (if appropriate) a multi-byte character of code - */ +// Emit (if appropriate) a multi-byte character of code static void regmbc(int c) { if (regcode == JUST_CALC_SIZE) { @@ -508,11 +480,9 @@ static void regmbc(int c) } } -/* - * Produce the bytes for equivalence class "c". - * Currently only handles latin1, latin9 and utf-8. - * NOTE: When changing this function, also change nfa_emit_equi_class() - */ +// Produce the bytes for equivalence class "c". +// Currently only handles latin1, latin9 and utf-8. +// NOTE: When changing this function, also change nfa_emit_equi_class() static void reg_equi_class(int c) { { @@ -1481,43 +1451,37 @@ static void reg_equi_class(int c) regmbc(c); } -/* - * Emit a node. - * Return pointer to generated code. - */ -static char_u *regnode(int op) +// Emit a node. +// Return pointer to generated code. +static uint8_t *regnode(int op) { - char_u *ret; + uint8_t *ret; ret = regcode; if (ret == JUST_CALC_SIZE) { regsize += 3; } else { - *regcode++ = (char_u)op; + *regcode++ = (uint8_t)op; *regcode++ = NUL; // Null "next" pointer. *regcode++ = NUL; } return ret; } -/* - * Write a four bytes number at "p" and return pointer to the next char. - */ -static char_u *re_put_uint32(char_u *p, uint32_t val) +// Write a four bytes number at "p" and return pointer to the next char. +static uint8_t *re_put_uint32(uint8_t *p, uint32_t val) { - *p++ = (char_u)((val >> 24) & 0377); - *p++ = (char_u)((val >> 16) & 0377); - *p++ = (char_u)((val >> 8) & 0377); - *p++ = (char_u)(val & 0377); + *p++ = (uint8_t)((val >> 24) & 0377); + *p++ = (uint8_t)((val >> 16) & 0377); + *p++ = (uint8_t)((val >> 8) & 0377); + *p++ = (uint8_t)(val & 0377); return p; } -/* - * regnext - dig the "next" pointer out of a node - * Returns NULL when calculating size, when there is no next item and when - * there is an error. - */ -static char_u *regnext(char_u *p) +// regnext - dig the "next" pointer out of a node +// Returns NULL when calculating size, when there is no next item and when +// there is an error. +static uint8_t *regnext(uint8_t *p) FUNC_ATTR_NONNULL_ALL { int offset; @@ -1539,7 +1503,7 @@ static char_u *regnext(char_u *p) } // Set the next-pointer at the end of a node chain. -static void regtail(char_u *p, char_u *val) +static void regtail(uint8_t *p, uint8_t *val) { int offset; @@ -1548,9 +1512,9 @@ static void regtail(char_u *p, char_u *val) } // Find last node. - char_u *scan = p; + uint8_t *scan = p; for (;;) { - char_u *temp = regnext(scan); + uint8_t *temp = regnext(scan); if (temp == NULL) { break; } @@ -1568,15 +1532,13 @@ static void regtail(char_u *p, char_u *val) if (offset > 0xffff) { reg_toolong = true; } else { - *(scan + 1) = (char_u)(((unsigned)offset >> 8) & 0377); - *(scan + 2) = (char_u)(offset & 0377); + *(scan + 1) = (uint8_t)(((unsigned)offset >> 8) & 0377); + *(scan + 2) = (uint8_t)(offset & 0377); } } -/* - * Like regtail, on item after a BRANCH; nop if none. - */ -static void regoptail(char_u *p, char_u *val) +// Like regtail, on item after a BRANCH; nop if none. +static void regoptail(uint8_t *p, uint8_t *val) { // When op is neither BRANCH nor BRACE_COMPLEX0-9, it is "operandless" if (p == NULL || p == JUST_CALC_SIZE @@ -1587,16 +1549,14 @@ static void regoptail(char_u *p, char_u *val) regtail(OPERAND(p), val); } -/* - * Insert an operator in front of already-emitted operand - * - * Means relocating the operand. - */ -static void reginsert(int op, char_u *opnd) +// Insert an operator in front of already-emitted operand +// +// Means relocating the operand. +static void reginsert(int op, uint8_t *opnd) { - char_u *src; - char_u *dst; - char_u *place; + uint8_t *src; + uint8_t *dst; + uint8_t *place; if (regcode == JUST_CALC_SIZE) { regsize += 3; @@ -1610,20 +1570,18 @@ static void reginsert(int op, char_u *opnd) } place = opnd; // Op node, where operand used to be. - *place++ = (char_u)op; + *place++ = (uint8_t)op; *place++ = NUL; *place = NUL; } -/* - * Insert an operator in front of already-emitted operand. - * Add a number to the operator. - */ -static void reginsert_nr(int op, long val, char_u *opnd) +// Insert an operator in front of already-emitted operand. +// Add a number to the operator. +static void reginsert_nr(int op, long val, uint8_t *opnd) { - char_u *src; - char_u *dst; - char_u *place; + uint8_t *src; + uint8_t *dst; + uint8_t *place; if (regcode == JUST_CALC_SIZE) { regsize += 7; @@ -1637,24 +1595,22 @@ static void reginsert_nr(int op, long val, char_u *opnd) } place = opnd; // Op node, where operand used to be. - *place++ = (char_u)op; + *place++ = (uint8_t)op; *place++ = NUL; *place++ = NUL; assert(val >= 0 && (uintmax_t)val <= UINT32_MAX); re_put_uint32(place, (uint32_t)val); } -/* - * Insert an operator in front of already-emitted operand. - * The operator has the given limit values as operands. Also set next pointer. - * - * Means relocating the operand. - */ -static void reginsert_limits(int op, long minval, long maxval, char_u *opnd) +// Insert an operator in front of already-emitted operand. +// The operator has the given limit values as operands. Also set next pointer. +// +// Means relocating the operand. +static void reginsert_limits(int op, long minval, long maxval, uint8_t *opnd) { - char_u *src; - char_u *dst; - char_u *place; + uint8_t *src; + uint8_t *dst; + uint8_t *place; if (regcode == JUST_CALC_SIZE) { regsize += 11; @@ -1668,7 +1624,7 @@ static void reginsert_limits(int op, long minval, long maxval, char_u *opnd) } place = opnd; // Op node, where operand used to be. - *place++ = (char_u)op; + *place++ = (uint8_t)op; *place++ = NUL; *place++ = NUL; assert(minval >= 0 && (uintmax_t)minval <= UINT32_MAX); @@ -1685,11 +1641,11 @@ static void reginsert_limits(int op, long minval, long maxval, char_u *opnd) static int seen_endbrace(int refnum) { if (!had_endbrace[refnum]) { - char_u *p; + uint8_t *p; // Trick: check if "@<=" or "@<!" follows, in which case // the \1 can appear before the referenced match. - for (p = (char_u *)regparse; *p != NUL; p++) { + for (p = (uint8_t *)regparse; *p != NUL; p++) { if (p[0] == '@' && p[1] == '<' && (p[2] == '!' || p[2] == '=')) { break; } @@ -1704,19 +1660,17 @@ static int seen_endbrace(int refnum) return true; } -/* - * Parse the lowest level. - * - * Optimization: gobbles an entire sequence of ordinary characters so that - * it can turn them into a single node, which is smaller to store and - * faster to run. Don't do this when one_exactly is set. - */ -static char_u *regatom(int *flagp) +// Parse the lowest level. +// +// Optimization: gobbles an entire sequence of ordinary characters so that +// it can turn them into a single node, which is smaller to store and +// faster to run. Don't do this when one_exactly is set. +static uint8_t *regatom(int *flagp) { - char_u *ret; + uint8_t *ret; int flags; int c; - char_u *p; + uint8_t *p; int extra = 0; int save_prev_at_start = prev_at_start; @@ -1792,7 +1746,7 @@ static char_u *regatom(int *flagp) case Magic('L'): case Magic('u'): case Magic('U'): - p = (char_u *)vim_strchr((char *)classchars, no_Magic(c)); + p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c)); if (p == NULL) { EMSG_RET_NULL(_("E63: invalid use of \\_")); } @@ -1854,17 +1808,17 @@ static char_u *regatom(int *flagp) case Magic('~'): // previous substitute pattern if (reg_prev_sub != NULL) { - char_u *lp; + uint8_t *lp; ret = regnode(EXACTLY); - lp = reg_prev_sub; + lp = (uint8_t *)reg_prev_sub; while (*lp != NUL) { regc(*lp++); } regc(NUL); if (*reg_prev_sub != NUL) { *flagp |= HASWIDTH; - if ((lp - reg_prev_sub) == 1) { + if ((lp - (uint8_t *)reg_prev_sub) == 1) { *flagp |= SIMPLE; } } @@ -1971,6 +1925,11 @@ static char_u *regatom(int *flagp) break; case '#': + if (regparse[0] == '=' && regparse[1] >= 48 && regparse[1] <= 50) { + // misplaced \%#=1 + semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]); + return FAIL; + } ret = regnode(CURSOR); break; @@ -1989,9 +1948,9 @@ static char_u *regatom(int *flagp) EMSG_ONE_RET_NULL; } { - char_u *lastbranch; - char_u *lastnode = NULL; - char_u *br; + uint8_t *lastbranch; + uint8_t *lastnode = NULL; + uint8_t *br; ret = NULL; while ((c = getchr()) != ']') { @@ -2091,6 +2050,7 @@ static char_u *regatom(int *flagp) uint32_t n = 0; int cmp; bool cur = false; + bool got_digit = false; cmp = c; if (cmp == '<' || cmp == '>') { @@ -2101,6 +2061,7 @@ static char_u *regatom(int *flagp) c = getchr(); } while (ascii_isdigit(c)) { + got_digit = true; n = n * 10 + (uint32_t)(c - '0'); c = getchr(); } @@ -2111,13 +2072,13 @@ static char_u *regatom(int *flagp) if (ret == JUST_CALC_SIZE) { regsize += 2; } else { - *regcode++ = (char_u)c; - *regcode++ = (char_u)cmp; + *regcode++ = (uint8_t)c; + *regcode++ = (uint8_t)cmp; } break; - } else if (c == 'l' || c == 'c' || c == 'v') { + } else if ((c == 'l' || c == 'c' || c == 'v') && (cur || got_digit)) { if (cur && n) { - semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c)); + semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c)); rc_did_emsg = true; return NULL; } @@ -2149,7 +2110,7 @@ static char_u *regatom(int *flagp) // put the number and the optional // comparator after the opcode regcode = re_put_uint32(regcode, n); - *regcode++ = (char_u)cmp; + *regcode++ = (uint8_t)cmp; } break; } @@ -2163,11 +2124,11 @@ static char_u *regatom(int *flagp) case Magic('['): collection: { - char_u *lp; + uint8_t *lp; // If there is no matching ']', we assume the '[' is a normal // character. This makes 'incsearch' and ":help [" work. - lp = skip_anyof(regparse); + lp = (uint8_t *)skip_anyof(regparse); if (*lp == ']') { // there is a matching ']' int startc = -1; // > 0 when next '-' is a range int endc; @@ -2204,7 +2165,7 @@ collection: endc = get_coll_element(®parse); } if (endc == 0) { - endc = mb_ptr2char_adv((const char_u **)®parse); + endc = mb_ptr2char_adv((const char **)®parse); } // Handle \o40, \x20 and \u20AC style sequences @@ -2236,10 +2197,10 @@ collection: // accepts "\t", "\e", etc., but only when the 'l' flag in // 'cpoptions' is not included. else if (*regparse == '\\' - && (vim_strchr(REGEXP_INRANGE, regparse[1]) != NULL + && (vim_strchr(REGEXP_INRANGE, (uint8_t)regparse[1]) != NULL || (!reg_cpo_lit && vim_strchr(REGEXP_ABBR, - regparse[1]) != NULL))) { + (uint8_t)regparse[1]) != NULL))) { regparse++; if (*regparse == 'n') { // '\n' in range: also match NL @@ -2282,8 +2243,7 @@ collection: if (c_class != 0) { // produce equivalence class reg_equi_class(c_class); - } else if ((c_class = - get_coll_element(®parse)) != 0) { + } else if ((c_class = get_coll_element(®parse)) != 0) { // produce a collating element regmbc(c_class); } else { @@ -2459,7 +2419,7 @@ do_multibyte: for (len = 0; c != NUL && (len == 0 || (re_multi_type(peekchr()) == NOT_MULTI && !one_exactly - && !is_Magic(c))); ++len) { + && !is_Magic(c))); len++) { c = no_Magic(c); { regmbc(c); @@ -2469,7 +2429,7 @@ do_multibyte: // Need to get composing character too. for (;;) { l = utf_ptr2len((char *)regparse); - if (!utf_composinglike((char_u *)regparse, (char_u *)regparse + l)) { + if (!utf_composinglike(regparse, regparse + l)) { break; } regmbc(utf_ptr2char((char *)regparse)); @@ -2493,20 +2453,18 @@ do_multibyte: return ret; } -/* - * Parse something followed by possible [*+=]. - * - * Note that the branching code sequences used for = and the general cases - * of * and + are somewhat optimized: they use the same NOTHING node as - * both the endmarker for their branch list and the body of the last branch. - * It might seem that this node could be dispensed with entirely, but the - * endmarker role is not redundant. - */ -static char_u *regpiece(int *flagp) +// Parse something followed by possible [*+=]. +// +// Note that the branching code sequences used for = and the general cases +// of * and + are somewhat optimized: they use the same NOTHING node as +// both the endmarker for their branch list and the body of the last branch. +// It might seem that this node could be dispensed with entirely, but the +// endmarker role is not redundant. +static uint8_t *regpiece(int *flagp) { - char_u *ret; + uint8_t *ret; int op; - char_u *next; + uint8_t *next; int flags; long minval; long maxval; @@ -2637,15 +2595,13 @@ static char_u *regpiece(int *flagp) return ret; } -/* - * Parse one alternative of an | or & operator. - * Implements the concatenation operator. - */ -static char_u *regconcat(int *flagp) +// Parse one alternative of an | or & operator. +// Implements the concatenation operator. +static uint8_t *regconcat(int *flagp) { - char_u *first = NULL; - char_u *chain = NULL; - char_u *latest; + uint8_t *first = NULL; + uint8_t *chain = NULL; + uint8_t *latest; int flags; int cont = true; @@ -2715,15 +2671,13 @@ static char_u *regconcat(int *flagp) return first; } -/* - * Parse one alternative of an | operator. - * Implements the & operator. - */ -static char_u *regbranch(int *flagp) +// Parse one alternative of an | operator. +// Implements the & operator. +static uint8_t *regbranch(int *flagp) { - char_u *ret; - char_u *chain = NULL; - char_u *latest; + uint8_t *ret; + uint8_t *chain = NULL; + uint8_t *latest; int flags; *flagp = WORST | HASNL; // Tentatively. @@ -2768,11 +2722,11 @@ static char_u *regbranch(int *flagp) /// follows makes it hard to avoid. /// /// @param paren REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN -static char_u *reg(int paren, int *flagp) +static uint8_t *reg(int paren, int *flagp) { - char_u *ret; - char_u *br; - char_u *ender; + uint8_t *ret; + uint8_t *br; + uint8_t *ender; int parno = 0; int flags; @@ -2867,31 +2821,29 @@ static char_u *reg(int paren, int *flagp) return ret; } -/* - * bt_regcomp() - compile a regular expression into internal code for the - * traditional back track matcher. - * Returns the program in allocated space. Returns NULL for an error. - * - * We can't allocate space until we know how big the compiled form will be, - * but we can't compile it (and thus know how big it is) until we've got a - * place to put the code. So we cheat: we compile it twice, once with code - * generation turned off and size counting turned on, and once "for real". - * This also means that we don't allocate space until we are sure that the - * thing really will compile successfully, and we never have to move the - * code and thus invalidate pointers into it. (Note that it has to be in - * one piece because free() must be able to free it all.) - * - * Whether upper/lower case is to be ignored is decided when executing the - * program, it does not matter here. - * - * Beware that the optimization-preparation code in here knows about some - * of the structure of the compiled regexp. - * "re_flags": RE_MAGIC and/or RE_STRING. - */ -static regprog_T *bt_regcomp(char_u *expr, int re_flags) +// bt_regcomp() - compile a regular expression into internal code for the +// traditional back track matcher. +// Returns the program in allocated space. Returns NULL for an error. +// +// We can't allocate space until we know how big the compiled form will be, +// but we can't compile it (and thus know how big it is) until we've got a +// place to put the code. So we cheat: we compile it twice, once with code +// generation turned off and size counting turned on, and once "for real". +// This also means that we don't allocate space until we are sure that the +// thing really will compile successfully, and we never have to move the +// code and thus invalidate pointers into it. (Note that it has to be in +// one piece because free() must be able to free it all.) +// +// Whether upper/lower case is to be ignored is decided when executing the +// program, it does not matter here. +// +// Beware that the optimization-preparation code in here knows about some +// of the structure of the compiled regexp. +// "re_flags": RE_MAGIC and/or RE_STRING. +static regprog_T *bt_regcomp(uint8_t *expr, int re_flags) { - char_u *scan; - char_u *longest; + uint8_t *scan; + uint8_t *longest; int len; int flags; @@ -2938,7 +2890,7 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags) r->regflags |= RF_LOOKBH; } // Remember whether this pattern has any \z specials in it. - r->reghasz = (char_u)re_has_z; + r->reghasz = (uint8_t)re_has_z; scan = r->program + 1; // First BRANCH. if (OP(regnext(scan)) == END) { // Only one top-level choice. scan = OPERAND(scan); @@ -2956,7 +2908,7 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags) || OP(scan) == NOTHING || OP(scan) == MOPEN + 0 || OP(scan) == NOPEN || OP(scan) == MCLOSE + 0 || OP(scan) == NCLOSE) { - char_u *regnext_scan = regnext(scan); + uint8_t *regnext_scan = regnext(scan); if (OP(regnext_scan) == EXACTLY) { r->regstart = utf_ptr2char((char *)OPERAND(regnext_scan)); } @@ -2976,9 +2928,9 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags) longest = NULL; len = 0; for (; scan != NULL; scan = regnext(scan)) { - if (OP(scan) == EXACTLY && STRLEN(OPERAND(scan)) >= (size_t)len) { + if (OP(scan) == EXACTLY && strlen((char *)OPERAND(scan)) >= (size_t)len) { longest = OPERAND(scan); - len = (int)STRLEN(OPERAND(scan)); + len = (int)strlen((char *)OPERAND(scan)); } } r->regmust = longest; @@ -2992,19 +2944,15 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags) return (regprog_T *)r; } -/* - * Check if during the previous call to vim_regcomp the EOL item "$" has been - * found. This is messy, but it works fine. - */ +// Check if during the previous call to vim_regcomp the EOL item "$" has been +// found. This is messy, but it works fine. int vim_regcomp_had_eol(void) { return had_eol; } -/* - * Get a number after a backslash that is inside []. - * When nothing is recognized return a backslash. - */ +// Get a number after a backslash that is inside []. +// When nothing is recognized return a backslash. static int coll_get_char(void) { int64_t nr = -1; @@ -3030,9 +2978,7 @@ static int coll_get_char(void) return (int)nr; } -/* - * Free a compiled regexp program, returned by bt_regcomp(). - */ +// Free a compiled regexp program, returned by bt_regcomp(). static void bt_regfree(regprog_T *prog) { xfree(prog); @@ -3040,11 +2986,9 @@ static void bt_regfree(regprog_T *prog) #define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input) -/* - * The arguments from BRACE_LIMITS are stored here. They are actually local - * to regmatch(), but they are here to reduce the amount of stack space used - * (it can be called recursively many times). - */ +// The arguments from BRACE_LIMITS are stored here. They are actually local +// to regmatch(), but they are here to reduce the amount of stack space used +// (it can be called recursively many times). static long bl_minval; static long bl_maxval; @@ -3070,7 +3014,7 @@ static void reg_restore(regsave_T *save, garray_T *gap) // only call reg_getline() when the line number changed to save // a bit of time rex.lnum = save->rs_u.pos.lnum; - rex.line = reg_getline(rex.lnum); + rex.line = (uint8_t *)reg_getline(rex.lnum); } rex.input = rex.line + save->rs_u.pos.col; } else { @@ -3101,13 +3045,11 @@ static bool reg_save_equal(const regsave_T *save) else /* NOLINT */ \ *(pp) = (savep)->se_u.ptr; } -/* - * Tentatively set the sub-expression start to the current position (after - * calling regmatch() they will have changed). Need to save the existing - * values for when there is no match. - * Use se_save() to use pointer (save_se_multi()) or position (save_se_one()), - * depending on REG_MULTI. - */ +// Tentatively set the sub-expression start to the current position (after +// calling regmatch() they will have changed). Need to save the existing +// values for when there is no match. +// Use se_save() to use pointer (save_se_multi()) or position (save_se_one()), +// depending on REG_MULTI. static void save_se_multi(save_se_T *savep, lpos_T *posp) { savep->se_u.pos = *posp; @@ -3115,7 +3057,7 @@ static void save_se_multi(save_se_T *savep, lpos_T *posp) posp->col = (colnr_T)(rex.input - rex.line); } -static void save_se_one(save_se_T *savep, char_u **pp) +static void save_se_one(save_se_T *savep, uint8_t **pp) { savep->se_u.ptr = *pp; *pp = rex.input; @@ -3125,14 +3067,14 @@ static void save_se_one(save_se_T *savep, char_u **pp) /// Advances rex.input (and rex.lnum) to just after the matched chars. /// /// @param maxcount maximum number of matches allowed -static int regrepeat(char_u *p, long maxcount) +static int regrepeat(uint8_t *p, long maxcount) { long count = 0; - char_u *opnd; + uint8_t *opnd; int mask; int testval = 0; - char_u *scan = rex.input; // Make local copy of rex.input for speed. + uint8_t *scan = rex.input; // Make local copy of rex.input for speed. opnd = OPERAND(p); switch (OP(p)) { case ANY: @@ -3192,7 +3134,7 @@ static int regrepeat(char_u *p, long maxcount) case SKWORD: case SKWORD + ADD_NL: while (count < maxcount) { - if (vim_iswordp_buf(scan, rex.reg_buf) + if (vim_iswordp_buf((char *)scan, rex.reg_buf) && (testval || !ascii_isdigit(*scan))) { MB_PTR_ADV(scan); } else if (*scan == NUL) { @@ -3443,12 +3385,12 @@ do_class: } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { scan++; } else if ((len = utfc_ptr2len((char *)scan)) > 1) { - if ((cstrchr(opnd, utf_ptr2char((char *)scan)) == NULL) == testval) { + if ((cstrchr((char *)opnd, utf_ptr2char((char *)scan)) == NULL) == testval) { break; } scan += len; } else { - if ((cstrchr(opnd, *scan) == NULL) == testval) { + if ((cstrchr((char *)opnd, *scan) == NULL) == testval) { break; } scan++; @@ -3487,11 +3429,9 @@ do_class: return (int)count; } -/* - * Push an item onto the regstack. - * Returns pointer to new item. Returns NULL when out of memory. - */ -static regitem_T *regstack_push(regstate_T state, char_u *scan) +// Push an item onto the regstack. +// Returns pointer to new item. Returns NULL when out of memory. +static regitem_T *regstack_push(regstate_T state, uint8_t *scan) { regitem_T *rp; @@ -3509,10 +3449,8 @@ static regitem_T *regstack_push(regstate_T state, char_u *scan) return rp; } -/* - * Pop an item from the regstack. - */ -static void regstack_pop(char_u **scan) +// Pop an item from the regstack. +static void regstack_pop(uint8_t **scan) { regitem_T *rp; @@ -3530,15 +3468,17 @@ static void save_subexpr(regbehind_T *bp) // When "rex.need_clear_subexpr" is set we don't need to save the values, only // remember that this flag needs to be set again when restoring. bp->save_need_clear_subexpr = rex.need_clear_subexpr; - if (!rex.need_clear_subexpr) { - for (int i = 0; i < NSUBEXP; i++) { - if (REG_MULTI) { - bp->save_start[i].se_u.pos = rex.reg_startpos[i]; - bp->save_end[i].se_u.pos = rex.reg_endpos[i]; - } else { - bp->save_start[i].se_u.ptr = rex.reg_startp[i]; - bp->save_end[i].se_u.ptr = rex.reg_endp[i]; - } + if (rex.need_clear_subexpr) { + return; + } + + for (int i = 0; i < NSUBEXP; i++) { + if (REG_MULTI) { + bp->save_start[i].se_u.pos = rex.reg_startpos[i]; + bp->save_end[i].se_u.pos = rex.reg_endpos[i]; + } else { + bp->save_start[i].se_u.ptr = rex.reg_startp[i]; + bp->save_end[i].se_u.ptr = rex.reg_endp[i]; } } } @@ -3549,15 +3489,17 @@ static void restore_subexpr(regbehind_T *bp) { // Only need to restore saved values when they are not to be cleared. rex.need_clear_subexpr = bp->save_need_clear_subexpr; - if (!rex.need_clear_subexpr) { - for (int i = 0; i < NSUBEXP; i++) { - if (REG_MULTI) { - rex.reg_startpos[i] = bp->save_start[i].se_u.pos; - rex.reg_endpos[i] = bp->save_end[i].se_u.pos; - } else { - rex.reg_startp[i] = bp->save_start[i].se_u.ptr; - rex.reg_endp[i] = bp->save_end[i].se_u.ptr; - } + if (rex.need_clear_subexpr) { + return; + } + + for (int i = 0; i < NSUBEXP; i++) { + if (REG_MULTI) { + rex.reg_startpos[i] = bp->save_start[i].se_u.pos; + rex.reg_endpos[i] = bp->save_end[i].se_u.pos; + } else { + rex.reg_startp[i] = bp->save_start[i].se_u.ptr; + rex.reg_endp[i] = bp->save_end[i].se_u.ptr; } } } @@ -3578,9 +3520,9 @@ static void restore_subexpr(regbehind_T *bp) /// just after the last matched character. /// - false when there is no match. Leaves rex.input and rex.lnum in an /// undefined state! -static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) +static bool regmatch(uint8_t *scan, proftime_T *tm, int *timed_out) { - char_u *next; // Next node. + uint8_t *next; // Next node. int op; int c; regitem_T *rp; @@ -3601,8 +3543,8 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) #ifdef REGEXP_DEBUG if (scan != NULL && regnarrate) { - mch_errmsg((char *)regprop(scan)); - mch_errmsg("(\n"); + os_errmsg((char *)regprop(scan)); + os_errmsg("(\n"); } #endif @@ -3628,18 +3570,18 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) #ifdef REGEXP_DEBUG if (regnarrate) { - mch_errmsg((char *)regprop(scan)); - mch_errmsg("...\n"); + os_errmsg((char *)regprop(scan)); + os_errmsg("...\n"); if (re_extmatch_in != NULL) { int i; - mch_errmsg(_("External submatches:\n")); + os_errmsg(_("External submatches:\n")); for (i = 0; i < NSUBEXP; i++) { - mch_errmsg(" \""); + os_errmsg(" \""); if (re_extmatch_in->matches[i] != NULL) { - mch_errmsg((char *)re_extmatch_in->matches[i]); + os_errmsg((char *)re_extmatch_in->matches[i]); } - mch_errmsg("\"\n"); + os_errmsg("\"\n"); } } } @@ -3709,7 +3651,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) // Line may have been freed, get it again. if (REG_MULTI) { - rex.line = reg_getline(rex.lnum); + rex.line = (uint8_t *)reg_getline(rex.lnum); rex.input = rex.line + col; } @@ -3720,7 +3662,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) pos = &fm->mark; const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum && pos->col == MAXCOL - ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum)) + ? (colnr_T)strlen((char *)reg_getline(pos->lnum - rex.reg_firstlnum)) : pos->col; if (pos->lnum == rex.lnum + rex.reg_firstlnum @@ -3764,7 +3706,8 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case RE_VCOL: if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL ? curwin : rex.reg_win, - rex.line, + rex.reg_firstlnum + rex.lnum, + (char *)rex.line, (colnr_T)(rex.input - rex.line)) + 1, scan)) { status = RA_NOMATCH; @@ -3777,7 +3720,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) } else { // Get class of current and previous char (if it exists). const int this_class = - mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); + mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab); if (this_class <= 1) { status = RA_NOMATCH; // Not on a word at all. } else if (reg_prev_class() == this_class) { @@ -3793,7 +3736,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) int this_class, prev_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); + this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab); prev_class = reg_prev_class(); if (this_class == prev_class || prev_class == 0 || prev_class == 1) { @@ -3828,7 +3771,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) break; case KWORD: - if (!vim_iswordp_buf(rex.input, rex.reg_buf)) { + if (!vim_iswordp_buf((char *)rex.input, rex.reg_buf)) { status = RA_NOMATCH; } else { ADVANCE_REGINPUT(); @@ -3837,7 +3780,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case SKWORD: if (ascii_isdigit(*rex.input) - || !vim_iswordp_buf(rex.input, rex.reg_buf)) { + || !vim_iswordp_buf((char *)rex.input, rex.reg_buf)) { status = RA_NOMATCH; } else { ADVANCE_REGINPUT(); @@ -4022,7 +3965,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case EXACTLY: { int len; - char_u *opnd; + uint8_t *opnd; opnd = OPERAND(scan); // Inline the first byte, for speed. @@ -4037,15 +3980,15 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) len = 1; // matched a single byte above } else { // Need to match first byte again for multi-byte. - len = (int)STRLEN(opnd); - if (cstrncmp(opnd, rex.input, &len) != 0) { + len = (int)strlen((char *)opnd); + if (cstrncmp((char *)opnd, (char *)rex.input, &len) != 0) { status = RA_NOMATCH; } } // Check for following composing character, unless %C // follows (skips over all composing chars). if (status != RA_NOMATCH - && utf_composinglike(rex.input, rex.input + len) + && utf_composinglike((char *)rex.input, (char *)rex.input + len) && !rex.reg_icombine && OP(next) != RE_COMPOSING) { // raaron: This code makes a composing character get @@ -4064,7 +4007,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case ANYBUT: if (c == NUL) { status = RA_NOMATCH; - } else if ((cstrchr(OPERAND(scan), c) == NULL) == (op == ANYOF)) { + } else if ((cstrchr((char *)OPERAND(scan), c) == NULL) == (op == ANYOF)) { status = RA_NOMATCH; } else { ADVANCE_REGINPUT(); @@ -4074,7 +4017,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case MULTIBYTECODE: { int i, len; - const char_u *opnd = OPERAND(scan); + const uint8_t *opnd = OPERAND(scan); // Safety check (just in case 'encoding' was changed since // compiling the program). if ((len = utfc_ptr2len((char *)opnd)) < 2) { @@ -4269,7 +4212,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) } else { // Compare current input with back-ref in the same line. len = (int)(rex.reg_endp[no] - rex.reg_startp[no]); - if (cstrncmp(rex.reg_startp[no], rex.input, &len) != 0) { + if (cstrncmp((char *)rex.reg_startp[no], (char *)rex.input, &len) != 0) { status = RA_NOMATCH; } } @@ -4282,8 +4225,8 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) && rex.reg_endpos[no].lnum == rex.lnum) { // Compare back-ref within the current line. len = rex.reg_endpos[no].col - rex.reg_startpos[no].col; - if (cstrncmp(rex.line + rex.reg_startpos[no].col, - rex.input, &len) != 0) { + if (cstrncmp((char *)rex.line + rex.reg_startpos[no].col, + (char *)rex.input, &len) != 0) { status = RA_NOMATCH; } } else { @@ -4318,8 +4261,8 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) no = op - ZREF; if (re_extmatch_in != NULL && re_extmatch_in->matches[no] != NULL) { - int len = (int)STRLEN(re_extmatch_in->matches[no]); - if (cstrncmp(re_extmatch_in->matches[no], rex.input, &len) != 0) { + int len = (int)strlen((char *)re_extmatch_in->matches[no]); + if (cstrncmp((char *)re_extmatch_in->matches[no], (char *)rex.input, &len) != 0) { status = RA_NOMATCH; } else { rex.input += len; @@ -4369,7 +4312,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) case BRACE_COMPLEX + 8: case BRACE_COMPLEX + 9: no = op - BRACE_COMPLEX; - ++brace_count[no]; + brace_count[no]++; // If not matched enough times yet, try one more if (brace_count[no] <= (brace_min[no] <= brace_max[no] @@ -4635,7 +4578,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) // Pop the state. Restore pointers when there is no match. if (status == RA_NOMATCH) { reg_restore(&rp->rs_un.regsave, &backpos); - --brace_count[rp->rs_no]; // decrement match count + brace_count[rp->rs_no]--; // decrement match count } regstack_pop(&scan); break; @@ -4645,7 +4588,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) if (status == RA_NOMATCH) { // There was no match, but we did find enough matches. reg_restore(&rp->rs_un.regsave, &backpos); - --brace_count[rp->rs_no]; + brace_count[rp->rs_no]--; // continue with the items after "\{}" status = RA_CONT; } @@ -4744,7 +4687,7 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) if (limit > 0 && ((rp->rs_un.regsave.rs_u.pos.lnum < behind_pos.rs_u.pos.lnum - ? (colnr_T)STRLEN(rex.line) + ? (colnr_T)strlen((char *)rex.line) : behind_pos.rs_u.pos.col) - rp->rs_un.regsave.rs_u.pos.col >= limit)) { no = FAIL; @@ -4757,15 +4700,15 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) } else { reg_restore(&rp->rs_un.regsave, &backpos); rp->rs_un.regsave.rs_u.pos.col = - (colnr_T)STRLEN(rex.line); + (colnr_T)strlen((char *)rex.line); } } else { - const char_u *const line = - reg_getline(rp->rs_un.regsave.rs_u.pos.lnum); + const uint8_t *const line = + (uint8_t *)reg_getline(rp->rs_un.regsave.rs_u.pos.lnum); rp->rs_un.regsave.rs_u.pos.col -= - utf_head_off(line, - line + rp->rs_un.regsave.rs_u.pos.col - 1) + utf_head_off((char *)line, + (char *)line + rp->rs_un.regsave.rs_u.pos.col - 1) + 1; } } else { @@ -4843,12 +4786,12 @@ static bool regmatch(char_u *scan, proftime_T *tm, int *timed_out) break; } rex.lnum--; - rex.line = reg_getline(rex.lnum); + rex.line = (uint8_t *)reg_getline(rex.lnum); // Just in case regrepeat() didn't count right. if (rex.line == NULL) { break; } - rex.input = rex.line + STRLEN(rex.line); + rex.input = rex.line + strlen((char *)rex.line); fast_breakcheck(); } else { MB_PTR_BACK(rex.line, rex.input); @@ -4974,13 +4917,13 @@ static long regtry(bt_regprog_T *prog, colnr_T col, proftime_T *tm, int *timed_o && reg_endzpos[i].lnum == reg_startzpos[i].lnum && reg_endzpos[i].col >= reg_startzpos[i].col) { re_extmatch_out->matches[i] = - vim_strnsave(reg_getline(reg_startzpos[i].lnum) + reg_startzpos[i].col, - (size_t)(reg_endzpos[i].col - reg_startzpos[i].col)); + (uint8_t *)xstrnsave((char *)reg_getline(reg_startzpos[i].lnum) + reg_startzpos[i].col, + (size_t)(reg_endzpos[i].col - reg_startzpos[i].col)); } } else { if (reg_startzp[i] != NULL && reg_endzp[i] != NULL) { re_extmatch_out->matches[i] = - vim_strnsave(reg_startzp[i], (size_t)(reg_endzp[i] - reg_startzp[i])); + (uint8_t *)xstrnsave((char *)reg_startzp[i], (size_t)(reg_endzp[i] - reg_startzp[i])); } } } @@ -4991,15 +4934,16 @@ static long regtry(bt_regprog_T *prog, colnr_T col, proftime_T *tm, int *timed_o /// Match a regexp against a string ("line" points to the string) or multiple /// lines (if "line" is NULL, use reg_getline()). /// -/// @param col column to start search +/// @param startcol column to start looking for match /// @param tm timeout limit or NULL /// @param timed_out flag set on timeout or NULL /// /// @return 0 for failure, or number of lines contained in the match. -static long bt_regexec_both(char_u *line, colnr_T col, proftime_T *tm, int *timed_out) +static long bt_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int *timed_out) { bt_regprog_T *prog; - char_u *s; + uint8_t *s; + colnr_T col = startcol; long retval = 0L; // Create "regstack" and "backpos" if they are not allocated yet. @@ -5022,13 +4966,13 @@ static long bt_regexec_both(char_u *line, colnr_T col, proftime_T *tm, int *time if (REG_MULTI) { prog = (bt_regprog_T *)rex.reg_mmatch->regprog; - line = reg_getline((linenr_T)0); + line = (uint8_t *)reg_getline((linenr_T)0); rex.reg_startpos = rex.reg_mmatch->startpos; rex.reg_endpos = rex.reg_mmatch->endpos; } else { prog = (bt_regprog_T *)rex.reg_match->regprog; - rex.reg_startp = rex.reg_match->startp; - rex.reg_endp = rex.reg_match->endp; + rex.reg_startp = (uint8_t **)rex.reg_match->startp; + rex.reg_endp = (uint8_t **)rex.reg_match->endp; } // Be paranoid... @@ -5067,15 +5011,15 @@ static long bt_regexec_both(char_u *line, colnr_T col, proftime_T *tm, int *time // This is used very often, esp. for ":global". Use two versions of // the loop to avoid overhead of conditions. if (!rex.reg_ic) { - while ((s = (char_u *)vim_strchr((char *)s, c)) != NULL) { - if (cstrncmp(s, prog->regmust, &prog->regmlen) == 0) { + while ((s = (uint8_t *)vim_strchr((char *)s, c)) != NULL) { + if (cstrncmp((char *)s, (char *)prog->regmust, &prog->regmlen) == 0) { break; // Found it. } MB_PTR_ADV(s); } } else { - while ((s = cstrchr(s, c)) != NULL) { - if (cstrncmp(s, prog->regmust, &prog->regmlen) == 0) { + while ((s = (uint8_t *)cstrchr((char *)s, c)) != NULL) { + if (cstrncmp((char *)s, (char *)prog->regmust, &prog->regmlen) == 0) { break; // Found it. } MB_PTR_ADV(s); @@ -5109,7 +5053,7 @@ static long bt_regexec_both(char_u *line, colnr_T col, proftime_T *tm, int *time while (!got_int) { if (prog->regstart != NUL) { // Skip until the char we know it must start with. - s = cstrchr(rex.line + col, prog->regstart); + s = (uint8_t *)cstrchr((char *)rex.line + col, prog->regstart); if (s == NULL) { retval = 0; break; @@ -5131,7 +5075,7 @@ static long bt_regexec_both(char_u *line, colnr_T col, proftime_T *tm, int *time // if not currently on the first line, get it again if (rex.lnum != 0) { rex.lnum = 0; - rex.line = reg_getline((linenr_T)0); + rex.line = (uint8_t *)reg_getline((linenr_T)0); } if (rex.line[col] == NUL) { break; @@ -5174,10 +5118,18 @@ theend: || (end->lnum == start->lnum && end->col < start->col)) { rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0]; } + + // startpos[0] may be set by "\zs", also return the column where + // the whole pattern matched. + rex.reg_mmatch->rmm_matchcol = col; } else { if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) { rex.reg_match->endp[0] = rex.reg_match->startp[0]; } + + // startpos[0] may be set by "\zs", also return the column where + // the whole pattern matched. + rex.reg_match->rm_matchcol = col; } } @@ -5193,7 +5145,7 @@ theend: /// @param col column to start looking for match /// /// @return 0 for failure, number of lines contained in the match otherwise. -static int bt_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col, bool line_lbr) +static int bt_regexec_nl(regmatch_T *rmp, uint8_t *line, colnr_T col, bool line_lbr) { rex.reg_match = rmp; rex.reg_mmatch = NULL; @@ -5225,24 +5177,12 @@ static int bt_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col, bool line_l static long bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col, proftime_T *tm, int *timed_out) { - rex.reg_match = NULL; - rex.reg_mmatch = rmp; - rex.reg_buf = buf; - rex.reg_win = win; - rex.reg_firstlnum = lnum; - rex.reg_maxline = rex.reg_buf->b_ml.ml_line_count - lnum; - rex.reg_line_lbr = false; - rex.reg_ic = rmp->rmm_ic; - rex.reg_icombine = false; - rex.reg_maxcol = rmp->rmm_maxcol; - + init_regexec_multi(rmp, win, buf, lnum); return bt_regexec_both(NULL, col, tm, timed_out); } -/* - * Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL. - */ -static int re_num_cmp(uint32_t val, char_u *scan) +// Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL. +static int re_num_cmp(uint32_t val, uint8_t *scan) { uint32_t n = (uint32_t)OPERAND_MIN(scan); @@ -5257,15 +5197,13 @@ static int re_num_cmp(uint32_t val, char_u *scan) #ifdef BT_REGEXP_DUMP -/* - * regdump - dump a regexp onto stdout in vaguely comprehensible form - */ -static void regdump(char_u *pattern, bt_regprog_T *r) +// regdump - dump a regexp onto stdout in vaguely comprehensible form +static void regdump(uint8_t *pattern, bt_regprog_T *r) { - char_u *s; + uint8_t *s; int op = EXACTLY; // Arbitrary non-END op. - char_u *next; - char_u *end = NULL; + uint8_t *next; + uint8_t *end = NULL; FILE *f; # ifdef BT_REGEXP_LOG @@ -5345,10 +5283,8 @@ static void regdump(char_u *pattern, bt_regprog_T *r) #ifdef REGEXP_DEBUG -/* - * regprop - printable representation of opcode - */ -static char_u *regprop(char_u *op) +// regprop - printable representation of opcode +static uint8_t *regprop(uint8_t *op) { char *p; static char buf[50]; @@ -5593,7 +5529,7 @@ static char_u *regprop(char_u *op) case MOPEN + 7: case MOPEN + 8: case MOPEN + 9: - sprintf(buf + STRLEN(buf), "MOPEN%d", OP(op) - MOPEN); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "MOPEN%d", OP(op) - MOPEN); p = NULL; break; case MCLOSE + 0: @@ -5608,7 +5544,7 @@ static char_u *regprop(char_u *op) case MCLOSE + 7: case MCLOSE + 8: case MCLOSE + 9: - sprintf(buf + STRLEN(buf), "MCLOSE%d", OP(op) - MCLOSE); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "MCLOSE%d", OP(op) - MCLOSE); p = NULL; break; case BACKREF + 1: @@ -5620,7 +5556,7 @@ static char_u *regprop(char_u *op) case BACKREF + 7: case BACKREF + 8: case BACKREF + 9: - sprintf(buf + STRLEN(buf), "BACKREF%d", OP(op) - BACKREF); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "BACKREF%d", OP(op) - BACKREF); p = NULL; break; case NOPEN: @@ -5638,7 +5574,7 @@ static char_u *regprop(char_u *op) case ZOPEN + 7: case ZOPEN + 8: case ZOPEN + 9: - sprintf(buf + STRLEN(buf), "ZOPEN%d", OP(op) - ZOPEN); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZOPEN%d", OP(op) - ZOPEN); p = NULL; break; case ZCLOSE + 1: @@ -5650,7 +5586,7 @@ static char_u *regprop(char_u *op) case ZCLOSE + 7: case ZCLOSE + 8: case ZCLOSE + 9: - sprintf(buf + STRLEN(buf), "ZCLOSE%d", OP(op) - ZCLOSE); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZCLOSE%d", OP(op) - ZCLOSE); p = NULL; break; case ZREF + 1: @@ -5662,7 +5598,7 @@ static char_u *regprop(char_u *op) case ZREF + 7: case ZREF + 8: case ZREF + 9: - sprintf(buf + STRLEN(buf), "ZREF%d", OP(op) - ZREF); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZREF%d", OP(op) - ZREF); p = NULL; break; case STAR: @@ -5702,7 +5638,8 @@ static char_u *regprop(char_u *op) case BRACE_COMPLEX + 7: case BRACE_COMPLEX + 8: case BRACE_COMPLEX + 9: - sprintf(buf + STRLEN(buf), "BRACE_COMPLEX%d", OP(op) - BRACE_COMPLEX); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "BRACE_COMPLEX%d", + OP(op) - BRACE_COMPLEX); p = NULL; break; case MULTIBYTECODE: @@ -5712,13 +5649,13 @@ static char_u *regprop(char_u *op) p = "NEWL"; break; default: - sprintf(buf + STRLEN(buf), "corrupt %d", OP(op)); + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "corrupt %d", OP(op)); p = NULL; break; } if (p != NULL) { STRCAT(buf, p); } - return (char_u *)buf; + return (uint8_t *)buf; } #endif // REGEXP_DEBUG diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index b313dfe877..16bb2db464 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -1,13 +1,11 @@ -/* - * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE - * - * This is NOT the original regular expression code as written by Henry - * Spencer. This code has been modified specifically for use with Vim, and - * should not be used apart from compiling Vim. If you want a good regular - * expression library, get the original code. - * - * NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE - */ +// NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE +// +// This is NOT the original regular expression code as written by Henry +// Spencer. This code has been modified specifically for use with Vim, and +// should not be used apart from compiling Vim. If you want a good regular +// expression library, get the original code. +// +// NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE #ifndef NVIM_REGEXP_DEFS_H #define NVIM_REGEXP_DEFS_H @@ -17,18 +15,32 @@ #include "nvim/pos.h" #include "nvim/types.h" -/* - * The number of sub-matches is limited to 10. - * The first one (index 0) is the whole match, referenced with "\0". - * The second one (index 1) is the first sub-match, referenced with "\1". - * This goes up to the tenth (index 9), referenced with "\9". - */ +/// Used for "magic_overruled". +typedef enum { + OPTION_MAGIC_NOT_SET, ///< p_magic not overruled + OPTION_MAGIC_ON, ///< magic on inside regexp + OPTION_MAGIC_OFF, ///< magic off inside regexp +} optmagic_T; + +/// Magicness of a pattern, used by regexp code. +/// The order and values matter: +/// magic <= MAGIC_OFF includes MAGIC_NONE +/// magic >= MAGIC_ON includes MAGIC_ALL +typedef enum { + MAGIC_NONE = 1, ///< "\V" very unmagic + MAGIC_OFF = 2, ///< "\M" or 'magic' off + MAGIC_ON = 3, ///< "\m" or 'magic' + MAGIC_ALL = 4, ///< "\v" very magic +} magic_T; + +// The number of sub-matches is limited to 10. +// The first one (index 0) is the whole match, referenced with "\0". +// The second one (index 1) is the first sub-match, referenced with "\1". +// This goes up to the tenth (index 9), referenced with "\9". #define NSUBEXP 10 -/* - * In the NFA engine: how many braces are allowed. - * TODO(RE): Use dynamic memory allocation instead of static, like here - */ +// In the NFA engine: how many braces are allowed. +// TODO(RE): Use dynamic memory allocation instead of static, like here #define NFA_MAX_BRACES 20 // In the NFA engine: how many states are allowed. @@ -55,17 +67,17 @@ typedef struct { regprog_T *regprog; lpos_T startpos[NSUBEXP]; lpos_T endpos[NSUBEXP]; + + colnr_T rmm_matchcol; ///< match start without "\zs" int rmm_ic; colnr_T rmm_maxcol; /// when not zero: maximum column } regmmatch_T; #include "nvim/buffer_defs.h" -/* - * Structure returned by vim_regcomp() to pass on to vim_regexec(). - * This is the general structure. For the actual matcher, two specific - * structures are used. See code below. - */ +// Structure returned by vim_regcomp() to pass on to vim_regexec(). +// This is the general structure. For the actual matcher, two specific +// structures are used. See code below. struct regprog { regengine_T *engine; unsigned regflags; @@ -74,11 +86,9 @@ struct regprog { bool re_in_use; ///< prog is being executed }; -/* - * Structure used by the back track matcher. - * These fields are only to be used in regexp.c! - * See regexp.c for an explanation. - */ +// Structure used by the back track matcher. +// These fields are only to be used in regexp.c! +// See regexp.c for an explanation. typedef struct { // These four members implement regprog_T. regengine_T *engine; @@ -107,9 +117,7 @@ struct nfa_state { int val; }; -/* - * Structure used by the NFA matcher. - */ +// Structure used by the NFA matcher. typedef struct { // These four members implement regprog_T. regengine_T *engine; @@ -127,29 +135,27 @@ typedef struct { int has_zend; // pattern contains \ze int has_backref; // pattern contains \1 .. \9 int reghasz; - char_u *pattern; + char *pattern; int nsubexp; // number of () int nstate; nfa_state_T state[1]; // actually longer.. } nfa_regprog_T; -/* - * Structure to be used for single-line matching. - * Sub-match "no" starts at "startp[no]" and ends just before "endp[no]". - * When there is no match, the pointer is NULL. - */ +// Structure to be used for single-line matching. +// Sub-match "no" starts at "startp[no]" and ends just before "endp[no]". +// When there is no match, the pointer is NULL. typedef struct { regprog_T *regprog; - char_u *startp[NSUBEXP]; - char_u *endp[NSUBEXP]; + char *startp[NSUBEXP]; + char *endp[NSUBEXP]; + + colnr_T rm_matchcol; ///< match start without "\zs" bool rm_ic; } regmatch_T; -/* - * Structure used to store external references: "\z\(\)" to "\z\1". - * Use a reference count to avoid the need to copy this around. When it goes - * from 1 to zero the matches need to be freed. - */ +// Structure used to store external references: "\z\(\)" to "\z\1". +// Use a reference count to avoid the need to copy this around. When it goes +// from 1 to zero the matches need to be freed. struct reg_extmatch { int16_t refcnt; char_u *matches[NSUBEXP]; diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 554def5b8a..93b03f0632 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -1,11 +1,9 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * NFA regular expression implementation. - * - * This file is included in "regexp.c". - */ +// NFA regular expression implementation. +// +// This file is included in "regexp.c". #include <assert.h> #include <inttypes.h> @@ -246,10 +244,10 @@ static int nfa_classcodes[] = { NFA_UPPER, NFA_NUPPER }; -static char_u e_nul_found[] = N_("E865: (NFA) Regexp end encountered prematurely"); -static char_u e_misplaced[] = N_("E866: (NFA regexp) Misplaced %c"); -static char_u e_ill_char_class[] = N_("E877: (NFA regexp) Invalid character class: %" PRId64); -static char_u e_value_too_large[] = N_("E951: \\% value too large"); +static char e_nul_found[] = N_("E865: (NFA) Regexp end encountered prematurely"); +static char e_misplaced[] = N_("E866: (NFA regexp) Misplaced %c"); +static char e_ill_char_class[] = N_("E877: (NFA regexp) Invalid character class: %" PRId64); +static char e_value_too_large[] = N_("E951: \\% value too large"); // Since the out pointers in the list are always // uninitialized, we use the pointers themselves @@ -278,10 +276,11 @@ typedef struct { colnr_T end_col; } multi[NSUBEXP]; struct linepos { - char_u *start; - char_u *end; + uint8_t *start; + uint8_t *end; } line[NSUBEXP]; } list; + colnr_T orig_start_col; // list.multi[0].start_col without \zs } regsub_T; typedef struct { @@ -297,7 +296,7 @@ struct nfa_pim_S { regsubs_T subs; // submatch info, only party used union { lpos_T pos; - char_u *ptr; + uint8_t *ptr; } end; // where the match must end }; @@ -355,7 +354,7 @@ static int nfa_ll_index = 0; /// Initialize internal variables before NFA compilation. /// /// @param re_flags @see vim_regcomp() -static void nfa_regcomp_start(char_u *expr, int re_flags) +static void nfa_regcomp_start(uint8_t *expr, int re_flags) { size_t postfix_size; size_t nstate_max; @@ -363,7 +362,7 @@ static void nfa_regcomp_start(char_u *expr, int re_flags) nstate = 0; istate = 0; // A reasonable estimation for maximum size - nstate_max = (STRLEN(expr) + 1) * 25; + nstate_max = (strlen((char *)expr) + 1) * 25; // Some items blow up in size, such as [A-z]. Add more space for that. // When it is still not enough realloc_post_list() will be used. @@ -383,10 +382,8 @@ static void nfa_regcomp_start(char_u *expr, int re_flags) regcomp_start(expr, re_flags); } -/* - * Figure out if the NFA state list starts with an anchor, must match at start - * of the line. - */ +// Figure out if the NFA state list starts with an anchor, must match at start +// of the line. static int nfa_get_reganch(nfa_state_T *start, int depth) { nfa_state_T *p = start; @@ -441,10 +438,8 @@ static int nfa_get_reganch(nfa_state_T *start, int depth) return 0; } -/* - * Figure out if the NFA state list starts with a character which must match - * at start of the match. - */ +// Figure out if the NFA state list starts with a character which must match +// at start of the match. static int nfa_get_regstart(nfa_state_T *start, int depth) { nfa_state_T *p = start; @@ -521,17 +516,15 @@ static int nfa_get_regstart(nfa_state_T *start, int depth) return 0; } -/* - * Figure out if the NFA state list contains just literal text and nothing - * else. If so return a string in allocated memory with what must match after - * regstart. Otherwise return NULL. - */ -static char_u *nfa_get_match_text(nfa_state_T *start) +// Figure out if the NFA state list contains just literal text and nothing +// else. If so return a string in allocated memory with what must match after +// regstart. Otherwise return NULL. +static uint8_t *nfa_get_match_text(nfa_state_T *start) { nfa_state_T *p = start; int len = 0; - char_u *ret; - char_u *s; + uint8_t *ret; + uint8_t *s; if (p->c != NFA_MOPEN) { return NULL; // just in case @@ -557,10 +550,8 @@ static char_u *nfa_get_match_text(nfa_state_T *start) return ret; } -/* - * Allocate more space for post_start. Called when - * running above the estimated number of states. - */ +// Allocate more space for post_start. Called when +// running above the estimated number of states. static void realloc_post_list(void) { // For weird patterns the number of states can be very high. Increasing by @@ -572,17 +563,15 @@ static void realloc_post_list(void) post_start = new_start; } -/* - * Search between "start" and "end" and try to recognize a - * character class in expanded form. For example [0-9]. - * On success, return the id the character class to be emitted. - * On failure, return 0 (=FAIL) - * Start points to the first char of the range, while end should point - * to the closing brace. - * Keep in mind that 'ignorecase' applies at execution time, thus [a-z] may - * need to be interpreted as [a-zA-Z]. - */ -static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) +// Search between "start" and "end" and try to recognize a +// character class in expanded form. For example [0-9]. +// On success, return the id the character class to be emitted. +// On failure, return 0 (=FAIL) +// Start points to the first char of the range, while end should point +// to the closing brace. +// Keep in mind that 'ignorecase' applies at execution time, thus [a-z] may +// need to be interpreted as [a-zA-Z]. +static int nfa_recognize_char_class(uint8_t *start, uint8_t *end, int extra_newl) { #define CLASS_not 0x80 #define CLASS_af 0x40 @@ -593,7 +582,7 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) #define CLASS_o9 0x02 #define CLASS_underscore 0x01 - char_u *p; + uint8_t *p; int config = 0; bool newl = extra_newl == true; @@ -700,14 +689,12 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) return FAIL; } -/* - * Produce the bytes for equivalence class "c". - * Currently only handles latin1, latin9 and utf-8. - * Emits bytes in postfix notation: 'a,b,NFA_OR,c,NFA_OR' is - * equivalent to 'a OR b OR c' - * - * NOTE! When changing this function, also update reg_equi_class() - */ +// Produce the bytes for equivalence class "c". +// Currently only handles latin1, latin9 and utf-8. +// Emits bytes in postfix notation: 'a,b,NFA_OR,c,NFA_OR' is +// equivalent to 'a OR b OR c' +// +// NOTE! When changing this function, also update reg_equi_class() static void nfa_emit_equi_class(int c) { #define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT); @@ -1748,7 +1735,7 @@ static void nfa_emit_equi_class(int c) case 0x1ef5: case 0x1ef7: case 0x1ef9: - EMIT2('y') EMIT2(y_acute) EMIT2(y_diaeresis) + EMIT2('y') EMIT2(y_acute) EMIT2(y_diaeresis) // NOLINT(whitespace/cast) EMIT2(0x177) EMIT2(0x1b4) EMIT2(0x233) EMIT2(0x24f) EMIT2(0x1e8f) EMIT2(0x1e99) EMIT2(0x1ef3) EMIT2(0x1ef5) EMIT2(0x1ef7) EMIT2(0x1ef9) @@ -1778,26 +1765,22 @@ static void nfa_emit_equi_class(int c) #undef EMIT2 } -/* - * Code to parse regular expression. - * - * We try to reuse parsing functions in regexp.c to - * minimize surprise and keep the syntax consistent. - */ - -/* - * Parse the lowest level. - * - * An atom can be one of a long list of items. Many atoms match one character - * in the text. It is often an ordinary character or a character class. - * Braces can be used to make a pattern into an atom. The "\z(\)" construct - * is only for syntax highlighting. - * - * atom ::= ordinary-atom - * or \( pattern \) - * or \%( pattern \) - * or \z( pattern \) - */ +// Code to parse regular expression. +// +// We try to reuse parsing functions in regexp.c to +// minimize surprise and keep the syntax consistent. + +// Parse the lowest level. +// +// An atom can be one of a long list of items. Many atoms match one character +// in the text. It is often an ordinary character or a character class. +// Braces can be used to make a pattern into an atom. The "\z(\)" construct +// is only for syntax highlighting. +// +// atom ::= ordinary-atom +// or \( pattern \) +// or \%( pattern \) +// or \z( pattern \) static int nfa_regatom(void) { int c; @@ -1805,9 +1788,9 @@ static int nfa_regatom(void) int equiclass; int collclass; int got_coll_char; - char_u *p; - char_u *endp; - char_u *old_regparse = (char_u *)regparse; + uint8_t *p; + uint8_t *endp; + uint8_t *old_regparse = (uint8_t *)regparse; int extra = 0; int emit_range; int negated; @@ -1862,9 +1845,7 @@ static int nfa_regatom(void) // "\_x" is character class plus newline FALLTHROUGH; - /* - * Character classes. - */ + // Character classes. case Magic('.'): case Magic('i'): case Magic('I'): @@ -1892,7 +1873,7 @@ static int nfa_regatom(void) case Magic('L'): case Magic('u'): case Magic('U'): - p = (char_u *)vim_strchr((char *)classchars, no_Magic(c)); + p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c)); if (p == NULL) { if (extra == NFA_ADD_NL) { semsg(_(e_ill_char_class), (int64_t)c); @@ -1905,7 +1886,7 @@ static int nfa_regatom(void) // When '.' is followed by a composing char ignore the dot, so that // the composing char is matched here. if (c == Magic('.') && utf_iscomposing(peekchr())) { - old_regparse = (char_u *)regparse; + old_regparse = (uint8_t *)regparse; c = getchr(); goto nfa_do_multibyte; } @@ -1951,7 +1932,7 @@ static int nfa_regatom(void) return FAIL; case Magic('~'): { - char_u *lp; + uint8_t *lp; // Previous substitute pattern. // Generated as "\%(pattern\)". @@ -1959,9 +1940,9 @@ static int nfa_regatom(void) emsg(_(e_nopresub)); return FAIL; } - for (lp = reg_prev_sub; *lp != NUL; MB_CPTR_ADV(lp)) { + for (lp = (uint8_t *)reg_prev_sub; *lp != NUL; MB_CPTR_ADV(lp)) { EMIT(utf_ptr2char((char *)lp)); - if (lp != reg_prev_sub) { + if (lp != (uint8_t *)reg_prev_sub) { EMIT(NFA_CONCAT); } } @@ -2094,6 +2075,12 @@ static int nfa_regatom(void) break; case '#': + if (regparse[0] == '=' && regparse[1] >= 48 + && regparse[1] <= 50) { + // misplaced \%#=1 + semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]); + return FAIL; + } EMIT(NFA_CURSOR); break; @@ -2141,6 +2128,7 @@ static int nfa_regatom(void) int64_t n = 0; const int cmp = c; bool cur = false; + bool got_digit = false; if (c == '<' || c == '>') { c = getchr(); @@ -2151,7 +2139,7 @@ static int nfa_regatom(void) } while (ascii_isdigit(c)) { if (cur) { - semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c)); + semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c)); return FAIL; } if (n > (INT32_MAX - (c - '0')) / 10) { @@ -2161,10 +2149,15 @@ static int nfa_regatom(void) } n = n * 10 + (c - '0'); c = getchr(); + got_digit = true; } if (c == 'l' || c == 'c' || c == 'v') { int32_t limit = INT32_MAX; + if (!cur && !got_digit) { + semsg(_(e_nfa_regexp_missing_value_in_chr), no_Magic(c)); + return FAIL; + } if (c == 'l') { if (cur) { n = curwin->w_cursor.lnum; @@ -2216,25 +2209,21 @@ static int nfa_regatom(void) case Magic('['): collection: - /* - * [abc] uses NFA_START_COLL - NFA_END_COLL - * [^abc] uses NFA_START_NEG_COLL - NFA_END_NEG_COLL - * Each character is produced as a regular state, using - * NFA_CONCAT to bind them together. - * Besides normal characters there can be: - * - character classes NFA_CLASS_* - * - ranges, two characters followed by NFA_RANGE. - */ - - p = (char_u *)regparse; - endp = skip_anyof((char *)p); + // [abc] uses NFA_START_COLL - NFA_END_COLL + // [^abc] uses NFA_START_NEG_COLL - NFA_END_NEG_COLL + // Each character is produced as a regular state, using + // NFA_CONCAT to bind them together. + // Besides normal characters there can be: + // - character classes NFA_CLASS_* + // - ranges, two characters followed by NFA_RANGE. + + p = (uint8_t *)regparse; + endp = (uint8_t *)skip_anyof((char *)p); if (*endp == ']') { - /* - * Try to reverse engineer character classes. For example, - * recognize that [0-9] stands for \d and [A-Za-z_] for \h, - * and perform the necessary substitutions in the NFA. - */ - int result = nfa_recognize_char_class((char_u *)regparse, endp, extra == NFA_ADD_NL); + // Try to reverse engineer character classes. For example, + // recognize that [0-9] stands for \d and [A-Za-z_] for \h, + // and perform the necessary substitutions in the NFA. + int result = nfa_recognize_char_class((uint8_t *)regparse, endp, extra == NFA_ADD_NL); if (result != FAIL) { if (result >= NFA_FIRST_NL && result <= NFA_LAST_NL) { EMIT(result - NFA_ADD_NL); @@ -2247,10 +2236,8 @@ collection: MB_PTR_ADV(regparse); return OK; } - /* - * Failed to recognize a character class. Use the simple - * version that turns [abc] into 'a' OR 'b' OR 'c' - */ + // Failed to recognize a character class. Use the simple + // version that turns [abc] into 'a' OR 'b' OR 'c' startc = -1; negated = false; if (*regparse == '^') { // negated range @@ -2268,7 +2255,7 @@ collection: } // Emit the OR branches for each character in the [] emit_range = false; - while ((char_u *)regparse < endp) { + while ((uint8_t *)regparse < endp) { int oldstartc = startc; startc = -1; got_coll_char = false; @@ -2375,10 +2362,10 @@ collection: // accepts "\t", "\e", etc., but only when the 'l' flag in // 'cpoptions' is not included. if (*regparse == '\\' - && (char_u *)regparse + 1 <= endp - && (vim_strchr(REGEXP_INRANGE, regparse[1]) != NULL + && (uint8_t *)regparse + 1 <= endp + && (vim_strchr(REGEXP_INRANGE, (uint8_t)regparse[1]) != NULL || (!reg_cpo_lit - && vim_strchr(REGEXP_ABBR, regparse[1]) + && vim_strchr(REGEXP_ABBR, (uint8_t)regparse[1]) != NULL))) { MB_PTR_ADV(regparse); @@ -2542,16 +2529,14 @@ nfa_do_multibyte: return OK; } -/* - * Parse something followed by possible [*+=]. - * - * A piece is an atom, possibly followed by a multi, an indication of how many - * times the atom can be matched. Example: "a*" matches any sequence of "a" - * characters: "", "a", "aa", etc. - * - * piece ::= atom - * or atom multi - */ +// Parse something followed by possible [*+=]. +// +// A piece is an atom, possibly followed by a multi, an indication of how many +// times the atom can be matched. Example: "a*" matches any sequence of "a" +// characters: "", "a", "aa", etc. +// +// piece ::= atom +// or atom multi static int nfa_regpiece(void) { int i; @@ -2589,17 +2574,15 @@ static int nfa_regpiece(void) break; case Magic('+'): - /* - * Trick: Normally, (a*)\+ would match the whole input "aaa". The - * first and only submatch would be "aaa". But the backtracking - * engine interprets the plus as "try matching one more time", and - * a* matches a second time at the end of the input, the empty - * string. - * The submatch will be the empty string. - * - * In order to be consistent with the old engine, we replace - * <atom>+ with <atom><atom>* - */ + // Trick: Normally, (a*)\+ would match the whole input "aaa". The + // first and only submatch would be "aaa". But the backtracking + // engine interprets the plus as "try matching one more time", and + // a* matches a second time at the end of the input, the empty + // string. + // The submatch will be the empty string. + // + // In order to be consistent with the old engine, we replace + // <atom>+ with <atom><atom>* restore_parse_state(&old_state); curchr = -1; if (nfa_regatom() == FAIL) { @@ -2758,16 +2741,14 @@ static int nfa_regpiece(void) return OK; } -/* - * Parse one or more pieces, concatenated. It matches a match for the - * first piece, followed by a match for the second piece, etc. Example: - * "f[0-9]b", first matches "f", then a digit and then "b". - * - * concat ::= piece - * or piece piece - * or piece piece piece - * etc. - */ +// Parse one or more pieces, concatenated. It matches a match for the +// first piece, followed by a match for the second piece, etc. Example: +// "f[0-9]b", first matches "f", then a digit and then "b". +// +// concat ::= piece +// or piece piece +// or piece piece piece +// etc. static int nfa_regconcat(void) { bool cont = true; @@ -2831,18 +2812,16 @@ static int nfa_regconcat(void) return OK; } -/* - * Parse a branch, one or more concats, separated by "\&". It matches the - * last concat, but only if all the preceding concats also match at the same - * position. Examples: - * "foobeep\&..." matches "foo" in "foobeep". - * ".*Peter\&.*Bob" matches in a line containing both "Peter" and "Bob" - * - * branch ::= concat - * or concat \& concat - * or concat \& concat \& concat - * etc. - */ +// Parse a branch, one or more concats, separated by "\&". It matches the +// last concat, but only if all the preceding concats also match at the same +// position. Examples: +// "foobeep\&..." matches "foo" in "foobeep". +// ".*Peter\&.*Bob" matches in a line containing both "Peter" and "Bob" +// +// branch ::= concat +// or concat \& concat +// or concat \& concat \& concat +// etc. static int nfa_regbranch(void) { int old_post_pos; @@ -2948,7 +2927,7 @@ static int nfa_reg(int paren) } #ifdef REGEXP_DEBUG -static char_u code[50]; +static uint8_t code[50]; static void nfa_set_code(int c) { @@ -3296,42 +3275,40 @@ static void nfa_set_code(int c) } static FILE *log_fd; -static char_u e_log_open_failed[] = +static uint8_t e_log_open_failed[] = N_("Could not open temporary log file for writing, displaying on stderr... "); -/* - * Print the postfix notation of the current regexp. - */ -static void nfa_postfix_dump(char_u *expr, int retval) +// Print the postfix notation of the current regexp. +static void nfa_postfix_dump(uint8_t *expr, int retval) { int *p; FILE *f; f = fopen(NFA_REGEXP_DUMP_LOG, "a"); - if (f != NULL) { - fprintf(f, "\n-------------------------\n"); - if (retval == FAIL) { - fprintf(f, ">>> NFA engine failed... \n"); - } else if (retval == OK) { - fprintf(f, ">>> NFA engine succeeded !\n"); - } - fprintf(f, "Regexp: \"%s\"\nPostfix notation (char): \"", expr); - for (p = post_start; *p && p < post_ptr; p++) { - nfa_set_code(*p); - fprintf(f, "%s, ", code); - } - fprintf(f, "\"\nPostfix notation (int): "); - for (p = post_start; *p && p < post_ptr; p++) { - fprintf(f, "%d ", *p); - } - fprintf(f, "\n\n"); - fclose(f); + if (f == NULL) { + return; + } + + fprintf(f, "\n-------------------------\n"); + if (retval == FAIL) { + fprintf(f, ">>> NFA engine failed... \n"); + } else if (retval == OK) { + fprintf(f, ">>> NFA engine succeeded !\n"); + } + fprintf(f, "Regexp: \"%s\"\nPostfix notation (char): \"", expr); + for (p = post_start; *p && p < post_ptr; p++) { + nfa_set_code(*p); + fprintf(f, "%s, ", code); } + fprintf(f, "\"\nPostfix notation (int): "); + for (p = post_start; *p && p < post_ptr; p++) { + fprintf(f, "%d ", *p); + } + fprintf(f, "\n\n"); + fclose(f); } -/* - * Print the NFA starting with a root node "state". - */ +// Print the NFA starting with a root node "state". static void nfa_print_state(FILE *debugf, nfa_state_T *state) { garray_T indent; @@ -3344,7 +3321,7 @@ static void nfa_print_state(FILE *debugf, nfa_state_T *state) static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) { - char_u *p; + uint8_t *p; if (state == NULL) { return; @@ -3353,15 +3330,15 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) fprintf(debugf, "(%2d)", abs(state->id)); // Output indent - p = (char_u *)indent->ga_data; + p = (uint8_t *)indent->ga_data; if (indent->ga_len >= 3) { int last = indent->ga_len - 3; - char_u save[2]; + uint8_t save[2]; - STRNCPY(save, &p[last], 2); - STRNCPY(&p[last], "+-", 2); + strncpy(save, &p[last], 2); // NOLINT(runtime/printf) + memcpy(&p[last], "+-", 2); fprintf(debugf, " %s", p); - STRNCPY(&p[last], save, 2); // NOLINT(runtime/printf) + strncpy(&p[last], save, 2); // NOLINT(runtime/printf) } else { fprintf(debugf, " %s", p); } @@ -3381,9 +3358,9 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) // grow indent for state->out indent->ga_len -= 1; if (state->out1) { - ga_concat(indent, (char_u *)"| "); + ga_concat(indent, (uint8_t *)"| "); } else { - ga_concat(indent, (char_u *)" "); + ga_concat(indent, (uint8_t *)" "); } ga_append(indent, NUL); @@ -3391,7 +3368,7 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) // replace last part of indent for state->out1 indent->ga_len -= 3; - ga_concat(indent, (char_u *)" "); + ga_concat(indent, (uint8_t *)" "); ga_append(indent, NUL); nfa_print_state2(debugf, state->out1, indent); @@ -3401,36 +3378,34 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) ga_append(indent, NUL); } -/* - * Print the NFA state machine. - */ +// Print the NFA state machine. static void nfa_dump(nfa_regprog_T *prog) { FILE *debugf = fopen(NFA_REGEXP_DUMP_LOG, "a"); - if (debugf != NULL) { - nfa_print_state(debugf, prog->start); + if (debugf == NULL) { + return; + } - if (prog->reganch) { - fprintf(debugf, "reganch: %d\n", prog->reganch); - } - if (prog->regstart != NUL) { - fprintf(debugf, "regstart: %c (decimal: %d)\n", - prog->regstart, prog->regstart); - } - if (prog->match_text != NULL) { - fprintf(debugf, "match_text: \"%s\"\n", prog->match_text); - } + nfa_print_state(debugf, prog->start); - fclose(debugf); + if (prog->reganch) { + fprintf(debugf, "reganch: %d\n", prog->reganch); + } + if (prog->regstart != NUL) { + fprintf(debugf, "regstart: %c (decimal: %d)\n", + prog->regstart, prog->regstart); } + if (prog->match_text != NULL) { + fprintf(debugf, "match_text: \"%s\"\n", prog->match_text); + } + + fclose(debugf); } -#endif /* REGEXP_DEBUG */ +#endif // REGEXP_DEBUG -/* - * Parse r.e. @expr and convert it into postfix form. - * Return the postfix string on success, NULL otherwise. - */ +// Parse r.e. @expr and convert it into postfix form. +// Return the postfix string on success, NULL otherwise. static int *re2post(void) { if (nfa_reg(REG_NOPAREN) == FAIL) { @@ -3442,18 +3417,14 @@ static int *re2post(void) // NB. Some of the code below is inspired by Russ's. -/* - * Represents an NFA state plus zero or one or two arrows exiting. - * if c == MATCH, no arrows out; matching state. - * If c == SPLIT, unlabeled arrows to out and out1 (if != NULL). - * If c < 256, labeled arrow with character c to out. - */ +// Represents an NFA state plus zero or one or two arrows exiting. +// if c == MATCH, no arrows out; matching state. +// If c == SPLIT, unlabeled arrows to out and out1 (if != NULL). +// If c < 256, labeled arrow with character c to out. static nfa_state_T *state_ptr; // points to nfa_prog->state -/* - * Allocate and initialize nfa_state_T. - */ +// Allocate and initialize nfa_state_T. static nfa_state_T *alloc_state(int c, nfa_state_T *out, nfa_state_T *out1) { nfa_state_T *s; @@ -3476,16 +3447,12 @@ static nfa_state_T *alloc_state(int c, nfa_state_T *out, nfa_state_T *out1) return s; } -/* - * A partially built NFA without the matching state filled in. - * Frag_T.start points at the start state. - * Frag_T.out is a list of places that need to be set to the - * next state for this fragment. - */ +// A partially built NFA without the matching state filled in. +// Frag_T.start points at the start state. +// Frag_T.out is a list of places that need to be set to the +// next state for this fragment. -/* - * Initialize a Frag_T struct and return it. - */ +// Initialize a Frag_T struct and return it. static Frag_T frag(nfa_state_T *start, Ptrlist *out) { Frag_T n; @@ -3495,9 +3462,7 @@ static Frag_T frag(nfa_state_T *start, Ptrlist *out) return n; } -/* - * Create singleton list containing just outp. - */ +// Create singleton list containing just outp. static Ptrlist *list1(nfa_state_T **outp) { Ptrlist *l; @@ -3507,9 +3472,7 @@ static Ptrlist *list1(nfa_state_T **outp) return l; } -/* - * Patch the list of states at out to point to start. - */ +// Patch the list of states at out to point to start. static void patch(Ptrlist *l, nfa_state_T *s) { Ptrlist *next; @@ -3520,9 +3483,7 @@ static void patch(Ptrlist *l, nfa_state_T *s) } } -/* - * Join the two lists l1 and l2, returning the combination. - */ +// Join the two lists l1 and l2, returning the combination. static Ptrlist *append(Ptrlist *l1, Ptrlist *l2) { Ptrlist *oldl1; @@ -3535,9 +3496,7 @@ static Ptrlist *append(Ptrlist *l1, Ptrlist *l2) return oldl1; } -/* - * Stack used for transforming postfix form into NFA. - */ +// Stack used for transforming postfix form into NFA. static Frag_T empty; static void st_error(int *postfix, int *end, int *p) @@ -3580,9 +3539,7 @@ static void st_error(int *postfix, int *end, int *p) emsg(_("E874: (NFA) Could not pop the stack!")); } -/* - * Push an item onto the stack. - */ +// Push an item onto the stack. static void st_push(Frag_T s, Frag_T **p, Frag_T *stack_end) { Frag_T *stackp = *p; @@ -3594,9 +3551,7 @@ static void st_push(Frag_T s, Frag_T **p, Frag_T *stack_end) *p = *p + 1; } -/* - * Pop an item from the stack. - */ +// Pop an item from the stack. static Frag_T st_pop(Frag_T **p, Frag_T *stack) { Frag_T *stackp; @@ -3609,10 +3564,8 @@ static Frag_T st_pop(Frag_T **p, Frag_T *stack) return **p; } -/* - * Estimate the maximum byte length of anything matching "state". - * When unknown or unlimited return -1. - */ +// Estimate the maximum byte length of anything matching "state". +// When unknown or unlimited return -1. static int nfa_max_width(nfa_state_T *startstate, int depth) { int l, r; @@ -3815,10 +3768,8 @@ static int nfa_max_width(nfa_state_T *startstate, int depth) return -1; } -/* - * Convert a postfix form into its equivalent NFA. - * Return the NFA start state on success, NULL otherwise. - */ +// Convert a postfix form into its equivalent NFA. +// Return the NFA start state on success, NULL otherwise. static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) { int *p; @@ -3854,7 +3805,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) stack_end = stack + (nstate + 1); } - for (p = postfix; p < end; ++p) { + for (p = postfix; p < end; p++) { switch (*p) { case NFA_CONCAT: // Concatenation. @@ -4338,15 +4289,13 @@ theend: #undef PUSH } -/* - * After building the NFA program, inspect it to add optimization hints. - */ +// After building the NFA program, inspect it to add optimization hints. static void nfa_postprocess(nfa_regprog_T *prog) { int i; int c; - for (i = 0; i < prog->nstate; ++i) { + for (i = 0; i < prog->nstate; i++) { c = prog->state[i].c; if (c == NFA_START_INVISIBLE || c == NFA_START_INVISIBLE_NEG @@ -4478,59 +4427,60 @@ static void clear_sub(regsub_T *sub) sub->in_use = 0; } -/* - * Copy the submatches from "from" to "to". - */ +// Copy the submatches from "from" to "to". static void copy_sub(regsub_T *to, regsub_T *from) { to->in_use = from->in_use; - if (from->in_use > 0) { - // Copy the match start and end positions. - if (REG_MULTI) { - memmove(&to->list.multi[0], &from->list.multi[0], - sizeof(struct multipos) * (size_t)from->in_use); - } else { - memmove(&to->list.line[0], &from->list.line[0], - sizeof(struct linepos) * (size_t)from->in_use); - } + if (from->in_use <= 0) { + return; + } + + // Copy the match start and end positions. + if (REG_MULTI) { + memmove(&to->list.multi[0], &from->list.multi[0], + sizeof(struct multipos) * (size_t)from->in_use); + to->orig_start_col = from->orig_start_col; + } else { + memmove(&to->list.line[0], &from->list.line[0], + sizeof(struct linepos) * (size_t)from->in_use); } } -/* - * Like copy_sub() but exclude the main match. - */ +// Like copy_sub() but exclude the main match. static void copy_sub_off(regsub_T *to, regsub_T *from) { if (to->in_use < from->in_use) { to->in_use = from->in_use; } - if (from->in_use > 1) { - // Copy the match start and end positions. - if (REG_MULTI) { - memmove(&to->list.multi[1], &from->list.multi[1], - sizeof(struct multipos) * (size_t)(from->in_use - 1)); - } else { - memmove(&to->list.line[1], &from->list.line[1], - sizeof(struct linepos) * (size_t)(from->in_use - 1)); - } + if (from->in_use <= 1) { + return; + } + + // Copy the match start and end positions. + if (REG_MULTI) { + memmove(&to->list.multi[1], &from->list.multi[1], + sizeof(struct multipos) * (size_t)(from->in_use - 1)); + } else { + memmove(&to->list.line[1], &from->list.line[1], + sizeof(struct linepos) * (size_t)(from->in_use - 1)); } } -/* - * Like copy_sub() but only do the end of the main match if \ze is present. - */ +// Like copy_sub() but only do the end of the main match if \ze is present. static void copy_ze_off(regsub_T *to, regsub_T *from) { - if (rex.nfa_has_zend) { - if (REG_MULTI) { - if (from->list.multi[0].end_lnum >= 0) { - to->list.multi[0].end_lnum = from->list.multi[0].end_lnum; - to->list.multi[0].end_col = from->list.multi[0].end_col; - } - } else { - if (from->list.line[0].end != NULL) { - to->list.line[0].end = from->list.line[0].end; - } + if (!rex.nfa_has_zend) { + return; + } + + if (REG_MULTI) { + if (from->list.multi[0].end_lnum >= 0) { + to->list.multi[0].end_lnum = from->list.multi[0].end_lnum; + to->list.multi[0].end_col = from->list.multi[0].end_col; + } + } else { + if (from->list.line[0].end != NULL) { + to->list.line[0].end = from->list.line[0].end; } } } @@ -4543,8 +4493,8 @@ static bool sub_equal(regsub_T *sub1, regsub_T *sub2) int todo; linenr_T s1; linenr_T s2; - char_u *sp1; - char_u *sp2; + uint8_t *sp1; + uint8_t *sp2; todo = sub1->in_use > sub2->in_use ? sub1->in_use : sub2->in_use; if (REG_MULTI) { @@ -4623,6 +4573,20 @@ static bool sub_equal(regsub_T *sub1, regsub_T *sub2) } #ifdef REGEXP_DEBUG +static void open_debug_log(TriState result) +{ + log_fd = fopen(NFA_REGEXP_RUN_LOG, "a"); + if (log_fd == NULL) { + emsg(_(e_log_open_failed)); + log_fd = stderr; + } + + fprintf(log_fd, "****************************\n"); + fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n"); + fprintf(log_fd, "MATCH = %s\n", result == kTrue ? "OK" : result == kNone ? "MAYBE" : "FALSE"); + fprintf(log_fd, "****************************\n"); +} + static void report_state(char *action, regsub_T *sub, nfa_state_T *state, int lid, nfa_pim_T *pim) { int col; @@ -4635,6 +4599,9 @@ static void report_state(char *action, regsub_T *sub, nfa_state_T *state, int li col = (int)(sub->list.line[0].start - rex.line); } nfa_set_code(state->c); + if (log_fd == NULL) { + open_debug_log(kNone); + } fprintf(log_fd, "> %s state %d to list %d. char %d: %s (start col %d)%s\n", action, abs(state->id), lid, state->c, code, col, pim_info(pim)); @@ -4822,7 +4789,7 @@ static regsubs_T *addstate(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs_ar nfa_thread_T *thread; struct multipos save_multipos; int save_in_use; - char_u *save_ptr; + uint8_t *save_ptr; int i; regsub_T *sub; regsubs_T *subs = subs_arg; @@ -4925,7 +4892,7 @@ static regsubs_T *addstate(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs_ar // When called from addstate_here() do insert before // existing states. if (add_here) { - for (k = 0; k < l->n && k < listindex; ++k) { + for (k = 0; k < l->n && k < listindex; k++) { if (l->t[k].state->id == state->id) { found = true; break; @@ -5065,7 +5032,7 @@ skip_add: save_in_use = -1; } else { save_in_use = sub->in_use; - for (i = sub->in_use; i < subidx; ++i) { + for (i = sub->in_use; i < subidx; i++) { sub->list.multi[i].start_lnum = -1; sub->list.multi[i].end_lnum = -1; } @@ -5086,7 +5053,7 @@ skip_add: save_in_use = -1; } else { save_in_use = sub->in_use; - for (i = sub->in_use; i < subidx; ++i) { + for (i = sub->in_use; i < subidx; i++) { sub->list.line[i].start = NULL; sub->list.line[i].end = NULL; } @@ -5279,15 +5246,13 @@ static regsubs_T *addstate_here(nfa_list_T *l, nfa_state_T *state, regsubs_T *su sizeof(nfa_thread_T) * (size_t)count); } } - --l->n; + l->n--; *ip = listidx - 1; return r; } -/* - * Check character class "class" against current character c. - */ +// Check character class "class" against current character c. static int check_char_class(int class, int c) { switch (class) { @@ -5421,8 +5386,8 @@ retempty: && sub->list.multi[subidx].end_lnum == rex.lnum) { len = sub->list.multi[subidx].end_col - sub->list.multi[subidx].start_col; - if (cstrncmp(rex.line + sub->list.multi[subidx].start_col, - rex.input, &len) == 0) { + if (cstrncmp((char *)rex.line + sub->list.multi[subidx].start_col, + (char *)rex.input, &len) == 0) { *bytelen = len; return true; } @@ -5441,7 +5406,7 @@ retempty: goto retempty; } len = (int)(sub->list.line[subidx].end - sub->list.line[subidx].start); - if (cstrncmp(sub->list.line[subidx].start, rex.input, &len) == 0) { + if (cstrncmp((char *)sub->list.line[subidx].start, (char *)rex.input, &len) == 0) { *bytelen = len; return true; } @@ -5465,19 +5430,17 @@ static int match_zref(int subidx, int *bytelen) return true; } - len = (int)STRLEN(re_extmatch_in->matches[subidx]); - if (cstrncmp(re_extmatch_in->matches[subidx], rex.input, &len) == 0) { + len = (int)strlen((char *)re_extmatch_in->matches[subidx]); + if (cstrncmp((char *)re_extmatch_in->matches[subidx], (char *)rex.input, &len) == 0) { *bytelen = len; return true; } return false; } -/* - * Save list IDs for all NFA states of "prog" into "list". - * Also reset the IDs to zero. - * Only used for the recursive value lastlist[1]. - */ +// Save list IDs for all NFA states of "prog" into "list". +// Also reset the IDs to zero. +// Only used for the recursive value lastlist[1]. static void nfa_save_listids(nfa_regprog_T *prog, int *list) { int i; @@ -5492,9 +5455,7 @@ static void nfa_save_listids(nfa_regprog_T *prog, int *list) } } -/* - * Restore list IDs from "list" to all NFA states. - */ +// Restore list IDs from "list" to all NFA states. static void nfa_restore_listids(nfa_regprog_T *prog, int *list) { int i; @@ -5518,11 +5479,9 @@ static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos) return val == pos; } -/* - * Recursively call nfa_regmatch() - * "pim" is NULL or contains info about a Postponed Invisible Match (start - * position). - */ +// Recursively call nfa_regmatch() +// "pim" is NULL or contains info about a Postponed Invisible Match (start +// position). static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog, regsubs_T *submatch, regsubs_T *m, int **listids, int *listids_len) FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6, 7) @@ -5573,10 +5532,10 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T // bytes if possible. if (state->val <= 0) { if (REG_MULTI) { - rex.line = reg_getline(--rex.lnum); + rex.line = (uint8_t *)reg_getline(--rex.lnum); if (rex.line == NULL) { // can't go before the first line - rex.line = reg_getline(++rex.lnum); + rex.line = (uint8_t *)reg_getline(++rex.lnum); } } rex.input = rex.line; @@ -5584,18 +5543,18 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T if (REG_MULTI && (int)(rex.input - rex.line) < state->val) { // Not enough bytes in this line, go to end of // previous line. - rex.line = reg_getline(--rex.lnum); + rex.line = (uint8_t *)reg_getline(--rex.lnum); if (rex.line == NULL) { // can't go before the first line - rex.line = reg_getline(++rex.lnum); + rex.line = (uint8_t *)reg_getline(++rex.lnum); rex.input = rex.line; } else { - rex.input = rex.line + STRLEN(rex.line); + rex.input = rex.line + strlen((char *)rex.line); } } if ((int)(rex.input - rex.line) >= state->val) { rex.input -= state->val; - rex.input -= utf_head_off(rex.line, rex.input); + rex.input -= utf_head_off((char *)rex.line, (char *)rex.input); } else { rex.input = rex.line; } @@ -5646,7 +5605,7 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T // restore position in input text rex.lnum = save_reglnum; if (REG_MULTI) { - rex.line = reg_getline(rex.lnum); + rex.line = (uint8_t *)reg_getline(rex.lnum); } rex.input = rex.line + save_reginput_col; if (result != NFA_TOO_EXPENSIVE) { @@ -5656,27 +5615,16 @@ static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T nfa_endp = save_nfa_endp; #ifdef REGEXP_DEBUG - log_fd = fopen(NFA_REGEXP_RUN_LOG, "a"); - if (log_fd != NULL) { - fprintf(log_fd, "****************************\n"); - fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n"); - fprintf(log_fd, "MATCH = %s\n", !result ? "false" : "OK"); - fprintf(log_fd, "****************************\n"); - } else { - emsg(_(e_log_open_failed)); - log_fd = stderr; - } + open_debug_log(result); #endif return result; } -/* - * Estimate the chance of a match with "state" failing. - * empty match: 0 - * NFA_ANY: 1 - * specific character: 99 - */ +// Estimate the chance of a match with "state" failing. +// empty match: 0 +// NFA_ANY: 1 +// specific character: 99 static int failure_chance(nfa_state_T *state, int depth) { int c = state->c; @@ -5831,12 +5779,10 @@ static int failure_chance(nfa_state_T *state, int depth) return 50; } -/* - * Skip until the char "c" we know a match must start with. - */ +// Skip until the char "c" we know a match must start with. static int skip_to_start(int c, colnr_T *colp) { - const char_u *const s = cstrchr(rex.line + *colp, c); + const uint8_t *const s = (uint8_t *)cstrchr((char *)rex.line + *colp, c); if (s == NULL) { return FAIL; } @@ -5844,22 +5790,20 @@ static int skip_to_start(int c, colnr_T *colp) return OK; } -/* - * Check for a match with match_text. - * Called after skip_to_start() has found regstart. - * Returns zero for no match, 1 for a match. - */ -static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) +// Check for a match with match_text. +// Called after skip_to_start() has found regstart. +// Returns zero for no match, 1 for a match. +static long find_match_text(colnr_T *startcol, int regstart, uint8_t *match_text) { #define PTR2LEN(x) utf_ptr2len(x) - colnr_T col = startcol; - int regstart_len = PTR2LEN((char *)rex.line + startcol); + colnr_T col = *startcol; + int regstart_len = PTR2LEN((char *)rex.line + col); for (;;) { bool match = true; - char_u *s1 = match_text; - char_u *s2 = rex.line + col + regstart_len; // skip regstart + uint8_t *s1 = match_text; + uint8_t *s2 = rex.line + col + regstart_len; // skip regstart while (*s1) { int c1_len = PTR2LEN((char *)s1); int c1 = utf_ptr2char((char *)s1); @@ -5887,6 +5831,7 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) rex.reg_startp[0] = rex.line + col; rex.reg_endp[0] = s2; } + *startcol = col; return 1L; } @@ -5896,6 +5841,8 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text) break; } } + + *startcol = col; return 0L; #undef PTR2LEN @@ -5971,16 +5918,15 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm #ifdef REGEXP_DEBUG log_fd = fopen(NFA_REGEXP_RUN_LOG, "a"); - if (log_fd != NULL) { - fprintf(log_fd, "**********************************\n"); - nfa_set_code(start->c); - fprintf(log_fd, " RUNNING nfa_regmatch() starting with state %d, code %s\n", - abs(start->id), code); - fprintf(log_fd, "**********************************\n"); - } else { + if (log_fd == NULL) { emsg(_(e_log_open_failed)); log_fd = stderr; } + fprintf(log_fd, "**********************************\n"); + nfa_set_code(start->c); + fprintf(log_fd, " RUNNING nfa_regmatch() starting with state %d, code %s\n", + abs(start->id), code); + fprintf(log_fd, "**********************************\n"); #endif thislist = &list[0]; @@ -6000,6 +5946,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm if (REG_MULTI) { m->norm.list.multi[0].start_lnum = rex.lnum; m->norm.list.multi[0].start_col = (colnr_T)(rex.input - rex.line); + m->norm.orig_start_col = m->norm.list.multi[0].start_col; } else { m->norm.list.line[0].start = rex.input; } @@ -6019,9 +5966,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm add_off = clen; \ } - /* - * Run for each character. - */ + // Run for each character. for (;;) { int curc = utf_ptr2char((char *)rex.input); int clen = utfc_ptr2len((char *)rex.input); @@ -6067,9 +6012,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm #ifdef NFA_REGEXP_DEBUG_LOG fprintf(debug, "\n-------------------\n"); #endif - /* - * If the state lists are empty we can stop. - */ + // If the state lists are empty we can stop. if (thislist->n == 0) { break; } @@ -6112,10 +6055,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm } #endif - /* - * Handle the possible codes of the current state. - * The most important is NFA_MATCH. - */ + // Handle the possible codes of the current state. + // The most important is NFA_MATCH. add_state = NULL; add_here = false; add_count = 0; @@ -6410,7 +6351,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm int this_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); + this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab); if (this_class <= 1) { result = false; } else if (reg_prev_class() == this_class) { @@ -6431,7 +6372,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm int this_class, prev_class; // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); + this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab); prev_class = reg_prev_class(); if (this_class == prev_class || prev_class == 0 || prev_class == 1) { @@ -6643,13 +6584,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm break; case NFA_KWORD: // \k - result = vim_iswordp_buf(rex.input, rex.reg_buf); + result = vim_iswordp_buf((char *)rex.input, rex.reg_buf); ADD_STATE_IF_MATCH(t->state); break; case NFA_SKWORD: // \K result = !ascii_isdigit(curc) - && vim_iswordp_buf(rex.input, rex.reg_buf); + && vim_iswordp_buf((char *)rex.input, rex.reg_buf); ADD_STATE_IF_MATCH(t->state); break; @@ -6910,7 +6851,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm result = col > t->state->val * ts; } if (!result) { - uintmax_t lts = win_linetabsize(wp, rex.line, col); + uintmax_t lts = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col); assert(t->state->val >= 0); result = nfa_re_num_cmp((uintmax_t)t->state->val, op, lts + 1); } @@ -6929,7 +6870,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm // Line may have been freed, get it again. if (REG_MULTI) { - rex.line = reg_getline(rex.lnum); + rex.line = (uint8_t *)reg_getline(rex.lnum); rex.input = rex.line + col; } @@ -6939,7 +6880,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm pos_T *pos = &fm->mark; const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum && pos->col == MAXCOL - ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum)) + ? (colnr_T)strlen((char *)reg_getline(pos->lnum - rex.reg_firstlnum)) : pos->col; result = pos->lnum == rex.lnum + rex.reg_firstlnum @@ -7185,6 +7126,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm if (REG_MULTI) { m->norm.list.multi[0].start_col = (colnr_T)(rex.input - rex.line) + clen; + m->norm.orig_start_col = + m->norm.list.multi[0].start_col; } else { m->norm.list.line[0].start = rex.input + clen; } @@ -7318,6 +7261,9 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm, int *ti rex.reg_endpos[i].lnum = subs.norm.list.multi[i].end_lnum; rex.reg_endpos[i].col = subs.norm.list.multi[i].end_col; } + if (rex.reg_mmatch != NULL) { + rex.reg_mmatch->rmm_matchcol = subs.norm.orig_start_col; + } if (rex.reg_startpos[0].lnum < 0) { rex.reg_startpos[0].lnum = 0; @@ -7362,15 +7308,15 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm, int *ti && mpos->start_lnum == mpos->end_lnum && mpos->end_col >= mpos->start_col) { re_extmatch_out->matches[i] = - vim_strnsave(reg_getline(mpos->start_lnum) + mpos->start_col, - (size_t)(mpos->end_col - mpos->start_col)); + (uint8_t *)xstrnsave((char *)reg_getline(mpos->start_lnum) + mpos->start_col, + (size_t)(mpos->end_col - mpos->start_col)); } } else { struct linepos *lpos = &subs.synt.list.line[i]; if (lpos->start != NULL && lpos->end != NULL) { re_extmatch_out->matches[i] = - vim_strnsave(lpos->start, (size_t)(lpos->end - lpos->start)); + (uint8_t *)xstrnsave((char *)lpos->start, (size_t)(lpos->end - lpos->start)); } } } @@ -7389,7 +7335,7 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm, int *ti /// /// @return <= 0 if there is no match and number of lines contained in the /// match otherwise. -static long nfa_regexec_both(char_u *line, colnr_T startcol, proftime_T *tm, int *timed_out) +static long nfa_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int *timed_out) { nfa_regprog_T *prog; long retval = 0L; @@ -7397,13 +7343,13 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, proftime_T *tm, int if (REG_MULTI) { prog = (nfa_regprog_T *)rex.reg_mmatch->regprog; - line = reg_getline((linenr_T)0); // relative to the cursor + line = (uint8_t *)reg_getline((linenr_T)0); // relative to the cursor rex.reg_startpos = rex.reg_mmatch->startpos; rex.reg_endpos = rex.reg_mmatch->endpos; } else { prog = (nfa_regprog_T *)rex.reg_match->regprog; - rex.reg_startp = rex.reg_match->startp; - rex.reg_endp = rex.reg_match->endp; + rex.reg_startp = (uint8_t **)rex.reg_match->startp; + rex.reg_endp = (uint8_t **)rex.reg_match->endp; } // Be paranoid... @@ -7460,7 +7406,13 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol, proftime_T *tm, int // If match_text is set it contains the full text that must match. // Nothing else to try. Doesn't handle combining chars well. if (prog->match_text != NULL && !rex.reg_icombine) { - return find_match_text(col, prog->regstart, prog->match_text); + retval = find_match_text(&col, prog->regstart, prog->match_text); + if (REG_MULTI) { + rex.reg_mmatch->rmm_matchcol = col; + } else { + rex.reg_match->rm_matchcol = col; + } + return retval; } } @@ -7500,17 +7452,19 @@ theend: if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) { rex.reg_match->endp[0] = rex.reg_match->startp[0]; } + + // startpos[0] may be set by "\zs", also return the column where + // the whole pattern matched. + rex.reg_match->rm_matchcol = col; } } return retval; } -/* - * Compile a regular expression into internal code for the NFA matcher. - * Returns the program in allocated space. Returns NULL for an error. - */ -static regprog_T *nfa_regcomp(char_u *expr, int re_flags) +// Compile a regular expression into internal code for the NFA matcher. +// Returns the program in allocated space. Returns NULL for an error. +static regprog_T *nfa_regcomp(uint8_t *expr, int re_flags) { nfa_regprog_T *prog = NULL; int *postfix; @@ -7535,11 +7489,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) goto fail; // Cascaded (syntax?) error } - /* - * In order to build the NFA, we parse the input regexp twice: - * 1. first pass to count size (so we can allocate space) - * 2. second to emit code - */ + // In order to build the NFA, we parse the input regexp twice: + // 1. first pass to count size (so we can allocate space) + // 2. second to emit code #ifdef REGEXP_DEBUG { FILE *f = fopen(NFA_REGEXP_RUN_LOG, "a"); @@ -7554,10 +7506,8 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) } #endif - /* - * PASS 1 - * Count number of NFA states in "nstate". Do not build the NFA. - */ + // PASS 1 + // Count number of NFA states in "nstate". Do not build the NFA. post2nfa(postfix, post_ptr, true); // allocate the regprog with space for the compiled regexp @@ -7566,10 +7516,8 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) state_ptr = prog->state; prog->re_in_use = false; - /* - * PASS 2 - * Build the NFA - */ + // PASS 2 + // Build the NFA prog->start = post2nfa(postfix, post_ptr, false); if (prog->start == NULL) { goto fail; @@ -7593,7 +7541,7 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) #endif // Remember whether this pattern has any \z specials in it. prog->reghasz = re_has_z; - prog->pattern = vim_strsave(expr); + prog->pattern = xstrdup((char *)expr); #ifdef REGEXP_DEBUG nfa_regengine.expr = NULL; #endif @@ -7613,16 +7561,16 @@ fail: goto out; } -/* - * Free a compiled regexp program, returned by nfa_regcomp(). - */ +// Free a compiled regexp program, returned by nfa_regcomp(). static void nfa_regfree(regprog_T *prog) { - if (prog != NULL) { - xfree(((nfa_regprog_T *)prog)->match_text); - xfree(((nfa_regprog_T *)prog)->pattern); - xfree(prog); + if (prog == NULL) { + return; } + + xfree(((nfa_regprog_T *)prog)->match_text); + xfree(((nfa_regprog_T *)prog)->pattern); + xfree(prog); } /// Match a regexp against a string. @@ -7634,7 +7582,7 @@ static void nfa_regfree(regprog_T *prog) /// @param col column to start looking for match /// /// @return <= 0 for failure, number of lines contained in the match otherwise. -static int nfa_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col, bool line_lbr) +static int nfa_regexec_nl(regmatch_T *rmp, uint8_t *line, colnr_T col, bool line_lbr) { rex.reg_match = rmp; rex.reg_mmatch = NULL; @@ -7686,16 +7634,6 @@ static int nfa_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col, bool line_ static long nfa_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col, proftime_T *tm, int *timed_out) { - rex.reg_match = NULL; - rex.reg_mmatch = rmp; - rex.reg_buf = buf; - rex.reg_win = win; - rex.reg_firstlnum = lnum; - rex.reg_maxline = rex.reg_buf->b_ml.ml_line_count - lnum; - rex.reg_line_lbr = false; - rex.reg_ic = rmp->rmm_ic; - rex.reg_icombine = false; - rex.reg_maxcol = rmp->rmm_maxcol; - + init_regexec_multi(rmp, win, buf, lnum); return nfa_regexec_both(NULL, col, tm, timed_out); } diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 914b21bb02..24500b80b9 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -5,25 +5,46 @@ /// /// Management of runtime files (including packages) +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdio.h> +#include <string.h> +#include <uv.h> + +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/debugger.h" #include "nvim/eval.h" #include "nvim/eval/userfunc.h" -#include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" +#include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" +#include "nvim/map.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/stdpaths_defs.h" +#include "nvim/path.h" #include "nvim/profile.h" #include "nvim/runtime.h" +#include "nvim/strings.h" +#include "nvim/usercmd.h" #include "nvim/vim.h" /// Structure used to store info for each sourced file. @@ -83,8 +104,7 @@ estack_T *estack_push(etype_T type, char *name, linenr_T lnum) void estack_push_ufunc(ufunc_T *ufunc, linenr_T lnum) { estack_T *entry = estack_push(ETYPE_UFUNC, - (char *)(ufunc->uf_name_exp != NULL - ? ufunc->uf_name_exp : ufunc->uf_name), + ufunc->uf_name_exp != NULL ? ufunc->uf_name_exp : ufunc->uf_name, lnum); if (entry != NULL) { entry->es_info.ufunc = ufunc; @@ -100,10 +120,11 @@ void estack_pop(void) } /// Get the current value for <sfile> in allocated memory. -/// @param which ESTACK_SFILE for <sfile> and ESTACK_STACK for <stack>. +/// @param which ESTACK_SFILE for <sfile>, ESTACK_STACK for <stack> or +/// ESTACK_SCRIPT for <script>. char *estack_sfile(estack_arg_T which) { - estack_T *entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1; + const estack_T *entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1; if (which == ESTACK_SFILE && entry->es_type != ETYPE_UFUNC) { if (entry->es_name == NULL) { return NULL; @@ -111,6 +132,26 @@ char *estack_sfile(estack_arg_T which) return xstrdup(entry->es_name); } + // If evaluated in a function or autocommand, return the path of the script + // where it is defined, at script level the current script path is returned + // instead. + if (which == ESTACK_SCRIPT) { + // Walk the stack backwards, starting from the current frame. + for (int idx = exestack.ga_len - 1; idx >= 0; idx--, entry--) { + if (entry->es_type == ETYPE_UFUNC || entry->es_type == ETYPE_AUCMD) { + const sctx_T *const def_ctx = (entry->es_type == ETYPE_UFUNC + ? &entry->es_info.ufunc->uf_script_ctx + : &entry->es_info.aucmd->script_ctx); + return def_ctx->sc_sid > 0 + ? xstrdup((SCRIPT_ITEM(def_ctx->sc_sid).sn_name)) + : NULL; + } else if (entry->es_type == ETYPE_SCRIPT) { + return xstrdup(entry->es_name); + } + } + return NULL; + } + // Give information about each stack entry up to the root. // For a function we compose the call stack, as it was done in the past: // "function One[123]..Two[456]..Three" @@ -171,20 +212,20 @@ void runtime_init(void) void ex_runtime(exarg_T *eap) { char *arg = eap->arg; - char *p = (char *)skiptowhite((char_u *)arg); - ptrdiff_t len = p - arg; + char *p = skiptowhite(arg); + size_t len = (size_t)(p - arg); int flags = eap->forceit ? DIP_ALL : 0; - if (STRNCMP(arg, "START", len) == 0) { + if (strncmp(arg, "START", len) == 0) { flags += DIP_START + DIP_NORTP; arg = skipwhite(arg + len); - } else if (STRNCMP(arg, "OPT", len) == 0) { + } else if (strncmp(arg, "OPT", len) == 0) { flags += DIP_OPT + DIP_NORTP; arg = skipwhite(arg + len); - } else if (STRNCMP(arg, "PACK", len) == 0) { + } else if (strncmp(arg, "PACK", len) == 0) { flags += DIP_START + DIP_OPT + DIP_NORTP; arg = skipwhite(arg + len); - } else if (STRNCMP(arg, "ALL", len) == 0) { + } else if (strncmp(arg, "ALL", len) == 0) { flags += DIP_START + DIP_OPT; arg = skipwhite(arg + len); } @@ -205,9 +246,9 @@ static void source_callback(char *fname, void *cookie) /// When "flags" has DIP_ERR: give an error message if there is no match. /// /// return FAIL when no file could be sourced, OK otherwise. -int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, void *cookie) +int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, void *cookie) { - char_u *tail; + char *tail; int num_files; char **files; int i; @@ -215,21 +256,21 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, // Make a copy of 'runtimepath'. Invoking the callback may change the // value. - char_u *rtp_copy = vim_strsave(path); + char *rtp_copy = xstrdup(path); char *buf = xmallocz(MAXPATHL); { if (p_verbose > 10 && name != NULL) { verbose_enter(); - smsg(_("Searching for \"%s\" in \"%s\""), name, (char *)path); + smsg(_("Searching for \"%s\" in \"%s\""), name, path); verbose_leave(); } // Loop over all entries in 'runtimepath'. - char *rtp = (char *)rtp_copy; + char *rtp = rtp_copy; while (*rtp != NUL && ((flags & DIP_ALL) || !did_one)) { // Copy the path from 'runtimepath' to buf[]. copy_option_part(&rtp, buf, MAXPATHL, ","); - size_t buflen = STRLEN(buf); + size_t buflen = strlen(buf); // Skip after or non-after directories. if (flags & (DIP_NOAFTER | DIP_AFTER)) { @@ -244,16 +285,16 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, if (name == NULL) { (*callback)(buf, cookie); did_one = true; - } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { + } else if (buflen + strlen(name) + 2 < MAXPATHL) { add_pathsep(buf); - tail = (char_u *)buf + STRLEN(buf); + tail = buf + strlen(buf); // Loop over all patterns in "name" char *np = name; while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { // Append the pattern from "name" to buf[]. - assert(MAXPATHL >= (tail - (char_u *)buf)); - copy_option_part(&np, (char *)tail, (size_t)(MAXPATHL - (tail - (char_u *)buf)), "\t "); + assert(MAXPATHL >= (tail - buf)); + copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); if (p_verbose > 10) { verbose_enter(); @@ -322,7 +363,7 @@ RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src) return dst; } -void runtime_search_path_unref(RuntimeSearchPath path, int *ref) +void runtime_search_path_unref(RuntimeSearchPath path, const int *ref) FUNC_ATTR_NONNULL_ALL { if (*ref) { @@ -342,19 +383,19 @@ void runtime_search_path_unref(RuntimeSearchPath path, int *ref) /// When "flags" has DIP_ERR: give an error message if there is no match. /// /// return FAIL when no file could be sourced, OK otherwise. -int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void *cookie) +int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *cookie) { - char_u *tail; + char *tail; int num_files; char **files; int i; bool did_one = false; - char_u buf[MAXPATHL]; + char buf[MAXPATHL]; if (p_verbose > 10 && name != NULL) { verbose_enter(); - smsg(_("Searching for \"%s\" in runtime path"), (char *)name); + smsg(_("Searching for \"%s\" in runtime path"), name); verbose_leave(); } @@ -376,17 +417,17 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void if (name == NULL) { (*callback)(item.path, cookie); - } else if (buflen + STRLEN(name) + 2 < MAXPATHL) { + } else if (buflen + strlen(name) + 2 < MAXPATHL) { STRCPY(buf, item.path); - add_pathsep((char *)buf); - tail = buf + STRLEN(buf); + add_pathsep(buf); + tail = buf + strlen(buf); // Loop over all patterns in "name" - char *np = (char *)name; + char *np = name; while (*np != NUL && ((flags & DIP_ALL) || !did_one)) { // Append the pattern from "name" to buf[]. assert(MAXPATHL >= (tail - buf)); - copy_option_part(&np, (char *)tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); + copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), "\t "); if (p_verbose > 10) { verbose_enter(); @@ -398,7 +439,7 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void | (flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0; // Expand wildcards, invoke the callback for each match. - char *(pat[]) = { (char *)buf }; + char *(pat[]) = { buf }; if (gen_expand_wildcards(1, pat, &num_files, &files, ew_flags) == OK) { for (i = 0; i < num_files; i++) { (*callback)(files[i], cookie); @@ -478,7 +519,7 @@ ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all, if (lua) { if (item->has_lua == kNone) { size_t size = (size_t)snprintf(buf, buf_len, "%s/lua/", item->path); - item->has_lua = (size < buf_len && os_isdir((char_u *)buf)); + item->has_lua = (size < buf_len && os_isdir(buf)); } if (item->has_lua == kFalse) { continue; @@ -513,18 +554,18 @@ done: /// If "name" is NULL calls callback for each entry in "path". Cookie is /// passed by reference in this case, setting it to NULL indicates that callback /// has done its job. -int do_in_path_and_pp(char_u *path, char_u *name, int flags, DoInRuntimepathCB callback, - void *cookie) +int do_in_path_and_pp(char *path, char *name, int flags, DoInRuntimepathCB callback, void *cookie) { int done = FAIL; if ((flags & DIP_NORTP) == 0) { - done |= do_in_path(path, (char *)((name && !*name) ? NULL : name), flags, callback, cookie); + done |= do_in_path(path, (name && !*name) ? NULL : name, flags, callback, + cookie); } if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_START)) { char *start_dir = "pack/*/start/*/%s%s"; // NOLINT - size_t len = STRLEN(start_dir) + STRLEN(name) + 6; + size_t len = strlen(start_dir) + strlen(name) + 6; char *s = xmallocz(len); // TODO(bfredl): get rid of random allocations char *suffix = (flags & DIP_AFTER) ? "after/" : ""; @@ -535,7 +576,7 @@ int do_in_path_and_pp(char_u *path, char_u *name, int flags, DoInRuntimepathCB c if (done == FAIL || (flags & DIP_ALL)) { start_dir = "start/*/%s%s"; // NOLINT - len = STRLEN(start_dir) + STRLEN(name) + 6; + len = strlen(start_dir) + strlen(name) + 6; s = xmallocz(len); vim_snprintf(s, len, start_dir, suffix, name); @@ -547,7 +588,7 @@ int do_in_path_and_pp(char_u *path, char_u *name, int flags, DoInRuntimepathCB c if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_OPT)) { char *opt_dir = "pack/*/opt/*/%s"; // NOLINT - size_t len = STRLEN(opt_dir) + STRLEN(name); + size_t len = strlen(opt_dir) + strlen(name); char *s = xmallocz(len); vim_snprintf(s, len, opt_dir, name); @@ -557,7 +598,7 @@ int do_in_path_and_pp(char_u *path, char_u *name, int flags, DoInRuntimepathCB c if (done == FAIL || (flags & DIP_ALL)) { opt_dir = "opt/*/%s"; // NOLINT - len = STRLEN(opt_dir) + STRLEN(name); + len = strlen(opt_dir) + strlen(name); s = xmallocz(len); vim_snprintf(s, len, opt_dir, name); @@ -604,18 +645,18 @@ static void expand_rtp_entry(RuntimeSearchPath *search_path, Map(String, handle_ } static void expand_pack_entry(RuntimeSearchPath *search_path, Map(String, handle_T) *rtp_used, - CharVec *after_path, char_u *pack_entry, size_t pack_entry_len) + CharVec *after_path, char *pack_entry, size_t pack_entry_len) { static char buf[MAXPATHL]; char *(start_pat[]) = { "/pack/*/start/*", "/start/*" }; // NOLINT for (int i = 0; i < 2; i++) { - if (pack_entry_len + STRLEN(start_pat[i]) + 1 > sizeof buf) { + if (pack_entry_len + strlen(start_pat[i]) + 1 > sizeof buf) { continue; } - STRLCPY(buf, pack_entry, sizeof buf); - STRLCPY(buf + pack_entry_len, start_pat[i], sizeof buf - pack_entry_len); + xstrlcpy(buf, pack_entry, sizeof buf); + xstrlcpy(buf + pack_entry_len, start_pat[i], sizeof buf - pack_entry_len); expand_rtp_entry(search_path, rtp_used, buf, false); - size_t after_size = STRLEN(buf) + 7; + size_t after_size = strlen(buf) + 7; char *after = xmallocz(after_size); xstrlcpy(after, buf, after_size); xstrlcat(after, "/after", after_size); @@ -630,7 +671,7 @@ static bool path_is_after(char *buf, size_t buflen) // "after" dir in SOME codepaths not not in ALL codepaths. return buflen >= 5 && (!(buflen >= 6) || vim_ispathsep(buf[buflen - 6])) - && STRCMP(buf + buflen - 5, "after") == 0; + && strcmp(buf + buflen - 5, "after") == 0; } RuntimeSearchPath runtime_search_path_build(void) @@ -643,32 +684,32 @@ RuntimeSearchPath runtime_search_path_build(void) RuntimeSearchPath search_path = KV_INITIAL_VALUE; CharVec after_path = KV_INITIAL_VALUE; - static char_u buf[MAXPATHL]; - for (char *entry = (char *)p_pp; *entry != NUL;) { + static char buf[MAXPATHL]; + for (char *entry = p_pp; *entry != NUL;) { char *cur_entry = entry; - copy_option_part(&entry, (char *)buf, MAXPATHL, ","); + copy_option_part(&entry, buf, MAXPATHL, ","); - String the_entry = { .data = cur_entry, .size = STRLEN(buf) }; + String the_entry = { .data = cur_entry, .size = strlen(buf) }; kv_push(pack_entries, the_entry); map_put(String, handle_T)(&pack_used, the_entry, 0); } char *rtp_entry; - for (rtp_entry = (char *)p_rtp; *rtp_entry != NUL;) { + for (rtp_entry = p_rtp; *rtp_entry != NUL;) { char *cur_entry = rtp_entry; - copy_option_part(&rtp_entry, (char *)buf, MAXPATHL, ","); - size_t buflen = STRLEN(buf); + copy_option_part(&rtp_entry, buf, MAXPATHL, ","); + size_t buflen = strlen(buf); - if (path_is_after((char *)buf, buflen)) { + if (path_is_after(buf, buflen)) { rtp_entry = cur_entry; break; } // fact: &rtp entries can contain wild chars - expand_rtp_entry(&search_path, &rtp_used, (char *)buf, false); + expand_rtp_entry(&search_path, &rtp_used, buf, false); - handle_T *h = map_ref(String, handle_T)(&pack_used, cstr_as_string((char *)buf), false); + handle_T *h = map_ref(String, handle_T)(&pack_used, cstr_as_string(buf), false); if (h) { (*h)++; expand_pack_entry(&search_path, &rtp_used, &after_path, buf, buflen); @@ -679,7 +720,7 @@ RuntimeSearchPath runtime_search_path_build(void) String item = kv_A(pack_entries, i); handle_T h = map_get(String, handle_T)(&pack_used, item); if (h == 0) { - expand_pack_entry(&search_path, &rtp_used, &after_path, (char_u *)item.data, item.size); + expand_pack_entry(&search_path, &rtp_used, &after_path, item.data, item.size); } } @@ -691,8 +732,8 @@ RuntimeSearchPath runtime_search_path_build(void) // "after" dirs in rtp for (; *rtp_entry != NUL;) { - copy_option_part(&rtp_entry, (char *)buf, MAXPATHL, ","); - expand_rtp_entry(&search_path, &rtp_used, (char *)buf, path_is_after((char *)buf, STRLEN(buf))); + copy_option_part(&rtp_entry, buf, MAXPATHL, ","); + expand_rtp_entry(&search_path, &rtp_used, buf, path_is_after(buf, strlen(buf))); } // strings are not owned @@ -746,13 +787,13 @@ int do_in_runtimepath(char *name, int flags, DoInRuntimepathCB callback, void *c { int success = FAIL; if (!(flags & DIP_NORTP)) { - success |= do_in_cached_path((name && !*name) ? NULL : (char_u *)name, flags, callback, cookie); + success |= do_in_cached_path((name && !*name) ? NULL : name, flags, callback, cookie); flags = (flags & ~DIP_START) | DIP_NORTP; } // TODO(bfredl): we could integrate disabled OPT dirs into the cached path // which would effectivize ":packadd myoptpack" as well if ((flags & (DIP_START|DIP_OPT)) && (success == FAIL || (flags & DIP_ALL))) { - success |= do_in_path_and_pp(p_rtp, (char_u *)name, flags, callback, cookie); + success |= do_in_path_and_pp(p_rtp, name, flags, callback, cookie); } return success; } @@ -768,7 +809,7 @@ int source_runtime(char *name, int flags) } /// Just like source_runtime(), but use "path" instead of 'runtimepath'. -int source_in_path(char_u *path, char_u *name, int flags) +int source_in_path(char *path, char *name, int flags) { return do_in_path_and_pp(path, name, flags, source_callback, NULL); } @@ -780,29 +821,37 @@ static void source_all_matches(char *pat) int num_files; char **files; - if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) == OK) { - for (int i = 0; i < num_files; i++) { - (void)do_source(files[i], false, DOSO_NONE); - } - FreeWild(num_files, files); + if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) != OK) { + return; } + + for (int i = 0; i < num_files; i++) { + (void)do_source(files[i], false, DOSO_NONE); + } + FreeWild(num_files, files); } /// Add the package directory to 'runtimepath' /// /// @param fname the package path /// @param is_pack whether the added dir is a "pack/*/start/*/" style package -static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) +static int add_pack_dir_to_rtp(char *fname, bool is_pack) { - char_u *p4, *p3, *p2, *p1, *p; - char_u *buf = NULL; + char *p; + char *buf = NULL; char *afterdir = NULL; int retval = FAIL; - p4 = p3 = p2 = p1 = get_past_head(fname); + char *p1 = get_past_head(fname); + char *p2 = p1; + char *p3 = p1; + char *p4 = p1; for (p = p1; *p; MB_PTR_ADV(p)) { if (vim_ispathsep_nocolon(*p)) { - p4 = p3; p3 = p2; p2 = p1; p1 = p; + p4 = p3; + p3 = p2; + p2 = p1; + p1 = p; } } @@ -812,9 +861,9 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) // // find the part up to "pack" in 'runtimepath' p4++; // append pathsep in order to expand symlink - char_u c = *p4; + char c = *p4; *p4 = NUL; - char *const ffname = fix_fname((char *)fname); + char *const ffname = fix_fname(fname); *p4 = c; if (ffname == NULL) { @@ -833,10 +882,10 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) for (const char *entry = (const char *)p_rtp; *entry != NUL;) { const char *cur_entry = entry; - copy_option_part((char **)&entry, (char *)buf, MAXPATHL, ","); + copy_option_part((char **)&entry, buf, MAXPATHL, ","); if (insp == NULL) { - add_pathsep((char *)buf); - char *const rtp_ffname = fix_fname((char *)buf); + add_pathsep(buf); + char *const rtp_ffname = fix_fname(buf); if (rtp_ffname == NULL) { goto theend; } @@ -848,7 +897,7 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) } } - if ((p = (char_u *)strstr((char *)buf, "after")) != NULL + if ((p = strstr(buf, "after")) != NULL && p > buf && vim_ispathsep(p[-1]) && (vim_ispathsep(p[5]) || p[5] == NUL || p[5] == ',')) { @@ -864,18 +913,18 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) if (insp == NULL) { // Both "fname" and "after" not found, append at the end. - insp = (const char *)p_rtp + STRLEN(p_rtp); + insp = (const char *)p_rtp + strlen(p_rtp); } // check if rtp/pack/name/start/name/after exists - afterdir = concat_fnames((char *)fname, "after", true); + afterdir = concat_fnames(fname, "after", true); size_t afterlen = 0; - if (is_pack ? pack_has_entries((char_u *)afterdir) : os_isdir((char_u *)afterdir)) { + if (is_pack ? pack_has_entries(afterdir) : os_isdir(afterdir)) { afterlen = strlen(afterdir) + 1; // add one for comma } - const size_t oldlen = STRLEN(p_rtp); - const size_t addlen = STRLEN(fname) + 1; // add one for comma + const size_t oldlen = strlen(p_rtp); + const size_t addlen = strlen(fname) + 1; // add one for comma const size_t new_rtp_capacity = oldlen + addlen + afterlen + 1; // add one for NUL ------------------------------------------^ char *const new_rtp = try_malloc(new_rtp_capacity); @@ -923,7 +972,7 @@ static int add_pack_dir_to_rtp(char_u *fname, bool is_pack) xstrlcat(new_rtp, afterdir, new_rtp_capacity); } - set_option_value("rtp", 0L, new_rtp, 0); + set_option_value_give_err("rtp", 0L, new_rtp, 0); xfree(new_rtp); retval = OK; @@ -937,29 +986,29 @@ theend: /// Load scripts in "plugin" directory of the package. /// For opt packages, also load scripts in "ftdetect" (start packages already /// load these from filetype.vim) -static int load_pack_plugin(bool opt, char_u *fname) +static int load_pack_plugin(bool opt, char *fname) { static const char *ftpat = "%s/ftdetect/*.vim"; // NOLINT - char *const ffname = fix_fname((char *)fname); - size_t len = strlen(ffname) + STRLEN(ftpat); - char_u *pat = xmallocz(len); + char *const ffname = fix_fname(fname); + size_t len = strlen(ffname) + strlen(ftpat); + char *pat = xmallocz(len); - vim_snprintf((char *)pat, len, "%s/plugin/**/*.vim", ffname); // NOLINT - source_all_matches((char *)pat); - vim_snprintf((char *)pat, len, "%s/plugin/**/*.lua", ffname); // NOLINT - source_all_matches((char *)pat); + vim_snprintf(pat, len, "%s/plugin/**/*.vim", ffname); // NOLINT + source_all_matches(pat); + vim_snprintf(pat, len, "%s/plugin/**/*.lua", ffname); // NOLINT + source_all_matches(pat); - char_u *cmd = vim_strsave((char_u *)"g:did_load_filetypes"); + char *cmd = xstrdup("g:did_load_filetypes"); // If runtime/filetype.vim wasn't loaded yet, the scripts will be // found when it loads. - if (opt && eval_to_number((char *)cmd) > 0) { + if (opt && eval_to_number(cmd) > 0) { do_cmdline_cmd("augroup filetypedetect"); - vim_snprintf((char *)pat, len, ftpat, ffname); - source_all_matches((char *)pat); + vim_snprintf(pat, len, ftpat, ffname); + source_all_matches(pat); vim_snprintf((char *)pat, len, "%s/ftdetect/*.lua", ffname); // NOLINT - source_all_matches((char *)pat); + source_all_matches(pat); do_cmdline_cmd("augroup END"); } xfree(cmd); @@ -974,7 +1023,7 @@ static int APP_ADD_DIR; static int APP_LOAD; static int APP_BOTH; -static void add_pack_plugin(bool opt, char_u *fname, void *cookie) +static void add_pack_plugin(bool opt, char *fname, void *cookie) { if (cookie != &APP_LOAD) { char *buf = xmalloc(MAXPATHL); @@ -983,7 +1032,7 @@ static void add_pack_plugin(bool opt, char_u *fname, void *cookie) const char *p = (const char *)p_rtp; while (*p != NUL) { copy_option_part((char **)&p, buf, MAXPATHL, ","); - if (path_fnamecmp(buf, (char *)fname) == 0) { + if (path_fnamecmp(buf, fname) == 0) { found = true; break; } @@ -1004,12 +1053,12 @@ static void add_pack_plugin(bool opt, char_u *fname, void *cookie) static void add_start_pack_plugin(char *fname, void *cookie) { - add_pack_plugin(false, (char_u *)fname, cookie); + add_pack_plugin(false, fname, cookie); } static void add_opt_pack_plugin(char *fname, void *cookie) { - add_pack_plugin(true, (char_u *)fname, cookie); + add_pack_plugin(true, fname, cookie); } /// Add all packages in the "start" directory to 'runtimepath'. @@ -1018,11 +1067,11 @@ void add_pack_start_dirs(void) do_in_path(p_pp, NULL, DIP_ALL + DIP_DIR, add_pack_start_dir, NULL); } -static bool pack_has_entries(char_u *buf) +static bool pack_has_entries(char *buf) { int num_files; char **files; - char *(pat[]) = { (char *)buf }; + char *(pat[]) = { buf }; if (gen_expand_wildcards(1, pat, &num_files, &files, EW_DIR) == OK) { FreeWild(num_files, files); } @@ -1031,14 +1080,14 @@ static bool pack_has_entries(char_u *buf) static void add_pack_start_dir(char *fname, void *cookie) { - static char_u buf[MAXPATHL]; + static char buf[MAXPATHL]; char *(start_pat[]) = { "/start/*", "/pack/*/start/*" }; // NOLINT for (int i = 0; i < 2; i++) { - if (STRLEN(fname) + STRLEN(start_pat[i]) + 1 > MAXPATHL) { + if (strlen(fname) + strlen(start_pat[i]) + 1 > MAXPATHL) { continue; } - STRLCPY(buf, fname, MAXPATHL); - STRLCAT(buf, start_pat[i], sizeof buf); + xstrlcpy(buf, fname, MAXPATHL); + xstrlcat(buf, start_pat[i], sizeof buf); if (pack_has_entries(buf)) { add_pack_dir_to_rtp(buf, true); } @@ -1072,12 +1121,12 @@ void ex_packloadall(exarg_T *eap) void load_plugins(void) { if (p_lpl) { - char_u *rtp_copy = p_rtp; - char_u *const plugin_pattern_vim = (char_u *)"plugin/**/*.vim"; // NOLINT - char_u *const plugin_pattern_lua = (char_u *)"plugin/**/*.lua"; // NOLINT + char *rtp_copy = p_rtp; + char *const plugin_pattern_vim = "plugin/**/*.vim"; // NOLINT + char *const plugin_pattern_lua = "plugin/**/*.lua"; // NOLINT if (!did_source_packages) { - rtp_copy = vim_strsave(p_rtp); + rtp_copy = xstrdup(p_rtp); add_pack_start_dirs(); } @@ -1094,8 +1143,8 @@ void load_plugins(void) } TIME_MSG("loading packages"); - source_runtime((char *)plugin_pattern_vim, DIP_ALL | DIP_AFTER); - source_runtime((char *)plugin_pattern_lua, DIP_ALL | DIP_AFTER); + source_runtime(plugin_pattern_vim, DIP_ALL | DIP_AFTER); + source_runtime(plugin_pattern_lua, DIP_ALL | DIP_AFTER); TIME_MSG("loading after plugins"); } } @@ -1113,108 +1162,87 @@ void ex_packadd(exarg_T *eap) continue; } - const size_t len = STRLEN(plugpat) + STRLEN(eap->arg) + 5; + const size_t len = strlen(plugpat) + strlen(eap->arg) + 5; char *pat = xmallocz(len); vim_snprintf(pat, len, plugpat, round == 1 ? "start" : "opt", eap->arg); // The first round don't give a "not found" error, in the second round // only when nothing was found in the first round. - res = do_in_path(p_pp, pat, DIP_ALL + DIP_DIR + (round == 2 && res == FAIL ? DIP_ERR : 0), - round == 1 ? add_start_pack_plugin : add_opt_pack_plugin, - eap->forceit ? &APP_ADD_DIR : &APP_BOTH); + res = + do_in_path(p_pp, pat, DIP_ALL + DIP_DIR + (round == 2 && res == FAIL ? DIP_ERR : 0), + round == 1 ? add_start_pack_plugin : add_opt_pack_plugin, + eap->forceit ? &APP_ADD_DIR : &APP_BOTH); xfree(pat); } } /// Expand color scheme, compiler or filetype names. /// Search from 'runtimepath': -/// 'runtimepath'/{dirnames}/{pat}.vim -/// When "flags" has DIP_START: search also from 'start' of 'packpath': -/// 'packpath'/pack/ * /start/ * /{dirnames}/{pat}.vim -/// When "flags" has DIP_OPT: search also from 'opt' of 'packpath': -/// 'packpath'/pack/ * /opt/ * /{dirnames}/{pat}.vim -/// When "flags" has DIP_LUA: search also performed for .lua files +/// 'runtimepath'/{dirnames}/{pat}.(vim|lua) +/// When "flags" has DIP_START: search also from "start" of 'packpath': +/// 'packpath'/pack/*/start/*/{dirnames}/{pat}.(vim|lua) +/// When "flags" has DIP_OPT: search also from "opt" of 'packpath': +/// 'packpath'/pack/*/opt/*/{dirnames}/{pat}.(vim|lua) /// "dirnames" is an array with one or more directory names. -int ExpandRTDir(char_u *pat, int flags, int *num_file, char ***file, char *dirnames[]) +int ExpandRTDir(char *pat, int flags, int *num_file, char ***file, char *dirnames[]) { *num_file = 0; *file = NULL; - size_t pat_len = STRLEN(pat); + size_t pat_len = strlen(pat); garray_T ga; ga_init(&ga, (int)sizeof(char *), 10); - // TODO(bfredl): this is bullshit, exandpath should not reinvent path logic. + // TODO(bfredl): this is bullshit, expandpath should not reinvent path logic. for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = STRLEN(dirnames[i]) + pat_len + 7; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "%s/%s*.vim", dirnames[i], pat); - globpath((char *)p_rtp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)s, size, "%s/%s*.lua", dirnames[i], pat); - globpath((char *)p_rtp, s, &ga, 0); - } + size_t size = strlen(dirnames[i]) + pat_len + 16; + char *s = xmalloc(size); + snprintf(s, size, "%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); + globpath(p_rtp, s, &ga, 0); xfree(s); } if (flags & DIP_START) { for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = STRLEN(dirnames[i]) + pat_len + 22; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "pack/*/start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT - globpath((char *)p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)s, size, "pack/*/start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath((char *)p_pp, s, &ga, 0); - } + size_t size = strlen(dirnames[i]) + pat_len + 31; + char *s = xmalloc(size); + snprintf(s, size, "pack/*/start/*/%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); // NOLINT + globpath(p_pp, s, &ga, 0); xfree(s); } for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = STRLEN(dirnames[i]) + pat_len + 22; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "start/*/%s/%s*.vim", dirnames[i], pat); // NOLINT - globpath((char *)p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)s, size, "start/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath((char *)p_pp, s, &ga, 0); - } + size_t size = strlen(dirnames[i]) + pat_len + 31; + char *s = xmalloc(size); + snprintf(s, size, "start/*/%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); // NOLINT + globpath(p_pp, s, &ga, 0); xfree(s); } } if (flags & DIP_OPT) { for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = STRLEN(dirnames[i]) + pat_len + 20; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT - globpath((char *)p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)s, size, "pack/*/opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath((char *)p_pp, s, &ga, 0); - } + size_t size = strlen(dirnames[i]) + pat_len + 29; + char *s = xmalloc(size); + snprintf(s, size, "pack/*/opt/*/%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); // NOLINT + globpath(p_pp, s, &ga, 0); xfree(s); } for (int i = 0; dirnames[i] != NULL; i++) { - size_t size = STRLEN(dirnames[i]) + pat_len + 20; - char_u *s = xmalloc(size); - snprintf((char *)s, size, "opt/*/%s/%s*.vim", dirnames[i], pat); // NOLINT - globpath((char *)p_pp, s, &ga, 0); - if (flags & DIP_LUA) { - snprintf((char *)s, size, "opt/*/%s/%s*.lua", dirnames[i], pat); // NOLINT - globpath((char *)p_pp, s, &ga, 0); - } + size_t size = strlen(dirnames[i]) + pat_len + 29; + char *s = xmalloc(size); + snprintf(s, size, "opt/*/%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat); // NOLINT + globpath(p_pp, s, &ga, 0); xfree(s); } } for (int i = 0; i < ga.ga_len; i++) { - char_u *match = ((char_u **)ga.ga_data)[i]; - char_u *s = match; - char_u *e = s + STRLEN(s); + char *match = ((char **)ga.ga_data)[i]; + char *s = match; + char *e = s + strlen(s); if (e - s > 4 && (STRNICMP(e - 4, ".vim", 4) == 0 - || ((flags & DIP_LUA) - && STRNICMP(e - 4, ".lua", 4) == 0))) { + || STRNICMP(e - 4, ".lua", 4) == 0)) { e -= 4; for (s = e; s > match; MB_PTR_BACK(match, s)) { if (vim_ispathsep(*s)) { @@ -1243,27 +1271,27 @@ int ExpandRTDir(char_u *pat, int flags, int *num_file, char ***file, char *dirna /// Expand loadplugin names: /// 'packpath'/pack/ * /opt/{pat} -int ExpandPackAddDir(char_u *pat, int *num_file, char ***file) +int ExpandPackAddDir(char *pat, int *num_file, char ***file) { garray_T ga; *num_file = 0; *file = NULL; - size_t pat_len = STRLEN(pat); + size_t pat_len = strlen(pat); ga_init(&ga, (int)sizeof(char *), 10); size_t buflen = pat_len + 26; - char_u *s = xmalloc(buflen); - snprintf((char *)s, buflen, "pack/*/opt/%s*", pat); // NOLINT - globpath((char *)p_pp, s, &ga, 0); - snprintf((char *)s, buflen, "opt/%s*", pat); // NOLINT - globpath((char *)p_pp, s, &ga, 0); + char *s = xmalloc(buflen); + snprintf(s, buflen, "pack/*/opt/%s*", pat); // NOLINT + globpath(p_pp, s, &ga, 0); + snprintf(s, buflen, "opt/%s*", pat); // NOLINT + globpath(p_pp, s, &ga, 0); xfree(s); for (int i = 0; i < ga.ga_len; i++) { - char_u *match = ((char_u **)ga.ga_data)[i]; - s = (char_u *)path_tail((char *)match); - memmove(match, s, STRLEN(s) + 1); + char *match = ((char **)ga.ga_data)[i]; + s = path_tail(match); + memmove(match, s, strlen(s) + 1); } if (GA_EMPTY(&ga)) { @@ -1423,7 +1451,7 @@ static inline char *add_dir(char *dest, const char *const dir, const size_t dir_ if (!after_pathsep(dest - 1, dest)) { *dest++ = PATHSEP; } -#if defined(WIN32) +#if defined(MSWIN) size_t size = (type == kXDGDataHome ? sizeof("nvim-data") - 1 : NVIM_SIZE); memmove(dest, (type == kXDGDataHome ? "nvim-data" : "nvim"), size); dest += size; @@ -1451,7 +1479,7 @@ char *get_lib_dir(void) // TODO(bfredl): too fragile? Ideally default_lib_dir would be made empty // in an appimage build if (strlen(default_lib_dir) != 0 - && os_isdir((const char_u *)default_lib_dir)) { + && os_isdir(default_lib_dir)) { return xstrdup(default_lib_dir); } @@ -1493,7 +1521,7 @@ char *runtimepath_default(bool clean_arg) if (data_home != NULL) { data_len = strlen(data_home); if (data_len != 0) { -#if defined(WIN32) +#if defined(MSWIN) size_t nvim_size = (sizeof("nvim-data") - 1); #else size_t nvim_size = NVIM_SIZE; @@ -1583,7 +1611,7 @@ static void cmd_source(char *fname, exarg_T *eap) // - after ":argdo", ":windo" or ":bufdo" // - another command follows // - inside a loop - openscript((char_u *)fname, global_busy || listcmd_busy || eap->nextcmd != NULL + openscript(fname, global_busy || listcmd_busy || eap->nextcmd != NULL || eap->cstack->cs_idx >= 0); // ":source" read ex commands @@ -1605,7 +1633,7 @@ void ex_options(exarg_T *eap) bool multi_mods = 0; buf[0] = NUL; - (void)add_win_cmd_modifers(buf, &cmdmod, &multi_mods); + (void)add_win_cmd_modifiers(buf, &cmdmod, &multi_mods); os_setenv("OPTWIN_CMD", buf, 1); cmd_source(SYS_OPTWIN_FILE, NULL); @@ -1636,7 +1664,7 @@ int source_level(void *cookie) /// If possible the handle is closed on exec(). static FILE *fopen_noinh_readbin(char *filename) { -#ifdef WIN32 +#ifdef MSWIN int fd_tmp = os_open(filename, O_RDONLY | O_BINARY | O_NOINHERIT, 0); #else int fd_tmp = os_open(filename, O_RDONLY, 0); @@ -1663,14 +1691,14 @@ static FILE *fopen_noinh_readbin(char *filename) /// /// @return true if this line did begin with a continuation (the next line /// should also be considered, if it exists); false otherwise -static bool concat_continued_line(garray_T *const ga, const int init_growsize, - const char_u *const p, size_t len) +static bool concat_continued_line(garray_T *const ga, const int init_growsize, const char *const p, + size_t len) FUNC_ATTR_NONNULL_ALL { - const char *const line = (char *)skipwhite_len(p, len); - len -= (size_t)((char_u *)line - p); + const char *const line = skipwhite_len((char *)p, len); + len -= (size_t)(line - p); // Skip lines starting with '\" ', concat lines starting with '\' - if (len >= 3 && STRNCMP(line, "\"\\ ", 3) == 0) { + if (len >= 3 && strncmp(line, "\"\\ ", 3) == 0) { return true; } else if (len == 0 || line[0] != '\\') { return false; @@ -1700,19 +1728,19 @@ typedef struct { static char *get_str_line(int c, void *cookie, int indent, bool do_concat) { GetStrLineCookie *p = cookie; - if (STRLEN(p->buf) <= p->offset) { + if (strlen(p->buf) <= p->offset) { return NULL; } const char *line = p->buf + p->offset; - const char *eol = (char *)skip_to_newline((char_u *)line); + const char *eol = skip_to_newline(line); garray_T ga; - ga_init(&ga, sizeof(char_u), 400); + ga_init(&ga, sizeof(char), 400); ga_concat_len(&ga, line, (size_t)(eol - line)); if (do_concat && vim_strchr(p_cpo, CPO_CONCAT) == NULL) { while (eol[0] != NUL) { line = eol + 1; - const char_u *const next_eol = skip_to_newline((char_u *)line); - if (!concat_continued_line(&ga, 400, (char_u *)line, (size_t)(next_eol - (char_u *)line))) { + const char *const next_eol = skip_to_newline(line); + if (!concat_continued_line(&ga, 400, line, (size_t)(next_eol - line))) { break; } eol = (char *)next_eol; @@ -1742,7 +1770,7 @@ scriptitem_T *new_script_item(char *const name, scid_T *const sid_out) SCRIPT_ITEM(script_items.ga_len).sn_name = NULL; SCRIPT_ITEM(script_items.ga_len).sn_prof_on = false; } - SCRIPT_ITEM(sid).sn_name = (char_u *)name; + SCRIPT_ITEM(sid).sn_name = name; new_script_vars(sid); // Allocate the local script variables to use for this script. return &SCRIPT_ITEM(sid); } @@ -1786,7 +1814,7 @@ static void cmd_source_buffer(const exarg_T *const eap) return; } garray_T ga; - ga_init(&ga, sizeof(char_u), 400); + ga_init(&ga, sizeof(char), 400); const linenr_T final_lnum = eap->line2; // Copy the contents to be executed. for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) { @@ -1794,10 +1822,10 @@ static void cmd_source_buffer(const exarg_T *const eap) if (ga.ga_len > 400) { ga_set_growsize(&ga, MIN(ga.ga_len, 8000)); } - ga_concat(&ga, (char *)ml_get(curr_lnum)); + ga_concat(&ga, ml_get(curr_lnum)); ga_append(&ga, NL); } - ((char_u *)ga.ga_data)[ga.ga_len - 1] = NUL; + ((char *)ga.ga_data)[ga.ga_len - 1] = NUL; const GetStrLineCookie cookie = { .buf = ga.ga_data, .offset = 0, @@ -1858,7 +1886,7 @@ int do_source(char *fname, int check_other, int is_vimrc) if (fname_exp == NULL) { return retval; } - if (os_isdir((char_u *)fname_exp)) { + if (os_isdir(fname_exp)) { smsg(_("Cannot source a directory: \"%s\""), fname); goto theend; } @@ -1880,7 +1908,7 @@ int do_source(char *fname, int check_other, int is_vimrc) cookie.fp = fopen_noinh_readbin(fname_exp); if (cookie.fp == NULL && check_other) { - // Try again, replacing file name ".vimrc" by "_vimrc" or vice versa, + // Try again, replacing file name ".nvimrc" by "_nvimrc" or vice versa, // and ".exrc" by "_exrc" or vice versa. p = path_tail(fname_exp); if ((*p == '.' || *p == '_') @@ -1935,7 +1963,7 @@ int do_source(char *fname, int check_other, int is_vimrc) cookie.finished = false; // Check if this script has a breakpoint. - cookie.breakpoint = dbg_find_breakpoint(true, (char_u *)fname_exp, (linenr_T)0); + cookie.breakpoint = dbg_find_breakpoint(true, fname_exp, (linenr_T)0); cookie.fname = fname_exp; cookie.dbg_tick = debug_tick; @@ -1964,7 +1992,7 @@ int do_source(char *fname, int check_other, int is_vimrc) si = get_current_script_id(&fname_exp, ¤t_sctx); // Keep the sourcing name/lnum, for recursive calls. - estack_push(ETYPE_SCRIPT, (char *)si->sn_name, 0); + estack_push(ETYPE_SCRIPT, si->sn_name, 0); if (l_do_profiling == PROF_YES) { bool forceit = false; @@ -1993,11 +2021,11 @@ int do_source(char *fname, int check_other, int is_vimrc) } else { // Read the first line so we can check for a UTF-8 BOM. firstline = (uint8_t *)getsourceline(0, (void *)&cookie, 0, true); - if (firstline != NULL && STRLEN(firstline) >= 3 && firstline[0] == 0xef + if (firstline != NULL && strlen((char *)firstline) >= 3 && firstline[0] == 0xef && firstline[1] == 0xbb && firstline[2] == 0xbf) { // Found BOM; setup conversion, skip over BOM and recode the line. - convert_setup(&cookie.conv, (char_u *)"utf-8", p_enc); - p = (char *)string_convert(&cookie.conv, (char_u *)firstline + 3, NULL); + convert_setup(&cookie.conv, "utf-8", p_enc); + p = string_convert(&cookie.conv, (char *)firstline + 3, NULL); if (p == NULL) { p = xstrdup((char *)firstline + 3); } @@ -2036,8 +2064,8 @@ int do_source(char *fname, int check_other, int is_vimrc) } if (l_time_fd != NULL) { - vim_snprintf((char *)IObuff, IOSIZE, "sourcing %s", fname); - time_msg((char *)IObuff, &start_time); + vim_snprintf(IObuff, IOSIZE, "sourcing %s", fname); + time_msg(IObuff, &start_time); time_pop(rel_time); } @@ -2093,14 +2121,14 @@ scriptitem_T *get_current_script_id(char **fnamep, sctx_T *ret_sctx) // - If a script is deleted and another script is written, with a // different name, the inode may be re-used. si = &SCRIPT_ITEM(script_sctx.sc_sid); - if (si->sn_name != NULL && FNAMECMP(si->sn_name, *fnamep) == 0) { + if (si->sn_name != NULL && path_fnamecmp(si->sn_name, *fnamep) == 0) { // Found it! break; } } if (script_sctx.sc_sid == 0) { si = new_script_item(*fnamep, &script_sctx.sc_sid); - *fnamep = xstrdup((char *)si->sn_name); + *fnamep = xstrdup(si->sn_name); } if (ret_sctx != NULL) { *ret_sctx = script_sctx; @@ -2112,12 +2140,17 @@ scriptitem_T *get_current_script_id(char **fnamep, sctx_T *ret_sctx) /// ":scriptnames" void ex_scriptnames(exarg_T *eap) { - if (eap->addr_count > 0) { + if (eap->addr_count > 0 || *eap->arg != NUL) { // :script {scriptId}: edit the script - if (eap->line2 < 1 || eap->line2 > script_items.ga_len) { + if (eap->addr_count > 0 && !SCRIPT_ID_VALID(eap->line2)) { emsg(_(e_invarg)); } else { - eap->arg = (char *)SCRIPT_ITEM(eap->line2).sn_name; + if (eap->addr_count > 0) { + eap->arg = SCRIPT_ITEM(eap->line2).sn_name; + } else { + expand_env(eap->arg, NameBuff, MAXPATHL); + eap->arg = NameBuff; + } do_exedit(eap, NULL); } return; @@ -2125,11 +2158,11 @@ void ex_scriptnames(exarg_T *eap) for (int i = 1; i <= script_items.ga_len && !got_int; i++) { if (SCRIPT_ITEM(i).sn_name != NULL) { - home_replace(NULL, (char *)SCRIPT_ITEM(i).sn_name, (char *)NameBuff, MAXPATHL, true); - vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff); + home_replace(NULL, SCRIPT_ITEM(i).sn_name, NameBuff, MAXPATHL, true); + vim_snprintf(IObuff, IOSIZE, "%3d: %s", i, NameBuff); if (!message_filtered(IObuff)) { msg_putchar('\n'); - msg_outtrans((char *)IObuff); + msg_outtrans(IObuff); line_breakcheck(); } } @@ -2151,40 +2184,40 @@ void scriptnames_slash_adjust(void) /// Get a pointer to a script name. Used for ":verbose set". /// Message appended to "Last set from " -char_u *get_scriptname(LastSet last_set, bool *should_free) +char *get_scriptname(LastSet last_set, bool *should_free) { *should_free = false; switch (last_set.script_ctx.sc_sid) { case SID_MODELINE: - return (char_u *)_("modeline"); + return _("modeline"); case SID_CMDARG: - return (char_u *)_("--cmd argument"); + return _("--cmd argument"); case SID_CARG: - return (char_u *)_("-c argument"); + return _("-c argument"); case SID_ENV: - return (char_u *)_("environment variable"); + return _("environment variable"); case SID_ERROR: - return (char_u *)_("error handler"); + return _("error handler"); case SID_WINLAYOUT: - return (char_u *)_("changed window size"); + return _("changed window size"); case SID_LUA: - return (char_u *)_("Lua"); + return _("Lua"); case SID_API_CLIENT: - snprintf((char *)IObuff, IOSIZE, _("API client (channel id %" PRIu64 ")"), last_set.channel_id); + snprintf(IObuff, IOSIZE, _("API client (channel id %" PRIu64 ")"), last_set.channel_id); return IObuff; case SID_STR: - return (char_u *)_("anonymous :source"); + return _("anonymous :source"); default: { - char *const sname = (char *)SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name; + char *const sname = SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name; if (sname == NULL) { - snprintf((char *)IObuff, IOSIZE, _("anonymous :source (script id %d)"), + snprintf(IObuff, IOSIZE, _("anonymous :source (script id %d)"), last_set.script_ctx.sc_sid); return IObuff; } *should_free = true; - return (char_u *)home_replace_save(NULL, sname); + return home_replace_save(NULL, sname); } } } @@ -2220,7 +2253,7 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat) // If breakpoints have been added/deleted need to check for it. if (sp->dbg_tick < debug_tick) { - sp->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, SOURCING_LNUM); + sp->breakpoint = dbg_find_breakpoint(true, sp->fname, SOURCING_LNUM); sp->dbg_tick = debug_tick; } if (do_profiling == PROF_YES) { @@ -2259,11 +2292,10 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat) || (p[0] == '"' && p[1] == '\\' && p[2] == ' '))) { garray_T ga; - ga_init(&ga, (int)sizeof(char_u), 400); + ga_init(&ga, (int)sizeof(char), 400); ga_concat(&ga, line); while (sp->nextline != NULL - && concat_continued_line(&ga, 400, (char_u *)sp->nextline, - STRLEN(sp->nextline))) { + && concat_continued_line(&ga, 400, sp->nextline, strlen(sp->nextline))) { xfree(sp->nextline); sp->nextline = get_one_sourceline(sp); } @@ -2277,7 +2309,7 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat) char *s; // Convert the encoding of the script line. - s = (char *)string_convert(&sp->conv, (char_u *)line, NULL); + s = string_convert(&sp->conv, line, NULL); if (s != NULL) { xfree(line); line = s; @@ -2286,9 +2318,9 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat) // Did we encounter a breakpoint? if (sp->breakpoint != 0 && sp->breakpoint <= SOURCING_LNUM) { - dbg_breakpoint((char_u *)sp->fname, SOURCING_LNUM); + dbg_breakpoint(sp->fname, SOURCING_LNUM); // Find next breakpoint. - sp->breakpoint = dbg_find_breakpoint(true, (char_u *)sp->fname, SOURCING_LNUM); + sp->breakpoint = dbg_find_breakpoint(true, sp->fname, SOURCING_LNUM); sp->dbg_tick = debug_tick; } @@ -2326,7 +2358,7 @@ retry: break; } - len = ga.ga_len + (int)STRLEN(buf + ga.ga_len); + len = ga.ga_len + (int)strlen(buf + ga.ga_len); #ifdef USE_CRNL // Ignore a trailing CTRL-Z, when in Dos mode. Only recognize the // CTRL-Z by its own, or after a NL. @@ -2410,14 +2442,14 @@ void ex_scriptencoding(exarg_T *eap) } if (*eap->arg != NUL) { - name = (char *)enc_canonize((char_u *)eap->arg); + name = enc_canonize(eap->arg); } else { name = eap->arg; } // Setup for conversion from the specified encoding to 'encoding'. sp = (struct source_cookie *)getline_cookie(eap->getline, eap->cookie); - convert_setup(&sp->conv, (char_u *)name, p_enc); + convert_setup(&sp->conv, name, p_enc); if (name != eap->arg) { xfree(name); diff --git a/src/nvim/runtime.h b/src/nvim/runtime.h index a255c6c096..97063b900c 100644 --- a/src/nvim/runtime.h +++ b/src/nvim/runtime.h @@ -3,10 +3,15 @@ #include <stdbool.h> +#include "klib/kvec.h" #include "nvim/autocmd.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/ex_eval_defs.h" +#include "nvim/garray.h" +#include "nvim/pos.h" +#include "nvim/types.h" typedef enum { ETYPE_TOP, ///< toplevel @@ -47,10 +52,11 @@ typedef enum { ESTACK_NONE, ESTACK_SFILE, ESTACK_STACK, + ESTACK_SCRIPT, } estack_arg_T; typedef struct scriptitem_S { - char_u *sn_name; + char *sn_name; bool sn_prof_on; ///< true when script is/was profiled bool sn_pr_force; ///< forceit: profile functions in this script proftime_T sn_pr_child; ///< time set when going into first child @@ -73,6 +79,7 @@ typedef struct scriptitem_S { /// Growarray to store info about already sourced scripts. extern garray_T script_items; #define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1]) +#define SCRIPT_ID_VALID(id) ((id) > 0 && (id) <= script_items.ga_len) typedef void (*DoInRuntimepathCB)(char *, void *); @@ -98,7 +105,6 @@ typedef kvec_t(char *) CharVec; #define DIP_NORTP 0x20 // do not use 'runtimepath' #define DIP_NOAFTER 0x40 // skip "after" directories #define DIP_AFTER 0x80 // only use "after" directories -#define DIP_LUA 0x100 // also use ".lua" files #define DIP_DIRFILE 0x200 // find both files and directories #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/screen.c b/src/nvim/screen.c index b471b93192..ebff52cd69 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -10,35 +10,44 @@ #include <assert.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" -#include "nvim/cmdexpand.h" #include "nvim/cursor.h" -#include "nvim/drawscreen.h" #include "nvim/eval.h" -#include "nvim/extmark.h" -#include "nvim/fileio.h" #include "nvim/fold.h" -#include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" +#include "nvim/grid_defs.h" #include "nvim/highlight.h" -#include "nvim/highlight_group.h" -#include "nvim/menu.h" +#include "nvim/mbyte.h" +#include "nvim/memline_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" +#include "nvim/normal.h" #include "nvim/option.h" -#include "nvim/optionstr.h" +#include "nvim/os/os.h" +#include "nvim/os/time.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/regexp.h" #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/statusline.h" -#include "nvim/ui_compositor.h" -#include "nvim/undo.h" +#include "nvim/strings.h" +#include "nvim/types.h" +#include "nvim/ui.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -69,7 +78,7 @@ bool conceal_cursor_line(const win_T *wp) } else { return false; } - return vim_strchr((char *)wp->w_p_cocu, c) != NULL; + return vim_strchr(wp->w_p_cocu, c) != NULL; } /// Whether cursorline is drawn in a special way @@ -155,8 +164,6 @@ void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endr } else { grid_fill(&wp->w_grid, row, endrow, n, wp->w_grid.cols, c1, c2, attr); } - - set_empty_rows(wp, row); } /// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much @@ -182,7 +189,7 @@ int compute_foldcolumn(win_T *wp, int col) /// /// Assume monocell characters /// @return number of chars added to \param p -size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) +size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) { int i = 0; int level; @@ -216,7 +223,7 @@ size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) symbol = '>'; } - len = utf_char2bytes(symbol, (char *)&p[char_counter]); + len = utf_char2bytes(symbol, &p[char_counter]); char_counter += (size_t)len; if (first_level + i >= level) { i++; @@ -230,7 +237,7 @@ size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) char_counter -= (size_t)len; memset(&p[char_counter], ' ', (size_t)len); } - len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, (char *)&p[char_counter]); + len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]); char_counter += (size_t)len; } @@ -239,250 +246,18 @@ size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum) /// Mirror text "str" for right-left displaying. /// Only works for single-byte characters (e.g., numbers). -void rl_mirror(char_u *str) +void rl_mirror(char *str) { - char_u *p1, *p2; - char_u t; + char *p1, *p2; + char t; - for (p1 = str, p2 = str + STRLEN(str) - 1; p1 < p2; p1++, p2--) { + for (p1 = str, p2 = str + strlen(str) - 1; p1 < p2; p1++, p2--) { t = *p1; *p1 = *p2; *p2 = t; } } -/// Get the length of an item as it will be shown in the status line. -static int wildmenu_match_len(expand_T *xp, char_u *s) -{ - int len = 0; - - int emenu = (xp->xp_context == EXPAND_MENUS - || xp->xp_context == EXPAND_MENUNAMES); - - // Check for menu separators - replace with '|'. - if (emenu && menu_is_separator((char *)s)) { - return 1; - } - - while (*s != NUL) { - s += skip_wildmenu_char(xp, s); - len += ptr2cells((char *)s); - MB_PTR_ADV(s); - } - - return len; -} - -/// Return the number of characters that should be skipped in the wildmenu -/// These are backslashes used for escaping. Do show backslashes in help tags. -static int skip_wildmenu_char(expand_T *xp, char_u *s) -{ - if ((rem_backslash(s) && xp->xp_context != EXPAND_HELP) - || ((xp->xp_context == EXPAND_MENUS - || xp->xp_context == EXPAND_MENUNAMES) - && (s[0] == '\t' - || (s[0] == '\\' && s[1] != NUL)))) { -#ifndef BACKSLASH_IN_FILENAME - // TODO(bfredl): Why in the actual fuck are we special casing the - // shell variety deep in the redraw logic? Shell special snowflakiness - // should already be eliminated multiple layers before reaching the - // screen infracstructure. - if (xp->xp_shell && csh_like_shell() && s[1] == '\\' && s[2] == '!') { - return 2; - } -#endif - return 1; - } - return 0; -} - -/// Show wildchar matches in the status line. -/// Show at least the "match" item. -/// We start at item 'first_match' in the list and show all matches that fit. -/// -/// If inversion is possible we use it. Else '=' characters are used. -/// -/// @param matches list of matches -void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, int showtail) -{ -#define L_MATCH(m) (showtail ? sm_gettail(matches[m], false) : matches[m]) - int row; - char_u *buf; - int len; - int clen; // length in screen cells - int fillchar; - int attr; - int i; - bool highlight = true; - char_u *selstart = NULL; - int selstart_col = 0; - char_u *selend = NULL; - static int first_match = 0; - bool add_left = false; - char_u *s; - int emenu; - int l; - - if (matches == NULL) { // interrupted completion? - return; - } - - buf = xmalloc((size_t)Columns * MB_MAXBYTES + 1); - - if (match == -1) { // don't show match but original text - match = 0; - highlight = false; - } - // count 1 for the ending ">" - clen = wildmenu_match_len(xp, (char_u *)L_MATCH(match)) + 3; - if (match == 0) { - first_match = 0; - } else if (match < first_match) { - // jumping left, as far as we can go - first_match = match; - add_left = true; - } else { - // check if match fits on the screen - for (i = first_match; i < match; i++) { - clen += wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2; - } - if (first_match > 0) { - clen += 2; - } - // jumping right, put match at the left - if ((long)clen > Columns) { - first_match = match; - // if showing the last match, we can add some on the left - clen = 2; - for (i = match; i < num_matches; i++) { - clen += wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2; - if ((long)clen >= Columns) { - break; - } - } - if (i == num_matches) { - add_left = true; - } - } - } - if (add_left) { - while (first_match > 0) { - clen += wildmenu_match_len(xp, (char_u *)L_MATCH(first_match - 1)) + 2; - if ((long)clen >= Columns) { - break; - } - first_match--; - } - } - - fillchar = fillchar_status(&attr, curwin); - - if (first_match == 0) { - *buf = NUL; - len = 0; - } else { - STRCPY(buf, "< "); - len = 2; - } - clen = len; - - i = first_match; - while (clen + wildmenu_match_len(xp, (char_u *)L_MATCH(i)) + 2 < Columns) { - if (i == match) { - selstart = buf + len; - selstart_col = clen; - } - - s = (char_u *)L_MATCH(i); - // Check for menu separators - replace with '|' - emenu = (xp->xp_context == EXPAND_MENUS - || xp->xp_context == EXPAND_MENUNAMES); - if (emenu && menu_is_separator((char *)s)) { - STRCPY(buf + len, transchar('|')); - l = (int)STRLEN(buf + len); - len += l; - clen += l; - } else { - for (; *s != NUL; s++) { - s += skip_wildmenu_char(xp, s); - clen += ptr2cells((char *)s); - if ((l = utfc_ptr2len((char *)s)) > 1) { - STRNCPY(buf + len, s, l); // NOLINT(runtime/printf) - s += l - 1; - len += l; - } else { - STRCPY(buf + len, transchar_byte(*s)); - len += (int)STRLEN(buf + len); - } - } - } - if (i == match) { - selend = buf + len; - } - - *(buf + len++) = ' '; - *(buf + len++) = ' '; - clen += 2; - if (++i == num_matches) { - break; - } - } - - if (i != num_matches) { - *(buf + len++) = '>'; - clen++; - } - - buf[len] = NUL; - - row = cmdline_row - 1; - if (row >= 0) { - if (wild_menu_showing == 0 || wild_menu_showing == WM_LIST) { - if (msg_scrolled > 0) { - // Put the wildmenu just above the command line. If there is - // no room, scroll the screen one line up. - if (cmdline_row == Rows - 1) { - msg_scroll_up(false); - msg_scrolled++; - } else { - cmdline_row++; - row++; - } - wild_menu_showing = WM_SCROLLED; - } else { - // Create status line if needed by setting 'laststatus' to 2. - // Set 'winminheight' to zero to avoid that the window is - // resized. - if (lastwin->w_status_height == 0 && global_stl_height() == 0) { - save_p_ls = (int)p_ls; - save_p_wmh = (int)p_wmh; - p_ls = 2; - p_wmh = 0; - last_status(false); - } - wild_menu_showing = WM_SHOWN; - } - } - - // Tricky: wildmenu can be drawn either over a status line, or at empty - // scrolled space in the message output - ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED) - ? &msg_grid_adj : &default_grid; - - grid_puts(grid, buf, row, 0, attr); - if (selstart != NULL && highlight) { - *selend = NUL; - grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); - } - - grid_fill(grid, row, row + 1, clen, Columns, - fillchar, fillchar, attr); - } - - win_redraw_last_status(topframe); - xfree(buf); -} - /// Only call if (wp->w_vsep_width != 0). /// /// @return true if the status line of window "wp" is connected to the status @@ -520,52 +295,54 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len) return false; } - { - buf_T *old_curbuf = curbuf; - win_T *old_curwin = curwin; - char *s; - - curbuf = wp->w_buffer; - curwin = wp; - STRCPY(buf, "b:keymap_name"); // must be writable - emsg_skip++; - s = p = eval_to_string(buf, NULL, false); - emsg_skip--; - curbuf = old_curbuf; - curwin = old_curwin; - if (p == NULL || *p == NUL) { - if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) { - p = (char *)wp->w_buffer->b_p_keymap; - } else { - p = "lang"; - } - } - if (vim_snprintf(buf, (size_t)len, fmt, p) > len - 1) { - buf[0] = NUL; + buf_T *old_curbuf = curbuf; + win_T *old_curwin = curwin; + char *s; + + curbuf = wp->w_buffer; + curwin = wp; + STRCPY(buf, "b:keymap_name"); // must be writable + emsg_skip++; + s = p = eval_to_string(buf, NULL, false); + emsg_skip--; + curbuf = old_curbuf; + curwin = old_curwin; + if (p == NULL || *p == NUL) { + if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) { + p = wp->w_buffer->b_p_keymap; + } else { + p = "lang"; } - xfree(s); } + if (vim_snprintf(buf, (size_t)len, fmt, p) > len - 1) { + buf[0] = NUL; + } + xfree(s); return buf[0] != NUL; } /// Prepare for 'hlsearch' highlighting. void start_search_hl(void) { - if (p_hls && !no_hlsearch) { - end_search_hl(); // just in case it wasn't called before - last_pat_prog(&screen_search_hl.rm); - // Set the time limit to 'redrawtime'. - screen_search_hl.tm = profile_setlimit(p_rdt); + if (!p_hls || no_hlsearch) { + return; } + + end_search_hl(); // just in case it wasn't called before + last_pat_prog(&screen_search_hl.rm); + // Set the time limit to 'redrawtime'. + screen_search_hl.tm = profile_setlimit(p_rdt); } /// Clean up for 'hlsearch' highlighting. void end_search_hl(void) { - if (screen_search_hl.rm.regprog != NULL) { - vim_regfree(screen_search_hl.rm.regprog); - screen_search_hl.rm.regprog = NULL; + if (screen_search_hl.rm.regprog == NULL) { + return; } + + vim_regfree(screen_search_hl.rm.regprog); + screen_search_hl.rm.regprog = NULL; } /// Check if there should be a delay. Used before clearing or redrawing the @@ -574,7 +351,8 @@ void check_for_delay(bool check_msg_scroll) { if ((emsg_on_display || (check_msg_scroll && msg_scroll)) && !did_wait_return - && emsg_silent == 0) { + && emsg_silent == 0 + && !in_assert_fails) { ui_flush(); os_delay(1006L, true); emsg_on_display = false; @@ -584,22 +362,6 @@ void check_for_delay(bool check_msg_scroll) } } -/// Clear status line, window bar or tab page line click definition table -/// -/// @param[out] tpcd Table to clear. -/// @param[in] tpcd_size Size of the table. -void stl_clear_click_defs(StlClickDefinition *const click_defs, const long click_defs_size) -{ - if (click_defs != NULL) { - for (long i = 0; i < click_defs_size; i++) { - if (i == 0 || click_defs[i].func != click_defs[i - 1].func) { - xfree(click_defs[i].func); - } - } - memset(click_defs, 0, (size_t)click_defs_size * sizeof(click_defs[0])); - } -} - /// Set cursor to its position in the current window. void setcursor(void) { @@ -620,7 +382,7 @@ void setcursor_mayforce(bool force) // With 'rightleft' set and the cursor on a double-wide character, // position it on the leftmost column. col = curwin->w_width_inner - curwin->w_wcol - - ((utf_ptr2cells((char *)get_cursor_pos_ptr()) == 2 + - ((utf_ptr2cells(get_cursor_pos_ptr()) == 2 && vim_isprintc(gchar_cursor())) ? 2 : 1); } @@ -734,13 +496,13 @@ int showmode(void) length = (Rows - msg_row) * Columns - 3; } if (edit_submode_extra != NULL) { - length -= vim_strsize((char *)edit_submode_extra); + length -= vim_strsize(edit_submode_extra); } if (length > 0) { if (edit_submode_pre != NULL) { - length -= vim_strsize((char *)edit_submode_pre); + length -= vim_strsize(edit_submode_pre); } - if (length - vim_strsize((char *)edit_submode) > 0) { + if (length - vim_strsize(edit_submode) > 0) { if (edit_submode_pre != NULL) { msg_puts_attr((const char *)edit_submode_pre, attr); } @@ -787,8 +549,8 @@ int showmode(void) if (curwin->w_p_arab) { msg_puts_attr(_(" Arabic"), attr); } else if (get_keymap_str(curwin, " (%s)", - (char *)NameBuff, MAXPATHL)) { - msg_puts_attr((char *)NameBuff, attr); + NameBuff, MAXPATHL)) { + msg_puts_attr(NameBuff, attr); } } if ((State & MODE_INSERT) && p_paste) { @@ -862,6 +624,7 @@ int showmode(void) if (redrawing() && last->w_status_height == 0 && global_stl_height() == 0) { win_redr_ruler(last, true); } + redraw_cmdline = false; redraw_mode = false; clear_cmdline = false; @@ -910,242 +673,23 @@ void clearmode(void) static void recording_mode(int attr) { msg_puts_attr(_("recording"), attr); - if (!shortmess(SHM_RECORDING)) { - char s[4]; - snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording); - msg_puts_attr(s, attr); - } -} - -/// Draw the tab pages line at the top of the Vim window. -void draw_tabline(void) -{ - int tabcount = 0; - int tabwidth = 0; - int col = 0; - int scol = 0; - int attr; - win_T *wp; - win_T *cwp; - int wincount; - int modified; - int c; - int len; - int attr_nosel = HL_ATTR(HLF_TP); - int attr_fill = HL_ATTR(HLF_TPF); - char_u *p; - int room; - int use_sep_chars = (t_colors < 8); - - if (default_grid.chars == NULL) { - return; - } - redraw_tabline = false; - - if (ui_has(kUITabline)) { - ui_ext_tabline_update(); + if (shortmess(SHM_RECORDING)) { return; } - if (tabline_height() < 1) { - return; - } - - // Init TabPageIdxs[] to zero: Clicking outside of tabs has no effect. - assert(Columns == tab_page_click_defs_size); - stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size); - - // Use the 'tabline' option if it's set. - if (*p_tal != NUL) { - int saved_did_emsg = did_emsg; - - // Check for an error. If there is one we would loop in redrawing the - // screen. Avoid that by making 'tabline' empty. - did_emsg = false; - win_redr_custom(NULL, false, false); - if (did_emsg) { - set_string_option_direct("tabline", -1, "", OPT_FREE, SID_ERROR); - } - did_emsg |= saved_did_emsg; - } else { - FOR_ALL_TABS(tp) { - tabcount++; - } - - if (tabcount > 0) { - tabwidth = (Columns - 1 + tabcount / 2) / tabcount; - } - - if (tabwidth < 6) { - tabwidth = 6; - } - - attr = attr_nosel; - tabcount = 0; - - FOR_ALL_TABS(tp) { - if (col >= Columns - 4) { - break; - } - - scol = col; - - if (tp == curtab) { - cwp = curwin; - wp = firstwin; - } else { - cwp = tp->tp_curwin; - wp = tp->tp_firstwin; - } - - if (tp->tp_topframe == topframe) { - attr = win_hl_attr(cwp, HLF_TPS); - } - if (use_sep_chars && col > 0) { - grid_putchar(&default_grid, '|', 0, col++, attr); - } - - if (tp->tp_topframe != topframe) { - attr = win_hl_attr(cwp, HLF_TP); - } - - grid_putchar(&default_grid, ' ', 0, col++, attr); - - modified = false; - - for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount) { - if (bufIsChanged(wp->w_buffer)) { - modified = true; - } - } - - if (modified || wincount > 1) { - if (wincount > 1) { - vim_snprintf((char *)NameBuff, MAXPATHL, "%d", wincount); - len = (int)STRLEN(NameBuff); - if (col + len >= Columns - 3) { - break; - } - grid_puts_len(&default_grid, NameBuff, len, 0, col, - hl_combine_attr(attr, win_hl_attr(cwp, HLF_T))); - col += len; - } - if (modified) { - grid_puts_len(&default_grid, (char_u *)"+", 1, 0, col++, attr); - } - grid_putchar(&default_grid, ' ', 0, col++, attr); - } - - room = scol - col + tabwidth - 1; - if (room > 0) { - // Get buffer name in NameBuff[] - get_trans_bufname(cwp->w_buffer); - shorten_dir(NameBuff); - len = vim_strsize((char *)NameBuff); - p = NameBuff; - while (len > room) { - len -= ptr2cells((char *)p); - MB_PTR_ADV(p); - } - if (len > Columns - col - 1) { - len = Columns - col - 1; - } - - grid_puts_len(&default_grid, p, (int)STRLEN(p), 0, col, attr); - col += len; - } - grid_putchar(&default_grid, ' ', 0, col++, attr); - - // Store the tab page number in tab_page_click_defs[], so that - // jump_to_mouse() knows where each one is. - tabcount++; - while (scol < col) { - tab_page_click_defs[scol++] = (StlClickDefinition) { - .type = kStlClickTabSwitch, - .tabnr = tabcount, - .func = NULL, - }; - } - } - - if (use_sep_chars) { - c = '_'; - } else { - c = ' '; - } - grid_fill(&default_grid, 0, 1, col, Columns, c, c, attr_fill); - - // Put an "X" for closing the current tab if there are several. - if (first_tabpage->tp_next != NULL) { - grid_putchar(&default_grid, 'X', 0, Columns - 1, attr_nosel); - tab_page_click_defs[Columns - 1] = (StlClickDefinition) { - .type = kStlClickTabClose, - .tabnr = 999, - .func = NULL, - }; - } - } - - // Reset the flag here again, in case evaluating 'tabline' causes it to be - // set. - redraw_tabline = false; -} - -static void ui_ext_tabline_update(void) -{ - Arena arena = ARENA_EMPTY; - arena_start(&arena, &ui_ext_fixblk); - - size_t n_tabs = 0; - FOR_ALL_TABS(tp) { - n_tabs++; - } - - Array tabs = arena_array(&arena, n_tabs); - FOR_ALL_TABS(tp) { - Dictionary tab_info = arena_dict(&arena, 2); - PUT_C(tab_info, "tab", TABPAGE_OBJ(tp->handle)); - - win_T *cwp = (tp == curtab) ? curwin : tp->tp_curwin; - get_trans_bufname(cwp->w_buffer); - PUT_C(tab_info, "name", STRING_OBJ(arena_string(&arena, cstr_as_string((char *)NameBuff)))); - - ADD_C(tabs, DICTIONARY_OBJ(tab_info)); - } - - size_t n_buffers = 0; - FOR_ALL_BUFFERS(buf) { - n_buffers += buf->b_p_bl ? 1 : 0; - } - - Array buffers = arena_array(&arena, n_buffers); - FOR_ALL_BUFFERS(buf) { - // Do not include unlisted buffers - if (!buf->b_p_bl) { - continue; - } - - Dictionary buffer_info = arena_dict(&arena, 2); - PUT_C(buffer_info, "buffer", BUFFER_OBJ(buf->handle)); - - get_trans_bufname(buf); - PUT_C(buffer_info, "name", STRING_OBJ(arena_string(&arena, cstr_as_string((char *)NameBuff)))); - - ADD_C(buffers, DICTIONARY_OBJ(buffer_info)); - } - - ui_call_tabline_update(curtab->handle, tabs, curbuf->handle, buffers); - arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); + char s[4]; + snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording); + msg_puts_attr(s, attr); } void get_trans_bufname(buf_T *buf) { if (buf_spname(buf) != NULL) { - STRLCPY(NameBuff, buf_spname(buf), MAXPATHL); + xstrlcpy(NameBuff, buf_spname(buf), MAXPATHL); } else { - home_replace(buf, buf->b_fname, (char *)NameBuff, MAXPATHL, true); + home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, true); } - trans_characters((char *)NameBuff, MAXPATHL); + trans_characters(NameBuff, MAXPATHL); } /// Get the character to use in a separator between vertically split windows. @@ -1174,7 +718,9 @@ bool redrawing(void) /// Return true if printing messages should currently be done. bool messaging(void) { - return !(p_lz && char_avail() && !KeyTyped) && ui_has_messages(); + // TODO(bfredl): with general support for "async" messages with p_ch, + // this should be re-enabled. + return !(p_lz && char_avail() && !KeyTyped) && (p_ch > 0 || ui_has(kUIMessages)); } #define COL_RULER 17 // columns needed by standard ruler @@ -1238,6 +784,16 @@ int number_width(win_T *wp) } wp->w_nrwidth_line_count = lnum; + // make best estimate for 'statuscolumn' + if (*wp->w_p_stc != NUL) { + char buf[MAXPATHL]; + wp->w_nrwidth_width = 0; + n = build_statuscol_str(wp, lnum, 0, 0, NUL, buf, NULL, NULL); + n = MAX(n, (wp->w_p_nu || wp->w_p_rnu) * (int)wp->w_p_nuw); + wp->w_nrwidth_width = MIN(n, MAX_NUMBERWIDTH); + return wp->w_nrwidth_width; + } + n = 0; do { lnum /= 10; @@ -1263,17 +819,15 @@ int number_width(win_T *wp) /// Calls mb_cptr2char_adv(p) and returns the character. /// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used. /// Returns 0 for invalid hex or invalid UTF-8 byte. -static int get_encoded_char_adv(char_u **p) +static int get_encoded_char_adv(const char **p) { - char_u *s = *p; + const char *s = *p; if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) { int64_t num = 0; - int bytes; - int n; - for (bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) { + for (int bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) { *p += 2; - n = hexhex2nr(*p); + int n = hexhex2nr(*p); if (n < 0) { return 0; } @@ -1284,8 +838,8 @@ static int get_encoded_char_adv(char_u **p) } // TODO(bfredl): use schar_T representation and utfc_ptr2len - int clen = utf_ptr2len((char *)s); - int c = mb_cptr2char_adv((const char_u **)p); + int clen = utf_ptr2len(s); + int c = mb_cptr2char_adv(p); if (clen == 1 && c > 127) { // Invalid UTF-8 byte return 0; } @@ -1295,26 +849,22 @@ static int get_encoded_char_adv(char_u **p) /// Handle setting 'listchars' or 'fillchars'. /// Assume monocell characters /// -/// @param varp either &curwin->w_p_lcs or &curwin->w_p_fcs +/// @param varp either the global or the window-local value. +/// @param apply if false, do not store the flags, only check for errors. /// @return error message, NULL if it's OK. -char *set_chars_option(win_T *wp, char_u **varp, bool set) +char *set_chars_option(win_T *wp, char **varp, bool apply) { - int round, i, len, len2, entries; - char_u *p, *s; - int c1; - int c2 = 0; - int c3 = 0; - char_u *last_multispace = NULL; // Last occurrence of "multispace:" - char_u *last_lmultispace = NULL; // Last occurrence of "leadmultispace:" + const char *last_multispace = NULL; // Last occurrence of "multispace:" + const char *last_lmultispace = NULL; // Last occurrence of "leadmultispace:" int multispace_len = 0; // Length of lcs-multispace string int lead_multispace_len = 0; // Length of lcs-leadmultispace string + const bool is_listchars = (varp == &p_lcs || varp == &wp->w_p_lcs); struct chars_tab { - int *cp; ///< char value + int *cp; ///< char value char *name; ///< char id - int def; ///< default value + int def; ///< default value }; - struct chars_tab *tab; // XXX: Characters taking 2 columns is forbidden (TUI limitation?). Set old defaults in this case. struct chars_tab fcs_tab[] = { @@ -1335,7 +885,9 @@ char *set_chars_option(win_T *wp, char_u **varp, bool set) { &wp->w_p_fcs_chars.diff, "diff", '-' }, { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, { &wp->w_p_fcs_chars.eob, "eob", '~' }, + { &wp->w_p_fcs_chars.lastline, "lastline", '@' }, }; + struct chars_tab lcs_tab[] = { { &wp->w_p_lcs_chars.eol, "eol", NUL }, { &wp->w_p_lcs_chars.ext, "extends", NUL }, @@ -1348,30 +900,33 @@ char *set_chars_option(win_T *wp, char_u **varp, bool set) { &wp->w_p_lcs_chars.conceal, "conceal", NUL }, }; - if (varp == &p_lcs || varp == &wp->w_p_lcs) { + struct chars_tab *tab; + int entries; + const char *value = *varp; + if (is_listchars) { tab = lcs_tab; entries = ARRAY_SIZE(lcs_tab); if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) { - varp = &p_lcs; + value = p_lcs; // local value is empty, use the global value } } else { tab = fcs_tab; entries = ARRAY_SIZE(fcs_tab); if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) { - varp = &p_fcs; + value = p_fcs; // local value is empty, use the global value } } // first round: check for valid value, second round: assign values - for (round = 0; round <= (set ? 1 : 0); round++) { + for (int round = 0; round <= (apply ? 1 : 0); round++) { if (round > 0) { // After checking that the value is valid: set defaults - for (i = 0; i < entries; i++) { + for (int i = 0; i < entries; i++) { if (tab[i].cp != NULL) { *(tab[i].cp) = tab[i].def; } } - if (varp == &p_lcs || varp == &wp->w_p_lcs) { + if (is_listchars) { wp->w_p_lcs_chars.tab1 = NUL; wp->w_p_lcs_chars.tab3 = NUL; @@ -1393,19 +948,20 @@ char *set_chars_option(win_T *wp, char_u **varp, bool set) } } } - p = *varp; + const char *p = value; while (*p) { + int i; for (i = 0; i < entries; i++) { - len = (int)STRLEN(tab[i].name); - if (STRNCMP(p, tab[i].name, len) == 0 + const size_t len = strlen(tab[i].name); + if (strncmp(p, tab[i].name, len) == 0 && p[len] == ':' && p[len + 1] != NUL) { - c2 = c3 = 0; - s = p + len + 1; - c1 = get_encoded_char_adv(&s); + const char *s = p + len + 1; + int c1 = get_encoded_char_adv(&s); if (c1 == 0 || char2cells(c1) > 1) { return e_invarg; } + int c2 = 0, c3 = 0; if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { if (*s == NUL) { return e_invarg; @@ -1438,19 +994,19 @@ char *set_chars_option(win_T *wp, char_u **varp, bool set) } if (i == entries) { - len = (int)STRLEN("multispace"); - len2 = (int)STRLEN("leadmultispace"); - if ((varp == &p_lcs || varp == &wp->w_p_lcs) - && STRNCMP(p, "multispace", len) == 0 + const size_t len = strlen("multispace"); + const size_t len2 = strlen("leadmultispace"); + if (is_listchars + && strncmp(p, "multispace", len) == 0 && p[len] == ':' && p[len + 1] != NUL) { - s = p + len + 1; + const char *s = p + len + 1; if (round == 0) { // Get length of lcs-multispace string in the first round last_multispace = p; multispace_len = 0; while (*s != NUL && *s != ',') { - c1 = get_encoded_char_adv(&s); + int c1 = get_encoded_char_adv(&s); if (c1 == 0 || char2cells(c1) > 1) { return e_invarg; } @@ -1464,24 +1020,24 @@ char *set_chars_option(win_T *wp, char_u **varp, bool set) } else { int multispace_pos = 0; while (*s != NUL && *s != ',') { - c1 = get_encoded_char_adv(&s); + int c1 = get_encoded_char_adv(&s); if (p == last_multispace) { wp->w_p_lcs_chars.multispace[multispace_pos++] = c1; } } p = s; } - } else if ((varp == &p_lcs || varp == &wp->w_p_lcs) - && STRNCMP(p, "leadmultispace", len2) == 0 + } else if (is_listchars + && strncmp(p, "leadmultispace", len2) == 0 && p[len2] == ':' && p[len2 + 1] != NUL) { - s = p + len2 + 1; + const char *s = p + len2 + 1; if (round == 0) { // get length of lcs-leadmultispace string in first round last_lmultispace = p; lead_multispace_len = 0; while (*s != NUL && *s != ',') { - c1 = get_encoded_char_adv(&s); + int c1 = get_encoded_char_adv(&s); if (c1 == 0 || char2cells(c1) > 1) { return e_invarg; } @@ -1495,7 +1051,7 @@ char *set_chars_option(win_T *wp, char_u **varp, bool set) } else { int multispace_pos = 0; while (*s != NUL && *s != ',') { - c1 = get_encoded_char_adv(&s); + int c1 = get_encoded_char_adv(&s); if (p == last_lmultispace) { wp->w_p_lcs_chars.leadmultispace[multispace_pos++] = c1; } @@ -1506,6 +1062,7 @@ char *set_chars_option(win_T *wp, char_u **varp, bool set) return e_invarg; } } + if (*p == ',') { p++; } diff --git a/src/nvim/screen.h b/src/nvim/screen.h index ea1c58cd80..1d8de8ca21 100644 --- a/src/nvim/screen.h +++ b/src/nvim/screen.h @@ -6,15 +6,10 @@ #include "nvim/buffer_defs.h" #include "nvim/fold.h" #include "nvim/grid_defs.h" +#include "nvim/macros.h" EXTERN match_T screen_search_hl; // used for 'hlsearch' highlight matching -/// Array defining what should be done when tabline is clicked -EXTERN StlClickDefinition *tab_page_click_defs INIT(= NULL); - -/// Size of the tab_page_click_defs array -EXTERN long tab_page_click_defs_size INIT(= 0); - #define W_ENDCOL(wp) ((wp)->w_wincol + (wp)->w_width) #define W_ENDROW(wp) ((wp)->w_winrow + (wp)->w_height) diff --git a/src/nvim/search.c b/src/nvim/search.c index c53d955974..b24b6ad27c 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -1,37 +1,39 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * search.c: code for normal mode searching commands - */ +// search.c: code for normal mode searching commands #include <assert.h> #include <inttypes.h> -#include <limits.h> // for INT_MAX on MSVC +#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cmdhist.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval.h" -#include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" -#include "nvim/func_attr.h" #include "nvim/getchar.h" -#include "nvim/indent.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" #include "nvim/indent_c.h" #include "nvim/insexpand.h" -#include "nvim/main.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -41,11 +43,13 @@ #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/os/fs.h" #include "nvim/os/input.h" #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/profile.h" #include "nvim/regexp.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/ui.h" @@ -82,8 +86,7 @@ // one for other searches. last_idx points to the one that was used the last // time. -static struct spat spats[2] = -{ +static struct spat spats[2] = { // Last used search pattern [0] = { NULL, true, false, 0, { '/', false, false, 0L }, NULL }, // Last used substitute pattern @@ -95,24 +98,23 @@ static int last_idx = 0; // index in spats[] for RE_LAST static char_u lastc[2] = { NUL, NUL }; // last character searched for static Direction lastcdir = FORWARD; // last direction of character search static int last_t_cmd = true; // last search t_cmd -static char_u lastc_bytes[MB_MAXBYTES + 1]; +static char lastc_bytes[MB_MAXBYTES + 1]; static int lastc_bytelen = 1; // >1 for multi-byte char // copy of spats[], for keeping the search patterns while executing autocmds static struct spat saved_spats[2]; +static char *saved_mr_pattern = NULL; static int saved_spats_last_idx = 0; static bool saved_spats_no_hlsearch = false; -static char_u *mr_pattern = NULL; // pattern used by search_regcomp() -static bool mr_pattern_alloced = false; // mr_pattern was allocated +// allocated copy of pattern used by search_regcomp() +static char *mr_pattern = NULL; -/* - * Type used by find_pattern_in_path() to remember which included files have - * been searched already. - */ +// Type used by find_pattern_in_path() to remember which included files have +// been searched already. typedef struct SearchedFile { FILE *fp; // File pointer - char_u *name; // Full name of file + char *name; // Full name of file linenr_T lnum; // Line we were up to in file int matched; // Found a match in this file } SearchedFile; @@ -131,17 +133,16 @@ typedef struct SearchedFile { /// @param regmatch return: pattern and ignore-case flag /// /// @return FAIL if failed, OK otherwise. -int search_regcomp(char_u *pat, int pat_save, int pat_use, int options, regmmatch_T *regmatch) +int search_regcomp(char *pat, char **used_pat, int pat_save, int pat_use, int options, + regmmatch_T *regmatch) { int magic; int i; rc_did_emsg = false; - magic = p_magic; + magic = magic_isset(); - /* - * If no pattern given, use a previously defined pattern. - */ + // If no pattern given, use a previously defined pattern. if (pat == NULL || *pat == NUL) { if (pat_use == RE_LAST) { i = last_idx; @@ -164,22 +165,19 @@ int search_regcomp(char_u *pat, int pat_save, int pat_use, int options, regmmatc add_to_history(HIST_SEARCH, pat, true, NUL); } - if (mr_pattern_alloced) { - xfree(mr_pattern); - mr_pattern_alloced = false; + if (used_pat) { + *used_pat = pat; } + xfree(mr_pattern); if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { mr_pattern = reverse_text(pat); - mr_pattern_alloced = true; } else { - mr_pattern = pat; + mr_pattern = xstrdup(pat); } - /* - * Save the currently used pattern in the appropriate place, - * unless the pattern should not be remembered. - */ + // Save the currently used pattern in the appropriate place, + // unless the pattern should not be remembered. if (!(options & SEARCH_KEEP) && (cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) { // search or global command if (pat_save == RE_SEARCH || pat_save == RE_BOTH) { @@ -193,72 +191,81 @@ int search_regcomp(char_u *pat, int pat_save, int pat_use, int options, regmmatc regmatch->rmm_ic = ignorecase(pat); regmatch->rmm_maxcol = 0; - regmatch->regprog = vim_regcomp((char *)pat, magic ? RE_MAGIC : 0); + regmatch->regprog = vim_regcomp(pat, magic ? RE_MAGIC : 0); if (regmatch->regprog == NULL) { return FAIL; } return OK; } -/* - * Get search pattern used by search_regcomp(). - */ -char_u *get_search_pat(void) +/// Get search pattern used by search_regcomp(). +char *get_search_pat(void) { return mr_pattern; } -void save_re_pat(int idx, char_u *pat, int magic) +void save_re_pat(int idx, char *pat, int magic) { - if (spats[idx].pat != pat) { - free_spat(&spats[idx]); - spats[idx].pat = vim_strsave(pat); - spats[idx].magic = magic; - spats[idx].no_scs = no_smartcase; - spats[idx].timestamp = os_time(); - spats[idx].additional_data = NULL; - last_idx = idx; - // If 'hlsearch' set and search pat changed: need redraw. - if (p_hls) { - redraw_all_later(SOME_VALID); - } - set_no_hlsearch(false); + if (spats[idx].pat == pat) { + return; + } + + free_spat(&spats[idx]); + spats[idx].pat = xstrdup(pat); + spats[idx].magic = magic; + spats[idx].no_scs = no_smartcase; + spats[idx].timestamp = os_time(); + spats[idx].additional_data = NULL; + last_idx = idx; + // If 'hlsearch' set and search pat changed: need redraw. + if (p_hls) { + redraw_all_later(UPD_SOME_VALID); } + set_no_hlsearch(false); } -/* - * Save the search patterns, so they can be restored later. - * Used before/after executing autocommands and user functions. - */ +// Save the search patterns, so they can be restored later. +// Used before/after executing autocommands and user functions. static int save_level = 0; void save_search_patterns(void) { - if (save_level++ == 0) { - saved_spats[0] = spats[0]; - if (spats[0].pat != NULL) { - saved_spats[0].pat = vim_strsave(spats[0].pat); - } - saved_spats[1] = spats[1]; - if (spats[1].pat != NULL) { - saved_spats[1].pat = vim_strsave(spats[1].pat); - } - saved_spats_last_idx = last_idx; - saved_spats_no_hlsearch = no_hlsearch; + if (save_level++ != 0) { + return; + } + + saved_spats[0] = spats[0]; + if (spats[0].pat != NULL) { + saved_spats[0].pat = xstrdup(spats[0].pat); } + saved_spats[1] = spats[1]; + if (spats[1].pat != NULL) { + saved_spats[1].pat = xstrdup(spats[1].pat); + } + if (mr_pattern == NULL) { + saved_mr_pattern = NULL; + } else { + saved_mr_pattern = xstrdup(mr_pattern); + } + saved_spats_last_idx = last_idx; + saved_spats_no_hlsearch = no_hlsearch; } void restore_search_patterns(void) { - if (--save_level == 0) { - free_spat(&spats[0]); - spats[0] = saved_spats[0]; - set_vv_searchforward(); - free_spat(&spats[1]); - spats[1] = saved_spats[1]; - last_idx = saved_spats_last_idx; - set_no_hlsearch(saved_spats_no_hlsearch); + if (--save_level != 0) { + return; } + + free_spat(&spats[0]); + spats[0] = saved_spats[0]; + set_vv_searchforward(); + free_spat(&spats[1]); + spats[1] = saved_spats[1]; + xfree(mr_pattern); + mr_pattern = saved_mr_pattern; + last_idx = saved_spats_last_idx; + set_no_hlsearch(saved_spats_no_hlsearch); } static inline void free_spat(struct spat *const spat) @@ -275,11 +282,7 @@ void free_search_patterns(void) CLEAR_FIELD(spats); - if (mr_pattern_alloced) { - xfree(mr_pattern); - mr_pattern_alloced = false; - mr_pattern = NULL; - } + XFREE_CLEAR(mr_pattern); } #endif @@ -308,7 +311,7 @@ void save_last_search_pattern(void) saved_last_search_spat = spats[RE_SEARCH]; if (spats[RE_SEARCH].pat != NULL) { - saved_last_search_spat.pat = vim_strsave(spats[RE_SEARCH].pat); + saved_last_search_spat.pat = xstrdup(spats[RE_SEARCH].pat); } saved_last_idx = last_idx; saved_no_hlsearch = no_hlsearch; @@ -349,22 +352,20 @@ static void restore_incsearch_state(void) search_match_lines = saved_search_match_lines; } -char_u *last_search_pattern(void) +char *last_search_pattern(void) { return spats[RE_SEARCH].pat; } -/* - * Return TRUE when case should be ignored for search pattern "pat". - * Uses the 'ignorecase' and 'smartcase' options. - */ -int ignorecase(char_u *pat) +/// Return true when case should be ignored for search pattern "pat". +/// Uses the 'ignorecase' and 'smartcase' options. +int ignorecase(char *pat) { return ignorecase_opt(pat, p_ic, p_scs); } /// As ignorecase() put pass the "ic" and "scs" flags. -int ignorecase_opt(char_u *pat, int ic_in, int scs) +int ignorecase_opt(char *pat, int ic_in, int scs) { int ic = ic_in; if (ic && !no_smartcase && scs @@ -378,20 +379,24 @@ int ignorecase_opt(char_u *pat, int ic_in, int scs) } /// Returns true if pattern `pat` has an uppercase character. -bool pat_has_uppercase(char_u *pat) +bool pat_has_uppercase(char *pat) FUNC_ATTR_NONNULL_ALL { - char_u *p = pat; + char *p = pat; + magic_T magic_val = MAGIC_ON; + + // get the magicness of the pattern + (void)skip_regexp_ex(pat, NUL, magic_isset(), NULL, NULL, &magic_val); while (*p != NUL) { - const int l = utfc_ptr2len((char *)p); + const int l = utfc_ptr2len(p); if (l > 1) { - if (mb_isupper(utf_ptr2char((char *)p))) { + if (mb_isupper(utf_ptr2char(p))) { return true; } p += l; - } else if (*p == '\\') { + } else if (*p == '\\' && magic_val <= MAGIC_ON) { if (p[1] == '_' && p[2] != NUL) { // skip "\_X" p += 3; } else if (p[1] == '%' && p[2] != NUL) { // skip "\%X" @@ -401,7 +406,13 @@ bool pat_has_uppercase(char_u *pat) } else { p += 1; } - } else if (mb_isupper(*p)) { + } else if ((*p == '%' || *p == '_') && magic_val == MAGIC_ALL) { + if (p[1] != NUL) { // skip "_X" and %X + p += 2; + } else { + p++; + } + } else if (mb_isupper((uint8_t)(*p))) { return true; } else { p++; @@ -413,7 +424,7 @@ bool pat_has_uppercase(char_u *pat) const char *last_csearch(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return (const char *)lastc_bytes; + return lastc_bytes; } int last_csearch_forward(void) @@ -423,10 +434,10 @@ int last_csearch_forward(void) int last_csearch_until(void) { - return last_t_cmd == TRUE; + return last_t_cmd == true; } -void set_last_csearch(int c, char_u *s, int len) +void set_last_csearch(int c, char *s, int len) { *lastc = (char_u)c; lastc_bytelen = len; @@ -447,32 +458,28 @@ void set_csearch_until(int t_cmd) last_t_cmd = t_cmd; } -char_u *last_search_pat(void) +char *last_search_pat(void) { return spats[last_idx].pat; } -/* - * Reset search direction to forward. For "gd" and "gD" commands. - */ +// Reset search direction to forward. For "gd" and "gD" commands. void reset_search_dir(void) { spats[0].off.dir = '/'; set_vv_searchforward(); } -/* - * Set the last search pattern. For ":let @/ =" and ShaDa file. - * Also set the saved search pattern, so that this works in an autocommand. - */ -void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) +// Set the last search pattern. For ":let @/ =" and ShaDa file. +// Also set the saved search pattern, so that this works in an autocommand. +void set_last_search_pat(const char *s, int idx, int magic, int setlast) { free_spat(&spats[idx]); // An empty string means that nothing should be matched. if (*s == NUL) { spats[idx].pat = NULL; } else { - spats[idx].pat = (char_u *)xstrdup((char *)s); + spats[idx].pat = xstrdup(s); } spats[idx].timestamp = os_time(); spats[idx].additional_data = NULL; @@ -480,8 +487,8 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) spats[idx].no_scs = false; spats[idx].off.dir = '/'; set_vv_searchforward(); - spats[idx].off.line = FALSE; - spats[idx].off.end = FALSE; + spats[idx].off.line = false; + spats[idx].off.end = false; spats[idx].off.off = 0; if (setlast) { last_idx = idx; @@ -492,29 +499,27 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast) if (spats[idx].pat == NULL) { saved_spats[idx].pat = NULL; } else { - saved_spats[idx].pat = vim_strsave(spats[idx].pat); + saved_spats[idx].pat = xstrdup(spats[idx].pat); } saved_spats_last_idx = last_idx; } // If 'hlsearch' set and search pat changed: need redraw. if (p_hls && idx == last_idx && !no_hlsearch) { - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } } -/* - * Get a regexp program for the last used search pattern. - * This is used for highlighting all matches in a window. - * Values returned in regmatch->regprog and regmatch->rmm_ic. - */ +// Get a regexp program for the last used search pattern. +// This is used for highlighting all matches in a window. +// Values returned in regmatch->regprog and regmatch->rmm_ic. void last_pat_prog(regmmatch_T *regmatch) { if (spats[last_idx].pat == NULL) { regmatch->regprog = NULL; return; } - ++emsg_off; // So it doesn't beep if bad expr - (void)search_regcomp((char_u *)"", 0, last_idx, SEARCH_KEEP, regmatch); + emsg_off++; // So it doesn't beep if bad expr + (void)search_regcomp("", NULL, 0, last_idx, SEARCH_KEEP, regmatch); emsg_off--; } @@ -541,13 +546,13 @@ void last_pat_prog(regmmatch_T *regmatch) /// @returns FAIL (zero) for failure, non-zero for success. /// the index of the first matching /// subpattern plus one; one if there was none. -int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, char_u *pat, +int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, char *pat, long count, int options, int pat_use, searchit_arg_T *extra_arg) { int found; linenr_T lnum; // no init to shut up Apollo cc regmmatch_T regmatch; - char_u *ptr; + char *ptr; colnr_T matchcol; lpos_T endpos; lpos_T matchpos; @@ -572,7 +577,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, timed_out = &extra_arg->sa_timed_out; } - if (search_regcomp(pat, RE_SEARCH, pat_use, + if (search_regcomp(pat, NULL, RE_SEARCH, pat_use, (options & (SEARCH_HIS + SEARCH_KEEP)), ®match) == FAIL) { if ((options & SEARCH_MSG) && !rc_did_emsg) { semsg(_("E383: Invalid search string: %s"), mr_pattern); @@ -580,9 +585,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, return FAIL; } - /* - * find the string - */ + // find the string do { // loop for count // When not accepting a match at the start position set "extra_col" to a // non-zero value. Don't do that when starting at MAXCOL, since MAXCOL + 1 @@ -594,10 +597,10 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, && pos->col < MAXCOL - 2) { // Watch out for the "col" being MAXCOL - 2, used in a closed fold. ptr = ml_get_buf(buf, pos->lnum, false); - if ((int)STRLEN(ptr) <= pos->col) { + if ((int)strlen(ptr) <= pos->col) { start_char_len = 1; } else { - start_char_len = utfc_ptr2len((char *)ptr + pos->col); + start_char_len = utfc_ptr2len(ptr + pos->col); } } else { start_char_len = 1; @@ -610,31 +613,29 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, start_pos = *pos; // remember start pos for detecting no match found = 0; // default: not found - at_first_line = TRUE; // default: start in first line + at_first_line = true; // default: start in first line if (pos->lnum == 0) { // correct lnum for when starting in line 0 pos->lnum = 1; pos->col = 0; - at_first_line = FALSE; // not in first line now + at_first_line = false; // not in first line now } - /* - * Start searching in current line, unless searching backwards and - * we're in column 0. - * If we are searching backwards, in column 0, and not including the - * current position, gain some efficiency by skipping back a line. - * Otherwise begin the search in the current line. - */ + // Start searching in current line, unless searching backwards and + // we're in column 0. + // If we are searching backwards, in column 0, and not including the + // current position, gain some efficiency by skipping back a line. + // Otherwise begin the search in the current line. if (dir == BACKWARD && start_pos.col == 0 && (options & SEARCH_START) == 0) { lnum = pos->lnum - 1; - at_first_line = FALSE; + at_first_line = false; } else { lnum = pos->lnum; } - for (loop = 0; loop <= 1; ++loop) { // loop twice if 'wrapscan' set + for (loop = 0; loop <= 1; loop++) { // loop twice if 'wrapscan' set for (; lnum > 0 && lnum <= buf->b_ml.ml_line_count; - lnum += dir, at_first_line = FALSE) { + lnum += dir, at_first_line = false) { // Stop after checking "stop_lnum", if it's set. if (stop_lnum != 0 && (dir == FORWARD ? lnum > stop_lnum : lnum < stop_lnum)) { @@ -664,18 +665,17 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, submatch = first_submatch(®match); // "lnum" may be past end of buffer for "\n\zs". if (lnum + matchpos.lnum > buf->b_ml.ml_line_count) { - ptr = (char_u *)""; + ptr = ""; } else { ptr = ml_get_buf(buf, lnum + matchpos.lnum, false); } - /* - * Forward search in the first line: match should be after - * the start position. If not, continue at the end of the - * match (this is vi compatible) or on the next char. - */ + // Forward search in the first line: match should be after + // the start position. If not, continue at the end of the + // match (this is vi compatible) or on the next char. if (dir == FORWARD && at_first_line) { match_ok = true; + // When the match starts in a next line it's certainly // past the start position. // When match lands on a NUL the cursor will be put @@ -689,11 +689,9 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, : ((int)matchpos.col - (ptr[matchpos.col] == NUL) < (int)start_pos.col + extra_col))) { - /* - * If vi-compatible searching, continue at the end - * of the match, otherwise continue one position - * forward. - */ + // If vi-compatible searching, continue at the end + // of the match, otherwise continue one position + // forward. if (vim_strchr(p_cpo, CPO_SEARCH) != NULL) { if (nmatched > 1) { // end is in next line, thus no match in @@ -702,20 +700,22 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, break; } matchcol = endpos.col; - // for empty match (matchcol == matchpos.col): advance one char + // for empty match: advance one char + if (matchcol == matchpos.col && ptr[matchcol] != NUL) { + matchcol += utfc_ptr2len(ptr + matchcol); + } } else { - // Prepare to start after first matched character. - matchcol = matchpos.col; - } - - if (matchcol == matchpos.col && ptr[matchcol] != NUL) { - matchcol += utfc_ptr2len((char *)ptr + matchcol); + // Advance "matchcol" to the next character. + // This uses rmm_matchcol, the actual start of + // the match, ignoring "\zs". + matchcol = regmatch.rmm_matchcol; + if (ptr[matchcol] != NUL) { + matchcol += utfc_ptr2len(ptr + matchcol); + } } - if (matchcol == 0 && (options & SEARCH_START)) { break; } - if (ptr[matchcol] == NUL || (nmatched = vim_regexec_multi(®match, win, buf, lnum, matchcol, tm, @@ -746,13 +746,11 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, } } if (dir == BACKWARD) { - /* - * Now, if there are multiple matches on this line, - * we have to get the last one. Or the last one before - * the cursor, if we're on that line. - * When putting the new cursor at the end, compare - * relative to the end of the match. - */ + // Now, if there are multiple matches on this line, + // we have to get the last one. Or the last one before + // the cursor, if we're on that line. + // When putting the new cursor at the end, compare + // relative to the end of the match. match_ok = false; for (;;) { // Remember a position that is before the start @@ -794,7 +792,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, // for empty match: advance one char if (matchcol == matchpos.col && ptr[matchcol] != NUL) { - matchcol += utfc_ptr2len((char *)ptr + matchcol); + matchcol += utfc_ptr2len(ptr + matchcol); } } else { // Stop when the match is in a next line. @@ -803,13 +801,12 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, } matchcol = matchpos.col; if (ptr[matchcol] != NUL) { - matchcol += utfc_ptr2len((char *)ptr + matchcol); + matchcol += utfc_ptr2len(ptr + matchcol); } } if (ptr[matchcol] == NUL - || (nmatched = - vim_regexec_multi(®match, win, buf, lnum + matchpos.lnum, matchcol, - tm, timed_out)) == 0) { + || (nmatched = vim_regexec_multi(®match, win, buf, lnum + matchpos.lnum, + matchcol, tm, timed_out)) == 0) { // If the search timed out, we did find a match // but it might be the wrong one, so that's not // OK. @@ -827,10 +824,8 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, ptr = ml_get_buf(buf, lnum + matchpos.lnum, false); } - /* - * If there is only a match after the cursor, skip - * this match. - */ + // If there is only a match after the cursor, skip + // this match. if (!match_ok) { continue; } @@ -849,7 +844,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, if (endpos.col == 0) { if (pos->lnum > 1) { // just in case pos->lnum--; - pos->col = (colnr_T)STRLEN(ml_get_buf(buf, pos->lnum, false)); + pos->col = (colnr_T)strlen(ml_get_buf(buf, pos->lnum, false)); } } else { pos->col--; @@ -901,7 +896,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, break; // if second loop, stop where started } } - at_first_line = FALSE; + at_first_line = false; // vim_regexec_multi() may clear "regprog" if (regmatch.regprog == NULL) { @@ -966,7 +961,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, // A pattern like "\n\zs" may go past the last line. if (pos->lnum > buf->b_ml.ml_line_count) { pos->lnum = buf->b_ml.ml_line_count; - pos->col = (int)STRLEN(ml_get_buf(buf, pos->lnum, false)); + pos->col = (int)strlen(ml_get_buf(buf, pos->lnum, false)); if (pos->col > 0) { pos->col--; } @@ -991,7 +986,7 @@ static int first_submatch(regmmatch_T *rp) { int submatch; - for (submatch = 1;; ++submatch) { + for (submatch = 1;; submatch++) { if (rp->startpos[submatch].lnum >= 0) { break; } @@ -1006,63 +1001,57 @@ static int first_submatch(regmmatch_T *rp) /// Highest level string search function. /// Search for the 'count'th occurrence of pattern 'pat' in direction 'dirc' /// -/// Careful: If spats[0].off.line == TRUE and spats[0].off.off == 0 this +/// Careful: If spats[0].off.line == true and spats[0].off.off == 0 this /// makes the movement linewise without moving the match position. /// /// @param dirc if 0: use previous dir. /// @param pat NULL or empty : use previous string. -/// @param options if TRUE and -/// SEARCH_REV == TRUE : go in reverse of previous dir. -/// SEARCH_ECHO == TRUE : echo the search command and handle options -/// SEARCH_MSG == TRUE : may give error message -/// SEARCH_OPT == TRUE : interpret optional flags -/// SEARCH_HIS == TRUE : put search pattern in history -/// SEARCH_NOOF == TRUE : don't add offset to position -/// SEARCH_MARK == TRUE : set previous context mark -/// SEARCH_KEEP == TRUE : keep previous search pattern -/// SEARCH_START == TRUE : accept match at curpos itself -/// SEARCH_PEEK == TRUE : check for typed char, cancel search +/// @param options if true and +/// SEARCH_REV == true : go in reverse of previous dir. +/// SEARCH_ECHO == true : echo the search command and handle options +/// SEARCH_MSG == true : may give error message +/// SEARCH_OPT == true : interpret optional flags +/// SEARCH_HIS == true : put search pattern in history +/// SEARCH_NOOF == true : don't add offset to position +/// SEARCH_MARK == true : set previous context mark +/// SEARCH_KEEP == true : keep previous search pattern +/// SEARCH_START == true : accept match at curpos itself +/// SEARCH_PEEK == true : check for typed char, cancel search /// @param oap can be NULL /// @param dirc '/' or '?' /// @param search_delim delimiter for search, e.g. '%' in s%regex%replacement /// @param sia optional arguments or NULL /// /// @return 0 for failure, 1 for found, 2 for found and line offset added. -int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, int options, +int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, long count, int options, searchit_arg_T *sia) { pos_T pos; // position of the last match - char_u *searchstr; + char *searchstr; struct soffset old_off; int retval; // Return value - char_u *p; + char *p; long c; - char_u *dircp; + char *dircp; char *strcopy = NULL; - char_u *ps; - char_u *msgbuf = NULL; + char *ps; + char *msgbuf = NULL; size_t len; bool has_offset = false; - /* - * A line offset is not remembered, this is vi compatible. - */ + // A line offset is not remembered, this is vi compatible. if (spats[0].off.line && vim_strchr(p_cpo, CPO_LINEOFF) != NULL) { spats[0].off.line = false; spats[0].off.off = 0; } - /* - * Save the values for when (options & SEARCH_KEEP) is used. - * (there is no "if ()" around this because gcc wants them initialized) - */ + // Save the values for when (options & SEARCH_KEEP) is used. + // (there is no "if ()" around this because gcc wants them initialized) old_off = spats[0].off; pos = curwin->w_cursor; // start searching at the cursor position - /* - * Find out the direction of the search. - */ + // Find out the direction of the search. if (dirc == 0) { dirc = (char_u)spats[0].off.dir; } else { @@ -1089,17 +1078,13 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, } } - /* - * Turn 'hlsearch' highlighting back on. - */ + // Turn 'hlsearch' highlighting back on. if (no_hlsearch && !(options & SEARCH_KEEP)) { - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); set_no_hlsearch(false); } - /* - * Repeat the search when pattern followed by ';', e.g. "/foo/;?bar". - */ + // Repeat the search when pattern followed by ';', e.g. "/foo/;?bar". for (;;) { bool show_top_bot_msg = false; @@ -1116,29 +1101,27 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, } } else { // make search_regcomp() use spats[RE_SEARCH].pat - searchstr = (char_u *)""; + searchstr = ""; } } if (pat != NULL && *pat != NUL) { // look for (new) offset - /* - * Find end of regular expression. - * If there is a matching '/' or '?', toss it. - */ - ps = (char_u *)strcopy; - p = skip_regexp(pat, search_delim, p_magic, &strcopy); - if (strcopy != (char *)ps) { + // Find end of regular expression. + // If there is a matching '/' or '?', toss it. + ps = strcopy; + p = skip_regexp_ex(pat, search_delim, magic_isset(), &strcopy, NULL, NULL); + if (strcopy != ps) { // made a copy of "pat" to change "\?" to "?" - searchcmdlen += (int)(STRLEN(pat) - STRLEN(strcopy)); - pat = (char_u *)strcopy; - searchstr = (char_u *)strcopy; + searchcmdlen += (int)(strlen(pat) - strlen(strcopy)); + pat = strcopy; + searchstr = strcopy; } if (*p == search_delim) { dircp = p; // remember where we put the NUL *p++ = NUL; } - spats[0].off.line = FALSE; - spats[0].off.end = FALSE; + spats[0].off.line = false; + spats[0].off.end = false; spats[0].off.off = 0; // Check for a line offset or a character offset. // For get_address (echo off) we don't check for a character @@ -1156,7 +1139,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, if (ascii_isdigit(*p) || *p == '+' || *p == '-') { // got an offset // 'nr' or '+nr' or '-nr' if (ascii_isdigit(*p) || ascii_isdigit(*(p + 1))) { - spats[0].off.off = atol((char *)p); + spats[0].off.off = atol(p); } else if (*p == '-') { // single '-' spats[0].off.off = -1; } else { // single '+' @@ -1176,8 +1159,8 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, if ((options & SEARCH_ECHO) && messaging() && !msg_silent && (!cmd_silent || !shortmess(SHM_SEARCHCOUNT))) { - char_u *trunc; - char_u off_buf[40]; + char *trunc; + char off_buf[40]; size_t off_len = 0; // Compute msg_row early. @@ -1187,7 +1170,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, if (!cmd_silent && (spats[0].off.line || spats[0].off.end || spats[0].off.off)) { p = off_buf; // -V507 - *p++ = (char_u)dirc; + *p++ = (char)dirc; if (spats[0].off.end) { *p++ = 'e'; } else if (!spats[0].off.line) { @@ -1198,10 +1181,10 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, } *p = NUL; if (spats[0].off.off != 0 || spats[0].off.line) { - snprintf((char *)p, sizeof(off_buf) - 1 - (size_t)(p - off_buf), + snprintf(p, sizeof(off_buf) - 1 - (size_t)(p - off_buf), "%" PRId64, spats[0].off.off); } - off_len = STRLEN(off_buf); + off_len = strlen(off_buf); } if (*searchstr == NUL) { @@ -1224,12 +1207,12 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, // Use up to 'showcmd' column. len = (size_t)((Rows - msg_row - 1) * Columns + sc_col - 1); } - if (len < STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3) { - len = STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3; + if (len < strlen(p) + off_len + SEARCH_STAT_BUF_LEN + 3) { + len = strlen(p) + off_len + SEARCH_STAT_BUF_LEN + 3; } } else { // Reserve enough space for the search pattern + offset. - len = STRLEN(p) + off_len + 3; + len = strlen(p) + off_len + 3; } xfree(msgbuf); @@ -1240,16 +1223,16 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, // do not fill the msgbuf buffer, if cmd_silent is set, leave it // empty for the search_stat feature. if (!cmd_silent) { - msgbuf[0] = (char_u)dirc; - if (utf_iscomposing(utf_ptr2char((char *)p))) { + msgbuf[0] = (char)dirc; + if (utf_iscomposing(utf_ptr2char(p))) { // Use a space to draw the composing char on. msgbuf[1] = ' '; - memmove(msgbuf + 2, p, STRLEN(p)); + memmove(msgbuf + 2, p, strlen(p)); } else { - memmove(msgbuf + 1, p, STRLEN(p)); + memmove(msgbuf + 1, p, strlen(p)); } if (off_len > 0) { - memmove(msgbuf + STRLEN(p) + 1, off_buf, off_len); + memmove(msgbuf + strlen(p) + 1, off_buf, off_len); } trunc = msg_strtrunc(msgbuf, true); @@ -1263,14 +1246,14 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, // it would be blanked out again very soon. Show it on the // left, but do reverse the text. if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { - char_u *r = reverse_text(trunc != NULL ? trunc : msgbuf); + char *r = reverse_text(trunc != NULL ? trunc : msgbuf); xfree(msgbuf); msgbuf = r; // move reversed text to beginning of buffer while (*r == ' ') { r++; } - size_t pat_len = (size_t)(msgbuf + STRLEN(msgbuf) - r); + size_t pat_len = (size_t)(msgbuf + strlen(msgbuf) - r); memmove(msgbuf, r, pat_len); // overwrite old text if ((size_t)(r - msgbuf) >= pat_len) { @@ -1279,7 +1262,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, memset(msgbuf + pat_len, ' ', (size_t)(r - msgbuf)); } } - msg_outtrans((char *)msgbuf); + msg_outtrans(msgbuf); msg_clr_eos(); msg_check(); @@ -1289,16 +1272,14 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, } } - /* - * If there is a character offset, subtract it from the current - * position, so we don't get stuck at "?pat?e+2" or "/pat/s-2". - * Skip this if pos.col is near MAXCOL (closed fold). - * This is not done for a line offset, because then we would not be vi - * compatible. - */ + // If there is a character offset, subtract it from the current + // position, so we don't get stuck at "?pat?e+2" or "/pat/s-2". + // Skip this if pos.col is near MAXCOL (closed fold). + // This is not done for a line offset, because then we would not be vi + // compatible. if (!spats[0].off.line && spats[0].off.off && pos.col < MAXCOL - 2) { if (spats[0].off.off > 0) { - for (c = spats[0].off.off; c; --c) { + for (c = spats[0].off.off; c; c--) { if (decl(&pos) == -1) { break; } @@ -1308,7 +1289,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, pos.col = MAXCOL; } } else { - for (c = spats[0].off.off; c; ++c) { + for (c = spats[0].off.off; c; c++) { if (incl(&pos) == -1) { break; } @@ -1330,7 +1311,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, RE_LAST, sia); if (dircp != NULL) { - *dircp = (char_u)search_delim; // restore second '/' or '?' for normal_cmd() + *dircp = (char)search_delim; // restore second '/' or '?' for normal_cmd() } if (!shortmess(SHM_SEARCH) @@ -1352,9 +1333,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, apply_autocmds(EVENT_SEARCHWRAPPED, NULL, NULL, false, NULL); } - /* - * Add character and/or line offset - */ + // Add character and/or line offset if (!(options & SEARCH_NOOF) || (pat != NULL && *pat == ';')) { pos_T org_pos = pos; @@ -1379,9 +1358,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, break; } } - } - // to the left, check for start of file - else { + } else { // to the left, check for start of file while (c++ < 0) { if (decl(&pos) == -1) { break; @@ -1421,7 +1398,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, break; } - dirc = *++pat; + dirc = (uint8_t)(*++pat); search_delim = dirc; if (dirc != '?' && dirc != '/') { retval = 0; @@ -1435,31 +1412,30 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, setpcmark(); } curwin->w_cursor = pos; - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; end_do_search: if ((options & SEARCH_KEEP) || (cmdmod.cmod_flags & CMOD_KEEPPATTERNS)) { spats[0].off = old_off; } + xfree(strcopy); xfree(msgbuf); return retval; } -/* - * search_for_exact_line(buf, pos, dir, pat) - * - * Search for a line starting with the given pattern (ignoring leading - * white-space), starting from pos and going in direction "dir". "pos" will - * contain the position of the match found. Blank lines match only if - * ADDING is set. If p_ic is set then the pattern must be in lowercase. - * Return OK for success, or FAIL if no line found. - */ -int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char_u *pat) +// search_for_exact_line(buf, pos, dir, pat) +// +// Search for a line starting with the given pattern (ignoring leading +// white-space), starting from pos and going in direction "dir". "pos" will +// contain the position of the match found. Blank lines match only if +// ADDING is set. If p_ic is set then the pattern must be in lowercase. +// Return OK for success, or FAIL if no line found. +int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char *pat) { linenr_T start = 0; - char_u *ptr; - char_u *p; + char *ptr; + char *p; if (buf->b_ml.ml_line_count == 0) { return FAIL; @@ -1494,21 +1470,20 @@ int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char_u *pat) start = pos->lnum; } ptr = ml_get_buf(buf, pos->lnum, false); - p = (char_u *)skipwhite((char *)ptr); + p = skipwhite(ptr); pos->col = (colnr_T)(p - ptr); // when adding lines the matching line may be empty but it is not // ignored because we are interested in the next line -- Acevedo - if ((compl_cont_status & CONT_ADDING) - && !(compl_cont_status & CONT_SOL)) { + if (compl_status_adding() && !compl_status_sol()) { if (mb_strcmp_ic((bool)p_ic, (const char *)p, (const char *)pat) == 0) { return OK; } } else if (*p != NUL) { // Ignore empty lines. // Expanding lines or words. - assert(compl_length >= 0); - if ((p_ic ? mb_strnicmp(p, pat, (size_t)compl_length) - : STRNCMP(p, pat, compl_length)) == 0) { + assert(ins_compl_len() >= 0); + if ((p_ic ? mb_strnicmp(p, pat, (size_t)ins_compl_len()) + : strncmp(p, pat, (size_t)ins_compl_len())) == 0) { return OK; } } @@ -1516,16 +1491,12 @@ int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char_u *pat) return FAIL; } -/* - * Character Searches - */ +// Character Searches -/* - * Search for a character in a line. If "t_cmd" is FALSE, move to the - * position of the character, otherwise move to just before the char. - * Do this "cap->count1" times. - * Return FAIL or OK. - */ +/// Search for a character in a line. If "t_cmd" is false, move to the +/// position of the character, otherwise move to just before the char. +/// Do this "cap->count1" times. +/// Return FAIL or OK. int searchc(cmdarg_T *cap, int t_cmd) FUNC_ATTR_NONNULL_ALL { @@ -1533,7 +1504,7 @@ int searchc(cmdarg_T *cap, int t_cmd) int dir = cap->arg; // true for searching forward long count = cap->count1; // repeat count int col; - char_u *p; + char *p; int len; bool stop = true; @@ -1542,13 +1513,13 @@ int searchc(cmdarg_T *cap, int t_cmd) *lastc = (char_u)c; set_csearch_direction(dir); set_csearch_until(t_cmd); - lastc_bytelen = utf_char2bytes(c, (char *)lastc_bytes); + lastc_bytelen = utf_char2bytes(c, lastc_bytes); if (cap->ncharC1 != 0) { lastc_bytelen += utf_char2bytes(cap->ncharC1, - (char *)lastc_bytes + lastc_bytelen); + lastc_bytes + lastc_bytelen); if (cap->ncharC2 != 0) { lastc_bytelen += utf_char2bytes(cap->ncharC2, - (char *)lastc_bytes + lastc_bytelen); + lastc_bytes + lastc_bytelen); } } } @@ -1581,12 +1552,12 @@ int searchc(cmdarg_T *cap, int t_cmd) p = get_cursor_line_ptr(); col = curwin->w_cursor.col; - len = (int)STRLEN(p); + len = (int)strlen(p); while (count--) { for (;;) { if (dir > 0) { - col += utfc_ptr2len((char *)p + col); + col += utfc_ptr2len(p + col); if (col >= len) { return FAIL; } @@ -1600,7 +1571,7 @@ int searchc(cmdarg_T *cap, int t_cmd) if (p[col] == c && stop) { break; } - } else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) { + } else if (strncmp(p + col, lastc_bytes, (size_t)lastc_bytelen) == 0 && stop) { break; } stop = true; @@ -1623,15 +1594,11 @@ int searchc(cmdarg_T *cap, int t_cmd) return OK; } -/* - * "Other" Searches - */ +// "Other" Searches -/* - * findmatch - find the matching paren or brace - * - * Improvement over vi: Braces inside quotes are ignored. - */ +// findmatch - find the matching paren or brace +// +// Improvement over vi: Braces inside quotes are ignored. pos_T *findmatch(oparg_T *oap, int initc) { return findmatchlimit(oap, initc, 0, 0); @@ -1642,7 +1609,7 @@ pos_T *findmatch(oparg_T *oap, int initc) // Update "*prevcol" to the column of the previous character, unless "prevcol" // is NULL. // Handles multibyte string correctly. -static bool check_prevcol(char_u *linep, int col, int ch, int *prevcol) +static bool check_prevcol(char *linep, int col, int ch, int *prevcol) { col--; if (col > 0) { @@ -1651,32 +1618,31 @@ static bool check_prevcol(char_u *linep, int col, int ch, int *prevcol) if (prevcol) { *prevcol = col; } - return col >= 0 && linep[col] == ch; + return col >= 0 && (uint8_t)linep[col] == ch; } -/* - * Raw string start is found at linep[startpos.col - 1]. - * Return true if the matching end can be found between startpos and endpos. - */ -static bool find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos) +/// Raw string start is found at linep[startpos.col - 1]. +/// +/// @return true if the matching end can be found between startpos and endpos. +static bool find_rawstring_end(char *linep, pos_T *startpos, pos_T *endpos) { - char_u *p; + char *p; linenr_T lnum; for (p = linep + startpos->col + 1; *p && *p != '('; p++) {} size_t delim_len = (size_t)((p - linep) - startpos->col - 1); - char_u *delim_copy = vim_strnsave(linep + startpos->col + 1, delim_len); + char *delim_copy = xstrnsave(linep + startpos->col + 1, delim_len); bool found = false; for (lnum = startpos->lnum; lnum <= endpos->lnum; lnum++) { - char_u *line = ml_get(lnum); + char *line = ml_get(lnum); for (p = line + (lnum == startpos->lnum ? startpos->col + 1 : 0); *p; p++) { if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col) { break; } if (*p == ')' - && STRNCMP(delim_copy, p + 1, delim_len) == 0 + && strncmp(delim_copy, p + 1, delim_len) == 0 && p[delim_len + 1] == '"') { found = true; break; @@ -1697,61 +1663,58 @@ static bool find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos) static void find_mps_values(int *initc, int *findc, bool *backwards, bool switchit) FUNC_ATTR_NONNULL_ALL { - char_u *ptr = curbuf->b_p_mps; + char *ptr = curbuf->b_p_mps; while (*ptr != NUL) { - if (utf_ptr2char((char *)ptr) == *initc) { + if (utf_ptr2char(ptr) == *initc) { if (switchit) { *findc = *initc; - *initc = utf_ptr2char((char *)ptr + utfc_ptr2len((char *)ptr) + 1); + *initc = utf_ptr2char(ptr + utfc_ptr2len(ptr) + 1); *backwards = true; } else { - *findc = utf_ptr2char((char *)ptr + utfc_ptr2len((char *)ptr) + 1); + *findc = utf_ptr2char(ptr + utfc_ptr2len(ptr) + 1); *backwards = false; } return; } - char_u *prev = ptr; - ptr += utfc_ptr2len((char *)ptr) + 1; - if (utf_ptr2char((char *)ptr) == *initc) { + char *prev = ptr; + ptr += utfc_ptr2len(ptr) + 1; + if (utf_ptr2char(ptr) == *initc) { if (switchit) { *findc = *initc; - *initc = utf_ptr2char((char *)prev); + *initc = utf_ptr2char(prev); *backwards = false; } else { - *findc = utf_ptr2char((char *)prev); + *findc = utf_ptr2char(prev); *backwards = true; } return; } - ptr += utfc_ptr2len((char *)ptr); + ptr += utfc_ptr2len(ptr); if (*ptr == ',') { ptr++; } } } -/* - * findmatchlimit -- find the matching paren or brace, if it exists within - * maxtravel lines of the cursor. A maxtravel of 0 means search until falling - * off the edge of the file. - * - * "initc" is the character to find a match for. NUL means to find the - * character at or after the cursor. Special values: - * '*' look for C-style comment / * - * '/' look for C-style comment / *, ignoring comment-end - * '#' look for preprocessor directives - * 'R' look for raw string start: R"delim(text)delim" (only backwards) - * - * flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#') - * FM_FORWARD search forwards (when initc is '/', '*' or '#') - * FM_BLOCKSTOP stop at start/end of block ({ or } in column 0) - * FM_SKIPCOMM skip comments (not implemented yet!) - * - * "oap" is only used to set oap->motion_type for a linewise motion, it can be - * NULL - */ - +// findmatchlimit -- find the matching paren or brace, if it exists within +// maxtravel lines of the cursor. A maxtravel of 0 means search until falling +// off the edge of the file. +// +// "initc" is the character to find a match for. NUL means to find the +// character at or after the cursor. Special values: +// '*' look for C-style comment / * +// '/' look for C-style comment / *, ignoring comment-end +// '#' look for preprocessor directives +// 'R' look for raw string start: R"delim(text)delim" (only backwards) +// +// flags: FM_BACKWARD search backwards (when initc is '/', '*' or '#') +// FM_FORWARD search forwards (when initc is '/', '*' or '#') +// FM_BLOCKSTOP stop at start/end of block ({ or } in column 0) +// FM_SKIPCOMM skip comments (not implemented yet!) +// +// "oap" is only used to set oap->motion_type for a linewise motion, it can be +// NULL pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) { static pos_T pos; // current search position @@ -1760,7 +1723,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) bool backwards = false; // init for gcc bool raw_string = false; // search for raw string bool inquote = false; // true when inside quotes - char_u *ptr; + char *ptr; int hash_dir = 0; // Direction searched for # things int comment_dir = 0; // Direction searched for comments int traveled = 0; // how far we've searched so far @@ -1773,7 +1736,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) pos = curwin->w_cursor; pos.coladd = 0; - char_u *linep = ml_get(pos.lnum); // pointer to current line + char *linep = ml_get(pos.lnum); // pointer to current line // vi compatible matching bool cpo_match = (vim_strchr(p_cpo, CPO_MATCH) != NULL); @@ -1789,12 +1752,10 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) dir = 0; } - /* - * if initc given, look in the table for the matching character - * '/' and '*' are special cases: look for start or end of comment. - * When '/' is used, we ignore running backwards into a star-slash, for - * "[*" command, we just want to find any comment. - */ + // if initc given, look in the table for the matching character + // '/' and '*' are special cases: look for start or end of comment. + // When '/' is used, we ignore running backwards into a star-slash, for + // "[*" command, we just want to find any comment. if (initc == '/' || initc == '*' || initc == 'R') { comment_dir = dir; if (initc == '/') { @@ -1812,31 +1773,25 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) return NULL; } } else { - /* - * Either initc is '#', or no initc was given and we need to look - * under the cursor. - */ + // Either initc is '#', or no initc was given and we need to look + // under the cursor. if (initc == '#') { hash_dir = dir; } else { - /* - * initc was not given, must look for something to match under - * or near the cursor. - * Only check for special things when 'cpo' doesn't have '%'. - */ + // initc was not given, must look for something to match under + // or near the cursor. + // Only check for special things when 'cpo' doesn't have '%'. if (!cpo_match) { // Are we before or at #if, #else etc.? - ptr = (char_u *)skipwhite((char *)linep); + ptr = skipwhite(linep); if (*ptr == '#' && pos.col <= (colnr_T)(ptr - linep)) { - ptr = (char_u *)skipwhite((char *)ptr + 1); - if (STRNCMP(ptr, "if", 2) == 0 - || STRNCMP(ptr, "endif", 5) == 0 - || STRNCMP(ptr, "el", 2) == 0) { + ptr = skipwhite(ptr + 1); + if (strncmp(ptr, "if", 2) == 0 + || strncmp(ptr, "endif", 5) == 0 + || strncmp(ptr, "el", 2) == 0) { hash_dir = 1; } - } - // Are we on a comment? - else if (linep[pos.col] == '/') { + } else if (linep[pos.col] == '/') { // Are we on a comment? if (linep[pos.col + 1] == '*') { comment_dir = FORWARD; backwards = false; @@ -1857,21 +1812,17 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } } - /* - * If we are not on a comment or the # at the start of a line, then - * look for brace anywhere on this line after the cursor. - */ + // If we are not on a comment or the # at the start of a line, then + // look for brace anywhere on this line after the cursor. if (!hash_dir && !comment_dir) { - /* - * Find the brace under or after the cursor. - * If beyond the end of the line, use the last character in - * the line. - */ + // Find the brace under or after the cursor. + // If beyond the end of the line, use the last character in + // the line. if (linep[pos.col] == NUL && pos.col) { - --pos.col; + pos.col--; } for (;;) { - initc = utf_ptr2char((char *)linep + pos.col); + initc = utf_ptr2char(linep + pos.col); if (initc == NUL) { break; } @@ -1880,11 +1831,11 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) if (findc) { break; } - pos.col += utfc_ptr2len((char *)linep + pos.col); + pos.col += utfc_ptr2len(linep + pos.col); } if (!findc) { // no brace in the line, maybe use " #if" then - if (!cpo_match && *skipwhite((char *)linep) == '#') { + if (!cpo_match && *skipwhite(linep) == '#') { hash_dir = 1; } else { return NULL; @@ -1902,17 +1853,15 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } } if (hash_dir) { - /* - * Look for matching #if, #else, #elif, or #endif - */ + // Look for matching #if, #else, #elif, or #endif if (oap != NULL) { oap->motion_type = kMTLineWise; // Linewise for this case only } if (initc != '#') { - ptr = (char_u *)skipwhite(skipwhite((char *)linep) + 1); - if (STRNCMP(ptr, "if", 2) == 0 || STRNCMP(ptr, "el", 2) == 0) { + ptr = skipwhite(skipwhite(linep) + 1); + if (strncmp(ptr, "if", 2) == 0 || strncmp(ptr, "el", 2) == 0) { hash_dir = 1; - } else if (STRNCMP(ptr, "endif", 5) == 0) { + } else if (strncmp(ptr, "endif", 5) == 0) { hash_dir = -1; } else { return NULL; @@ -1930,36 +1879,36 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) pos.lnum += hash_dir; linep = ml_get(pos.lnum); line_breakcheck(); // check for CTRL-C typed - ptr = (char_u *)skipwhite((char *)linep); + ptr = skipwhite(linep); if (*ptr != '#') { continue; } pos.col = (colnr_T)(ptr - linep); - ptr = (char_u *)skipwhite((char *)ptr + 1); + ptr = skipwhite(ptr + 1); if (hash_dir > 0) { - if (STRNCMP(ptr, "if", 2) == 0) { + if (strncmp(ptr, "if", 2) == 0) { count++; - } else if (STRNCMP(ptr, "el", 2) == 0) { + } else if (strncmp(ptr, "el", 2) == 0) { if (count == 0) { return &pos; } - } else if (STRNCMP(ptr, "endif", 5) == 0) { + } else if (strncmp(ptr, "endif", 5) == 0) { if (count == 0) { return &pos; } count--; } } else { - if (STRNCMP(ptr, "if", 2) == 0) { + if (strncmp(ptr, "if", 2) == 0) { if (count == 0) { return &pos; } count--; - } else if (initc == '#' && STRNCMP(ptr, "el", 2) == 0) { + } else if (initc == '#' && strncmp(ptr, "el", 2) == 0) { if (count == 0) { return &pos; } - } else if (STRNCMP(ptr, "endif", 5) == 0) { + } else if (strncmp(ptr, "endif", 5) == 0) { count++; } } @@ -1989,10 +1938,8 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } while (!got_int) { - /* - * Go to the next position, forward or backward. We could use - * inc() and dec() here, but that is much slower - */ + // Go to the next position, forward or backward. We could use + // inc() and dec() here, but that is much slower if (backwards) { // char to match is inside of comment, don't search outside if (lispcomm && pos.col < (colnr_T)comment_col) { @@ -2002,14 +1949,14 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) if (pos.lnum == 1) { // start of file break; } - --pos.lnum; + pos.lnum--; if (maxtravel > 0 && ++traveled > maxtravel) { break; } linep = ml_get(pos.lnum); - pos.col = (colnr_T)STRLEN(linep); // pos.col on trailing NUL + pos.col = (colnr_T)strlen(linep); // pos.col on trailing NUL do_quotes = -1; line_breakcheck(); @@ -2037,7 +1984,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) || lispcomm) { break; } - ++pos.lnum; + pos.lnum++; if (maxtravel && traveled++ > maxtravel) { break; @@ -2051,7 +1998,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) comment_col = check_linecomment(linep); } } else { - pos.col += utfc_ptr2len((char *)linep + pos.col); + pos.col += utfc_ptr2len(linep + pos.col); } } @@ -2066,23 +2013,21 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) if (comment_dir) { // Note: comments do not nest, and we ignore quotes in them - // TODO: ignore comment brackets inside strings + // TODO(vim): ignore comment brackets inside strings if (comment_dir == FORWARD) { if (linep[pos.col] == '*' && linep[pos.col + 1] == '/') { pos.col++; return &pos; } } else { // Searching backwards - /* - * A comment may contain / * or / /, it may also start or end - * with / * /. Ignore a / * after / / and after *. - */ + // A comment may contain / * or / /, it may also start or end + // with / * /. Ignore a / * after / / and after *. if (pos.col == 0) { continue; } else if (raw_string) { if (linep[pos.col - 1] == 'R' && linep[pos.col] == '"' - && vim_strchr((char *)linep + pos.col + 1, '(') != NULL) { + && vim_strchr(linep + pos.col + 1, '(') != NULL) { // Possible start of raw string. Now that we have the // delimiter we can check if it ends before where we // started searching, or before the previously found @@ -2119,20 +2064,16 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) continue; } - /* - * If smart matching ('cpoptions' does not contain '%'), braces inside - * of quotes are ignored, but only if there is an even number of - * quotes in the line. - */ + // If smart matching ('cpoptions' does not contain '%'), braces inside + // of quotes are ignored, but only if there is an even number of + // quotes in the line. if (cpo_match) { do_quotes = 0; } else if (do_quotes == -1) { - /* - * Count the number of quotes in the line, skipping \" and '"'. - * Watch out for "\\". - */ + // Count the number of quotes in the line, skipping \" and '"'. + // Watch out for "\\". at_start = do_quotes; - for (ptr = linep; *ptr; ++ptr) { + for (ptr = linep; *ptr; ptr++) { if (ptr == linep + pos.col + backwards) { at_start = (do_quotes & 1); } @@ -2146,10 +2087,8 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } do_quotes &= 1; // result is 1 with even number of quotes - /* - * If we find an uneven count, check current line and previous - * one for a '\' at the end. - */ + // If we find an uneven count, check current line and previous + // one for a '\' at the end. if (!do_quotes) { inquote = false; if (ptr[-1] == '\\') { @@ -2164,7 +2103,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } if (pos.lnum > 1) { ptr = ml_get(pos.lnum - 1); - if (*ptr && *(ptr + STRLEN(ptr) - 1) == '\\') { + if (*ptr && *(ptr + strlen(ptr) - 1) == '\\') { do_quotes = 1; if (start_in_quotes == kNone) { inquote = at_start; @@ -2185,18 +2124,16 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) start_in_quotes = kFalse; } - /* - * If 'smartmatch' is set: - * Things inside quotes are ignored by setting 'inquote'. If we - * find a quote without a preceding '\' invert 'inquote'. At the - * end of a line not ending in '\' we reset 'inquote'. - * - * In lines with an uneven number of quotes (without preceding '\') - * we do not know which part to ignore. Therefore we only set - * inquote if the number of quotes in a line is even, unless this - * line or the previous one ends in a '\'. Complicated, isn't it? - */ - const int c = utf_ptr2char((char *)linep + pos.col); + // If 'smartmatch' is set: + // Things inside quotes are ignored by setting 'inquote'. If we + // find a quote without a preceding '\' invert 'inquote'. At the + // end of a line not ending in '\' we reset 'inquote'. + // + // In lines with an uneven number of quotes (without preceding '\') + // we do not know which part to ignore. Therefore we only set + // inquote if the number of quotes in a line is even, unless this + // line or the previous one ends in a '\'. Complicated, isn't it? + const int c = utf_ptr2char(linep + pos.col); switch (c) { case NUL: // at end of line without trailing backslash, reset inquote @@ -2212,7 +2149,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) if (do_quotes) { int col; - for (col = pos.col - 1; col >= 0; --col) { + for (col = pos.col - 1; col >= 0; col--) { if (linep[col] != '\\') { break; } @@ -2224,13 +2161,11 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) } break; - /* - * If smart matching ('cpoptions' does not contain '%'): - * Skip things in single quotes: 'x' or '\x'. Be careful for single - * single quotes, eg jon's. Things like '\233' or '\x3f' are not - * skipped, there is never a brace in them. - * Ignore this when finding matches for `'. - */ + // If smart matching ('cpoptions' does not contain '%'): + // Skip things in single quotes: 'x' or '\x'. Be careful for single + // single quotes, eg jon's. Things like '\233' or '\x3f' are not + // skipped, there is never a brace in them. + // Ignore this when finding matches for `'. case '\'': if (!cpo_match && initc != '\'' && findc != '\'') { if (backwards) { @@ -2258,10 +2193,8 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) FALLTHROUGH; default: - /* - * For Lisp skip over backslashed (), {} and []. - * (actually, we skip #\( et al) - */ + // For Lisp skip over backslashed (), {} and []. + // (actually, we skip #\( et al) if (curbuf->b_p_lisp && vim_strchr("(){}[]", c) != NULL && pos.col > 1 @@ -2306,15 +2239,15 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) /// Check if line[] contains a / / comment. /// @returns MAXCOL if not, otherwise return the column. -int check_linecomment(const char_u *line) +int check_linecomment(const char *line) { - const char_u *p = line; // scan from start + const char *p = line; // scan from start // skip Lispish one-line comments if (curbuf->b_p_lisp) { if (vim_strchr((char *)p, ';') != NULL) { // there may be comments bool in_str = false; // inside of string - while ((p = (char_u *)strpbrk((char *)p, "\";")) != NULL) { + while ((p = strpbrk((char *)p, "\";")) != NULL) { if (*p == '"') { if (in_str) { if (*(p - 1) != '\\') { // skip escaped quote @@ -2336,7 +2269,7 @@ int check_linecomment(const char_u *line) p = NULL; } } else { - while ((p = (char_u *)vim_strchr((char *)p, '/')) != NULL) { + while ((p = vim_strchr((char *)p, '/')) != NULL) { // Accept a double /, unless it's preceded with * and followed by *, // because * / / * is an end and start of a C comment. Only // accept the position if it is not inside a string. @@ -2371,21 +2304,19 @@ void showmatch(int c) long save_siso; int save_state; colnr_T save_dollar_vcol; - char_u *p; + char *p; - /* - * Only show match for chars in the 'matchpairs' option. - */ + // Only show match for chars in the 'matchpairs' option. // 'matchpairs' is "x:y,x:y" for (p = curbuf->b_p_mps; *p != NUL; p++) { - if (utf_ptr2char((char *)p) == c && (curwin->w_p_rl ^ p_ri)) { + if (utf_ptr2char(p) == c && (curwin->w_p_rl ^ p_ri)) { break; } - p += utfc_ptr2len((char *)p) + 1; - if (utf_ptr2char((char *)p) == c && !(curwin->w_p_rl ^ p_ri)) { + p += utfc_ptr2len(p) + 1; + if (utf_ptr2char(p) == c && !(curwin->w_p_rl ^ p_ri)) { break; } - p += utfc_ptr2len((char *)p); + p += utfc_ptr2len(p); if (*p == NUL) { return; } @@ -2396,1865 +2327,63 @@ void showmatch(int c) if ((lpos = findmatch(NULL, NUL)) == NULL) { // no match, so beep vim_beep(BO_MATCH); - } else if (lpos->lnum >= curwin->w_topline - && lpos->lnum < curwin->w_botline) { - if (!curwin->w_p_wrap) { - getvcol(curwin, lpos, NULL, &vcol, NULL); - } - if (curwin->w_p_wrap - || (vcol >= curwin->w_leftcol - && vcol < curwin->w_leftcol + curwin->w_width_inner)) { - mpos = *lpos; // save the pos, update_screen() may change it - save_cursor = curwin->w_cursor; - save_so = *so; - save_siso = *siso; - // Handle "$" in 'cpo': If the ')' is typed on top of the "$", - // stop displaying the "$". - if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol) { - dollar_vcol = -1; - } - curwin->w_virtcol++; // do display ')' just before "$" - update_screen(VALID); // show the new char first - - save_dollar_vcol = dollar_vcol; - save_state = State; - State = MODE_SHOWMATCH; - ui_cursor_shape(); // may show different cursor shape - curwin->w_cursor = mpos; // move to matching char - *so = 0; // don't use 'scrolloff' here - *siso = 0; // don't use 'sidescrolloff' here - show_cursor_info(false); - setcursor(); - ui_flush(); - // Restore dollar_vcol(), because setcursor() may call curs_rows() - // which resets it if the matching position is in a previous line - // and has a higher column number. - dollar_vcol = save_dollar_vcol; - - /* - * brief pause, unless 'm' is present in 'cpo' and a character is - * available. - */ - if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) { - os_delay((uint64_t)p_mat * 100L + 8, true); - } else if (!char_avail()) { - os_delay((uint64_t)p_mat * 100L + 9, false); - } - curwin->w_cursor = save_cursor; // restore cursor position - *so = save_so; - *siso = save_siso; - State = save_state; - ui_cursor_shape(); // may show different cursor shape - } - } -} - -// Find the start of the next sentence, searching in the direction specified -// by the "dir" argument. The cursor is positioned on the start of the next -// sentence when found. If the next sentence is found, return OK. Return FAIL -// otherwise. See ":h sentence" for the precise definition of a "sentence" -// text object. -int findsent(Direction dir, long count) -{ - pos_T pos, tpos; - int c; - int (*func)(pos_T *); - bool noskip = false; // do not skip blanks - - pos = curwin->w_cursor; - if (dir == FORWARD) { - func = incl; - } else { - func = decl; - } - - while (count--) { - const pos_T prev_pos = pos; - - // if on an empty line, skip up to a non-empty line - if (gchar_pos(&pos) == NUL) { - do { - if ((*func)(&pos) == -1) { - break; - } - } while (gchar_pos(&pos) == NUL); - if (dir == FORWARD) { - goto found; - } - // if on the start of a paragraph or a section and searching forward, - // go to the next line - } else if (dir == FORWARD && pos.col == 0 - && startPS(pos.lnum, NUL, false)) { - if (pos.lnum == curbuf->b_ml.ml_line_count) { - return FAIL; - } - pos.lnum++; - goto found; - } else if (dir == BACKWARD) { - decl(&pos); - } - - // go back to the previous non-white non-punctuation character - bool found_dot = false; - while (c = gchar_pos(&pos), ascii_iswhite(c) - || vim_strchr(".!?)]\"'", c) != NULL) { - tpos = pos; - if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) { - break; - } - if (found_dot) { - break; - } - if (vim_strchr(".!?", c) != NULL) { - found_dot = true; - } - if (vim_strchr(")]\"'", c) != NULL - && vim_strchr(".!?)]\"'", gchar_pos(&tpos)) == NULL) { - break; - } - decl(&pos); - } - - // remember the line where the search started - const int startlnum = pos.lnum; - const bool cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL; - - for (;;) { // find end of sentence - c = gchar_pos(&pos); - if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, FALSE))) { - if (dir == BACKWARD && pos.lnum != startlnum) { - ++pos.lnum; - } - break; - } - if (c == '.' || c == '!' || c == '?') { - tpos = pos; - do { - if ((c = inc(&tpos)) == -1) { - break; - } - } while (vim_strchr(")]\"'", c = gchar_pos(&tpos)) - != NULL); - if (c == -1 || (!cpo_J && (c == ' ' || c == '\t')) || c == NUL - || (cpo_J && (c == ' ' && inc(&tpos) >= 0 - && gchar_pos(&tpos) == ' '))) { - pos = tpos; - if (gchar_pos(&pos) == NUL) { // skip NUL at EOL - inc(&pos); - } - break; - } - } - if ((*func)(&pos) == -1) { - if (count) { - return FAIL; - } - noskip = true; - break; - } - } -found: - // skip white space - while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t')) { - if (incl(&pos) == -1) { - break; - } - } - - if (equalpos(prev_pos, pos)) { - // didn't actually move, advance one character and try again - if ((*func)(&pos) == -1) { - if (count) { - return FAIL; - } - break; - } - count++; - } - } - - setpcmark(); - curwin->w_cursor = pos; - return OK; -} - -/// Find the next paragraph or section in direction 'dir'. -/// Paragraphs are currently supposed to be separated by empty lines. -/// If 'what' is NUL we go to the next paragraph. -/// If 'what' is '{' or '}' we go to the next section. -/// If 'both' is TRUE also stop at '}'. -/// -/// @param pincl Return: true if last char is to be included -/// -/// @return TRUE if the next paragraph or section was found. -bool findpar(bool *pincl, int dir, long count, int what, int both) -{ - linenr_T curr; - bool did_skip; // true after separating lines have been skipped - bool first; // true on first line - linenr_T fold_first; // first line of a closed fold - linenr_T fold_last; // last line of a closed fold - bool fold_skipped; // true if a closed fold was skipped this - // iteration - - curr = curwin->w_cursor.lnum; - - while (count--) { - did_skip = false; - for (first = true;; first = false) { - if (*ml_get(curr) != NUL) { - did_skip = true; - } - - // skip folded lines - fold_skipped = false; - if (first && hasFolding(curr, &fold_first, &fold_last)) { - curr = ((dir > 0) ? fold_last : fold_first) + dir; - fold_skipped = true; - } - - if (!first && did_skip && startPS(curr, what, both)) { - break; - } - - if (fold_skipped) { - curr -= dir; - } - if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count) { - if (count) { - return false; - } - curr -= dir; - break; - } - } - } - setpcmark(); - if (both && *ml_get(curr) == '}') { // include line with '}' - curr++; - } - curwin->w_cursor.lnum = curr; - if (curr == curbuf->b_ml.ml_line_count && what != '}') { - char_u *line = ml_get(curr); - - // Put the cursor on the last character in the last line and make the - // motion inclusive. - if ((curwin->w_cursor.col = (colnr_T)STRLEN(line)) != 0) { - curwin->w_cursor.col--; - curwin->w_cursor.col -= utf_head_off(line, line + curwin->w_cursor.col); - *pincl = true; - } - } else { - curwin->w_cursor.col = 0; - } - return true; -} - -/* - * check if the string 's' is a nroff macro that is in option 'opt' - */ -static int inmacro(char_u *opt, char_u *s) -{ - char_u *macro; - - for (macro = opt; macro[0]; macro++) { - // Accept two characters in the option being equal to two characters - // in the line. A space in the option matches with a space in the - // line or the line having ended. - if ((macro[0] == s[0] - || (macro[0] == ' ' - && (s[0] == NUL || s[0] == ' '))) - && (macro[1] == s[1] - || ((macro[1] == NUL || macro[1] == ' ') - && (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) { - break; - } - macro++; - if (macro[0] == NUL) { - break; - } - } - return macro[0] != NUL; -} - -/* - * startPS: return TRUE if line 'lnum' is the start of a section or paragraph. - * If 'para' is '{' or '}' only check for sections. - * If 'both' is TRUE also stop at '}' - */ -int startPS(linenr_T lnum, int para, int both) -{ - char_u *s; - - s = ml_get(lnum); - if (*s == para || *s == '\f' || (both && *s == '}')) { - return true; - } - if (*s == '.' && (inmacro(p_sections, s + 1) - || (!para && inmacro(p_para, s + 1)))) { - return true; - } - return false; -} - -/* - * The following routines do the word searches performed by the 'w', 'W', - * 'b', 'B', 'e', and 'E' commands. - */ - -/* - * To perform these searches, characters are placed into one of three - * classes, and transitions between classes determine word boundaries. - * - * The classes are: - * - * 0 - white space - * 1 - punctuation - * 2 or higher - keyword characters (letters, digits and underscore) - */ - -static int cls_bigword; // TRUE for "W", "B" or "E" - -/* - * cls() - returns the class of character at curwin->w_cursor - * - * If a 'W', 'B', or 'E' motion is being done (cls_bigword == TRUE), chars - * from class 2 and higher are reported as class 1 since only white space - * boundaries are of interest. - */ -static int cls(void) -{ - int c; - - c = gchar_cursor(); - if (c == ' ' || c == '\t' || c == NUL) { - return 0; - } - - c = utf_class(c); - - // If cls_bigword is TRUE, report all non-blanks as class 1. - if (c != 0 && cls_bigword) { - return 1; - } - return c; -} - -/// fwd_word(count, type, eol) - move forward one word -/// -/// @return FAIL if the cursor was already at the end of the file. -/// If eol is TRUE, last word stops at end of line (for operators). -/// -/// @param bigword "W", "E" or "B" -int fwd_word(long count, int bigword, int eol) -{ - int sclass; // starting class - int i; - int last_line; - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) { - // When inside a range of folded lines, move to the last char of the - // last line. - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { - coladvance(MAXCOL); - } - sclass = cls(); - - /* - * We always move at least one character, unless on the last - * character in the buffer. - */ - last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count); - i = inc_cursor(); - if (i == -1 || (i >= 1 && last_line)) { // started at last char in file - return FAIL; - } - if (i >= 1 && eol && count == 0) { // started at last char in line - return OK; - } - - /* - * Go one char past end of current word (if any) - */ - if (sclass != 0) { - while (cls() == sclass) { - i = inc_cursor(); - if (i == -1 || (i >= 1 && eol && count == 0)) { - return OK; - } - } - } - - /* - * go to next non-white - */ - while (cls() == 0) { - /* - * We'll stop if we land on a blank line - */ - if (curwin->w_cursor.col == 0 && *get_cursor_line_ptr() == NUL) { - break; - } - - i = inc_cursor(); - if (i == -1 || (i >= 1 && eol && count == 0)) { - return OK; - } - } - } - return OK; -} - -/* - * bck_word() - move backward 'count' words - * - * If stop is TRUE and we are already on the start of a word, move one less. - * - * Returns FAIL if top of the file was reached. - */ -int bck_word(long count, int bigword, int stop) -{ - int sclass; // starting class - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) { - /* When inside a range of folded lines, move to the first char of the - * first line. */ - if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) { - curwin->w_cursor.col = 0; - } - sclass = cls(); - if (dec_cursor() == -1) { // started at start of file - return FAIL; - } - - if (!stop || sclass == cls() || sclass == 0) { - /* - * Skip white space before the word. - * Stop on an empty line. - */ - while (cls() == 0) { - if (curwin->w_cursor.col == 0 - && LINEEMPTY(curwin->w_cursor.lnum)) { - goto finished; - } - if (dec_cursor() == -1) { // hit start of file, stop here - return OK; - } - } - - /* - * Move backward to start of this word. - */ - if (skip_chars(cls(), BACKWARD)) { - return OK; - } - } - - inc_cursor(); // overshot - forward one -finished: - stop = FALSE; - } - return OK; -} - -/* - * end_word() - move to the end of the word - * - * There is an apparent bug in the 'e' motion of the real vi. At least on the - * System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e' - * motion crosses blank lines. When the real vi crosses a blank line in an - * 'e' motion, the cursor is placed on the FIRST character of the next - * non-blank line. The 'E' command, however, works correctly. Since this - * appears to be a bug, I have not duplicated it here. - * - * Returns FAIL if end of the file was reached. - * - * If stop is TRUE and we are already on the end of a word, move one less. - * If empty is TRUE stop on an empty line. - */ -int end_word(long count, int bigword, int stop, int empty) -{ - int sclass; // starting class - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) { - /* When inside a range of folded lines, move to the last char of the - * last line. */ - if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { - coladvance(MAXCOL); - } - sclass = cls(); - if (inc_cursor() == -1) { - return FAIL; - } - - /* - * If we're in the middle of a word, we just have to move to the end - * of it. - */ - if (cls() == sclass && sclass != 0) { - /* - * Move forward to end of the current word - */ - if (skip_chars(sclass, FORWARD)) { - return FAIL; - } - } else if (!stop || sclass == 0) { - /* - * We were at the end of a word. Go to the end of the next word. - * First skip white space, if 'empty' is TRUE, stop at empty line. - */ - while (cls() == 0) { - if (empty && curwin->w_cursor.col == 0 - && LINEEMPTY(curwin->w_cursor.lnum)) { - goto finished; - } - if (inc_cursor() == -1) { // hit end of file, stop here - return FAIL; - } - } - - /* - * Move forward to the end of this word. - */ - if (skip_chars(cls(), FORWARD)) { - return FAIL; - } - } - dec_cursor(); // overshot - one char backward -finished: - stop = FALSE; // we move only one word less - } - return OK; -} - -/// Move back to the end of the word. -/// -/// @param bigword TRUE for "B" -/// @param eol if true, then stop at end of line. -/// -/// @return FAIL if start of the file was reached. -int bckend_word(long count, int bigword, bool eol) -{ - int sclass; // starting class - int i; - - curwin->w_cursor.coladd = 0; - cls_bigword = bigword; - while (--count >= 0) { - sclass = cls(); - if ((i = dec_cursor()) == -1) { - return FAIL; - } - if (eol && i == 1) { - return OK; - } - - /* - * Move backward to before the start of this word. - */ - if (sclass != 0) { - while (cls() == sclass) { - if ((i = dec_cursor()) == -1 || (eol && i == 1)) { - return OK; - } - } - } - - /* - * Move backward to end of the previous word - */ - while (cls() == 0) { - if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum)) { - break; - } - if ((i = dec_cursor()) == -1 || (eol && i == 1)) { - return OK; - } - } - } - return OK; -} - -/// Skip a row of characters of the same class. -/// -/// @return true when end-of-file reached, false otherwise. -static bool skip_chars(int cclass, int dir) -{ - while (cls() == cclass) { - if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1) { - return true; - } - } - return false; -} - -/* - * Go back to the start of the word or the start of white space - */ -static void back_in_line(void) -{ - int sclass; // starting class - - sclass = cls(); - for (;;) { - if (curwin->w_cursor.col == 0) { // stop at start of line - break; - } - dec_cursor(); - if (cls() != sclass) { // stop at start of word - inc_cursor(); - break; - } - } -} - -static void find_first_blank(pos_T *posp) -{ - int c; - - while (decl(posp) != -1) { - c = gchar_pos(posp); - if (!ascii_iswhite(c)) { - incl(posp); - break; - } - } -} - -/// Skip count/2 sentences and count/2 separating white spaces. -/// -/// @param at_start_sent cursor is at start of sentence -static void findsent_forward(long count, bool at_start_sent) -{ - while (count--) { - findsent(FORWARD, 1L); - if (at_start_sent) { - find_first_blank(&curwin->w_cursor); - } - if (count == 0 || at_start_sent) { - decl(&curwin->w_cursor); - } - at_start_sent = !at_start_sent; - } -} - -/// Find word under cursor, cursor at end. -/// Used while an operator is pending, and in Visual mode. -/// -/// @param include TRUE: include word and white space -/// @param bigword FALSE == word, TRUE == WORD -int current_word(oparg_T *oap, long count, int include, int bigword) -{ - pos_T start_pos; - pos_T pos; - bool inclusive = true; - int include_white = FALSE; - - cls_bigword = bigword; - clearpos(&start_pos); - - // Correct cursor when 'selection' is exclusive - if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor)) { - dec_cursor(); - } - - /* - * When Visual mode is not active, or when the VIsual area is only one - * character, select the word and/or white space under the cursor. - */ - if (!VIsual_active || equalpos(curwin->w_cursor, VIsual)) { - /* - * Go to start of current word or white space. - */ - back_in_line(); - start_pos = curwin->w_cursor; - - /* - * If the start is on white space, and white space should be included - * (" word"), or start is not on white space, and white space should - * not be included ("word"), find end of word. - */ - if ((cls() == 0) == include) { - if (end_word(1L, bigword, TRUE, TRUE) == FAIL) { - return FAIL; - } - } else { - /* - * If the start is not on white space, and white space should be - * included ("word "), or start is on white space and white - * space should not be included (" "), find start of word. - * If we end up in the first column of the next line (single char - * word) back up to end of the line. - */ - fwd_word(1L, bigword, TRUE); - if (curwin->w_cursor.col == 0) { - decl(&curwin->w_cursor); - } else { - oneleft(); - } - - if (include) { - include_white = TRUE; - } - } - - if (VIsual_active) { - // should do something when inclusive == false ! - VIsual = start_pos; - redraw_curbuf_later(INVERTED); // update the inversion - } else { - oap->start = start_pos; - oap->motion_type = kMTCharWise; - } - count--; - } - - /* - * When count is still > 0, extend with more objects. - */ - while (count > 0) { - inclusive = true; - if (VIsual_active && lt(curwin->w_cursor, VIsual)) { - /* - * In Visual mode, with cursor at start: move cursor back. - */ - if (decl(&curwin->w_cursor) == -1) { - return FAIL; - } - if (include != (cls() != 0)) { - if (bck_word(1L, bigword, TRUE) == FAIL) { - return FAIL; - } - } else { - if (bckend_word(1L, bigword, true) == FAIL) { - return FAIL; - } - (void)incl(&curwin->w_cursor); - } - } else { - /* - * Move cursor forward one word and/or white area. - */ - if (incl(&curwin->w_cursor) == -1) { - return FAIL; - } - if (include != (cls() == 0)) { - if (fwd_word(1L, bigword, TRUE) == FAIL && count > 1) { - return FAIL; - } - /* - * If end is just past a new-line, we don't want to include - * the first character on the line. - * Put cursor on last char of white. - */ - if (oneleft() == FAIL) { - inclusive = false; - } - } else { - if (end_word(1L, bigword, TRUE, TRUE) == FAIL) { - return FAIL; - } - } - } - count--; - } - - if (include_white && (cls() != 0 - || (curwin->w_cursor.col == 0 && !inclusive))) { - /* - * If we don't include white space at the end, move the start - * to include some white space there. This makes "daw" work - * better on the last word in a sentence (and "2daw" on last-but-one - * word). Also when "2daw" deletes "word." at the end of the line - * (cursor is at start of next line). - * But don't delete white space at start of line (indent). - */ - pos = curwin->w_cursor; // save cursor position - curwin->w_cursor = start_pos; - if (oneleft() == OK) { - back_in_line(); - if (cls() == 0 && curwin->w_cursor.col > 0) { - if (VIsual_active) { - VIsual = curwin->w_cursor; - } else { - oap->start = curwin->w_cursor; - } - } - } - curwin->w_cursor = pos; // put cursor back at end - } - - if (VIsual_active) { - if (*p_sel == 'e' && inclusive && ltoreq(VIsual, curwin->w_cursor)) { - inc_cursor(); - } - if (VIsual_mode == 'V') { - VIsual_mode = 'v'; - redraw_cmdline = true; // show mode later - } - } else { - oap->inclusive = inclusive; - } - - return OK; -} - -/* - * Find sentence(s) under the cursor, cursor at end. - * When Visual active, extend it by one or more sentences. - */ -int current_sent(oparg_T *oap, long count, int include) -{ - pos_T start_pos; - pos_T pos; - bool start_blank; - int c; - bool at_start_sent; - long ncount; - - start_pos = curwin->w_cursor; - pos = start_pos; - findsent(FORWARD, 1L); // Find start of next sentence. - - /* - * When the Visual area is bigger than one character: Extend it. - */ - if (VIsual_active && !equalpos(start_pos, VIsual)) { -extend: - if (lt(start_pos, VIsual)) { - /* - * Cursor at start of Visual area. - * Find out where we are: - * - in the white space before a sentence - * - in a sentence or just after it - * - at the start of a sentence - */ - at_start_sent = true; - decl(&pos); - while (lt(pos, curwin->w_cursor)) { - c = gchar_pos(&pos); - if (!ascii_iswhite(c)) { - at_start_sent = false; - break; - } - incl(&pos); - } - if (!at_start_sent) { - findsent(BACKWARD, 1L); - if (equalpos(curwin->w_cursor, start_pos)) { - at_start_sent = true; // exactly at start of sentence - } else { - // inside a sentence, go to its end (start of next) - findsent(FORWARD, 1L); - } - } - if (include) { // "as" gets twice as much as "is" - count *= 2; - } - while (count--) { - if (at_start_sent) { - find_first_blank(&curwin->w_cursor); - } - c = gchar_cursor(); - if (!at_start_sent || (!include && !ascii_iswhite(c))) { - findsent(BACKWARD, 1L); - } - at_start_sent = !at_start_sent; - } - } else { - /* - * Cursor at end of Visual area. - * Find out where we are: - * - just before a sentence - * - just before or in the white space before a sentence - * - in a sentence - */ - incl(&pos); - at_start_sent = true; - if (!equalpos(pos, curwin->w_cursor)) { // not just before a sentence - at_start_sent = false; - while (lt(pos, curwin->w_cursor)) { - c = gchar_pos(&pos); - if (!ascii_iswhite(c)) { - at_start_sent = true; - break; - } - incl(&pos); - } - if (at_start_sent) { // in the sentence - findsent(BACKWARD, 1L); - } else { // in/before white before a sentence - curwin->w_cursor = start_pos; - } - } - - if (include) { // "as" gets twice as much as "is" - count *= 2; - } - findsent_forward(count, at_start_sent); - if (*p_sel == 'e') { - ++curwin->w_cursor.col; - } - } - return OK; - } - - /* - * If the cursor started on a blank, check if it is just before the start - * of the next sentence. - */ - while (c = gchar_pos(&pos), ascii_iswhite(c)) { - incl(&pos); - } - if (equalpos(pos, curwin->w_cursor)) { - start_blank = true; - find_first_blank(&start_pos); // go back to first blank - } else { - start_blank = false; - findsent(BACKWARD, 1L); - start_pos = curwin->w_cursor; - } - if (include) { - ncount = count * 2; - } else { - ncount = count; - if (start_blank) { - ncount--; - } - } - if (ncount > 0) { - findsent_forward(ncount, true); - } else { - decl(&curwin->w_cursor); - } - - if (include) { - /* - * If the blank in front of the sentence is included, exclude the - * blanks at the end of the sentence, go back to the first blank. - * If there are no trailing blanks, try to include leading blanks. - */ - if (start_blank) { - find_first_blank(&curwin->w_cursor); - c = gchar_pos(&curwin->w_cursor); - if (ascii_iswhite(c)) { - decl(&curwin->w_cursor); - } - } else if (c = gchar_cursor(), !ascii_iswhite(c)) { - find_first_blank(&start_pos); - } - } - - if (VIsual_active) { - // Avoid getting stuck with "is" on a single space before a sentence. - if (equalpos(start_pos, curwin->w_cursor)) { - goto extend; - } - if (*p_sel == 'e') { - ++curwin->w_cursor.col; - } - VIsual = start_pos; - VIsual_mode = 'v'; - redraw_cmdline = true; // show mode later - redraw_curbuf_later(INVERTED); // update the inversion - } else { - // include a newline after the sentence, if there is one - if (incl(&curwin->w_cursor) == -1) { - oap->inclusive = true; - } else { - oap->inclusive = false; - } - oap->start = start_pos; - oap->motion_type = kMTCharWise; - } - return OK; -} - -/// Find block under the cursor, cursor at end. -/// "what" and "other" are two matching parenthesis/brace/etc. -/// -/// @param include TRUE == include white space -/// @param what '(', '{', etc. -/// @param other ')', '}', etc. -int current_block(oparg_T *oap, long count, int include, int what, int other) -{ - pos_T old_pos; - pos_T *pos = NULL; - pos_T start_pos; - pos_T *end_pos; - pos_T old_start, old_end; - char *save_cpo; - bool sol = false; // '{' at start of line - - old_pos = curwin->w_cursor; - old_end = curwin->w_cursor; // remember where we started - old_start = old_end; - - /* - * If we start on '(', '{', ')', '}', etc., use the whole block inclusive. - */ - if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { - setpcmark(); - if (what == '{') { // ignore indent - while (inindent(1)) { - if (inc_cursor() != 0) { - break; - } - } - } - if (gchar_cursor() == what) { - // cursor on '(' or '{', move cursor just after it - ++curwin->w_cursor.col; - } - } else if (lt(VIsual, curwin->w_cursor)) { - old_start = VIsual; - curwin->w_cursor = VIsual; // cursor at low end of Visual - } else { - old_end = VIsual; - } - - // Search backwards for unclosed '(', '{', etc.. - // Put this position in start_pos. - // Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the - // user wants. - save_cpo = p_cpo; - p_cpo = vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%"; - if ((pos = findmatch(NULL, what)) != NULL) { - while (count-- > 0) { - if ((pos = findmatch(NULL, what)) == NULL) { - break; - } - curwin->w_cursor = *pos; - start_pos = *pos; // the findmatch for end_pos will overwrite *pos - } - } else { - while (count-- > 0) { - if ((pos = findmatchlimit(NULL, what, FM_FORWARD, 0)) == NULL) { - break; - } - curwin->w_cursor = *pos; - start_pos = *pos; // the findmatch for end_pos will overwrite *pos - } - } - p_cpo = save_cpo; - - /* - * Search for matching ')', '}', etc. - * Put this position in curwin->w_cursor. - */ - if (pos == NULL || (end_pos = findmatch(NULL, other)) == NULL) { - curwin->w_cursor = old_pos; - return FAIL; - } - curwin->w_cursor = *end_pos; - - // Try to exclude the '(', '{', ')', '}', etc. when "include" is FALSE. - // If the ending '}', ')' or ']' is only preceded by indent, skip that - // indent. But only if the resulting area is not smaller than what we - // started with. - while (!include) { - incl(&start_pos); - sol = (curwin->w_cursor.col == 0); - decl(&curwin->w_cursor); - while (inindent(1)) { - sol = true; - if (decl(&curwin->w_cursor) != 0) { - break; - } - } - - // In Visual mode, when the resulting area is not bigger than what we - // started with, extend it to the next block, and then exclude again. - // Don't try to expand the area if the area is empty. - if (!lt(start_pos, old_start) && !lt(old_end, curwin->w_cursor) - && !equalpos(start_pos, curwin->w_cursor) - && VIsual_active) { - curwin->w_cursor = old_start; - decl(&curwin->w_cursor); - if ((pos = findmatch(NULL, what)) == NULL) { - curwin->w_cursor = old_pos; - return FAIL; - } - start_pos = *pos; - curwin->w_cursor = *pos; - if ((end_pos = findmatch(NULL, other)) == NULL) { - curwin->w_cursor = old_pos; - return FAIL; - } - curwin->w_cursor = *end_pos; - } else { - break; - } - } - - if (VIsual_active) { - if (*p_sel == 'e') { - inc(&curwin->w_cursor); - } - if (sol && gchar_cursor() != NUL) { - inc(&curwin->w_cursor); // include the line break - } - VIsual = start_pos; - VIsual_mode = 'v'; - redraw_curbuf_later(INVERTED); // update the inversion - showmode(); - } else { - oap->start = start_pos; - oap->motion_type = kMTCharWise; - oap->inclusive = false; - if (sol) { - incl(&curwin->w_cursor); - } else if (ltoreq(start_pos, curwin->w_cursor)) { - // Include the character under the cursor. - oap->inclusive = true; - } else { - // End is before the start (no text in between <>, [], etc.): don't - // operate on any text. - curwin->w_cursor = start_pos; - } - } - - return OK; -} - -/// @param end_tag when true, return true if the cursor is on "</aaa>". -/// -/// @return true if the cursor is on a "<aaa>" tag. Ignore "<aaa/>". -static bool in_html_tag(bool end_tag) -{ - char_u *line = get_cursor_line_ptr(); - char_u *p; - int c; - int lc = NUL; - pos_T pos; - - for (p = line + curwin->w_cursor.col; p > line;) { - if (*p == '<') { // find '<' under/before cursor - break; - } - MB_PTR_BACK(line, p); - if (*p == '>') { // find '>' before cursor - break; - } - } - if (*p != '<') { - return false; - } - - pos.lnum = curwin->w_cursor.lnum; - pos.col = (colnr_T)(p - line); - - MB_PTR_ADV(p); - if (end_tag) { - // check that there is a '/' after the '<' - return *p == '/'; - } - - // check that there is no '/' after the '<' - if (*p == '/') { - return false; - } - - // check that the matching '>' is not preceded by '/' - for (;;) { - if (inc(&pos) < 0) { - return false; - } - c = *ml_get_pos(&pos); - if (c == '>') { - break; - } - lc = c; - } - return lc != '/'; -} - -/// Find tag block under the cursor, cursor at end. -/// -/// @param include true == include white space -int current_tagblock(oparg_T *oap, long count_arg, bool include) -{ - long count = count_arg; - pos_T old_pos; - pos_T start_pos; - pos_T end_pos; - pos_T old_start, old_end; - char_u *p; - char_u *cp; - int len; - bool do_include = include; - bool save_p_ws = p_ws; - int retval = FAIL; - int is_inclusive = true; - - p_ws = false; - - old_pos = curwin->w_cursor; - old_end = curwin->w_cursor; // remember where we started - old_start = old_end; - if (!VIsual_active || *p_sel == 'e') { - decl(&old_end); // old_end is inclusive - } - /* - * If we start on "<aaa>" select that block. - */ - if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { - setpcmark(); - - // ignore indent - while (inindent(1)) { - if (inc_cursor() != 0) { - break; - } - } - - if (in_html_tag(false)) { - // cursor on start tag, move to its '>' - while (*get_cursor_pos_ptr() != '>') { - if (inc_cursor() < 0) { - break; - } - } - } else if (in_html_tag(true)) { - // cursor on end tag, move to just before it - while (*get_cursor_pos_ptr() != '<') { - if (dec_cursor() < 0) { - break; - } - } - dec_cursor(); - old_end = curwin->w_cursor; - } - } else if (lt(VIsual, curwin->w_cursor)) { - old_start = VIsual; - curwin->w_cursor = VIsual; // cursor at low end of Visual - } else { - old_end = VIsual; - } - -again: - /* - * Search backwards for unclosed "<aaa>". - * Put this position in start_pos. - */ - for (long n = 0; n < count; n++) { - if (do_searchpair("<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", - "", - "</[^>]*>", BACKWARD, NULL, 0, - NULL, (linenr_T)0, 0L) <= 0) { - curwin->w_cursor = old_pos; - goto theend; - } - } - start_pos = curwin->w_cursor; - - /* - * Search for matching "</aaa>". First isolate the "aaa". - */ - inc_cursor(); - p = get_cursor_pos_ptr(); - for (cp = p; - *cp != NUL && *cp != '>' && !ascii_iswhite(*cp); - MB_PTR_ADV(cp)) {} - len = (int)(cp - p); - if (len == 0) { - curwin->w_cursor = old_pos; - goto theend; - } - const size_t spat_len = (size_t)len + 39; - char *const spat = xmalloc(spat_len); - const size_t epat_len = (size_t)len + 9; - char *const epat = xmalloc(epat_len); - snprintf(spat, spat_len, - "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p); - snprintf(epat, epat_len, "</%.*s>\\c", len, p); - - const int r = (int)do_searchpair(spat, "", epat, FORWARD, NULL, 0, NULL, (linenr_T)0, 0L); - - xfree(spat); - xfree(epat); - - if (r < 1 || lt(curwin->w_cursor, old_end)) { - // Can't find other end or it's before the previous end. Could be a - // HTML tag that doesn't have a matching end. Search backwards for - // another starting tag. - count = 1; - curwin->w_cursor = start_pos; - goto again; - } - - if (do_include) { - // Include up to the '>'. - while (*get_cursor_pos_ptr() != '>') { - if (inc_cursor() < 0) { - break; - } - } - } else { - char_u *c = get_cursor_pos_ptr(); - // Exclude the '<' of the end tag. - // If the closing tag is on new line, do not decrement cursor, but make - // operation exclusive, so that the linefeed will be selected - if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0) { - // do not decrement cursor - is_inclusive = false; - } else if (*c == '<') { - dec_cursor(); - } - } - end_pos = curwin->w_cursor; - - if (!do_include) { - // Exclude the start tag. - curwin->w_cursor = start_pos; - while (inc_cursor() >= 0) { - if (*get_cursor_pos_ptr() == '>') { - inc_cursor(); - start_pos = curwin->w_cursor; - break; - } - } - curwin->w_cursor = end_pos; - - // If we are in Visual mode and now have the same text as before set - // "do_include" and try again. - if (VIsual_active - && equalpos(start_pos, old_start) - && equalpos(end_pos, old_end)) { - do_include = true; - curwin->w_cursor = old_start; - count = count_arg; - goto again; - } - } - - if (VIsual_active) { - // If the end is before the start there is no text between tags, select - // the char under the cursor. - if (lt(end_pos, start_pos)) { - curwin->w_cursor = start_pos; - } else if (*p_sel == 'e') { - inc_cursor(); - } - VIsual = start_pos; - VIsual_mode = 'v'; - redraw_curbuf_later(INVERTED); // update the inversion - showmode(); - } else { - oap->start = start_pos; - oap->motion_type = kMTCharWise; - if (lt(end_pos, start_pos)) { - // End is before the start: there is no text between tags; operate - // on an empty area. - curwin->w_cursor = start_pos; - oap->inclusive = false; - } else { - oap->inclusive = is_inclusive; - } - } - retval = OK; - -theend: - p_ws = save_p_ws; - return retval; -} - -/// @param include TRUE == include white space -/// @param type 'p' for paragraph, 'S' for section -int current_par(oparg_T *oap, long count, int include, int type) -{ - linenr_T start_lnum; - linenr_T end_lnum; - int white_in_front; - int dir; - int start_is_white; - int prev_start_is_white; - int retval = OK; - int do_white = FALSE; - int t; - int i; - - if (type == 'S') { // not implemented yet - return FAIL; - } - - start_lnum = curwin->w_cursor.lnum; - - /* - * When visual area is more than one line: extend it. - */ - if (VIsual_active && start_lnum != VIsual.lnum) { -extend: - if (start_lnum < VIsual.lnum) { - dir = BACKWARD; - } else { - dir = FORWARD; - } - for (i = (int)count; --i >= 0;) { - if (start_lnum == - (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) { - retval = FAIL; - break; - } - - prev_start_is_white = -1; - for (t = 0; t < 2; ++t) { - start_lnum += dir; - start_is_white = linewhite(start_lnum); - if (prev_start_is_white == start_is_white) { - start_lnum -= dir; - break; - } - for (;;) { - if (start_lnum == (dir == BACKWARD - ? 1 : curbuf->b_ml.ml_line_count)) { - break; - } - if (start_is_white != linewhite(start_lnum + dir) - || (!start_is_white - && startPS(start_lnum + (dir > 0 - ? 1 : 0), 0, 0))) { - break; - } - start_lnum += dir; - } - if (!include) { - break; - } - if (start_lnum == (dir == BACKWARD - ? 1 : curbuf->b_ml.ml_line_count)) { - break; - } - prev_start_is_white = start_is_white; - } - } - curwin->w_cursor.lnum = start_lnum; - curwin->w_cursor.col = 0; - return retval; - } - - /* - * First move back to the start_lnum of the paragraph or white lines - */ - white_in_front = linewhite(start_lnum); - while (start_lnum > 1) { - if (white_in_front) { // stop at first white line - if (!linewhite(start_lnum - 1)) { - break; - } - } else { // stop at first non-white line of start of paragraph - if (linewhite(start_lnum - 1) || startPS(start_lnum, 0, 0)) { - break; - } - } - start_lnum--; - } - - /* - * Move past the end of any white lines. - */ - end_lnum = start_lnum; - while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) { - end_lnum++; - } - - end_lnum--; - i = (int)count; - if (!include && white_in_front) { - i--; - } - while (i--) { - if (end_lnum == curbuf->b_ml.ml_line_count) { - return FAIL; - } - - if (!include) { - do_white = linewhite(end_lnum + 1); - } - - if (include || !do_white) { - end_lnum++; - // skip to end of paragraph - while (end_lnum < curbuf->b_ml.ml_line_count - && !linewhite(end_lnum + 1) - && !startPS(end_lnum + 1, 0, 0)) { - end_lnum++; - } - } - - if (i == 0 && white_in_front && include) { - break; - } - - /* - * skip to end of white lines after paragraph - */ - if (include || do_white) { - while (end_lnum < curbuf->b_ml.ml_line_count - && linewhite(end_lnum + 1)) { - end_lnum++; - } - } - } - - /* - * If there are no empty lines at the end, try to find some empty lines at - * the start (unless that has been done already). - */ - if (!white_in_front && !linewhite(end_lnum) && include) { - while (start_lnum > 1 && linewhite(start_lnum - 1)) { - start_lnum--; - } - } - - if (VIsual_active) { - // Problem: when doing "Vipipip" nothing happens in a single white - // line, we get stuck there. Trap this here. - if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) { - goto extend; - } - if (VIsual.lnum != start_lnum) { - VIsual.lnum = start_lnum; - VIsual.col = 0; - } - VIsual_mode = 'V'; - redraw_curbuf_later(INVERTED); // update the inversion - showmode(); - } else { - oap->start.lnum = start_lnum; - oap->start.col = 0; - oap->motion_type = kMTLineWise; - } - curwin->w_cursor.lnum = end_lnum; - curwin->w_cursor.col = 0; - - return OK; -} - -/// Search quote char from string line[col]. -/// Quote character escaped by one of the characters in "escape" is not counted -/// as a quote. -/// -/// @param escape escape characters, can be NULL -/// -/// @return column number of "quotechar" or -1 when not found. -static int find_next_quote(char_u *line, int col, int quotechar, char_u *escape) -{ - int c; - - for (;;) { - c = line[col]; - if (c == NUL) { - return -1; - } else if (escape != NULL && vim_strchr((char *)escape, c)) { - col++; - if (line[col] == NUL) { - return -1; - } - } else if (c == quotechar) { - break; - } - col += utfc_ptr2len((char *)line + col); - } - return col; -} - -/// Search backwards in "line" from column "col_start" to find "quotechar". -/// Quote character escaped by one of the characters in "escape" is not counted -/// as a quote. -/// -/// @param escape escape characters, can be NULL -/// -/// @return the found column or zero. -static int find_prev_quote(char_u *line, int col_start, int quotechar, char_u *escape) -{ - int n; - - while (col_start > 0) { - col_start--; - col_start -= utf_head_off(line, line + col_start); - n = 0; - if (escape != NULL) { - while (col_start - n > 0 && vim_strchr((char *)escape, - line[col_start - n - 1]) != NULL) { - n++; - } - } - if (n & 1) { - col_start -= n; // uneven number of escape chars, skip it - } else if (line[col_start] == quotechar) { - break; - } - } - return col_start; -} - -/// Find quote under the cursor, cursor at end. -/// -/// @param include true == include quote char -/// @param quotechar Quote character -/// -/// @return true if found, else false. -bool current_quote(oparg_T *oap, long count, bool include, int quotechar) - FUNC_ATTR_NONNULL_ALL -{ - char_u *line = get_cursor_line_ptr(); - int col_end; - int col_start = curwin->w_cursor.col; - bool inclusive = false; - bool vis_empty = true; // Visual selection <= 1 char - bool vis_bef_curs = false; // Visual starts before cursor - bool did_exclusive_adj = false; // adjusted pos for 'selection' - bool inside_quotes = false; // Looks like "i'" done before - bool selected_quote = false; // Has quote inside selection - int i; - bool restore_vis_bef = false; // resotre VIsual on abort - - // When 'selection' is "exclusive" move the cursor to where it would be - // with 'selection' "inclusive", so that the logic is the same for both. - // The cursor then is moved forward after adjusting the area. - if (VIsual_active) { - // this only works within one line - if (VIsual.lnum != curwin->w_cursor.lnum) { - return false; - } - - vis_bef_curs = lt(VIsual, curwin->w_cursor); - vis_empty = equalpos(VIsual, curwin->w_cursor); - if (*p_sel == 'e') { - if (vis_bef_curs) { - dec_cursor(); - did_exclusive_adj = true; - } else if (!vis_empty) { - dec(&VIsual); - did_exclusive_adj = true; - } - vis_empty = equalpos(VIsual, curwin->w_cursor); - if (!vis_bef_curs && !vis_empty) { - // VIsual needs to be start of Visual selection. - pos_T t = curwin->w_cursor; - - curwin->w_cursor = VIsual; - VIsual = t; - vis_bef_curs = true; - restore_vis_bef = true; - } - } - } - - if (!vis_empty) { - // Check if the existing selection exactly spans the text inside - // quotes. - if (vis_bef_curs) { - inside_quotes = VIsual.col > 0 - && line[VIsual.col - 1] == quotechar - && line[curwin->w_cursor.col] != NUL - && line[curwin->w_cursor.col + 1] == quotechar; - i = VIsual.col; - col_end = curwin->w_cursor.col; - } else { - inside_quotes = curwin->w_cursor.col > 0 - && line[curwin->w_cursor.col - 1] == quotechar - && line[VIsual.col] != NUL - && line[VIsual.col + 1] == quotechar; - i = curwin->w_cursor.col; - col_end = VIsual.col; - } - - // Find out if we have a quote in the selection. - while (i <= col_end) { - // check for going over the end of the line, which can happen if - // the line was changed after the Visual area was selected. - if (line[i] == NUL) { - break; - } - if (line[i++] == quotechar) { - selected_quote = true; - break; - } - } - } - - if (!vis_empty && line[col_start] == quotechar) { - // Already selecting something and on a quote character. Find the - // next quoted string. - if (vis_bef_curs) { - // Assume we are on a closing quote: move to after the next - // opening quote. - col_start = find_next_quote(line, col_start + 1, quotechar, NULL); - if (col_start < 0) { - goto abort_search; - } - col_end = find_next_quote(line, col_start + 1, quotechar, - curbuf->b_p_qe); - if (col_end < 0) { - // We were on a starting quote perhaps? - col_end = col_start; - col_start = curwin->w_cursor.col; - } - } else { - col_end = find_prev_quote(line, col_start, quotechar, NULL); - if (line[col_end] != quotechar) { - goto abort_search; - } - col_start = find_prev_quote(line, col_end, quotechar, - curbuf->b_p_qe); - if (line[col_start] != quotechar) { - // We were on an ending quote perhaps? - col_start = col_end; - col_end = curwin->w_cursor.col; - } - } - } else if (line[col_start] == quotechar || !vis_empty) { - int first_col = col_start; - - if (!vis_empty) { - if (vis_bef_curs) { - first_col = find_next_quote(line, col_start, quotechar, NULL); - } else { - first_col = find_prev_quote(line, col_start, quotechar, NULL); - } - } - // The cursor is on a quote, we don't know if it's the opening or - // closing quote. Search from the start of the line to find out. - // Also do this when there is a Visual area, a' may leave the cursor - // in between two strings. - col_start = 0; - for (;;) { - // Find open quote character. - col_start = find_next_quote(line, col_start, quotechar, NULL); - if (col_start < 0 || col_start > first_col) { - goto abort_search; - } - // Find close quote character. - col_end = find_next_quote(line, col_start + 1, quotechar, - curbuf->b_p_qe); - if (col_end < 0) { - goto abort_search; - } - // If is cursor between start and end quote character, it is - // target text object. - if (col_start <= first_col && first_col <= col_end) { - break; - } - col_start = col_end + 1; - } - } else { - // Search backward for a starting quote. - col_start = find_prev_quote(line, col_start, quotechar, curbuf->b_p_qe); - if (line[col_start] != quotechar) { - // No quote before the cursor, look after the cursor. - col_start = find_next_quote(line, col_start, quotechar, NULL); - if (col_start < 0) { - goto abort_search; - } - } - - // Find close quote character. - col_end = find_next_quote(line, col_start + 1, quotechar, - curbuf->b_p_qe); - if (col_end < 0) { - goto abort_search; - } + return; } - // When "include" is true, include spaces after closing quote or before - // the starting quote. - if (include) { - if (ascii_iswhite(line[col_end + 1])) { - while (ascii_iswhite(line[col_end + 1])) { - col_end++; - } - } else { - while (col_start > 0 && ascii_iswhite(line[col_start - 1])) { - col_start--; - } - } + if (lpos->lnum < curwin->w_topline || lpos->lnum >= curwin->w_botline) { + return; } - // Set start position. After vi" another i" must include the ". - // For v2i" include the quotes. - if (!include && count < 2 && (vis_empty || !inside_quotes)) { - col_start++; - } - curwin->w_cursor.col = col_start; - if (VIsual_active) { - // Set the start of the Visual area when the Visual area was empty, we - // were just inside quotes or the Visual area didn't start at a quote - // and didn't include a quote. - if (vis_empty - || (vis_bef_curs - && !selected_quote - && (inside_quotes - || (line[VIsual.col] != quotechar - && (VIsual.col == 0 - || line[VIsual.col - 1] != quotechar))))) { - VIsual = curwin->w_cursor; - redraw_curbuf_later(INVERTED); - } - } else { - oap->start = curwin->w_cursor; - oap->motion_type = kMTCharWise; + if (!curwin->w_p_wrap) { + getvcol(curwin, lpos, NULL, &vcol, NULL); } - // Set end position. - curwin->w_cursor.col = col_end; - if ((include || count > 1 - // After vi" another i" must include the ". - || (!vis_empty && inside_quotes) - ) && inc_cursor() == 2) { - inclusive = true; - } - if (VIsual_active) { - if (vis_empty || vis_bef_curs) { - // decrement cursor when 'selection' is not exclusive - if (*p_sel != 'e') { - dec_cursor(); - } - } else { - // Cursor is at start of Visual area. Set the end of the Visual - // area when it was just inside quotes or it didn't end at a - // quote. - if (inside_quotes - || (!selected_quote - && line[VIsual.col] != quotechar - && (line[VIsual.col] == NUL - || line[VIsual.col + 1] != quotechar))) { - dec_cursor(); - VIsual = curwin->w_cursor; - } - curwin->w_cursor.col = col_start; - } - if (VIsual_mode == 'V') { - VIsual_mode = 'v'; - redraw_cmdline = true; // show mode later - } - } else { - // Set inclusive and other oap's flags. - oap->inclusive = inclusive; + bool col_visible = curwin->w_p_wrap + || (vcol >= curwin->w_leftcol + && vcol < curwin->w_leftcol + curwin->w_width_inner); + if (!col_visible) { + return; } - return true; - -abort_search: - if (VIsual_active && *p_sel == 'e') { - if (did_exclusive_adj) { - inc_cursor(); - } - if (restore_vis_bef) { - pos_T t = curwin->w_cursor; - - curwin->w_cursor = VIsual; - VIsual = t; - } - } - return false; + mpos = *lpos; // save the pos, update_screen() may change it + save_cursor = curwin->w_cursor; + save_so = *so; + save_siso = *siso; + // Handle "$" in 'cpo': If the ')' is typed on top of the "$", + // stop displaying the "$". + if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol) { + dollar_vcol = -1; + } + curwin->w_virtcol++; // do display ')' just before "$" + update_screen(); // show the new char first + + save_dollar_vcol = dollar_vcol; + save_state = State; + State = MODE_SHOWMATCH; + ui_cursor_shape(); // may show different cursor shape + curwin->w_cursor = mpos; // move to matching char + *so = 0; // don't use 'scrolloff' here + *siso = 0; // don't use 'sidescrolloff' here + show_cursor_info(false); + setcursor(); + ui_flush(); + // Restore dollar_vcol(), because setcursor() may call curs_rows() + // which resets it if the matching position is in a previous line + // and has a higher column number. + dollar_vcol = save_dollar_vcol; + + // brief pause, unless 'm' is present in 'cpo' and a character is + // available. + if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) { + os_delay((uint64_t)p_mat * 100L + 8, true); + } else if (!char_avail()) { + os_delay((uint64_t)p_mat * 100L + 9, false); + } + curwin->w_cursor = save_cursor; // restore cursor position + *so = save_so; + *siso = save_siso; + State = save_state; + ui_cursor_shape(); // may show different cursor shape } /// Find next search match under cursor, cursor at end. @@ -4292,8 +2421,7 @@ int current_search(long count, bool forward) } // Is the pattern is zero-width?, this time, don't care about the direction - int zero_width = is_zero_width(spats[last_idx].pat, true, &curwin->w_cursor, - FORWARD); + int zero_width = is_zero_width(spats[last_idx].pat, true, &curwin->w_cursor, FORWARD); if (zero_width == -1) { return FAIL; // pattern not found } @@ -4347,7 +2475,7 @@ int current_search(long count, bool forward) } else { // try again from end of buffer // searching backwards, so set pos to last line and col pos.lnum = curwin->w_buffer->b_ml.ml_line_count; - pos.col = (colnr_T)STRLEN(ml_get(curwin->w_buffer->b_ml.ml_line_count)); + pos.col = (colnr_T)strlen(ml_get(curwin->w_buffer->b_ml.ml_line_count)); } } } @@ -4389,7 +2517,7 @@ int current_search(long count, bool forward) may_start_select('c'); setmouse(); - redraw_curbuf_later(INVERTED); + redraw_curbuf_later(UPD_INVERTED); showmode(); return OK; @@ -4399,8 +2527,8 @@ int current_search(long count, bool forward) /// If move is true, check from the beginning of the buffer, /// else from position "cur". /// "direction" is FORWARD or BACKWARD. -/// Returns TRUE, FALSE or -1 for failure. -static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direction) +/// Returns true, false or -1 for failure. +static int is_zero_width(char *pattern, int move, pos_T *cur, Direction direction) { regmmatch_T regmatch; int nmatched = 0; @@ -4413,7 +2541,7 @@ static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direct pattern = spats[last_idx].pat; } - if (search_regcomp(pattern, RE_SEARCH, RE_SEARCH, + if (search_regcomp(pattern, NULL, RE_SEARCH, RE_SEARCH, SEARCH_KEEP, ®match) == FAIL) { return -1; } @@ -4456,76 +2584,76 @@ static int is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direct return result; } -/* - * return TRUE if line 'lnum' is empty or has white chars only. - */ +/// return true if line 'lnum' is empty or has white chars only. int linewhite(linenr_T lnum) { - char_u *p; + char *p; - p = (char_u *)skipwhite((char *)ml_get(lnum)); + p = skipwhite(ml_get(lnum)); return *p == NUL; } -// Add the search count "[3/19]" to "msgbuf". -// See update_search_stat() for other arguments. +/// Add the search count "[3/19]" to "msgbuf". +/// See update_search_stat() for other arguments. static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, bool show_top_bot_msg, - char_u *msgbuf, bool recompute, int maxcount, long timeout) + char *msgbuf, bool recompute, int maxcount, long timeout) { searchstat_T stat; update_search_stat(dirc, pos, cursor_pos, &stat, recompute, maxcount, timeout); - if (stat.cur > 0) { - char t[SEARCH_STAT_BUF_LEN]; - - if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { - if (stat.incomplete == 1) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); - } else if (stat.cnt > maxcount && stat.cur > maxcount) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", - maxcount, maxcount); - } else if (stat.cnt > maxcount) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]", - maxcount, stat.cur); - } else { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", - stat.cnt, stat.cur); - } + if (stat.cur <= 0) { + return; + } + + char t[SEARCH_STAT_BUF_LEN]; + + if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { + if (stat.incomplete == 1) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); + } else if (stat.cnt > maxcount && stat.cur > maxcount) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", + maxcount, maxcount); + } else if (stat.cnt > maxcount) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/%d]", + maxcount, stat.cur); } else { - if (stat.incomplete == 1) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); - } else if (stat.cnt > maxcount && stat.cur > maxcount) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", - maxcount, maxcount); - } else if (stat.cnt > maxcount) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]", - stat.cur, maxcount); - } else { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", - stat.cur, stat.cnt); - } + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", + stat.cnt, stat.cur); } - - size_t len = strlen(t); - if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) { - memmove(t + 2, t, len); - t[0] = 'W'; - t[1] = ' '; - len += 2; + } else { + if (stat.incomplete == 1) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); + } else if (stat.cnt > maxcount && stat.cur > maxcount) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>%d/>%d]", + maxcount, maxcount); + } else if (stat.cnt > maxcount) { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/>%d]", + stat.cur, maxcount); + } else { + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[%d/%d]", + stat.cur, stat.cnt); } + } - memmove(msgbuf + STRLEN(msgbuf) - len, t, len); - if (dirc == '?' && stat.cur == maxcount + 1) { - stat.cur = -1; - } + size_t len = strlen(t); + if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) { + memmove(t + 2, t, len); + t[0] = 'W'; + t[1] = ' '; + len += 2; + } - // keep the message even after redraw, but don't put in history - msg_hist_off = true; - msg_ext_set_kind("search_count"); - give_warning((char *)msgbuf, false); - msg_hist_off = false; + memmove(msgbuf + strlen(msgbuf) - len, t, len); + if (dirc == '?' && stat.cur == maxcount + 1) { + stat.cur = -1; } + + // keep the message even after redraw, but don't put in history + msg_hist_off = true; + msg_ext_set_kind("search_count"); + give_warning(msgbuf, false); + msg_hist_off = false; } // Add the search count information to "stat". @@ -4547,7 +2675,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst static int incomplete = 0; static int last_maxcount = SEARCH_STAT_DEF_MAX_COUNT; static int chgtick = 0; - static char_u *lastpat = NULL; + static char *lastpat = NULL; static buf_T *lbuf = NULL; proftime_T start; @@ -4571,8 +2699,8 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst // XXX: above comment should be "no MB_STRCMP function" ? if (!(chgtick == buf_get_changedtick(curbuf) && lastpat != NULL // suppress clang/NULL passed as nonnull parameter - && STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0 - && STRLEN(lastpat) == STRLEN(spats[last_idx].pat) + && STRNICMP(lastpat, spats[last_idx].pat, strlen(lastpat)) == 0 + && strlen(lastpat) == strlen(spats[last_idx].pat) && equalpos(lastpos, *cursor_pos) && lbuf == curbuf) || wraparound || cur < 0 || (maxcount > 0 && cur > maxcount) @@ -4622,7 +2750,7 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst } if (done_search) { xfree(lastpat); - lastpat = vim_strsave(spats[last_idx].pat); + lastpat = xstrdup(spats[last_idx].pat); chgtick = (int)buf_get_changedtick(curbuf); lbuf = curbuf; lastpos = p; @@ -4637,10 +2765,10 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst } // "searchcount()" function -void f_searchcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { pos_T pos = curwin->w_cursor; - char_u *pattern = NULL; + char *pattern = NULL; int maxcount = SEARCH_STAT_DEF_MAX_COUNT; long timeout = SEARCH_STAT_DEF_TIMEOUT; bool recompute = true; @@ -4684,9 +2812,9 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } } - di = tv_dict_find(dict, (const char *)"pattern", -1); + di = tv_dict_find(dict, "pattern", -1); if (di != NULL) { - pattern = (char_u *)tv_get_string_chk(&di->di_tv); + pattern = (char *)tv_get_string_chk(&di->di_tv); if (pattern == NULL) { return; } @@ -4732,7 +2860,7 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, FunPtr fptr) goto the_end; } xfree(spats[last_idx].pat); - spats[last_idx].pat = vim_strsave(pattern); + spats[last_idx].pat = xstrdup(pattern); } if (spats[last_idx].pat == NULL || *spats[last_idx].pat == NUL) { goto the_end; // the previous pattern was never defined @@ -4830,8 +2958,8 @@ typedef struct { #define FUZZY_MATCH_RECURSION_LIMIT 10 /// Compute a score for a fuzzy matched string. The matching character locations -/// are in 'matches'. -static int fuzzy_match_compute_score(const char_u *const str, const int strSz, +/// are in "matches". +static int fuzzy_match_compute_score(const char *const str, const int strSz, const uint32_t *const matches, const int numMatches) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE { @@ -4868,14 +2996,14 @@ static int fuzzy_match_compute_score(const char_u *const str, const int strSz, // Check for bonuses based on neighbor character value if (currIdx > 0) { // Camel case - const char_u *p = str; - int neighbor; + const char *p = str; + int neighbor = ' '; for (uint32_t sidx = 0; sidx < currIdx; sidx++) { - neighbor = utf_ptr2char((char *)p); + neighbor = utf_ptr2char(p); MB_PTR_ADV(p); } - const int curr = utf_ptr2char((char *)p); + const int curr = utf_ptr2char(p); if (mb_islower(neighbor) && mb_isupper(curr)) { score += CAMEL_BONUS; @@ -4895,13 +3023,12 @@ static int fuzzy_match_compute_score(const char_u *const str, const int strSz, return score; } -/// Perform a recursive search for fuzzy matching 'fuzpat' in 'str'. +/// Perform a recursive search for fuzzy matching "fuzpat" in "str". /// @return the number of matching characters. -static int fuzzy_match_recursive(const char_u *fuzpat, const char_u *str, uint32_t strIdx, - int *const outScore, const char_u *const strBegin, - const int strLen, const uint32_t *const srcMatches, - uint32_t *const matches, const int maxMatches, int nextMatch, - int *const recursionCount) +static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t strIdx, + int *const outScore, const char *const strBegin, const int strLen, + const uint32_t *const srcMatches, uint32_t *const matches, + const int maxMatches, int nextMatch, int *const recursionCount) FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5, 8, 11) FUNC_ATTR_WARN_UNUSED_RESULT { // Recursion params @@ -4942,7 +3069,7 @@ static int fuzzy_match_recursive(const char_u *fuzpat, const char_u *str, uint32 // Recursive call that "skips" this match uint32_t recursiveMatches[MAX_FUZZY_MATCHES]; int recursiveScore = 0; - const char_u *const next_char = str + utfc_ptr2len((char *)str); + const char *const next_char = (char *)str + utfc_ptr2len((char *)str); if (fuzzy_match_recursive(fuzpat, next_char, strIdx + 1, &recursiveScore, strBegin, strLen, matches, recursiveMatches, sizeof(recursiveMatches) / sizeof(recursiveMatches[0]), nextMatch, @@ -4996,11 +3123,11 @@ static int fuzzy_match_recursive(const char_u *fuzpat, const char_u *str, uint32 /// Uses char_u for match indices. Therefore patterns are limited to /// MAX_FUZZY_MATCHES characters. /// -/// @return true if 'pat_arg' matches 'str'. Also returns the match score in -/// 'outScore' and the matching character positions in 'matches'. -bool fuzzy_match(char_u *const str, const char_u *const pat_arg, const bool matchseq, +/// @return true if "pat_arg" matches "str". Also returns the match score in +/// "outScore" and the matching character positions in "matches". +bool fuzzy_match(char *const str, const char *const pat_arg, const bool matchseq, int *const outScore, uint32_t *const matches, const int maxMatches) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL { const int len = mb_charlen(str); bool complete = false; @@ -5008,22 +3135,22 @@ bool fuzzy_match(char_u *const str, const char_u *const pat_arg, const bool matc *outScore = 0; - char_u *const save_pat = vim_strsave(pat_arg); - char_u *pat = save_pat; - char_u *p = pat; + char *const save_pat = xstrdup(pat_arg); + char *pat = save_pat; + char *p = pat; - // Try matching each word in 'pat_arg' in 'str' + // Try matching each word in "pat_arg" in "str" while (true) { if (matchseq) { complete = true; } else { // Extract one word from the pattern (separated by space) - p = (char_u *)skipwhite((char *)p); + p = skipwhite(p); if (*p == NUL) { break; } pat = p; - while (*p != NUL && !ascii_iswhite(utf_ptr2char((char *)p))) { + while (*p != NUL && !ascii_iswhite(utf_ptr2char(p))) { MB_PTR_ADV(p); } if (*p == NUL) { // processed all the words @@ -5035,7 +3162,8 @@ bool fuzzy_match(char_u *const str, const char_u *const pat_arg, const bool matc int score = 0; int recursionCount = 0; const int matchCount - = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL, matches + numMatches, + = fuzzy_match_recursive(pat, str, 0, &score, str, len, NULL, + matches + numMatches, maxMatches - numMatches, 0, &recursionCount); if (matchCount == 0) { numMatches = 0; @@ -5071,17 +3199,17 @@ static int fuzzy_match_item_compare(const void *const s1, const void *const s2) return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1; } -/// Fuzzy search the string 'str' in a list of 'items' and return the matching -/// strings in 'fmatchlist'. -/// If 'matchseq' is true, then for multi-word search strings, match all the +/// Fuzzy search the string "str" in a list of "items" and return the matching +/// strings in "fmatchlist". +/// If "matchseq" is true, then for multi-word search strings, match all the /// words in sequence. -/// If 'items' is a list of strings, then search for 'str' in the list. -/// If 'items' is a list of dicts, then either use 'key' to lookup the string -/// for each item or use 'item_cb' Funcref function to get the string. -/// If 'retmatchpos' is true, then return a list of positions where 'str' +/// If "items" is a list of strings, then search for "str" in the list. +/// If "items" is a list of dicts, then either use "key" to lookup the string +/// for each item or use "item_cb" Funcref function to get the string. +/// If "retmatchpos" is true, then return a list of positions where "str" /// matches for each item. -static void fuzzy_match_in_list(list_T *const l, char_u *const str, const bool matchseq, - const char_u *const key, Callback *const item_cb, +static void fuzzy_match_in_list(list_T *const l, char *const str, const bool matchseq, + const char *const key, Callback *const item_cb, const bool retmatchpos, list_T *const fmatchlist, const long max_matches) FUNC_ATTR_NONNULL_ARG(2, 5, 7) @@ -5104,17 +3232,17 @@ static void fuzzy_match_in_list(list_T *const l, char_u *const str, const bool m break; } - char_u *itemstr = NULL; + char *itemstr = NULL; typval_T rettv; rettv.v_type = VAR_UNKNOWN; const typval_T *const tv = TV_LIST_ITEM_TV(li); if (tv->v_type == VAR_STRING) { // list of strings - itemstr = (char_u *)tv->vval.v_string; + itemstr = tv->vval.v_string; } else if (tv->v_type == VAR_DICT && (key != NULL || item_cb->type != kCallbackNone)) { // For a dict, either use the specified key to lookup the string or // use the specified callback function to get the string. if (key != NULL) { - itemstr = (char_u *)tv_dict_get_string(tv->vval.v_dict, (const char *)key, false); + itemstr = tv_dict_get_string(tv->vval.v_dict, (const char *)key, false); } else { typval_T argv[2]; @@ -5125,7 +3253,7 @@ static void fuzzy_match_in_list(list_T *const l, char_u *const str, const bool m argv[1].v_type = VAR_UNKNOWN; if (callback_call(item_cb, 1, argv, &rettv)) { if (rettv.v_type == VAR_STRING) { - itemstr = (char_u *)rettv.vval.v_string; + itemstr = rettv.vval.v_string; } } tv_dict_unref(tv->vval.v_dict); @@ -5140,13 +3268,13 @@ static void fuzzy_match_in_list(list_T *const l, char_u *const str, const bool m items[match_count].score = score; // Copy the list of matching positions in itemstr to a list, if - // 'retmatchpos' is set. + // "retmatchpos" is set. if (retmatchpos) { items[match_count].lmatchpos = tv_list_alloc(kListLenMayKnow); int j = 0; - const char_u *p = str; + const char *p = (char *)str; while (*p != NUL) { - if (!ascii_iswhite(utf_ptr2char((char *)p)) || matchseq) { + if (!ascii_iswhite(utf_ptr2char(p)) || matchseq) { tv_list_append_number(items[match_count].lmatchpos, matches[j]); j++; } @@ -5214,8 +3342,8 @@ static void fuzzy_match_in_list(list_T *const l, char_u *const str, const bool m xfree(items); } -/// Do fuzzy matching. Returns the list of matched strings in 'rettv'. -/// If 'retmatchpos' is true, also returns the matching character positions. +/// Do fuzzy matching. Returns the list of matched strings in "rettv". +/// If "retmatchpos" is true, also returns the matching character positions. static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, const bool retmatchpos) FUNC_ATTR_NONNULL_ALL @@ -5231,7 +3359,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, } Callback cb = CALLBACK_NONE; - const char_u *key = NULL; + const char *key = NULL; bool matchseq = false; long max_matches = 0; if (argvars[2].v_type != VAR_UNKNOWN) { @@ -5250,7 +3378,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, semsg(_(e_invarg2), tv_get_string(&di->di_tv)); return; } - key = (const char_u *)tv_get_string(&di->di_tv); + key = tv_get_string(&di->di_tv); } else if (!tv_dict_get_callback(d, "text_cb", -1, &cb)) { semsg(_(e_invargval), "text_cb"); return; @@ -5281,35 +3409,139 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, tv_list_append_list(rettv->vval.v_list, tv_list_alloc(kListLenUnknown)); } - fuzzy_match_in_list(argvars[0].vval.v_list, (char_u *)tv_get_string(&argvars[1]), matchseq, key, + fuzzy_match_in_list(argvars[0].vval.v_list, + (char *)tv_get_string(&argvars[1]), matchseq, key, &cb, retmatchpos, rettv->vval.v_list, max_matches); callback_free(&cb); } /// "matchfuzzy()" function -void f_matchfuzzy(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchfuzzy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_fuzzymatch(argvars, rettv, false); } /// "matchfuzzypos()" function -void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_matchfuzzypos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { do_fuzzymatch(argvars, rettv, true); } +/// Same as fuzzy_match_item_compare() except for use with a string match +static int fuzzy_match_str_compare(const void *const s1, const void *const s2) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + const int v1 = ((fuzmatch_str_T *)s1)->score; + const int v2 = ((fuzmatch_str_T *)s2)->score; + const int idx1 = ((fuzmatch_str_T *)s1)->idx; + const int idx2 = ((fuzmatch_str_T *)s2)->idx; + + return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1; +} + +/// Sort fuzzy matches by score +static void fuzzy_match_str_sort(fuzmatch_str_T *const fm, const int sz) + FUNC_ATTR_NONNULL_ALL +{ + // Sort the list by the descending order of the match score + qsort(fm, (size_t)sz, sizeof(fuzmatch_str_T), fuzzy_match_str_compare); +} + +/// Same as fuzzy_match_item_compare() except for use with a function name +/// string match. <SNR> functions should be sorted to the end. +static int fuzzy_match_func_compare(const void *const s1, const void *const s2) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + const int v1 = ((fuzmatch_str_T *)s1)->score; + const int v2 = ((fuzmatch_str_T *)s2)->score; + const int idx1 = ((fuzmatch_str_T *)s1)->idx; + const int idx2 = ((fuzmatch_str_T *)s2)->idx; + const char *const str1 = ((fuzmatch_str_T *)s1)->str; + const char *const str2 = ((fuzmatch_str_T *)s2)->str; + + if (*str1 != '<' && *str2 == '<') { + return -1; + } + if (*str1 == '<' && *str2 != '<') { + return 1; + } + return v1 == v2 ? (idx1 - idx2) : v1 > v2 ? -1 : 1; +} + +/// Sort fuzzy matches of function names by score. +/// <SNR> functions should be sorted to the end. +static void fuzzy_match_func_sort(fuzmatch_str_T *const fm, const int sz) + FUNC_ATTR_NONNULL_ALL +{ + // Sort the list by the descending order of the match score + qsort(fm, (size_t)sz, sizeof(fuzmatch_str_T), fuzzy_match_func_compare); +} + +/// Fuzzy match "pat" in "str". +/// @returns 0 if there is no match. Otherwise, returns the match score. +int fuzzy_match_str(char *const str, const char *const pat) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (str == NULL || pat == NULL) { + return 0; + } + + int score = 0; + uint32_t matchpos[MAX_FUZZY_MATCHES]; + fuzzy_match(str, pat, true, &score, matchpos, sizeof(matchpos) / sizeof(matchpos[0])); + + return score; +} + +/// Copy a list of fuzzy matches into a string list after sorting the matches by +/// the fuzzy score. Frees the memory allocated for "fuzmatch". +void fuzzymatches_to_strmatches(fuzmatch_str_T *const fuzmatch, char ***const matches, + const int count, const bool funcsort) + FUNC_ATTR_NONNULL_ARG(2) +{ + if (count <= 0) { + return; + } + + *matches = xmalloc((size_t)count * sizeof(char *)); + + // Sort the list by the descending order of the match score + if (funcsort) { + fuzzy_match_func_sort(fuzmatch, count); + } else { + fuzzy_match_str_sort(fuzmatch, count); + } + + for (int i = 0; i < count; i++) { + (*matches)[i] = fuzmatch[i].str; + } + xfree(fuzmatch); +} + +/// Free a list of fuzzy string matches. +void fuzmatch_str_free(fuzmatch_str_T *const fuzmatch, int count) +{ + if (count <= 0 || fuzmatch == NULL) { + return; + } + while (count--) { + xfree(fuzmatch[count].str); + } + xfree(fuzmatch); +} + /// Get line "lnum" and copy it into "buf[LSIZE]". /// The copy is made because the regexp may make the line invalid when using a /// mark. -static char_u *get_line_and_copy(linenr_T lnum, char_u *buf) +static char *get_line_and_copy(linenr_T lnum, char *buf) { - char_u *line = ml_get(lnum); - STRLCPY(buf, line, LSIZE); + char *line = ml_get(lnum); + xstrlcpy(buf, line, LSIZE); return buf; } /// Find identifiers or defines in included files. -/// If p_ic && (compl_cont_status & CONT_SOL) then ptr must be in lowercase. +/// If p_ic && compl_status_sol() then ptr must be in lowercase. /// /// @param ptr pointer to search pattern /// @param dir direction of expansion @@ -5320,7 +3552,7 @@ static char_u *get_line_and_copy(linenr_T lnum, char_u *buf) /// @param action What to do when we find it /// @param start_lnum first line to start searching /// @param end_lnum last line for searching -void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bool skip_comments, +void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool skip_comments, int type, long count, int action, linenr_T start_lnum, linenr_T end_lnum) { SearchedFile *files; // Stack of included files @@ -5328,19 +3560,19 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo int max_path_depth = 50; long match_count = 1; - char_u *pat; - char_u *new_fname; - char_u *curr_fname = (char_u *)curbuf->b_fname; - char_u *prev_fname = NULL; + char *pat; + char *new_fname; + char *curr_fname = curbuf->b_fname; + char *prev_fname = NULL; linenr_T lnum; int depth; int depth_displayed; // For type==CHECK_PATH int old_files; int already_searched; - char_u *file_line; - char_u *line; - char_u *p; - char_u save_char; + char *file_line; + char *line; + char *p; + char save_char; bool define_matched; regmatch_T regmatch; regmatch_T incl_regmatch; @@ -5349,9 +3581,8 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo bool did_show = false; bool found = false; int i; - char_u *already = NULL; - char_u *startp = NULL; - char_u *inc_opt = NULL; + char *already = NULL; + char *startp = NULL; win_T *curwin_save = NULL; const int l_g_do_tagpreview = g_do_tagpreview; @@ -5362,36 +3593,37 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo file_line = xmalloc(LSIZE); if (type != CHECK_PATH && type != FIND_DEFINE - // when CONT_SOL is set compare "ptr" with the beginning of the line - // is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo - && !(compl_cont_status & CONT_SOL)) { - pat = xmalloc(len + 5); + // when CONT_SOL is set compare "ptr" with the beginning of the + // line is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo + && !compl_status_sol()) { + size_t patlen = len + 5; + pat = xmalloc(patlen); assert(len <= INT_MAX); - sprintf((char *)pat, whole ? "\\<%.*s\\>" : "%.*s", (int)len, ptr); + snprintf(pat, patlen, whole ? "\\<%.*s\\>" : "%.*s", (int)len, ptr); // ignore case according to p_ic, p_scs and pat regmatch.rm_ic = ignorecase(pat); - regmatch.regprog = vim_regcomp((char *)pat, p_magic ? RE_MAGIC : 0); + regmatch.regprog = vim_regcomp(pat, magic_isset() ? RE_MAGIC : 0); xfree(pat); if (regmatch.regprog == NULL) { goto fpip_end; } } - inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc; + char *inc_opt = (*curbuf->b_p_inc == NUL) ? p_inc : curbuf->b_p_inc; if (*inc_opt != NUL) { - incl_regmatch.regprog = vim_regcomp((char *)inc_opt, p_magic ? RE_MAGIC : 0); + incl_regmatch.regprog = vim_regcomp(inc_opt, magic_isset() ? RE_MAGIC : 0); if (incl_regmatch.regprog == NULL) { goto fpip_end; } - incl_regmatch.rm_ic = FALSE; // don't ignore case in incl. pat. + incl_regmatch.rm_ic = false; // don't ignore case in incl. pat. } if (type == FIND_DEFINE && (*curbuf->b_p_def != NUL || *p_def != NUL)) { def_regmatch.regprog = vim_regcomp(*curbuf->b_p_def == NUL - ? (char *)p_def : (char *)curbuf->b_p_def, - p_magic ? RE_MAGIC : 0); + ? p_def : curbuf->b_p_def, + magic_isset() ? RE_MAGIC : 0); if (def_regmatch.regprog == NULL) { goto fpip_end; } - def_regmatch.rm_ic = FALSE; // don't ignore case in define pat. + def_regmatch.rm_ic = false; // don't ignore case in define pat. } files = xcalloc((size_t)max_path_depth, sizeof(SearchedFile)); old_files = max_path_depth; @@ -5408,11 +3640,11 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo for (;;) { if (incl_regmatch.regprog != NULL - && vim_regexec(&incl_regmatch, (char *)line, (colnr_T)0)) { - char_u *p_fname = (curr_fname == (char_u *)curbuf->b_fname) - ? (char_u *)curbuf->b_ffname : curr_fname; + && vim_regexec(&incl_regmatch, line, (colnr_T)0)) { + char *p_fname = (curr_fname == curbuf->b_fname) + ? curbuf->b_ffname : curr_fname; - if (inc_opt != NULL && strstr((char *)inc_opt, "\\zs") != NULL) { + if (inc_opt != NULL && strstr(inc_opt, "\\zs") != NULL) { // Use text from '\zs' to '\ze' (or end) of 'include'. new_fname = find_file_name_in_path(incl_regmatch.startp[0], (size_t)(incl_regmatch.endp[0] @@ -5422,9 +3654,10 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo } else { // Use text after match with 'include'. new_fname = file_name_in_line(incl_regmatch.endp[0], 0, - FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname, NULL); + FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname, + NULL); } - already_searched = FALSE; + already_searched = false; if (new_fname != NULL) { // Check whether we have already searched in this file for (i = 0;; i++) { @@ -5434,7 +3667,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo if (i == max_path_depth) { break; } - if (path_full_compare((char *)new_fname, (char *)files[i].name, true, + if (path_full_compare(new_fname, files[i].name, true, true) & kEqualFiles) { if (type != CHECK_PATH && action == ACTION_SHOW_ALL && files[i].matched) { @@ -5484,12 +3717,10 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo // 'includeexpr' is set. msg_outtrans_attr(new_fname, HL_ATTR(HLF_D)); } else { - /* - * Isolate the file name. - * Include the surrounding "" or <> if present. - */ + // Isolate the file name. + // Include the surrounding "" or <> if present. if (inc_opt != NULL - && strstr((char *)inc_opt, "\\zs") != NULL) { + && strstr(inc_opt, "\\zs") != NULL) { // pattern contains \zs, use the match p = incl_regmatch.startp[0]; i = (int)(incl_regmatch.endp[0] @@ -5497,14 +3728,14 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo } else { // find the file name after the end of the match for (p = incl_regmatch.endp[0]; - *p && !vim_isfilec(*p); p++) {} - for (i = 0; vim_isfilec(p[i]); i++) {} + *p && !vim_isfilec((uint8_t)(*p)); p++) {} + for (i = 0; vim_isfilec((uint8_t)p[i]); i++) {} } if (i == 0) { // Nothing found, use the rest of the line. p = incl_regmatch.endp[0]; - i = (int)STRLEN(p); + i = (int)strlen(p); } else if (p > line) { // Avoid checking before the start of the line, can // happen if \zs appears in the regexp. @@ -5530,7 +3761,6 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo } } } - ui_flush(); // output each line directly } if (new_fname != NULL) { @@ -5544,7 +3774,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo bigger[i].fp = NULL; bigger[i].name = NULL; bigger[i].lnum = 0; - bigger[i].matched = FALSE; + bigger[i].matched = false; } for (i = old_files; i < max_path_depth; i++) { bigger[i + max_path_depth] = files[i]; @@ -5554,7 +3784,7 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo xfree(files); files = bigger; } - if ((files[depth + 1].fp = os_fopen((char *)new_fname, "r")) == NULL) { + if ((files[depth + 1].fp = os_fopen(new_fname, "r")) == NULL) { xfree(new_fname); } else { if (++depth == old_files) { @@ -5565,60 +3795,54 @@ void find_pattern_in_path(char_u *ptr, Direction dir, size_t len, bool whole, bo } files[depth].name = curr_fname = new_fname; files[depth].lnum = 0; - files[depth].matched = FALSE; + files[depth].matched = false; if (action == ACTION_EXPAND) { msg_hist_off = true; // reset in msg_trunc_attr() - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Scanning included file: %s"), - (char *)new_fname); - msg_trunc_attr((char *)IObuff, true, HL_ATTR(HLF_R)); + new_fname); + msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R)); } else if (p_verbose >= 5) { verbose_enter(); - smsg(_("Searching included file %s"), - (char *)new_fname); + smsg(_("Searching included file %s"), new_fname); verbose_leave(); } } } } else { - /* - * Check if the line is a define (type == FIND_DEFINE) - */ + // Check if the line is a define (type == FIND_DEFINE) p = line; search_line: define_matched = false; if (def_regmatch.regprog != NULL - && vim_regexec(&def_regmatch, (char *)line, (colnr_T)0)) { + && vim_regexec(&def_regmatch, line, (colnr_T)0)) { // Pattern must be first identifier after 'define', so skip // to that position before checking for match of pattern. Also // don't let it match beyond the end of this identifier. p = def_regmatch.endp[0]; - while (*p && !vim_iswordc(*p)) { + while (*p && !vim_iswordc((uint8_t)(*p))) { p++; } define_matched = true; } - /* - * Look for a match. Don't do this if we are looking for a - * define and this line didn't match define_prog above. - */ + // Look for a match. Don't do this if we are looking for a + // define and this line didn't match define_prog above. if (def_regmatch.regprog == NULL || define_matched) { - if (define_matched - || (compl_cont_status & CONT_SOL)) { + if (define_matched || compl_status_sol()) { // compare the first "len" chars from "ptr" - startp = (char_u *)skipwhite((char *)p); + startp = skipwhite(p); if (p_ic) { matched = !mb_strnicmp(startp, ptr, len); } else { - matched = !STRNCMP(startp, ptr, len); + matched = !strncmp(startp, ptr, len); } if (matched && define_matched && whole - && vim_iswordc(startp[len])) { + && vim_iswordc((uint8_t)startp[len])) { matched = false; } } else if (regmatch.regprog != NULL - && vim_regexec(®match, (char *)line, (colnr_T)(p - line))) { + && vim_regexec(®match, line, (colnr_T)(p - line))) { matched = true; startp = regmatch.startp[0]; // Check if the line is not a comment line (unless we are @@ -5626,21 +3850,19 @@ search_line: // is not considered to be a comment line. if (skip_comments) { if ((*line != '#' - || STRNCMP(skipwhite((char *)line + 1), "define", 6) != 0) - && get_leader_len((char *)line, NULL, false, true)) { + || strncmp(skipwhite(line + 1), "define", 6) != 0) + && get_leader_len(line, NULL, false, true)) { matched = false; } - /* - * Also check for a "/ *" or "/ /" before the match. - * Skips lines like "int backwards; / * normal index - * * /" when looking for "normal". - * Note: Doesn't skip "/ *" in comments. - */ - p = (char_u *)skipwhite((char *)line); + // Also check for a "/ *" or "/ /" before the match. + // Skips lines like "int backwards; / * normal index + // * /" when looking for "normal". + // Note: Doesn't skip "/ *" in comments. + p = skipwhite(line); if (matched || (p[0] == '/' && p[1] == '*') || p[0] == '*') { - for (p = line; *p && p < startp; ++p) { + for (p = line; *p && p < startp; p++) { if (matched && p[0] == '/' && (p[1] == '*' || p[1] == '/')) { @@ -5664,15 +3886,14 @@ search_line: if (matched) { if (action == ACTION_EXPAND) { bool cont_s_ipos = false; - char_u *aux; if (depth == -1 && lnum == curwin->w_cursor.lnum) { break; } found = true; - aux = p = startp; - if (compl_cont_status & CONT_ADDING) { - p += compl_length; + char *aux = p = startp; + if (compl_status_adding()) { + p += ins_compl_len(); if (vim_iswordp(p)) { goto exit_matched; } @@ -5681,9 +3902,9 @@ search_line: p = find_word_end(p); i = (int)(p - aux); - if ((compl_cont_status & CONT_ADDING) && i == compl_length) { - // IOSIZE > compl_length, so the STRNCPY works - STRNCPY(IObuff, aux, i); + if (compl_status_adding() && i == ins_compl_len()) { + // IOSIZE > compl_length, so the strncpy works + strncpy(IObuff, aux, (size_t)i); // NOLINT(runtime/printf) // Get the next line: when "depth" < 0 from the current // buffer, otherwise from the included file. Jump to @@ -5701,7 +3922,7 @@ search_line: // we read a line, set "already" to check this "line" later // if depth >= 0 we'll increase files[depth].lnum far // below -- Acevedo - already = aux = p = (char_u *)skipwhite((char *)line); + already = aux = p = skipwhite(line); p = find_word_start(p); p = find_word_end(p); if (p > aux) { @@ -5721,20 +3942,20 @@ search_line: if (p - aux >= IOSIZE - i) { p = aux + IOSIZE - i - 1; } - STRNCPY(IObuff + i, aux, p - aux); + strncpy(IObuff + i, aux, (size_t)(p - aux)); // NOLINT(runtime/printf) i += (int)(p - aux); cont_s_ipos = true; } IObuff[i] = NUL; aux = IObuff; - if (i == compl_length) { + if (i == ins_compl_len()) { goto exit_matched; } } const int add_r = ins_compl_add_infercase(aux, i, p_ic, - curr_fname == (char_u *)curbuf->b_fname + curr_fname == curbuf->b_fname ? NULL : curr_fname, dir, cont_s_ipos); if (add_r == OK) { @@ -5820,14 +4041,14 @@ search_line: } if (action != ACTION_SHOW) { curwin->w_cursor.col = (colnr_T)(startp - line); - curwin->w_set_curswant = TRUE; + curwin->w_set_curswant = true; } if (l_g_do_tagpreview != 0 && curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were validate_cursor(); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); win_enter(curwin_save, true); } break; @@ -5838,9 +4059,9 @@ exit_matched: // are not at the end of it already if (def_regmatch.regprog == NULL && action == ACTION_EXPAND - && !(compl_cont_status & CONT_SOL) + && !compl_status_sol() && *startp != NUL - && *(p = startp + utfc_ptr2len((char *)startp)) != NUL) { + && *(p = startp + utfc_ptr2len(startp)) != NUL) { goto search_line; } } @@ -5852,11 +4073,9 @@ exit_matched: break; } - /* - * Read the next line. When reading an included file and encountering - * end-of-file, close the file and continue in the file that included - * it. - */ + // Read the next line. When reading an included file and encountering + // end-of-file, close the file and continue in the file that included + // it. while (depth >= 0 && !already && vim_fgets(line = file_line, LSIZE, files[depth].fp)) { fclose(files[depth].fp); @@ -5864,7 +4083,7 @@ exit_matched: files[old_files].name = files[depth].name; files[old_files].matched = files[depth].matched; depth--; - curr_fname = (depth == -1) ? (char_u *)curbuf->b_fname + curr_fname = (depth == -1) ? curbuf->b_fname : files[depth].name; if (depth < depth_displayed) { depth_displayed = depth; @@ -5873,7 +4092,7 @@ exit_matched: if (depth >= 0) { // we could read the line files[depth].lnum++; // Remove any CR and LF from the line. - i = (int)STRLEN(line); + i = (int)strlen(line); if (i > 0 && line[i - 1] == '\n') { line[--i] = NUL; } @@ -5929,11 +4148,11 @@ fpip_end: vim_regfree(def_regmatch.regprog); } -static void show_pat_in_path(char_u *line, int type, bool did_show, int action, FILE *fp, +static void show_pat_in_path(char *line, int type, bool did_show, int action, FILE *fp, linenr_T *lnum, long count) FUNC_ATTR_NONNULL_ARG(1, 6) { - char_u *p; + char *p; if (did_show) { msg_putchar('\n'); // cursor below last one @@ -5944,7 +4163,7 @@ static void show_pat_in_path(char_u *line, int type, bool did_show, int action, return; } for (;;) { - p = line + STRLEN(line) - 1; + p = line + strlen(line) - 1; if (fp != NULL) { // We used fgets(), so get rid of newline at end if (p >= line && *p == '\n') { @@ -5956,15 +4175,14 @@ static void show_pat_in_path(char_u *line, int type, bool did_show, int action, *(p + 1) = NUL; } if (action == ACTION_SHOW_ALL) { - snprintf((char *)IObuff, IOSIZE, "%3ld: ", count); // Show match nr. + snprintf(IObuff, IOSIZE, "%3ld: ", count); // Show match nr. msg_puts((const char *)IObuff); - snprintf((char *)IObuff, IOSIZE, "%4" PRIdLINENR, *lnum); // Show line nr. + snprintf(IObuff, IOSIZE, "%4" PRIdLINENR, *lnum); // Show line nr. // Highlight line numbers. msg_puts_attr((const char *)IObuff, HL_ATTR(HLF_N)); msg_puts(" "); } - msg_prt_line(line, FALSE); - ui_flush(); // show one line at a time + msg_prt_line(line, false); // Definition continues until line that doesn't end with '\' if (got_int || type != FIND_DEFINE || p < line || *p != '\\') { @@ -5975,7 +4193,7 @@ static void show_pat_in_path(char_u *line, int type, bool did_show, int action, if (vim_fgets(line, LSIZE, fp)) { // end of file break; } - ++*lnum; + (*lnum)++; } else { if (++*lnum > curbuf->b_ml.ml_line_count) { break; diff --git a/src/nvim/search.h b/src/nvim/search.h index ff843bb59e..2f140ba840 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -6,8 +6,11 @@ #include "nvim/buffer_defs.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/normal.h" #include "nvim/os/time.h" +#include "nvim/pos.h" +#include "nvim/types.h" #include "nvim/vim.h" // Values for the find_pattern_in_path() function args 'type' and 'action': @@ -70,7 +73,7 @@ typedef struct soffset { /// Structure containing last search pattern and its attributes. typedef struct spat { - char_u *pat; ///< The pattern (in allocated memory) or NULL. + char *pat; ///< The pattern (in allocated memory) or NULL. bool magic; ///< Magicness of the pattern. bool no_scs; ///< No smartcase for this pattern. Timestamp timestamp; ///< Time of the last change. @@ -96,6 +99,14 @@ typedef struct searchstat { int last_maxcount; // the max count of the last search } searchstat_T; +/// Fuzzy matched string list item. Used for fuzzy match completion. Items are +/// usually sorted by "score". The "idx" member is used for stable-sort. +typedef struct { + int idx; + char *str; + int score; +} fuzmatch_str_T; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "search.h.generated.h" #endif diff --git a/src/nvim/sha256.c b/src/nvim/sha256.c index 53c9cb7c81..db647f3ecb 100644 --- a/src/nvim/sha256.c +++ b/src/nvim/sha256.c @@ -13,11 +13,13 @@ /// Vim specific notes: /// sha256_self_test() is implicitly called once. -#include <stddef.h> // for size_t -#include <stdio.h> // for snprintf(). +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> -#include "nvim/sha256.h" // for context_sha256_T -#include "nvim/vim.h" // for STRCPY()/STRLEN(). +#include "nvim/sha256.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "sha256.c.generated.h" @@ -30,10 +32,10 @@ } #define PUT_UINT32(n, b, i) { \ - (b)[(i)] = (char_u)((n) >> 24); \ - (b)[(i) + 1] = (char_u)((n) >> 16); \ - (b)[(i) + 2] = (char_u)((n) >> 8); \ - (b)[(i) + 3] = (char_u)((n)); \ + (b)[(i)] = (uint8_t)((n) >> 24); \ + (b)[(i) + 1] = (uint8_t)((n) >> 16); \ + (b)[(i) + 2] = (uint8_t)((n) >> 8); \ + (b)[(i) + 3] = (uint8_t)((n)); \ } void sha256_start(context_sha256_T *ctx) @@ -51,7 +53,7 @@ void sha256_start(context_sha256_T *ctx) ctx->state[7] = 0x5BE0CD19; } -static void sha256_process(context_sha256_T *ctx, const char_u data[SHA256_BUFFER_SIZE]) +static void sha256_process(context_sha256_T *ctx, const uint8_t data[SHA256_BUFFER_SIZE]) { uint32_t temp1, temp2, W[SHA256_BUFFER_SIZE]; uint32_t A, B, C, D, E, F, G, H; @@ -178,7 +180,7 @@ static void sha256_process(context_sha256_T *ctx, const char_u data[SHA256_BUFFE ctx->state[7] += H; } -void sha256_update(context_sha256_T *ctx, const char_u *input, size_t length) +void sha256_update(context_sha256_T *ctx, const uint8_t *input, size_t length) { if (length == 0) { return; @@ -196,7 +198,7 @@ void sha256_update(context_sha256_T *ctx, const char_u *input, size_t length) size_t fill = SHA256_BUFFER_SIZE - left; if (left && (length >= fill)) { - memcpy((void *)(ctx->buffer + left), (void *)input, fill); + memcpy(ctx->buffer + left, input, fill); sha256_process(ctx, ctx->buffer); length -= fill; input += fill; @@ -210,22 +212,22 @@ void sha256_update(context_sha256_T *ctx, const char_u *input, size_t length) } if (length) { - memcpy((void *)(ctx->buffer + left), (void *)input, length); + memcpy(ctx->buffer + left, input, length); } } -static char_u sha256_padding[SHA256_BUFFER_SIZE] = { +static uint8_t sha256_padding[SHA256_BUFFER_SIZE] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; -void sha256_finish(context_sha256_T *ctx, char_u digest[SHA256_SUM_SIZE]) +void sha256_finish(context_sha256_T *ctx, uint8_t digest[SHA256_SUM_SIZE]) { uint32_t last, padn; uint32_t high, low; - char_u msglen[8]; + uint8_t msglen[8]; high = (ctx->total[0] >> 29) | (ctx->total[1] << 3); low = (ctx->total[0] << 3); @@ -263,7 +265,7 @@ void sha256_finish(context_sha256_T *ctx, char_u digest[SHA256_SUM_SIZE]) const char *sha256_bytes(const uint8_t *restrict buf, size_t buf_len, const uint8_t *restrict salt, size_t salt_len) { - char_u sha256sum[SHA256_SUM_SIZE]; + uint8_t sha256sum[SHA256_SUM_SIZE]; static char hexit[SHA256_BUFFER_SIZE + 1]; // buf size + NULL context_sha256_T ctx; @@ -307,8 +309,8 @@ bool sha256_self_test(void) { char output[SHA256_BUFFER_SIZE + 1]; // buf size + NULL context_sha256_T ctx; - char_u buf[1000]; - char_u sha256sum[SHA256_SUM_SIZE]; + uint8_t buf[1000]; + uint8_t sha256sum[SHA256_SUM_SIZE]; const char *hexit; static bool sha256_self_tested = false; @@ -339,7 +341,7 @@ bool sha256_self_test(void) } } - if (memcmp(output, sha_self_test_vector[i], SHA256_BUFFER_SIZE)) { + if (memcmp(output, sha_self_test_vector[i], SHA256_BUFFER_SIZE) != 0) { failures = true; output[sizeof(output) - 1] = '\0'; diff --git a/src/nvim/sha256.h b/src/nvim/sha256.h index b52d300de6..eeb4031509 100644 --- a/src/nvim/sha256.h +++ b/src/nvim/sha256.h @@ -2,9 +2,9 @@ #define NVIM_SHA256_H #include <stddef.h> -#include <stdint.h> // for uint32_t +#include <stdint.h> -#include "nvim/types.h" // for char_u +#include "nvim/types.h" #define SHA256_BUFFER_SIZE 64 #define SHA256_SUM_SIZE 32 @@ -12,7 +12,7 @@ typedef struct { uint32_t total[2]; uint32_t state[8]; - char_u buffer[SHA256_BUFFER_SIZE]; + uint8_t buffer[SHA256_BUFFER_SIZE]; } context_sha256_T; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/shada.c b/src/nvim/shada.c index daa8e99d31..90a01aaf97 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2,44 +2,53 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <errno.h> #include <inttypes.h> -#include <msgpack.h> +#include <msgpack/object.h> +#include <msgpack/pack.h> +#include <msgpack/sbuffer.h> +#include <msgpack/unpack.h> #include <stdbool.h> #include <stddef.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/stat.h> #include <uv.h> +#include "auto/config.h" +#include "klib/khash.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/buffer.h" -#include "nvim/buffer_defs.h" #include "nvim/cmdhist.h" +#include "nvim/eval.h" #include "nvim/eval/decode.h" #include "nvim/eval/encode.h" #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/fileio.h" #include "nvim/garray.h" +#include "nvim/gettext.h" #include "nvim/globals.h" -#include "nvim/lib/khash.h" -#include "nvim/lib/kvec.h" +#include "nvim/hashtab.h" #include "nvim/macros.h" #include "nvim/mark.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/msgpack_rpc/helpers.h" +#include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/fileio.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/pos.h" -#include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/search.h" #include "nvim/shada.h" @@ -166,7 +175,7 @@ typedef enum { /// Possible results of shada_write function. typedef enum { - kSDWriteSuccessfull, ///< Writing was successful. + kSDWriteSuccessful, ///< Writing was successful. kSDWriteReadNotShada, ///< Writing was successful, but when reading it ///< attempted to read file that did not look like ///< a ShaDa file. @@ -288,8 +297,6 @@ typedef struct { } data; } ShadaEntry; -struct hm_llist_entry; - /// One entry in sized linked list typedef struct hm_llist_entry { ShadaEntry data; ///< Entry data. @@ -454,10 +461,7 @@ static const ShadaEntry sd_default_values[] = { .additional_data = NULL), DEF_SDE(Variable, global_var, .name = NULL, - .value = { - .v_type = VAR_UNKNOWN, - .vval = { .v_string = NULL } - }, + .value = { .v_type = VAR_UNKNOWN, .vval = { .v_string = NULL } }, .additional_elements = NULL), DEF_SDE(GlobalMark, filemark, .name = '"', @@ -752,7 +756,7 @@ static int open_shada_file_for_reading(const char *const fname, ShaDaReadDef *sd return error; } - assert(STRCMP(p_enc, "utf-8") == 0); + assert(strcmp(p_enc, "utf-8") == 0); return 0; } @@ -882,10 +886,10 @@ static const void *shada_hist_iter(const void *const iter, const uint8_t history .data = { .history_item = { .histtype = history_type, - .string = (char *)hist_he.hisstr, + .string = hist_he.hisstr, .sep = (char)(history_type == HIST_SEARCH - ? (char)hist_he.hisstr[STRLEN(hist_he.hisstr) + 1] - : 0), + ? hist_he.hisstr[strlen(hist_he.hisstr) + 1] + : 0), .additional_elements = hist_he.additional_elements, } } @@ -1008,7 +1012,7 @@ static inline void hms_to_he_array(const HistoryMergerState *const hms_p, HMLL_FORALL(&hms_p->hmll, cur_entry, { hist->timestamp = cur_entry->data.timestamp; hist->hisnum = (int)(hist - hist_array) + 1; - hist->hisstr = (char_u *)cur_entry->data.data.history_item.string; + hist->hisstr = cur_entry->data.data.history_item.string; hist->additional_elements = cur_entry->data.data.history_item.additional_elements; hist++; @@ -1053,7 +1057,7 @@ static buf_T *find_buffer(khash_t(fnamebufs) *const fname_bufs, const char *cons kh_key(fname_bufs, k) = xstrdup(fname); FOR_ALL_BUFFERS(buf) { if (buf->b_ffname != NULL) { - if (FNAMECMP(fname, buf->b_ffname) == 0) { + if (path_fnamecmp(fname, buf->b_ffname) == 0) { kh_val(fname_bufs, k) = buf; return buf; } @@ -1127,34 +1131,35 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) const bool get_old_files = (flags & (kShaDaGetOldfiles | kShaDaForceit) && (force || tv_list_len(oldfiles_list) == 0)); const bool want_marks = flags & kShaDaWantMarks; - const unsigned srni_flags = (unsigned)( - (flags & kShaDaWantInfo - ? (kSDReadUndisableableData - | kSDReadRegisters - | kSDReadGlobalMarks - | (p_hi ? kSDReadHistory : 0) - | (find_shada_parameter('!') != NULL - ? kSDReadVariables - : 0) - | (find_shada_parameter('%') != NULL - && ARGCOUNT == 0 - ? kSDReadBufferList - : 0)) - : 0) - | (want_marks && get_shada_parameter('\'') > 0 - ? kSDReadLocalMarks | kSDReadChanges - : 0) - | (get_old_files - ? kSDReadLocalMarks - : 0)); + const unsigned srni_flags = + (unsigned)( + (flags & kShaDaWantInfo + ? (kSDReadUndisableableData + | kSDReadRegisters + | kSDReadGlobalMarks + | (p_hi ? kSDReadHistory : 0) + | (find_shada_parameter('!') != NULL + ? kSDReadVariables + : 0) + | (find_shada_parameter('%') != NULL + && ARGCOUNT == 0 + ? kSDReadBufferList + : 0)) + : 0) + | (want_marks && get_shada_parameter('\'') > 0 + ? kSDReadLocalMarks | kSDReadChanges + : 0) + | (get_old_files + ? kSDReadLocalMarks + : 0)); if (srni_flags == 0) { // Nothing to do. return; } HistoryMergerState hms[HIST_COUNT]; if (srni_flags & kSDReadHistory) { - for (uint8_t i = 0; i < HIST_COUNT; i++) { - hms_init(&hms[i], i, (size_t)p_hi, true, true); + for (HistoryType i = 0; i < HIST_COUNT; i++) { + hms_init(&hms[i], (uint8_t)i, (size_t)p_hi, true, true); } } ShadaEntry cur_entry; @@ -1191,17 +1196,18 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) case kSDItemSearchPattern: if (!force) { SearchPattern pat; - (cur_entry.data.search_pattern.is_substitute_pattern - ? &get_substitute_pattern - : &get_search_pattern)(&pat); + if (cur_entry.data.search_pattern.is_substitute_pattern) { + get_substitute_pattern(&pat); + } else { + get_search_pattern(&pat); + } if (pat.pat != NULL && pat.timestamp >= cur_entry.timestamp) { shada_free_shada_entry(&cur_entry); break; } } - (cur_entry.data.search_pattern.is_substitute_pattern - ? &set_substitute_pattern - : &set_search_pattern)((SearchPattern) { + + SearchPattern spat = (SearchPattern) { .magic = cur_entry.data.search_pattern.magic, .no_scs = !cur_entry.data.search_pattern.smartcase, .off = { @@ -1210,10 +1216,17 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) .end = cur_entry.data.search_pattern.place_cursor_at_end, .off = cur_entry.data.search_pattern.offset, }, - .pat = (char_u *)cur_entry.data.search_pattern.pat, + .pat = cur_entry.data.search_pattern.pat, .additional_data = cur_entry.data.search_pattern.additional_data, .timestamp = cur_entry.timestamp, - }); + }; + + if (cur_entry.data.search_pattern.is_substitute_pattern) { + set_substitute_pattern(spat); + } else { + set_search_pattern(spat); + } + if (cur_entry.data.search_pattern.is_last_used) { set_last_used_pattern(cur_entry.data.search_pattern.is_substitute_pattern); set_no_hlsearch(!cur_entry.data.search_pattern.highlighted); @@ -1238,7 +1251,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) // string is close to useless: you can only use it with :& or :~ and // that’s all because s//~ is not available until the first call to // regtilde. Vim was not calling this for some reason. - (void)(char *)regtilde((char_u *)cur_entry.data.sub_string.sub, p_magic, false); + (void)regtilde(cur_entry.data.sub_string.sub, magic_isset(), false); // Do not free shada entry: its allocated memory was saved above. break; case kSDItemHistoryEntry: @@ -1313,9 +1326,9 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) MERGE_JUMPS(curwin->w_jumplistlen, curwin->w_jumplist, xfmark_T, fmark.timestamp, fmark.mark, cur_entry, (buf == NULL - ? (jl_entry.fname != NULL - && STRCMP(fm.fname, jl_entry.fname) == 0) - : fm.fmark.fnum == jl_entry.fmark.fnum), + ? (jl_entry.fname != NULL + && strcmp(fm.fname, jl_entry.fname) == 0) + : fm.fmark.fnum == jl_entry.fmark.fnum), free_xfmark, SDE_TO_XFMARK, ADJUST_IDX, DUMMY_AFTERFREE); #undef SDE_TO_XFMARK #undef ADJUST_IDX @@ -1327,7 +1340,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) case kSDItemBufferList: for (size_t i = 0; i < cur_entry.data.buffer_list.size; i++) { char *const sfname = - (char *)path_try_shorten_fname((char_u *)cur_entry.data.buffer_list.buffers[i].fname); + path_try_shorten_fname(cur_entry.data.buffer_list.buffers[i].fname); buf_T *const buf = buflist_new(cur_entry.data.buffer_list.buffers[i].fname, sfname, 0, BLN_LISTED); if (buf != NULL) { @@ -1410,12 +1423,12 @@ shada_read_main_cycle_end: // memory for the history string itself and separator character which // may be assigned right away. if (srni_flags & kSDReadHistory) { - for (uint8_t i = 0; i < HIST_COUNT; i++) { + for (HistoryType i = 0; i < HIST_COUNT; i++) { hms_insert_whole_neovim_history(&hms[i]); clr_history(i); int *new_hisidx; int *new_hisnum; - histentry_T *hist = hist_get_array(i, &new_hisidx, &new_hisnum); + histentry_T *hist = hist_get_array((uint8_t)i, &new_hisidx, &new_hisnum); if (hist != NULL) { hms_to_he_array(&hms[i], hist, new_hisidx, new_hisnum); } @@ -1469,15 +1482,15 @@ static char *shada_filename(const char *file) if (p_shadafile != NULL && *p_shadafile != NUL) { file = p_shadafile; } else { - if ((file = (char *)find_shada_parameter('n')) == NULL || *file == NUL) { - file = shada_get_default_file(); + if ((file = find_shada_parameter('n')) == NULL || *file == NUL) { + file = shada_get_default_file(); } // XXX It used to be one level lower, so that whatever is in // `p_shadafile` was expanded. I intentionally moved it here // because various expansions must have already be done by the shell. // If shell is not performing them then they should be done in main.c // where arguments are parsed, *not here*. - expand_env((char_u *)file, &(NameBuff[0]), MAXPATHL); + expand_env((char *)file, &(NameBuff[0]), MAXPATHL); file = (const char *)&(NameBuff[0]); } } @@ -1505,7 +1518,7 @@ static char *shada_filename(const char *file) /// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no /// restrictions. /// -/// @return kSDWriteSuccessfull, kSDWriteFailed or kSDWriteIgnError. +/// @return kSDWriteSuccessful, kSDWriteFailed or kSDWriteIgnError. static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntry entry, const size_t max_kbyte) FUNC_ATTR_NONNULL_ALL @@ -1551,6 +1564,18 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr (sd_default_values[(entry).type].data.attr == (entry).data.attr) #define ONE_IF_NOT_DEFAULT(entry, attr) \ ((size_t)(!CHECK_DEFAULT(entry, attr))) + +#define PACK_BOOL(entry, name, attr) \ + do { \ + if (!CHECK_DEFAULT(entry, search_pattern.attr)) { \ + PACK_STATIC_STR(name); \ + if (sd_default_values[(entry).type].data.search_pattern.attr) { \ + msgpack_pack_false(spacker); \ + } else { \ + msgpack_pack_true(spacker); \ + } \ + } \ + } while (0) switch (entry.type) { case kSDItemMissing: abort(); @@ -1634,17 +1659,6 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(SEARCH_KEY_PAT); PACK_BIN(cstr_as_string(entry.data.search_pattern.pat)); -#define PACK_BOOL(entry, name, attr) \ - do { \ - if (!CHECK_DEFAULT(entry, search_pattern.attr)) { \ - PACK_STATIC_STR(name); \ - if (sd_default_values[(entry).type].data.search_pattern.attr) { \ - msgpack_pack_false(spacker); \ - } else { \ - msgpack_pack_true(spacker); \ - } \ - } \ - } while (0) PACK_BOOL(entry, SEARCH_KEY_MAGIC, magic); PACK_BOOL(entry, SEARCH_KEY_IS_LAST_USED, is_last_used); PACK_BOOL(entry, SEARCH_KEY_SMARTCASE, smartcase); @@ -1688,8 +1702,8 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr msgpack_pack_long(spacker, entry.data.filemark.mark.col); } assert(entry.type == kSDItemJump || entry.type == kSDItemChange - ? CHECK_DEFAULT(entry, filemark.name) - : true); + ? CHECK_DEFAULT(entry, filemark.name) + : true); if (!CHECK_DEFAULT(entry, filemark.name)) { PACK_STATIC_STR(KEY_NAME_CHAR); msgpack_pack_uint8(spacker, (uint8_t)entry.data.filemark.name); @@ -1699,15 +1713,14 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr break; } case kSDItemRegister: { - const size_t map_size = (size_t)( - 2 // Register contents and name + const size_t map_size = (size_t)(2 // Register contents and name + ONE_IF_NOT_DEFAULT(entry, reg.type) + ONE_IF_NOT_DEFAULT(entry, reg.width) + ONE_IF_NOT_DEFAULT(entry, reg.is_unnamed) // Additional entries, if any: + (size_t)(entry.data.reg.additional_data == NULL - ? 0 - : entry.data.reg.additional_data->dv_hashtab.ht_used)); + ? 0 + : entry.data.reg.additional_data->dv_hashtab.ht_used)); msgpack_pack_map(spacker, map_size); PACK_STATIC_STR(REG_KEY_CONTENTS); msgpack_pack_array(spacker, entry.data.reg.contents_size); @@ -1813,7 +1826,7 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr } msgpack_packer_free(spacker); msgpack_sbuffer_destroy(&sbuf); - return kSDWriteSuccessfull; + return kSDWriteSuccessful; shada_pack_entry_error: msgpack_packer_free(spacker); msgpack_sbuffer_destroy(&sbuf); @@ -1833,7 +1846,7 @@ static inline ShaDaWriteResult shada_pack_pfreed_entry(msgpack_packer *const pac const size_t max_kbyte) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE { - ShaDaWriteResult ret = kSDWriteSuccessfull; + ShaDaWriteResult ret = kSDWriteSuccessful; ret = shada_pack_entry(packer, entry.data, max_kbyte); if (entry.can_free_entry) { shada_free_shada_entry(&entry.data); @@ -1952,6 +1965,28 @@ static const char *shada_format_entry(const ShadaEntry entry) ret[0] = 0; vim_snprintf(S_LEN(ret), "%s", "[ ] ts=%" PRIu64 " "); // ^ Space for `can_free_entry` +#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \ + do { \ + typval_T ad_tv = { \ + .v_type = VAR_DICT, \ + .vval.v_dict = entry.data.filemark.additional_data \ + }; \ + size_t ad_len; \ + char *const ad = encode_tv2string(&ad_tv, &ad_len); \ + vim_snprintf_add(S_LEN(ret), \ + entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \ + "pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \ + "ad={%p:[%zu]%.64s} }", \ + name_fmt_arg, \ + strlen(entry.data.filemark.fname), \ + entry.data.filemark.fname, \ + entry.data.filemark.mark.lnum, \ + entry.data.filemark.mark.col, \ + entry.data.filemark.mark.coladd, \ + (void *)entry.data.filemark.additional_data, \ + ad_len, \ + ad); \ + } while (0) switch (entry.type) { case kSDItemMissing: vim_snprintf_add(S_LEN(ret), "Missing"); @@ -1980,28 +2015,6 @@ static const char *shada_format_entry(const ShadaEntry entry) case kSDItemVariable: vim_snprintf_add(S_LEN(ret), "Variable { TODO }"); break; -#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \ - do { \ - typval_T ad_tv = { \ - .v_type = VAR_DICT, \ - .vval.v_dict = entry.data.filemark.additional_data \ - }; \ - size_t ad_len; \ - char *const ad = encode_tv2string(&ad_tv, &ad_len); \ - vim_snprintf_add(S_LEN(ret), \ - entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \ - "pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \ - "ad={%p:[%zu]%.64s} }", \ - name_fmt_arg, \ - strlen(entry.data.filemark.fname), \ - entry.data.filemark.fname, \ - entry.data.filemark.mark.lnum, \ - entry.data.filemark.mark.col, \ - entry.data.filemark.mark.coladd, \ - (void *)entry.data.filemark.additional_data, \ - ad_len, \ - ad); \ - } while (0) case kSDItemGlobalMark: FORMAT_MARK_ENTRY("GlobalMark", " name='%c',", entry.data.filemark.name); break; @@ -2047,9 +2060,35 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re msgpack_packer *const packer) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - ShaDaWriteResult ret = kSDWriteSuccessfull; + ShaDaWriteResult ret = kSDWriteSuccessful; ShadaEntry entry; ShaDaReadResult srni_ret; + +#define COMPARE_WITH_ENTRY(wms_entry_, entry) \ + do { \ + PossiblyFreedShadaEntry *const wms_entry = (wms_entry_); \ + if (wms_entry->data.type != kSDItemMissing) { \ + if (wms_entry->data.timestamp >= (entry).timestamp) { \ + shada_free_shada_entry(&(entry)); \ + break; \ + } \ + if (wms_entry->can_free_entry) { \ + shada_free_shada_entry(&wms_entry->data); \ + } \ + } \ + *wms_entry = pfs_entry; \ + } while (0) + +#define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \ + do { \ + if ((entry).can_free_entry) { \ + shada_free_shada_entry(&(entry).data); \ + } \ + } while (0) + +#define SDE_TO_PFSDE(entry) \ + ((PossiblyFreedShadaEntry) { .can_free_entry = true, .data = (entry) }) + while ((srni_ret = shada_read_next_item(sd_reader, &entry, srni_flags, max_kbyte)) != kSDReadStatusFinished) { @@ -2067,20 +2106,6 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re case kSDReadStatusMalformed: continue; } -#define COMPARE_WITH_ENTRY(wms_entry_, entry) \ - do { \ - PossiblyFreedShadaEntry *const wms_entry = (wms_entry_); \ - if (wms_entry->data.type != kSDItemMissing) { \ - if (wms_entry->data.timestamp >= (entry).timestamp) { \ - shada_free_shada_entry(&(entry)); \ - break; \ - } \ - if (wms_entry->can_free_entry) { \ - shada_free_shada_entry(&wms_entry->data); \ - } \ - } \ - *wms_entry = pfs_entry; \ - } while (0) const PossiblyFreedShadaEntry pfs_entry = { .can_free_entry = true, .data = entry, @@ -2097,8 +2122,8 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re break; case kSDItemSearchPattern: COMPARE_WITH_ENTRY((entry.data.search_pattern.is_substitute_pattern - ? &wms->sub_search_pattern - : &wms->search_pattern), entry); + ? &wms->sub_search_pattern + : &wms->search_pattern), entry); break; case kSDItemSubString: COMPARE_WITH_ENTRY(&wms->replacement, entry); @@ -2220,14 +2245,6 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re *wms_entry = pfs_entry; } } else { -#define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \ - do { \ - if ((entry).can_free_entry) { \ - shada_free_shada_entry(&(entry).data); \ - } \ - } while (0) -#define SDE_TO_PFSDE(entry) \ - ((PossiblyFreedShadaEntry) { .can_free_entry = true, .data = (entry) }) #define AFTERFREE_DUMMY(entry) #define DUMMY_IDX_ADJ(i) MERGE_JUMPS(filemarks->changes_size, filemarks->changes, @@ -2361,7 +2378,7 @@ static inline void add_search_pattern(PossiblyFreedShadaEntry *const ret_pse, .is_substitute_pattern = is_substitute_pattern, .highlighted = ((is_substitute_pattern ^ search_last_used) && search_highlighted), - .pat = (char *)pat.pat, + .pat = pat.pat, .additional_data = pat.additional_data, .search_backward = (!is_substitute_pattern && pat.off.dir == '?'), } @@ -2482,7 +2499,7 @@ static int hist_type2char(const int type) static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef *const sd_reader) FUNC_ATTR_NONNULL_ARG(1) { - ShaDaWriteResult ret = kSDWriteSuccessfull; + ShaDaWriteResult ret = kSDWriteSuccessful; int max_kbyte_i = get_shada_parameter('s'); if (max_kbyte_i < 0) { max_kbyte_i = 10; @@ -2506,7 +2523,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef bool dump_history = false; // Initialize history merger - for (uint8_t i = 0; i < HIST_COUNT; i++) { + for (HistoryType i = 0; i < HIST_COUNT; i++) { long num_saved = get_shada_parameter(hist_type2char(i)); if (num_saved == -1) { num_saved = p_hi; @@ -2514,7 +2531,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef if (num_saved > 0) { dump_history = true; dump_one_history[i] = true; - hms_init(&wms->hms[i], i, (size_t)num_saved, sd_reader != NULL, false); + hms_init(&wms->hms[i], (uint8_t)i, (size_t)num_saved, sd_reader != NULL, false); } else { dump_one_history[i] = false; } @@ -2642,7 +2659,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef } tv_clear(&vartv); tv_clear(&tgttv); - if (spe_ret == kSDWriteSuccessfull) { + if (spe_ret == kSDWriteSuccessful) { int kh_ret; (void)kh_put(strset, &wms->dumped_variables, name, &kh_ret); } @@ -2650,8 +2667,6 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef } // Initialize jump list - setpcmark(); - cleanup_jumplist(curwin, false); wms->jumps_size = shada_init_jumps(wms->jumps, &removable_bufs); const bool search_highlighted = !(no_hlsearch @@ -2807,7 +2822,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef if (sd_reader != NULL) { const ShaDaWriteResult srww_ret = shada_read_when_writing(sd_reader, srni_flags, max_kbyte, wms, packer); - if (srww_ret != kSDWriteSuccessfull) { + if (srww_ret != kSDWriteSuccessful) { ret = srww_ret; } } @@ -3007,10 +3022,9 @@ shada_write_file_open: {} assert(sd_reader.close != NULL); sd_reader.close(&sd_reader); return FAIL; - } else { - (*wp)++; - goto shada_write_file_open; } + (*wp)++; + goto shada_write_file_open; } else { semsg(_(SERR "System error while opening temporary ShaDa file %s " "for writing: %s"), tempname, os_strerror(error)); @@ -3023,7 +3037,7 @@ shada_write_file_nomerge: {} if (tail != fname) { const char tail_save = *tail; *tail = NUL; - if (!os_isdir((char_u *)fname)) { + if (!os_isdir(fname)) { int ret; char *failed_dir; if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir)) != 0) { @@ -3068,37 +3082,42 @@ shada_write_file_nomerge: {} if (!nomerge) { sd_reader.close(&sd_reader); bool did_remove = false; - if (sw_ret == kSDWriteSuccessfull) { -#ifdef UNIX - // For Unix we check the owner of the file. It's not very nice to - // overwrite a user’s viminfo file after a "su root", with a - // viminfo file that the user can't read. + if (sw_ret == kSDWriteSuccessful) { FileInfo old_info; - if (os_fileinfo(fname, &old_info)) { - if (getuid() == ROOT_UID) { - if (old_info.stat.st_uid != ROOT_UID - || old_info.stat.st_gid != getgid()) { - const uv_uid_t old_uid = (uv_uid_t)old_info.stat.st_uid; - const uv_gid_t old_gid = (uv_gid_t)old_info.stat.st_gid; - const int fchown_ret = os_fchown(file_fd(sd_writer.cookie), - old_uid, old_gid); - if (fchown_ret != 0) { - semsg(_(RNERR "Failed setting uid and gid for file %s: %s"), - tempname, os_strerror(fchown_ret)); - goto shada_write_file_did_not_remove; - } + if (!os_fileinfo(fname, &old_info) + || S_ISDIR(old_info.stat.st_mode) +#ifdef UNIX + // For Unix we check the owner of the file. It's not very nice + // to overwrite a user's viminfo file after a "su root", with a + // viminfo file that the user can't read. + || (getuid() != ROOT_UID + && !(old_info.stat.st_uid == getuid() + ? (old_info.stat.st_mode & 0200) + : (old_info.stat.st_gid == getgid() + ? (old_info.stat.st_mode & 0020) + : (old_info.stat.st_mode & 0002)))) +#endif + ) { + semsg(_("E137: ShaDa file is not writable: %s"), fname); + goto shada_write_file_did_not_remove; + } +#ifdef UNIX + if (getuid() == ROOT_UID) { + if (old_info.stat.st_uid != ROOT_UID + || old_info.stat.st_gid != getgid()) { + const uv_uid_t old_uid = (uv_uid_t)old_info.stat.st_uid; + const uv_gid_t old_gid = (uv_gid_t)old_info.stat.st_gid; + const int fchown_ret = os_fchown(file_fd(sd_writer.cookie), + old_uid, old_gid); + if (fchown_ret != 0) { + semsg(_(RNERR "Failed setting uid and gid for file %s: %s"), + tempname, os_strerror(fchown_ret)); + goto shada_write_file_did_not_remove; } - } else if (!(old_info.stat.st_uid == getuid() - ? (old_info.stat.st_mode & 0200) - : (old_info.stat.st_gid == getgid() - ? (old_info.stat.st_mode & 0020) - : (old_info.stat.st_mode & 0002)))) { - semsg(_("E137: ShaDa file is not writable: %s"), fname); - goto shada_write_file_did_not_remove; } } #endif - if (vim_rename((char_u *)tempname, (char_u *)fname) == -1) { + if (vim_rename(tempname, fname) == -1) { semsg(_(RNERR "Can't rename ShaDa file from %s to %s!"), tempname, fname); } else { @@ -3115,9 +3134,7 @@ shada_write_file_nomerge: {} } } if (!did_remove) { -#ifdef UNIX shada_write_file_did_not_remove: -#endif semsg(_(RNERR "Do not forget to remove %s or rename it manually to %s."), tempname, fname); } @@ -3247,13 +3264,12 @@ static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader, char *const buff semsg(_(SERR "System error while reading ShaDa file: %s"), sd_reader->error); return kSDReadStatusReadError; - } else { - semsg(_(RCERR "Error while reading ShaDa file: " - "last entry specified that it occupies %" PRIu64 " bytes, " - "but file ended earlier"), - (uint64_t)length); - return kSDReadStatusNotShaDa; } + semsg(_(RCERR "Error while reading ShaDa file: " + "last entry specified that it occupies %" PRIu64 " bytes, " + "but file ended earlier"), + (uint64_t)length); + return kSDReadStatusNotShaDa; } return kSDReadStatusSuccess; } @@ -3336,7 +3352,7 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, const error_desc #define CHECK_KEY(key, \ expected) ((key).via.str.size == (sizeof(expected) - 1) \ - && STRNCMP((key).via.str.ptr, expected, (sizeof(expected) - 1)) == 0) + && strncmp((key).via.str.ptr, expected, (sizeof(expected) - 1)) == 0) #define CLEAR_GA_AND_ERROR_OUT(ga) \ do { \ ga_clear(&(ga)); \ @@ -3566,16 +3582,15 @@ shada_read_next_item_start: entry->type = kSDItemMissing; } return spm_ret; - } else { - entry->data.unknown_item.contents = xmalloc(length); - const ShaDaReadResult fl_ret = - fread_len(sd_reader, entry->data.unknown_item.contents, length); - if (fl_ret != kSDReadStatusSuccess) { - shada_free_shada_entry(entry); - entry->type = kSDItemMissing; - } - return fl_ret; } + entry->data.unknown_item.contents = xmalloc(length); + const ShaDaReadResult fl_ret = + fread_len(sd_reader, entry->data.unknown_item.contents, length); + if (fl_ret != kSDReadStatusSuccess) { + shada_free_shada_entry(entry); + entry->type = kSDItemMissing; + } + return fl_ret; } msgpack_unpacked unpacked; @@ -3996,12 +4011,12 @@ static bool shada_removable(const char *name) bool retval = false; char *new_name = home_replace_save(NULL, (char *)name); - for (p = (char *)p_shada; *p;) { + for (p = p_shada; *p;) { (void)copy_option_part(&p, part, ARRAY_SIZE(part), ", "); if (part[0] == 'r') { - home_replace(NULL, part + 1, (char *)NameBuff, MAXPATHL, true); - size_t n = STRLEN(NameBuff); - if (mb_strnicmp((char_u *)NameBuff, (char_u *)new_name, n) == 0) { + home_replace(NULL, part + 1, NameBuff, MAXPATHL, true); + size_t n = strlen(NameBuff); + if (mb_strnicmp(NameBuff, new_name, n) == 0) { retval = true; break; } @@ -4021,13 +4036,11 @@ static bool shada_removable(const char *name) static inline size_t shada_init_jumps(PossiblyFreedShadaEntry *jumps, khash_t(bufset) *const removable_bufs) { - if (!curwin->w_jumplistlen) { - return 0; - } - + // Initialize jump list size_t jumps_size = 0; const void *jump_iter = NULL; - + setpcmark(); + cleanup_jumplist(curwin, false); do { xfmark_T fm; jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm); @@ -4100,7 +4113,6 @@ void shada_encode_jumps(msgpack_sbuffer *const sbuf) khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset); find_removable_bufs(&removable_bufs); PossiblyFreedShadaEntry jumps[JUMPLISTSIZE]; - cleanup_jumplist(curwin, true); size_t jumps_size = shada_init_jumps(jumps, &removable_bufs); msgpack_packer packer; msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write); diff --git a/src/nvim/sign.c b/src/nvim/sign.c index f1ddbfd147..d0c093d93a 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -5,20 +5,42 @@ // sign.c: functions for managing with signs // +#include <inttypes.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + #include "nvim/ascii.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/fold.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memline_defs.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" #include "nvim/option.h" +#include "nvim/pos.h" #include "nvim/sign.h" -#include "nvim/syntax.h" +#include "nvim/sign_defs.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -26,11 +48,11 @@ typedef struct sign sign_T; struct sign { - sign_T *sn_next; // next sign in list + sign_T *sn_next; // next sign in list int sn_typenr; // type number of sign - char_u *sn_name; // name of sign - char_u *sn_icon; // name of pixmap - char_u *sn_text; // text used instead of pixmap + char *sn_name; // name of sign + char *sn_icon; // name of pixmap + char *sn_text; // text used instead of pixmap int sn_line_hl; // highlight ID for line int sn_text_hl; // highlight ID for text int sn_cul_hl; // highlight ID for text on current line when 'cursorline' is set @@ -71,18 +93,17 @@ void init_signs(void) /// A new sign in group 'groupname' is added. If the group is not present, /// create it. Otherwise reference the group. -/// -static signgroup_T *sign_group_ref(const char_u *groupname) +static signgroup_T *sign_group_ref(const char *groupname) { hash_T hash; hashitem_T *hi; signgroup_T *group; hash = hash_hash(groupname); - hi = hash_lookup(&sg_table, (char *)groupname, STRLEN(groupname), hash); + hi = hash_lookup(&sg_table, (char *)groupname, strlen(groupname), hash); if (HASHITEM_EMPTY(hi)) { // new group - group = xmalloc(sizeof(signgroup_T) + STRLEN(groupname)); + group = xmalloc(sizeof(signgroup_T) + strlen(groupname)); STRCPY(group->sg_name, groupname); group->sg_refcount = 1; @@ -99,35 +120,35 @@ static signgroup_T *sign_group_ref(const char_u *groupname) /// A sign in group 'groupname' is removed. If all the signs in this group are /// removed, then remove the group. -static void sign_group_unref(char_u *groupname) +static void sign_group_unref(char *groupname) { - signgroup_T *group; + hashitem_T *hi = hash_find(&sg_table, groupname); + if (HASHITEM_EMPTY(hi)) { + return; + } - hashitem_T *hi = hash_find(&sg_table, (char *)groupname); - if (!HASHITEM_EMPTY(hi)) { - group = HI2SG(hi); - group->sg_refcount--; - if (group->sg_refcount == 0) { - // All the signs in this group are removed - hash_remove(&sg_table, hi); - xfree(group); - } + signgroup_T *group = HI2SG(hi); + group->sg_refcount--; + if (group->sg_refcount == 0) { + // All the signs in this group are removed + hash_remove(&sg_table, hi); + xfree(group); } } /// @return true if 'sign' is in 'group'. /// A sign can either be in the global group (sign->group == NULL) /// or in a named group. If 'group' is '*', then the sign is part of the group. -static bool sign_in_group(sign_entry_T *sign, const char_u *group) +static bool sign_in_group(sign_entry_T *sign, const char *group) { - return ((group != NULL && STRCMP(group, "*") == 0) + return ((group != NULL && strcmp(group, "*") == 0) || (group == NULL && sign->se_group == NULL) || (group != NULL && sign->se_group != NULL - && STRCMP(group, sign->se_group->sg_name) == 0)); + && strcmp(group, sign->se_group->sg_name) == 0)); } /// Get the next free sign identifier in the specified group -static int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) +static int sign_group_get_next_signid(buf_T *buf, const char *groupname) { int id = 1; signgroup_T *group = NULL; @@ -154,7 +175,7 @@ static int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) // Check whether this sign is already placed in the buffer found = true; FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (id == sign->se_id && sign_in_group(sign, groupname)) { + if (id == sign->se_id && sign_in_group(sign, (char *)groupname)) { found = false; // sign identifier is in use break; } @@ -177,7 +198,7 @@ static int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) /// @param typenr typenr of sign we are adding /// @param has_text_or_icon sign has text or icon static void insert_sign(buf_T *buf, sign_entry_T *prev, sign_entry_T *next, int id, - const char_u *group, int prio, linenr_T lnum, int typenr, + const char *group, int prio, linenr_T lnum, int typenr, bool has_text_or_icon) { sign_entry_T *newsign = xmalloc(sizeof(sign_entry_T)); @@ -203,7 +224,7 @@ static void insert_sign(buf_T *buf, sign_entry_T *prev, sign_entry_T *next, int // When adding first sign need to redraw the windows to create the // column for signs. if (buf->b_signlist == NULL) { - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); changed_line_abv_curs(); } @@ -224,7 +245,7 @@ static void insert_sign(buf_T *buf, sign_entry_T *prev, sign_entry_T *next, int /// @param lnum line number which gets the mark /// @param typenr typenr of sign we are adding /// @param has_text_or_icon sign has text or icon -static void insert_sign_by_lnum_prio(buf_T *buf, sign_entry_T *prev, int id, const char_u *group, +static void insert_sign_by_lnum_prio(buf_T *buf, sign_entry_T *prev, int id, const char *group, int prio, linenr_T lnum, int typenr, bool has_text_or_icon) { sign_entry_T *sign; @@ -259,7 +280,7 @@ static sign_T *find_sign_by_typenr(int typenr) } /// Get the name of a sign by its typenr. -static char_u *sign_typenr2name(int typenr) +static char *sign_typenr2name(int typenr) { sign_T *sp; @@ -268,7 +289,7 @@ static char_u *sign_typenr2name(int typenr) return sp->sn_name; } } - return (char_u *)_("[Deleted]"); + return _("[Deleted]"); } /// Return information about a sign in a Dict @@ -280,7 +301,7 @@ static dict_T *sign_get_info(sign_entry_T *sign) ? (char *)"" : (char *)sign->se_group->sg_name)); tv_dict_add_nr(d, S_LEN("lnum"), sign->se_lnum); - tv_dict_add_str(d, S_LEN("name"), (char *)sign_typenr2name(sign->se_typenr)); + tv_dict_add_str(d, S_LEN("name"), sign_typenr2name(sign->se_typenr)); tv_dict_add_nr(d, S_LEN("priority"), sign->se_priority); return d; @@ -369,7 +390,7 @@ static void sign_sort_by_prio_on_line(buf_T *buf, sign_entry_T *sign) /// @param lnum line number which gets the mark /// @param typenr typenr of sign we are adding /// @param has_text_or_icon sign has text or icon -static void buf_addsign(buf_T *buf, int id, const char_u *groupname, int prio, linenr_T lnum, +static void buf_addsign(buf_T *buf, int id, const char *groupname, int prio, linenr_T lnum, int typenr, bool has_text_or_icon) { sign_entry_T *sign; // a sign in the signlist @@ -416,13 +437,13 @@ static void buf_addsign(buf_T *buf, int id, const char_u *groupname, int prio, l /// @param group sign group /// @param typenr typenr of sign we are adding /// @param prio sign priority -static linenr_T buf_change_sign_type(buf_T *buf, int markId, const char_u *group, int typenr, +static linenr_T buf_change_sign_type(buf_T *buf, int markId, const char *group, int typenr, int prio) { sign_entry_T *sign; // a sign in the signlist FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (sign->se_id == markId && sign_in_group(sign, group)) { + if (sign->se_id == markId && sign_in_group(sign, (char *)group)) { sign->se_typenr = typenr; sign->se_priority = prio; sign_sort_by_prio_on_line(buf, sign); @@ -534,7 +555,7 @@ int buf_get_signattrs(buf_T *buf, linenr_T lnum, SignTextAttrs sattrs[], HlPriAt /// /// @return the line number of the deleted sign. If multiple signs are deleted, /// then returns the line number of the last sign deleted. -static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) +static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char *group) { sign_entry_T **lastp; // pointer to pointer to current sign sign_entry_T *sign; // a sign in a b_signlist @@ -555,10 +576,10 @@ static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) lnum = sign->se_lnum; buf_signcols_del_check(buf, lnum, lnum); if (sign->se_group != NULL) { - sign_group_unref(sign->se_group->sg_name); + sign_group_unref((char *)sign->se_group->sg_name); } xfree(sign); - redraw_buf_line_later(buf, lnum); + redraw_buf_line_later(buf, lnum, false); // Check whether only one sign needs to be deleted // If deleting a sign with a specific identifier in a particular // group or deleting any sign at a particular line number, delete @@ -576,7 +597,7 @@ static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) // When deleting the last sign the cursor position may change, because the // sign columns no longer shows. And the 'signcolumn' may be hidden. if (buf->b_signlist == NULL) { - redraw_buf_later(buf, NOT_VALID); + redraw_buf_later(buf, UPD_NOT_VALID); changed_line_abv_curs(); } @@ -590,7 +611,7 @@ static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) /// @param buf buffer to store sign in /// @param id sign ID /// @param group sign group -static int buf_findsign(buf_T *buf, int id, char_u *group) +static int buf_findsign(buf_T *buf, int id, char *group) { sign_entry_T *sign; // a sign in the signlist @@ -609,7 +630,7 @@ static int buf_findsign(buf_T *buf, int id, char_u *group) /// @param buf buffer whose sign we are searching for /// @param lnum line number of sign /// @param groupname sign group name -static sign_entry_T *buf_getsign_at_line(buf_T *buf, linenr_T lnum, char_u *groupname) +static sign_entry_T *buf_getsign_at_line(buf_T *buf, linenr_T lnum, char *groupname) { sign_entry_T *sign; // a sign in the signlist @@ -633,7 +654,7 @@ static sign_entry_T *buf_getsign_at_line(buf_T *buf, linenr_T lnum, char_u *grou /// @param buf buffer whose sign we are searching for /// @param lnum line number of sign /// @param groupname sign group name -static int buf_findsign_id(buf_T *buf, linenr_T lnum, char_u *groupname) +static int buf_findsign_id(buf_T *buf, linenr_T lnum, char *groupname) { sign_entry_T *sign; // a sign in the signlist @@ -661,13 +682,13 @@ void buf_delete_signs(buf_T *buf, char *group) lastp = &buf->b_signlist; for (sign = buf->b_signlist; sign != NULL; sign = next) { next = sign->se_next; - if (sign_in_group(sign, (char_u *)group)) { + if (sign_in_group(sign, group)) { *lastp = next; if (next != NULL) { next->se_prev = sign->se_prev; } if (sign->se_group != NULL) { - sign_group_unref(sign->se_group->sg_name); + sign_group_unref((char *)sign->se_group->sg_name); } xfree(sign); } else { @@ -678,7 +699,7 @@ void buf_delete_signs(buf_T *buf, char *group) } /// List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers. -static void sign_list_placed(buf_T *rbuf, char_u *sign_group) +static void sign_list_placed(buf_T *rbuf, char *sign_group) { buf_T *buf; sign_entry_T *sign; @@ -725,7 +746,27 @@ static void sign_list_placed(buf_T *rbuf, char_u *sign_group) } } -/// Adjust a placed sign for inserted/deleted lines. +/// Adjust or delete a placed sign for inserted/deleted lines. +/// +/// @return the new line number of the sign, or 0 if the sign is in deleted lines. +static linenr_T sign_adjust_one(const linenr_T se_lnum, linenr_T line1, linenr_T line2, + linenr_T amount, linenr_T amount_after) +{ + if (se_lnum < line1) { + // Ignore changes to lines after the sign + return se_lnum; + } + if (se_lnum > line2) { + // Lines inserted or deleted before the sign + return se_lnum + amount_after; + } + if (amount == MAXLNUM) { // sign in deleted lines + return 0; + } + return se_lnum + amount; +} + +/// Adjust placed signs for inserted/deleted lines. void sign_mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after) { sign_entry_T *sign; // a sign in a b_signlist @@ -736,9 +777,7 @@ void sign_mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T int is_fixed = 0; int signcol = win_signcol_configured(curwin, &is_fixed); - bool delete = amount == MAXLNUM; - - if (delete) { + if (amount == MAXLNUM) { // deleting buf_signcols_del_check(curbuf, line1, line2); } @@ -746,11 +785,10 @@ void sign_mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T for (sign = curbuf->b_signlist; sign != NULL; sign = next) { next = sign->se_next; - new_lnum = sign->se_lnum; - if (sign->se_lnum >= line1 && sign->se_lnum <= line2) { - if (!delete) { - new_lnum += amount; - } else if (!is_fixed || signcol >= 2) { + + new_lnum = sign_adjust_one(sign->se_lnum, line1, line2, amount, amount_after); + if (new_lnum == 0) { // sign in deleted lines + if (!is_fixed || signcol >= 2) { *lastp = next; if (next) { next->se_prev = last; @@ -758,19 +796,23 @@ void sign_mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T xfree(sign); continue; } - } else if (sign->se_lnum > line2) { - new_lnum += amount_after; - } - // If the new sign line number is past the last line in the buffer, - // then don't adjust the line number. Otherwise, it will always be past - // the last line and will not be visible. - if (sign->se_lnum >= line1 && new_lnum <= curbuf->b_ml.ml_line_count) { - sign->se_lnum = new_lnum; + } else { + // If the new sign line number is past the last line in the buffer, + // then don't adjust the line number. Otherwise, it will always be past + // the last line and will not be visible. + if (new_lnum <= curbuf->b_ml.ml_line_count) { + sign->se_lnum = new_lnum; + } } last = sign; lastp = &sign->se_next; } + + new_lnum = sign_adjust_one(curbuf->b_signcols.sentinel, line1, line2, amount, amount_after); + if (new_lnum != 0) { + curbuf->b_signcols.sentinel = new_lnum; + } } /// Find index of a ":sign" subcmd from its name. @@ -778,14 +820,14 @@ void sign_mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T /// /// @param begin_cmd begin of sign subcmd /// @param end_cmd just after sign subcmd -static int sign_cmd_idx(char_u *begin_cmd, char_u *end_cmd) +static int sign_cmd_idx(char *begin_cmd, char *end_cmd) { int idx; - char_u save = *end_cmd; + char save = *end_cmd; - *end_cmd = (char_u)NUL; + *end_cmd = NUL; for (idx = 0;; idx++) { - if (cmds[idx] == NULL || STRCMP(begin_cmd, cmds[idx]) == 0) { + if (cmds[idx] == NULL || strcmp(begin_cmd, cmds[idx]) == 0) { break; } } @@ -794,7 +836,7 @@ static int sign_cmd_idx(char_u *begin_cmd, char_u *end_cmd) } /// Find a sign by name. Also returns pointer to the previous sign. -static sign_T *sign_find(const char_u *name, sign_T **sp_prev) +static sign_T *sign_find(const char *name, sign_T **sp_prev) { sign_T *sp; @@ -802,7 +844,7 @@ static sign_T *sign_find(const char_u *name, sign_T **sp_prev) *sp_prev = NULL; } for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (STRCMP(sp->sn_name, name) == 0) { + if (strcmp(sp->sn_name, name) == 0) { break; } if (sp_prev != NULL) { @@ -814,7 +856,7 @@ static sign_T *sign_find(const char_u *name, sign_T **sp_prev) } /// Allocate a new sign -static sign_T *alloc_new_sign(char_u *name) +static sign_T *alloc_new_sign(char *name) { sign_T *sp; sign_T *lp; @@ -848,28 +890,28 @@ static sign_T *alloc_new_sign(char_u *name) next_sign_typenr = 1; // wrap around } - sp->sn_name = vim_strsave(name); + sp->sn_name = xstrdup(name); return sp; } /// Initialize the icon information for a new sign -static void sign_define_init_icon(sign_T *sp, char_u *icon) +static void sign_define_init_icon(sign_T *sp, char *icon) { xfree(sp->sn_icon); - sp->sn_icon = vim_strsave(icon); + sp->sn_icon = xstrdup(icon); backslash_halve(sp->sn_icon); } /// Initialize the text for a new sign -static int sign_define_init_text(sign_T *sp, char_u *text) +static int sign_define_init_text(sign_T *sp, char *text) { - char_u *s; - char_u *endp; + char *s; + char *endp; int cells; size_t len; - endp = text + (int)STRLEN(text); + endp = text + (int)strlen(text); for (s = text; s + 1 < endp; s++) { if (*s == '\\') { // Remove a backslash, so that it is possible @@ -880,11 +922,11 @@ static int sign_define_init_text(sign_T *sp, char_u *text) } // Count cells and check for non-printable chars cells = 0; - for (s = text; s < endp; s += utfc_ptr2len((char *)s)) { - if (!vim_isprintc(utf_ptr2char((char *)s))) { + for (s = text; s < endp; s += utfc_ptr2len(s)) { + if (!vim_isprintc(utf_ptr2char(s))) { break; } - cells += utf_ptr2cells((char *)s); + cells += utf_ptr2cells(s); } // Currently must be empty, one or two display cells if (s != endp || cells > 2) { @@ -900,7 +942,7 @@ static int sign_define_init_text(sign_T *sp, char_u *text) // Allocate one byte more if we need to pad up // with a space. len = (size_t)(endp - text + ((cells == 1) ? 1 : 0)); - sp->sn_text = vim_strnsave(text, len); + sp->sn_text = xstrnsave(text, len); if (cells == 1) { STRCPY(sp->sn_text + len - 1, " "); @@ -910,8 +952,8 @@ static int sign_define_init_text(sign_T *sp, char_u *text) } /// Define a new sign or update an existing sign -static int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_u *text, - char_u *texthl, char_u *culhl, char *numhl) +static int sign_define_by_name(char *name, char *icon, char *linehl, char *text, char *texthl, + char *culhl, char *numhl) { sign_T *sp_prev; sign_T *sp; @@ -934,7 +976,7 @@ static int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_ // non-empty sign list. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer->b_signlist != NULL) { - redraw_buf_later(wp->w_buffer, NOT_VALID); + redraw_buf_later(wp->w_buffer, UPD_NOT_VALID); } } } @@ -952,7 +994,7 @@ static int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_ if (*linehl == NUL) { sp->sn_line_hl = 0; } else { - sp->sn_line_hl = syn_check_group((char *)linehl, STRLEN(linehl)); + sp->sn_line_hl = syn_check_group(linehl, strlen(linehl)); } } @@ -960,7 +1002,7 @@ static int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_ if (*texthl == NUL) { sp->sn_text_hl = 0; } else { - sp->sn_text_hl = syn_check_group((char *)texthl, STRLEN(texthl)); + sp->sn_text_hl = syn_check_group(texthl, strlen(texthl)); } } @@ -968,7 +1010,7 @@ static int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_ if (*culhl == NUL) { sp->sn_cul_hl = 0; } else { - sp->sn_cul_hl = syn_check_group((char *)culhl, STRLEN(culhl)); + sp->sn_cul_hl = syn_check_group(culhl, strlen(culhl)); } } @@ -976,7 +1018,7 @@ static int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_ if (*numhl == NUL) { sp->sn_num_hl = 0; } else { - sp->sn_num_hl = syn_check_group(numhl, STRLEN(numhl)); + sp->sn_num_hl = syn_check_group(numhl, strlen(numhl)); } } @@ -984,7 +1026,7 @@ static int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_ } /// Free the sign specified by 'name'. -static int sign_undefine_by_name(const char_u *name) +static int sign_undefine_by_name(const char *name) { sign_T *sp_prev; sign_T *sp; @@ -1000,7 +1042,7 @@ static int sign_undefine_by_name(const char_u *name) } /// List the signs matching 'name' -static void sign_list_by_name(char_u *name) +static void sign_list_by_name(char *name) { sign_T *sp; @@ -1024,7 +1066,7 @@ static void may_force_numberwidth_recompute(buf_T *buf, int unplace) } /// Place a sign at the specified file location or update a sign. -static int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign_name, buf_T *buf, +static int sign_place(int *sign_id, const char *sign_group, const char *sign_name, buf_T *buf, linenr_T lnum, int prio) { sign_T *sp; @@ -1035,7 +1077,7 @@ static int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign } for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (STRCMP(sp->sn_name, sign_name) == 0) { + if (strcmp(sp->sn_name, sign_name) == 0) { break; } } @@ -1044,7 +1086,7 @@ static int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign return FAIL; } if (*sign_id == 0) { - *sign_id = sign_group_get_next_signid(buf, sign_group); + *sign_id = sign_group_get_next_signid(buf, (char *)sign_group); } if (lnum > 0) { @@ -1053,17 +1095,17 @@ static int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign bool has_text_or_icon = sp->sn_text != NULL || sp->sn_icon != NULL; buf_addsign(buf, *sign_id, - sign_group, + (char *)sign_group, prio, lnum, sp->sn_typenr, has_text_or_icon); } else { // ":sign place {id} file={fname}": change sign type and/or priority - lnum = buf_change_sign_type(buf, *sign_id, sign_group, sp->sn_typenr, prio); + lnum = buf_change_sign_type(buf, *sign_id, (char *)sign_group, sp->sn_typenr, prio); } if (lnum > 0) { - redraw_buf_line_later(buf, lnum); + redraw_buf_line_later(buf, lnum, false); // When displaying signs in the 'number' column, if the width of the // number column is less than 2, then force recomputing the width. @@ -1077,15 +1119,15 @@ static int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign } /// Unplace the specified sign -static int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum) +static int sign_unplace(int sign_id, char *sign_group, buf_T *buf, linenr_T atlnum) { if (buf->b_signlist == NULL) { // No signs in the buffer return OK; } if (sign_id == 0) { // Delete all the signs in the specified buffer - redraw_buf_later(buf, NOT_VALID); - buf_delete_signs(buf, (char *)sign_group); + redraw_buf_later(buf, UPD_NOT_VALID); + buf_delete_signs(buf, sign_group); } else { linenr_T lnum; @@ -1094,7 +1136,7 @@ static int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T at if (lnum == 0) { return FAIL; } - redraw_buf_line_later(buf, lnum); + redraw_buf_line_later(buf, lnum, false); } // When all the signs in a buffer are removed, force recomputing the @@ -1108,7 +1150,7 @@ static int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T at } /// Unplace the sign at the current cursor line. -static void sign_unplace_at_cursor(char_u *groupname) +static void sign_unplace_at_cursor(char *groupname) { int id = -1; @@ -1121,7 +1163,7 @@ static void sign_unplace_at_cursor(char_u *groupname) } /// Jump to a sign. -static linenr_T sign_jump(int sign_id, char_u *sign_group, buf_T *buf) +static linenr_T sign_jump(int sign_id, char *sign_group, buf_T *buf) { linenr_T lnum; @@ -1140,7 +1182,7 @@ static linenr_T sign_jump(int sign_id, char_u *sign_group, buf_T *buf) emsg(_("E934: Cannot jump to a buffer that does not have a name")); return -1; } - size_t cmdlen = STRLEN(buf->b_fname) + 24; + size_t cmdlen = strlen(buf->b_fname) + 24; char *cmd = xmallocz(cmdlen); snprintf(cmd, cmdlen, "e +%" PRId64 " %s", (int64_t)lnum, buf->b_fname); @@ -1154,49 +1196,49 @@ static linenr_T sign_jump(int sign_id, char_u *sign_group, buf_T *buf) } /// ":sign define {name} ..." command -static void sign_define_cmd(char_u *sign_name, char_u *cmdline) -{ - char_u *arg; - char_u *p = cmdline; - char_u *icon = NULL; - char_u *text = NULL; - char_u *linehl = NULL; - char_u *texthl = NULL; - char_u *culhl = NULL; - char_u *numhl = NULL; +static void sign_define_cmd(char *sign_name, char *cmdline) +{ + char *arg; + char *p = cmdline; + char *icon = NULL; + char *text = NULL; + char *linehl = NULL; + char *texthl = NULL; + char *culhl = NULL; + char *numhl = NULL; int failed = false; // set values for a defined sign. for (;;) { - arg = (char_u *)skipwhite((char *)p); + arg = skipwhite(p); if (*arg == NUL) { break; } - p = (char_u *)skiptowhite_esc((char *)arg); - if (STRNCMP(arg, "icon=", 5) == 0) { + p = skiptowhite_esc(arg); + if (strncmp(arg, "icon=", 5) == 0) { arg += 5; XFREE_CLEAR(icon); - icon = vim_strnsave(arg, (size_t)(p - arg)); - } else if (STRNCMP(arg, "text=", 5) == 0) { + icon = xstrnsave(arg, (size_t)(p - arg)); + } else if (strncmp(arg, "text=", 5) == 0) { arg += 5; XFREE_CLEAR(text); - text = vim_strnsave(arg, (size_t)(p - arg)); - } else if (STRNCMP(arg, "linehl=", 7) == 0) { + text = xstrnsave(arg, (size_t)(p - arg)); + } else if (strncmp(arg, "linehl=", 7) == 0) { arg += 7; XFREE_CLEAR(linehl); - linehl = vim_strnsave(arg, (size_t)(p - arg)); - } else if (STRNCMP(arg, "texthl=", 7) == 0) { + linehl = xstrnsave(arg, (size_t)(p - arg)); + } else if (strncmp(arg, "texthl=", 7) == 0) { arg += 7; XFREE_CLEAR(texthl); - texthl = vim_strnsave(arg, (size_t)(p - arg)); - } else if (STRNCMP(arg, "culhl=", 6) == 0) { + texthl = xstrnsave(arg, (size_t)(p - arg)); + } else if (strncmp(arg, "culhl=", 6) == 0) { arg += 6; XFREE_CLEAR(culhl); - culhl = vim_strnsave(arg, (size_t)(p - arg)); - } else if (STRNCMP(arg, "numhl=", 6) == 0) { + culhl = xstrnsave(arg, (size_t)(p - arg)); + } else if (strncmp(arg, "numhl=", 6) == 0) { arg += 6; XFREE_CLEAR(numhl); - numhl = vim_strnsave(arg, (size_t)(p - arg)); + numhl = xstrnsave(arg, (size_t)(p - arg)); } else { semsg(_(e_invarg2), arg); failed = true; @@ -1205,7 +1247,8 @@ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) } if (!failed) { - sign_define_by_name(sign_name, icon, linehl, text, texthl, culhl, (char *)numhl); + sign_define_by_name(sign_name, icon, linehl, text, + texthl, culhl, numhl); } xfree(icon); @@ -1217,7 +1260,7 @@ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) } /// ":sign place" command -static void sign_place_cmd(buf_T *buf, linenr_T lnum, char_u *sign_name, int id, char_u *group, +static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, char *group, int prio) { if (id <= 0) { @@ -1250,7 +1293,7 @@ static void sign_place_cmd(buf_T *buf, linenr_T lnum, char_u *sign_name, int id, } /// ":sign unplace" command -static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, char_u *sign_name, int id, char_u *group) +static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int id, char *group) { if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0')) { emsg(_(e_invarg)); @@ -1272,7 +1315,7 @@ static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, char_u *sign_name, int i // :sign unplace * group=* FOR_ALL_BUFFERS(cbuf) { if (cbuf->b_signlist != NULL) { - buf_delete_signs(cbuf, (char *)group); + buf_delete_signs(cbuf, group); } } } @@ -1307,7 +1350,7 @@ static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, char_u *sign_name, int i /// :sign jump {id} buffer={nr} /// :sign jump {id} group={group} file={fname} /// :sign jump {id} group={group} buffer={nr} -static void sign_jump_cmd(buf_T *buf, linenr_T lnum, char_u *sign_name, int id, char_u *group) +static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int id, char *group) { if (sign_name == NULL && group == NULL && id == -1) { emsg(_(e_argreq)); @@ -1329,40 +1372,40 @@ static void sign_jump_cmd(buf_T *buf, linenr_T lnum, char_u *sign_name, int id, /// ":sign jump" commands. /// The supported arguments are: line={lnum} name={name} group={group} /// priority={prio} and file={fname} or buffer={nr}. -static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *signid, - char_u **group, int *prio, buf_T **buf, linenr_T *lnum) +static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid, char **group, + int *prio, buf_T **buf, linenr_T *lnum) { - char_u *arg1; - char_u *name; - char_u *filename = NULL; + char *arg1; + char *name; + char *filename = NULL; int lnum_arg = false; // first arg could be placed sign id arg1 = arg; if (ascii_isdigit(*arg)) { - *signid = getdigits_int((char **)&arg, true, 0); + *signid = getdigits_int(&arg, true, 0); if (!ascii_iswhite(*arg) && *arg != NUL) { *signid = -1; arg = arg1; } else { - arg = (char_u *)skipwhite((char *)arg); + arg = skipwhite(arg); } } while (*arg != NUL) { - if (STRNCMP(arg, "line=", 5) == 0) { + if (strncmp(arg, "line=", 5) == 0) { arg += 5; - *lnum = atoi((char *)arg); + *lnum = atoi(arg); arg = skiptowhite(arg); lnum_arg = true; - } else if (STRNCMP(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE) { + } else if (strncmp(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE) { if (*signid != -1) { emsg(_(e_invarg)); return FAIL; } *signid = -2; arg = skiptowhite(arg + 1); - } else if (STRNCMP(arg, "name=", 5) == 0) { + } else if (strncmp(arg, "name=", 5) == 0) { arg += 5; name = arg; arg = skiptowhite(arg); @@ -1373,27 +1416,27 @@ static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *si name++; } *sign_name = name; - } else if (STRNCMP(arg, "group=", 6) == 0) { + } else if (strncmp(arg, "group=", 6) == 0) { arg += 6; *group = arg; arg = skiptowhite(arg); if (*arg != NUL) { *arg++ = NUL; } - } else if (STRNCMP(arg, "priority=", 9) == 0) { + } else if (strncmp(arg, "priority=", 9) == 0) { arg += 9; - *prio = atoi((char *)arg); + *prio = atoi(arg); arg = skiptowhite(arg); - } else if (STRNCMP(arg, "file=", 5) == 0) { + } else if (strncmp(arg, "file=", 5) == 0) { arg += 5; filename = arg; - *buf = buflist_findname_exp((char *)arg); + *buf = buflist_findname_exp(arg); break; - } else if (STRNCMP(arg, "buffer=", 7) == 0) { + } else if (strncmp(arg, "buffer=", 7) == 0) { arg += 7; filename = arg; - *buf = buflist_findnr(getdigits_int((char **)&arg, true, 0)); - if (*skipwhite((char *)arg) != NUL) { + *buf = buflist_findnr(getdigits_int(&arg, true, 0)); + if (*skipwhite(arg) != NUL) { semsg(_(e_trailing_arg), arg); } break; @@ -1401,7 +1444,7 @@ static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *si emsg(_(e_invarg)); return FAIL; } - arg = (char_u *)skipwhite((char *)arg); + arg = skipwhite(arg); } if (filename != NULL && *buf == NULL) { @@ -1421,8 +1464,8 @@ static int parse_sign_cmd_args(int cmd, char_u *arg, char_u **sign_name, int *si /// ":sign" command void ex_sign(exarg_T *eap) { - char_u *arg = (char_u *)eap->arg; - char_u *p; + char *arg = eap->arg; + char *p; int idx; sign_T *sp; @@ -1433,7 +1476,7 @@ void ex_sign(exarg_T *eap) semsg(_("E160: Unknown sign command: %s"), arg); return; } - arg = (char_u *)skipwhite((char *)p); + arg = skipwhite(p); if (idx <= SIGNCMD_LIST) { // Define, undefine or list signs. @@ -1445,7 +1488,7 @@ void ex_sign(exarg_T *eap) } else if (*arg == NUL) { emsg(_("E156: Missing sign name")); } else { - char_u *name; + char *name; // Isolate the sign name. If it's a number skip leading zeroes, // so that "099" and "99" are the same sign. But keep "0". @@ -1456,7 +1499,7 @@ void ex_sign(exarg_T *eap) while (arg[0] == '0' && arg[1] != NUL) { arg++; } - name = vim_strsave(arg); + name = xstrdup(arg); if (idx == SIGNCMD_DEFINE) { sign_define_cmd(name, p); @@ -1474,8 +1517,8 @@ void ex_sign(exarg_T *eap) } else { int id = -1; linenr_T lnum = -1; - char_u *sign_name = NULL; - char_u *group = NULL; + char *sign_name = NULL; + char *group = NULL; int prio = SIGN_DEF_PRIO; buf_T *buf = NULL; @@ -1500,12 +1543,12 @@ static void sign_getinfo(sign_T *sp, dict_T *retdict) { const char *p; - tv_dict_add_str(retdict, S_LEN("name"), (char *)sp->sn_name); + tv_dict_add_str(retdict, S_LEN("name"), sp->sn_name); if (sp->sn_icon != NULL) { - tv_dict_add_str(retdict, S_LEN("icon"), (char *)sp->sn_icon); + tv_dict_add_str(retdict, S_LEN("icon"), sp->sn_icon); } if (sp->sn_text != NULL) { - tv_dict_add_str(retdict, S_LEN("text"), (char *)sp->sn_text); + tv_dict_add_str(retdict, S_LEN("text"), sp->sn_text); } if (sp->sn_line_hl > 0) { p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, false); @@ -1539,13 +1582,13 @@ static void sign_getinfo(sign_T *sp, dict_T *retdict) /// If 'name' is NULL, return a list of all the defined signs. /// Otherwise, return information about the specified sign. -static void sign_getlist(const char_u *name, list_T *retlist) +static void sign_getlist(const char *name, list_T *retlist) { sign_T *sp = first_sign; dict_T *dict; if (name != NULL) { - sp = sign_find(name, NULL); + sp = sign_find((char *)name, NULL); if (sp == NULL) { return; } @@ -1577,8 +1620,8 @@ list_T *get_buffer_signs(buf_T *buf) return l; } -/// Return information about all the signs placed in a buffer -static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const char_u *sign_group, +/// @return information about all the signs placed in a buffer +static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const char *sign_group, list_T *retlist) { dict_T *d; @@ -1594,7 +1637,7 @@ static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const tv_dict_add_list(d, S_LEN("signs"), l); FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if (!sign_in_group(sign, sign_group)) { + if (!sign_in_group(sign, (char *)sign_group)) { continue; } if ((lnum == 0 && sign_id == 0) @@ -1609,7 +1652,7 @@ static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const /// Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the /// sign placed at the line number. If 'lnum' is zero, return all the signs /// placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers. -static void sign_get_placed(buf_T *buf, linenr_T lnum, int sign_id, const char_u *sign_group, +static void sign_get_placed(buf_T *buf, linenr_T lnum, int sign_id, const char *sign_group, list_T *retlist) { if (buf != NULL) { @@ -1629,12 +1672,12 @@ static void sign_list_defined(sign_T *sp) smsg("sign %s", sp->sn_name); if (sp->sn_icon != NULL) { msg_puts(" icon="); - msg_outtrans((char *)sp->sn_icon); + msg_outtrans(sp->sn_icon); msg_puts(_(" (not supported)")); } if (sp->sn_text != NULL) { msg_puts(" text="); - msg_outtrans((char *)sp->sn_text); + msg_outtrans(sp->sn_text); } if (sp->sn_line_hl > 0) { msg_puts(" linehl="); @@ -1710,8 +1753,8 @@ static enum { EXP_SIGN_GROUPS, // expand with name of placed sign groups } expand_what; -// Return the n'th sign name (used for command line completion) -static char_u *get_nth_sign_name(int idx) +/// @return the n'th sign name (used for command line completion) +static char *get_nth_sign_name(int idx) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // Complete with name of signs already defined @@ -1724,8 +1767,8 @@ static char_u *get_nth_sign_name(int idx) return NULL; } -// Return the n'th sign group name (used for command line completion) -static char_u *get_nth_sign_group_name(int idx) +/// @return the n'th sign group name (used for command line completion) +static char *get_nth_sign_group_name(int idx) { // Complete with name of sign groups already defined int current_idx = 0; @@ -1735,7 +1778,7 @@ static char_u *get_nth_sign_group_name(int idx) todo--; if (current_idx++ == idx) { signgroup_T *const group = HI2SG(hi); - return group->sg_name; + return (char *)group->sg_name; } } } @@ -1768,26 +1811,26 @@ char *get_sign_name(expand_T *xp, int idx) return unplace_arg[idx]; } case EXP_SIGN_NAMES: - return (char *)get_nth_sign_name(idx); + return get_nth_sign_name(idx); case EXP_SIGN_GROUPS: - return (char *)get_nth_sign_group_name(idx); + return get_nth_sign_group_name(idx); default: return NULL; } } /// Handle command line completion for :sign command. -void set_context_in_sign_cmd(expand_T *xp, char_u *arg) +void set_context_in_sign_cmd(expand_T *xp, char *arg) { - char_u *end_subcmd; - char_u *last; + char *end_subcmd; + char *last; int cmd_idx; - char_u *begin_subcmd_args; + char *begin_subcmd_args; // Default: expand subcommands. xp->xp_context = EXPAND_SIGN; expand_what = EXP_SUBCMD; - xp->xp_pattern = (char *)arg; + xp->xp_pattern = arg; end_subcmd = skiptowhite(arg); if (*end_subcmd == NUL) { @@ -1801,7 +1844,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) // :sign {subcmd} {subcmd_args} // | // begin_subcmd_args - begin_subcmd_args = (char_u *)skipwhite((char *)end_subcmd); + begin_subcmd_args = skipwhite(end_subcmd); // Expand last argument of subcmd. // @@ -1810,21 +1853,21 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) // p // Loop until reaching last argument. - char_u *p = begin_subcmd_args; + char *p = begin_subcmd_args; do { - p = (char_u *)skipwhite((char *)p); + p = skipwhite(p); last = p; p = skiptowhite(p); } while (*p != NUL); - p = (char_u *)vim_strchr((char *)last, '='); + p = vim_strchr(last, '='); // :sign define {name} {args}... {last}= // | | // last p if (p == NULL) { // Expand last argument name (before equal sign). - xp->xp_pattern = (char *)last; + xp->xp_pattern = last; switch (cmd_idx) { case SIGNCMD_DEFINE: expand_what = EXP_DEFINE; @@ -1854,26 +1897,26 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) } } else { // Expand last argument value (after equal sign). - xp->xp_pattern = (char *)p + 1; + xp->xp_pattern = p + 1; switch (cmd_idx) { case SIGNCMD_DEFINE: - if (STRNCMP(last, "texthl", 6) == 0 - || STRNCMP(last, "linehl", 6) == 0 - || STRNCMP(last, "culhl", 5) == 0 - || STRNCMP(last, "numhl", 5) == 0) { + if (strncmp(last, "texthl", 6) == 0 + || strncmp(last, "linehl", 6) == 0 + || strncmp(last, "culhl", 5) == 0 + || strncmp(last, "numhl", 5) == 0) { xp->xp_context = EXPAND_HIGHLIGHT; - } else if (STRNCMP(last, "icon", 4) == 0) { + } else if (strncmp(last, "icon", 4) == 0) { xp->xp_context = EXPAND_FILES; } else { xp->xp_context = EXPAND_NOTHING; } break; case SIGNCMD_PLACE: - if (STRNCMP(last, "name", 4) == 0) { + if (strncmp(last, "name", 4) == 0) { expand_what = EXP_SIGN_NAMES; - } else if (STRNCMP(last, "group", 5) == 0) { + } else if (strncmp(last, "group", 5) == 0) { expand_what = EXP_SIGN_GROUPS; - } else if (STRNCMP(last, "file", 4) == 0) { + } else if (strncmp(last, "file", 4) == 0) { xp->xp_context = EXPAND_BUFFERS; } else { xp->xp_context = EXPAND_NOTHING; @@ -1881,9 +1924,9 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) break; case SIGNCMD_UNPLACE: case SIGNCMD_JUMP: - if (STRNCMP(last, "group", 5) == 0) { + if (strncmp(last, "group", 5) == 0) { expand_what = EXP_SIGN_GROUPS; - } else if (STRNCMP(last, "file", 4) == 0) { + } else if (strncmp(last, "file", 4) == 0) { xp->xp_context = EXPAND_BUFFERS; } else { xp->xp_context = EXPAND_NOTHING; @@ -1928,8 +1971,8 @@ static int sign_define_from_dict(const char *name_arg, dict_T *dict) numhl = tv_dict_get_string(dict, "numhl", true); } - if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, - (char_u *)text, (char_u *)texthl, (char_u *)culhl, numhl) + if (sign_define_by_name(name, icon, linehl, + text, texthl, culhl, numhl) == OK) { retval = 0; } @@ -1964,7 +2007,7 @@ static void sign_define_multiple(list_T *l, list_T *retlist) } /// "sign_define()" function -void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *name; @@ -1995,7 +2038,7 @@ void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_getdefined()" function -void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_getdefined(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *name = NULL; @@ -2005,11 +2048,11 @@ void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) name = tv_get_string(&argvars[0]); } - sign_getlist((const char_u *)name, rettv->vval.v_list); + sign_getlist(name, rettv->vval.v_list); } /// "sign_getplaced()" function -void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { buf_T *buf = NULL; dict_T *dict; @@ -2062,12 +2105,11 @@ void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - sign_get_placed(buf, lnum, sign_id, (const char_u *)group, - rettv->vval.v_list); + sign_get_placed(buf, lnum, sign_id, group, rettv->vval.v_list); } /// "sign_jump()" function -void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_jump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int sign_id; char *sign_group = NULL; @@ -2103,7 +2145,7 @@ void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) goto cleanup; } - rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf); + rettv->vval.v_number = sign_jump(sign_id, sign_group, buf); cleanup: xfree(sign_group); @@ -2115,8 +2157,8 @@ static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *n typval_T *buf_tv, dict_T *dict) { int sign_id = 0; - char_u *group = NULL; - char_u *sign_name = NULL; + char *group = NULL; + char *sign_name = NULL; buf_T *buf = NULL; dictitem_T *di; linenr_T lnum = 0; @@ -2154,14 +2196,14 @@ static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *n if (group_tv == NULL) { group = NULL; // global group } else { - group = (char_u *)tv_get_string_chk(group_tv); + group = (char *)tv_get_string_chk(group_tv); if (group == NULL) { goto cleanup; } if (group[0] == '\0') { // global sign group group = NULL; } else { - group = vim_strsave(group); + group = xstrdup(group); } } @@ -2175,7 +2217,7 @@ static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *n if (name_tv == NULL) { goto cleanup; } - sign_name = (char_u *)tv_get_string_chk(name_tv); + sign_name = (char *)tv_get_string_chk(name_tv); if (sign_name == NULL) { goto cleanup; } @@ -2225,7 +2267,7 @@ cleanup: } /// "sign_place()" function -void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_place(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { dict_T *dict = NULL; @@ -2243,7 +2285,7 @@ void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_placelist()" function. Place multiple signs. -void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int sign_id; @@ -2269,13 +2311,13 @@ void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// Undefine multiple signs static void sign_undefine_multiple(list_T *l, list_T *retlist) { - char_u *name; + char *name; int retval; TV_LIST_ITER_CONST(l, li, { retval = -1; - name = (char_u *)tv_get_string_chk(TV_LIST_ITEM_TV(li)); - if (name != NULL && (sign_undefine_by_name(name) == OK)) { + name = (char *)tv_get_string_chk(TV_LIST_ITEM_TV(li)); + if (name != NULL && (sign_undefine_by_name((char *)name) == OK)) { retval = 0; } tv_list_append_number(retlist, retval); @@ -2283,7 +2325,7 @@ static void sign_undefine_multiple(list_T *l, list_T *retlist) } /// "sign_undefine()" function -void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_undefine(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *name; @@ -2308,7 +2350,7 @@ void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - if (sign_undefine_by_name((const char_u *)name) == OK) { + if (sign_undefine_by_name(name) == OK) { rettv->vval.v_number = 0; } } @@ -2321,20 +2363,20 @@ static int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict) dictitem_T *di; int sign_id = 0; buf_T *buf = NULL; - char_u *group = NULL; + char *group = NULL; int retval = -1; // sign group if (group_tv != NULL) { - group = (char_u *)tv_get_string(group_tv); + group = (char *)tv_get_string(group_tv); } else { - group = (char_u *)tv_dict_get_string(dict, "group", false); + group = tv_dict_get_string(dict, "group", false); } if (group != NULL) { if (group[0] == '\0') { // global sign group group = NULL; } else { - group = vim_strsave(group); + group = xstrdup(group); } } @@ -2373,7 +2415,7 @@ cleanup: } /// "sign_unplace()" function -void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_unplace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { dict_T *dict = NULL; @@ -2396,7 +2438,7 @@ void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "sign_unplacelist()" function -void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int retval; diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index a4fb325ec8..16e783aab7 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -10,9 +10,9 @@ // Sign group typedef struct signgroup_S { - uint16_t sg_refcount; // number of signs in this group - int sg_next_sign_id; // next sign id for this group - char_u sg_name[1]; // sign group name + uint16_t sg_refcount; // number of signs in this group + int sg_next_sign_id; // next sign id for this group + char sg_name[1]; // sign group name } signgroup_T; // Macros to get the sign group structure from the group name @@ -34,7 +34,7 @@ struct sign_entry { /// Sign attributes. Used by the screen refresh routines. typedef struct { - char_u *text; + char *text; int hl_attr_id; int priority; } SignTextAttrs; @@ -51,4 +51,4 @@ typedef enum { SIGN_TEXT, } SignType; -#endif // NVIM_SIGN_DEFS_H +#endif // NVIM_SIGN_DEFS_H diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 1e44d328b3..8e18be5bd1 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -50,7 +50,7 @@ // Use SPELL_PRINTTREE for debugging: dump the word tree after adding a word. // Only use it for small word lists! -// Use SPELL_COMPRESS_ALLWAYS for debugging: compress the word tree after +// Use SPELL_COMPRESS_ALWAYS for debugging: compress the word tree after // adding a word. Only use it for small word lists! // Use DEBUG_TRIEWALK to print the changes made in suggest_trie_walk() for a @@ -60,74 +60,84 @@ #include <inttypes.h> #include <limits.h> #include <stdbool.h> -#include <stdlib.h> -#include <string.h> -#include <wctype.h> - -// for offsetof() #include <stddef.h> +#include <stdio.h> +#include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/func_attr.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/hashtab.h" -#include "nvim/input.h" +#include "nvim/highlight_defs.h" #include "nvim/insexpand.h" +#include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/os/fs.h" #include "nvim/os/input.h" -#include "nvim/os/os.h" -#include "nvim/os_unix.h" +#include "nvim/os/os_defs.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/regexp.h" +#include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/spell.h" +#include "nvim/spell_defs.h" #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" #include "nvim/strings.h" #include "nvim/syntax.h" +#include "nvim/types.h" #include "nvim/undo.h" +#include "nvim/vim.h" +#include "nvim/window.h" // Result values. Lower number is accepted over higher one. -#define SP_BANNED (-1) -#define SP_RARE 0 -#define SP_OK 1 -#define SP_LOCAL 2 -#define SP_BAD 3 +enum { + SP_BANNED = -1, + SP_RARE = 0, + SP_OK = 1, + SP_LOCAL = 2, + SP_BAD = 3, +}; // First language that is loaded, start of the linked list of loaded // languages. slang_T *first_lang = NULL; // file used for "zG" and "zW" -char_u *int_wordlist = NULL; +char *int_wordlist = NULL; // Structure to store info for word matching. typedef struct matchinf_S { langp_T *mi_lp; // info for language and region // pointers to original text to be checked - char_u *mi_word; // start of word being checked - char_u *mi_end; // end of matching word so far - char_u *mi_fend; // next char to be added to mi_fword - char_u *mi_cend; // char after what was used for + char *mi_word; // start of word being checked + char *mi_end; // end of matching word so far + char *mi_fend; // next char to be added to mi_fword + char *mi_cend; // char after what was used for // mi_capflags // case-folded text - char_u mi_fword[MAXWLEN + 1]; // mi_word case-folded + char mi_fword[MAXWLEN + 1]; // mi_word case-folded int mi_fwordlen; // nr of valid bytes in mi_fword // for when checking word after a prefix @@ -150,20 +160,20 @@ typedef struct matchinf_S { win_T *mi_win; // buffer being checked // for NOBREAK - int mi_result2; // "mi_resul" without following word - char_u *mi_end2; // "mi_end" without following word + int mi_result2; // "mi_result" without following word + char *mi_end2; // "mi_end" without following word } matchinf_T; // Structure used for the cookie argument of do_in_runtimepath(). typedef struct spelload_S { - char_u sl_lang[MAXWLEN + 1]; // language name + char sl_lang[MAXWLEN + 1]; // language name slang_T *sl_slang; // resulting slang_T struct int sl_nobreak; // NOBREAK language found } spelload_T; #define SY_MAXLEN 30 typedef struct syl_item_S { - char_u sy_chars[SY_MAXLEN]; // the sequence of chars + char sy_chars[SY_MAXLEN]; // the sequence of chars int sy_len; } syl_item_T; @@ -184,8 +194,8 @@ int did_set_spelltab; char *e_format = N_("E759: Format error in spell file"); // Remember what "z?" replaced. -char_u *repl_from = NULL; -char_u *repl_to = NULL; +char *repl_from = NULL; +char *repl_to = NULL; /// Main spell-checking function. /// "ptr" points to a character that could be the start of a word. @@ -204,21 +214,19 @@ char_u *repl_to = NULL; /// /// @return the length of the word in bytes, also when it's OK, so that the /// caller can skip over the word. -size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docount) +size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount) { matchinf_T mi; // Most things are put in "mi" so that it can // be passed to functions quickly. size_t nrlen = 0; // found a number first - int c; size_t wrongcaplen = 0; - int lpi; bool count_word = docount; - bool use_camel_case = *wp->w_s->b_p_spo != NUL; + bool use_camel_case = (wp->w_s->b_p_spo_flags & SPO_CAMEL) != 0; bool camel_case = false; // A word never starts at a space or a control character. Return quickly // then, skipping over the character. - if (*ptr <= ' ') { + if ((uint8_t)(*ptr) <= ' ') { return 1; } @@ -234,11 +242,11 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou // julifeest". if (*ptr >= '0' && *ptr <= '9') { if (*ptr == '0' && (ptr[1] == 'b' || ptr[1] == 'B')) { - mi.mi_end = (char_u *)skipbin((char *)ptr + 2); + mi.mi_end = (char *)skipbin(ptr + 2); } else if (*ptr == '0' && (ptr[1] == 'x' || ptr[1] == 'X')) { mi.mi_end = skiphex(ptr + 2); } else { - mi.mi_end = (char_u *)skipdigits((char *)ptr); + mi.mi_end = skipdigits(ptr); } nrlen = (size_t)(mi.mi_end - ptr); } @@ -250,7 +258,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou bool this_upper = false; // init for gcc if (use_camel_case) { - c = utf_ptr2char((char *)mi.mi_fend); + int c = utf_ptr2char(mi.mi_fend); this_upper = SPELL_ISUPPER(c); } @@ -258,7 +266,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou MB_PTR_ADV(mi.mi_fend); if (use_camel_case) { const bool prev_upper = this_upper; - c = utf_ptr2char((char *)mi.mi_fend); + int c = utf_ptr2char(mi.mi_fend); this_upper = SPELL_ISUPPER(c); camel_case = !prev_upper && this_upper; } @@ -267,7 +275,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou if (capcol != NULL && *capcol == 0 && wp->w_s->b_cap_prog != NULL) { // Check word starting with capital letter. - c = utf_ptr2char((char *)ptr); + int c = utf_ptr2char(ptr); if (!SPELL_ISUPPER(c)) { wrongcaplen = (size_t)(mi.mi_fend - ptr); } @@ -294,7 +302,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou (void)spell_casefold(wp, ptr, (int)(mi.mi_fend - ptr), mi.mi_fword, MAXWLEN + 1); - mi.mi_fwordlen = (int)STRLEN(mi.mi_fword); + mi.mi_fwordlen = (int)strlen(mi.mi_fword); if (camel_case && mi.mi_fwordlen > 0) { // introduce a fake word end space into the folded word. @@ -308,7 +316,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou // Loop over the languages specified in 'spelllang'. // We check them all, because a word may be matched longer in another // language. - for (lpi = 0; lpi < wp->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < wp->w_s->b_langp.ga_len; lpi++) { mi.mi_lp = LANGP_ENTRY(wp->w_s->b_langp, lpi); // If reloading fails the language is still in the list but everything @@ -358,21 +366,21 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou // Check for end of sentence. regmatch.regprog = wp->w_s->b_cap_prog; regmatch.rm_ic = false; - int r = vim_regexec(®match, (char *)ptr, 0); + int r = vim_regexec(®match, ptr, 0); wp->w_s->b_cap_prog = regmatch.regprog; if (r) { *capcol = (int)(regmatch.endp[0] - ptr); } } - return (size_t)(utfc_ptr2len((char *)ptr)); + return (size_t)(utfc_ptr2len(ptr)); } else if (mi.mi_end == ptr) { // Always include at least one character. Required for when there // is a mixup in "midword". MB_PTR_ADV(mi.mi_end); } else if (mi.mi_result == SP_BAD && LANGP_ENTRY(wp->w_s->b_langp, 0)->lp_slang->sl_nobreak) { - char_u *p, *fp; + char *p, *fp; int save_result = mi.mi_result; // First language in 'spelllang' is NOBREAK. Find first position @@ -427,7 +435,7 @@ static void find_word(matchinf_T *mip, int mode) { int wlen = 0; int flen; - char_u *ptr; + char *ptr; slang_T *slang = mip->mi_lp->lp_slang; char_u *byts; idx_T *idxs; @@ -436,7 +444,7 @@ static void find_word(matchinf_T *mip, int mode) // Check for word with matching case in keep-case tree. ptr = mip->mi_word; flen = 9999; // no case folding, always enough bytes - byts = slang->sl_kbyts; + byts = (char_u *)slang->sl_kbyts; idxs = slang->sl_kidxs; if (mode == FIND_KEEPCOMPOUND) { @@ -447,7 +455,7 @@ static void find_word(matchinf_T *mip, int mode) // Check for case-folded in case-folded tree. ptr = mip->mi_fword; flen = mip->mi_fwordlen; // available case-folded bytes - byts = slang->sl_fbyts; + byts = (char_u *)slang->sl_fbyts; idxs = slang->sl_fidxs; if (mode == FIND_PREFIX) { @@ -468,7 +476,6 @@ static void find_word(matchinf_T *mip, int mode) int endlen[MAXWLEN]; // length at possible word endings idx_T endidx[MAXWLEN]; // possible word endings int endidxcnt = 0; - int len; int c; // Repeat advancing in the tree until: @@ -480,7 +487,7 @@ static void find_word(matchinf_T *mip, int mode) flen = fold_more(mip); } - len = byts[arridx++]; + int len = byts[arridx++]; // If the first possible byte is a zero the word could end here. // Remember this index, we first check for the longest word. @@ -511,7 +518,7 @@ static void find_word(matchinf_T *mip, int mode) } // Perform a binary search in the list of accepted bytes. - c = ptr[wlen]; + c = (uint8_t)ptr[wlen]; if (c == TAB) { // <Tab> is handled like <Space> c = ' '; } @@ -555,7 +562,7 @@ static void find_word(matchinf_T *mip, int mode) } } - char_u *p; + char *p; bool word_ends; // Verify that one of the possible endings is valid. Try the longest @@ -585,8 +592,8 @@ static void find_word(matchinf_T *mip, int mode) // when folding case. This can be slow, take a shortcut when the // case-folded word is equal to the keep-case word. p = mip->mi_word; - if (STRNCMP(ptr, p, wlen) != 0) { - for (char_u *s = ptr; s < ptr + wlen; MB_PTR_ADV(s)) { + if (strncmp(ptr, p, (size_t)wlen) != 0) { + for (char *s = ptr; s < ptr + wlen; MB_PTR_ADV(s)) { MB_PTR_ADV(p); } wlen = (int)(p - mip->mi_word); @@ -597,7 +604,7 @@ static void find_word(matchinf_T *mip, int mode) // prefix ID. // Repeat this if there are more flags/region alternatives until there // is a match. - for (len = byts[arridx - 1]; len > 0 && byts[arridx] == 0; len--, arridx++) { + for (int len = byts[arridx - 1]; len > 0 && byts[arridx] == 0; len--, arridx++) { uint32_t flags = (uint32_t)idxs[arridx]; // For the fold-case tree check that the case of the checked word @@ -616,11 +623,10 @@ static void find_word(matchinf_T *mip, int mode) || !spell_valid_case(mip->mi_capflags, (int)flags)) { continue; } - } - // When mode is FIND_PREFIX the word must support the prefix: - // check the prefix ID and the condition. Do that for the list at - // mip->mi_prefarridx that find_prefix() filled. - else if (mode == FIND_PREFIX && !prefix_found) { + } else if (mode == FIND_PREFIX && !prefix_found) { + // When mode is FIND_PREFIX the word must support the prefix: + // check the prefix ID and the condition. Do that for the list at + // mip->mi_prefarridx that find_prefix() filled. c = valid_word_prefix(mip->mi_prefcnt, mip->mi_prefarridx, (int)flags, mip->mi_word + mip->mi_cprefixlen, slang, @@ -681,7 +687,8 @@ static void find_word(matchinf_T *mip, int mode) } // Quickly check if compounding is possible with this flag. - if (!byte_in_str(mip->mi_complen == 0 ? slang->sl_compstartflags : slang->sl_compallflags, + if (!byte_in_str(mip->mi_complen == + 0 ? slang->sl_compstartflags : slang->sl_compallflags, (int)((unsigned)flags >> 24))) { continue; } @@ -697,10 +704,10 @@ static void find_word(matchinf_T *mip, int mode) // Need to check the caps type of the appended compound // word. - if (STRNCMP(ptr, mip->mi_word, mip->mi_compoff) != 0) { + if (strncmp(ptr, mip->mi_word, (size_t)mip->mi_compoff) != 0) { // case folding may have changed the length p = mip->mi_word; - for (char_u *s = ptr; s < ptr + mip->mi_compoff; MB_PTR_ADV(s)) { + for (char *s = ptr; s < ptr + mip->mi_compoff; MB_PTR_ADV(s)) { MB_PTR_ADV(p); } } else { @@ -733,14 +740,14 @@ static void find_word(matchinf_T *mip, int mode) mip->mi_compflags[mip->mi_complen] = (char_u)((unsigned)flags >> 24); mip->mi_compflags[mip->mi_complen + 1] = NUL; if (word_ends) { - char_u fword[MAXWLEN] = { 0 }; + char fword[MAXWLEN] = { 0 }; if (slang->sl_compsylmax < MAXWLEN) { // "fword" is only needed for checking syllables. if (ptr == mip->mi_word) { (void)spell_casefold(mip->mi_win, ptr, wlen, fword, MAXWLEN); } else { - STRLCPY(fword, ptr, endlen[endidxcnt] + 1); + xstrlcpy(fword, ptr, (size_t)endlen[endidxcnt] + 1); } } if (!can_compound(slang, fword, mip->mi_compflags)) { @@ -752,9 +759,8 @@ static void find_word(matchinf_T *mip, int mode) // COMPOUNDRULE, discard the compounded word. continue; } - } - // Check NEEDCOMPOUND: can't use word without compounding. - else if (flags & WF_NEEDCOMP) { + } else if (flags & WF_NEEDCOMP) { + // skip if word is only valid in a compound continue; } @@ -762,7 +768,7 @@ static void find_word(matchinf_T *mip, int mode) if (!word_ends) { int save_result = mip->mi_result; - char_u *save_end = mip->mi_end; + char *save_end = mip->mi_end; langp_T *save_lp = mip->mi_lp; // Check that a valid word follows. If there is one and we @@ -782,8 +788,8 @@ static void find_word(matchinf_T *mip, int mode) // folding case. This can be slow, take a shortcut when // the case-folded word is equal to the keep-case word. p = mip->mi_fword; - if (STRNCMP(ptr, p, wlen) != 0) { - for (char_u *s = ptr; s < ptr + wlen; MB_PTR_ADV(s)) { + if (strncmp(ptr, p, (size_t)wlen) != 0) { + for (char *s = ptr; s < ptr + wlen; MB_PTR_ADV(s)) { MB_PTR_ADV(p); } mip->mi_compoff = (int)(p - mip->mi_fword); @@ -792,14 +798,14 @@ static void find_word(matchinf_T *mip, int mode) #if 0 c = mip->mi_compoff; #endif - ++mip->mi_complen; + mip->mi_complen++; if (flags & WF_COMPROOT) { - ++mip->mi_compextra; + mip->mi_compextra++; } // For NOBREAK we need to try all NOBREAK languages, at least // to find the ".add" file(s). - for (int lpi = 0; lpi < mip->mi_win->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < mip->mi_win->w_s->b_langp.ga_len; lpi++) { if (slang->sl_nobreak) { mip->mi_lp = LANGP_ENTRY(mip->mi_win->w_s->b_langp, lpi); if (mip->mi_lp->lp_slang->sl_fidxs == NULL @@ -833,9 +839,9 @@ static void find_word(matchinf_T *mip, int mode) break; } } - --mip->mi_complen; + mip->mi_complen--; if (flags & WF_COMPROOT) { - --mip->mi_compextra; + mip->mi_compextra--; } mip->mi_lp = save_lp; @@ -903,19 +909,16 @@ static void find_word(matchinf_T *mip, int mode) /// end of ptr[wlen] and the second part matches after it. /// /// @param gap &sl_comppat -bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) +bool match_checkcompoundpattern(char *ptr, int wlen, garray_T *gap) { - char_u *p; - int len; - for (int i = 0; i + 1 < gap->ga_len; i += 2) { - p = ((char_u **)gap->ga_data)[i + 1]; - if (STRNCMP(ptr + wlen, p, STRLEN(p)) == 0) { + char *p = ((char **)gap->ga_data)[i + 1]; + if (strncmp(ptr + wlen, p, strlen(p)) == 0) { // Second part matches at start of following compound word, now // check if first part matches at end of previous word. - p = ((char_u **)gap->ga_data)[i]; - len = (int)STRLEN(p); - if (len <= wlen && STRNCMP(ptr + wlen - len, p, len) == 0) { + p = ((char **)gap->ga_data)[i]; + int len = (int)strlen(p); + if (len <= wlen && strncmp(ptr + wlen - len, p, (size_t)len) == 0) { return true; } } @@ -923,20 +926,20 @@ bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) return false; } -// Returns true if "flags" is a valid sequence of compound flags and "word" -// does not have too many syllables. -bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) +/// @return true if "flags" is a valid sequence of compound flags and "word" +/// does not have too many syllables. +bool can_compound(slang_T *slang, const char *word, const uint8_t *flags) FUNC_ATTR_NONNULL_ALL { - char_u uflags[MAXWLEN * 2] = { 0 }; + char uflags[MAXWLEN * 2] = { 0 }; if (slang->sl_compprog == NULL) { return false; } // Need to convert the single byte flags to utf8 characters. - char_u *p = uflags; + char *p = uflags; for (int i = 0; flags[i] != NUL; i++) { - p += utf_char2bytes(flags[i], (char *)p); + p += utf_char2bytes(flags[i], p); } *p = NUL; p = uflags; @@ -949,7 +952,7 @@ bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) // COMPOUNDWORDMAX then compounding is not allowed. if (slang->sl_compsylmax < MAXWLEN && count_syllables(slang, word) > slang->sl_compsylmax) { - return (int)STRLEN(flags) < slang->sl_compmax; + return (int)strlen((char *)flags) < slang->sl_compmax; } return true; } @@ -958,18 +961,14 @@ bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) // compound rule. This is used to stop trying a compound if the flags // collected so far can't possibly match any compound rule. // Caller must check that slang->sl_comprules is not NULL. -bool match_compoundrule(slang_T *slang, char_u *compflags) +bool match_compoundrule(slang_T *slang, const char_u *compflags) { - char_u *p; - int i; - int c; - // loop over all the COMPOUNDRULE entries - for (p = slang->sl_comprules; *p != NUL; ++p) { + for (char_u *p = (char_u *)slang->sl_comprules; *p != NUL; p++) { // loop over the flags in the compound word we have made, match // them against the current rule entry - for (i = 0;; ++i) { - c = compflags[i]; + for (int i = 0;; i++) { + int c = compflags[i]; if (c == NUL) { // found a rule that matches for the flags we have so far return true; @@ -1015,15 +1014,12 @@ bool match_compoundrule(slang_T *slang, char_u *compflags) /// @param totprefcnt nr of prefix IDs /// @param arridx idx in sl_pidxs[] /// @param cond_req only use prefixes with a condition -int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang, +int valid_word_prefix(int totprefcnt, int arridx, int flags, char *word, slang_T *slang, bool cond_req) { - int prefcnt; - int pidx; - int prefid = (int)((unsigned)flags >> 24); - for (prefcnt = totprefcnt - 1; prefcnt >= 0; prefcnt--) { - pidx = slang->sl_pidxs[arridx + prefcnt]; + for (int prefcnt = totprefcnt - 1; prefcnt >= 0; prefcnt--) { + int pidx = slang->sl_pidxs[arridx + prefcnt]; // Check the prefix ID. if (prefid != (pidx & 0xff)) { @@ -1063,30 +1059,23 @@ int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang static void find_prefix(matchinf_T *mip, int mode) { idx_T arridx = 0; - int len; int wlen = 0; - int flen; - int c; - char_u *ptr; - idx_T lo, hi, m; slang_T *slang = mip->mi_lp->lp_slang; - char_u *byts; - idx_T *idxs; - byts = slang->sl_pbyts; + char_u *byts = (char_u *)slang->sl_pbyts; if (byts == NULL) { return; // array is empty } // We use the case-folded word here, since prefixes are always // case-folded. - ptr = mip->mi_fword; - flen = mip->mi_fwordlen; // available case-folded bytes + char_u *ptr = (char_u *)mip->mi_fword; + int flen = mip->mi_fwordlen; // available case-folded bytes if (mode == FIND_COMPOUND) { // Skip over the previously found word(s). ptr += mip->mi_compoff; flen -= mip->mi_compoff; } - idxs = slang->sl_pidxs; + idx_T *idxs = slang->sl_pidxs; // Repeat advancing in the tree until: // - there is a byte that doesn't match, @@ -1097,7 +1086,7 @@ static void find_prefix(matchinf_T *mip, int mode) flen = fold_more(mip); } - len = byts[arridx++]; + int len = byts[arridx++]; // If the first possible byte is a zero the prefix could end here. // Check if the following word matches and supports the prefix. @@ -1137,11 +1126,11 @@ static void find_prefix(matchinf_T *mip, int mode) } // Perform a binary search in the list of accepted bytes. - c = ptr[wlen]; - lo = arridx; - hi = arridx + len - 1; + int c = ptr[wlen]; + idx_T lo = arridx; + idx_T hi = arridx + len - 1; while (lo < hi) { - m = (lo + hi) / 2; + idx_T m = (lo + hi) / 2; if (byts[m] > c) { hi = m - 1; } else if (byts[m] < c) { @@ -1169,10 +1158,7 @@ static void find_prefix(matchinf_T *mip, int mode) // Return the length of the folded chars in bytes. static int fold_more(matchinf_T *mip) { - int flen; - char_u *p; - - p = mip->mi_fend; + char *p = mip->mi_fend; do { MB_PTR_ADV(mip->mi_fend); } while (*mip->mi_fend != NUL && spell_iswordp(mip->mi_fend, mip->mi_win)); @@ -1185,7 +1171,7 @@ static int fold_more(matchinf_T *mip) (void)spell_casefold(mip->mi_win, p, (int)(mip->mi_fend - p), mip->mi_fword + mip->mi_fwordlen, MAXWLEN - mip->mi_fwordlen); - flen = (int)STRLEN(mip->mi_fword + mip->mi_fwordlen); + int flen = (int)strlen(mip->mi_fword + mip->mi_fwordlen); mip->mi_fwordlen += flen; return flen; } @@ -1214,6 +1200,31 @@ bool no_spell_checking(win_T *wp) return false; } +static void decor_spell_nav_start(win_T *wp) +{ + decor_state = (DecorState){ 0 }; + decor_redraw_reset(wp->w_buffer, &decor_state); +} + +static bool decor_spell_nav_col(win_T *wp, linenr_T lnum, linenr_T *decor_lnum, int col, + char **decor_error) +{ + if (*decor_lnum != lnum) { + decor_providers_invoke_spell(wp, lnum - 1, col, lnum - 1, -1, decor_error); + decor_redraw_line(wp->w_buffer, lnum - 1, &decor_state); + *decor_lnum = lnum; + } + decor_redraw_col(wp->w_buffer, col, col, false, &decor_state); + return decor_state.spell == kTrue; +} + +static inline bool can_syn_spell(win_T *wp, linenr_T lnum, int col) +{ + bool can_spell; + (void)syn_get_id(wp, lnum, col, false, &can_spell, false); + return can_spell; +} + /// Moves to the next spell error. /// "curline" is false for "[s", "]s", "[S" and "]S". /// "curline" is true to find word under/after cursor in the same line. @@ -1227,21 +1238,16 @@ bool no_spell_checking(win_T *wp) /// @return 0 if not found, length of the badly spelled word otherwise. size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *attrp) { - linenr_T lnum; pos_T found_pos; size_t found_len = 0; - char_u *line; - char_u *p; - char_u *endp; hlf_T attr = HLF_COUNT; size_t len; int has_syntax = syntax_present(wp); - int col; - bool can_spell; - char_u *buf = NULL; + colnr_T col; + char *buf = NULL; size_t buflen = 0; int skip = 0; - int capcol = -1; + colnr_T capcol = -1; bool found_one = false; bool wrapped = false; @@ -1249,6 +1255,8 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att return 0; } + size_t ret = 0; + // Start looking for bad word at the start of the line, because we can't // start halfway through a word, we don't know where it starts or ends. // @@ -1258,13 +1266,26 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att // We concatenate the start of the next line, so that wrapped words work // (e.g. "et<line-break>cetera"). Doesn't work when searching backwards // though... - lnum = wp->w_cursor.lnum; + linenr_T lnum = wp->w_cursor.lnum; clearpos(&found_pos); + char *decor_error = NULL; + // Ephemeral extmarks are currently stored in the global decor_state. + // When looking for spell errors, we need to: + // - temporarily reset decor_state + // - run the _on_spell_nav decor callback for each line we look at + // - detect if any spell marks are present + // - restore decor_state to the value saved here. + // TODO(lewis6991): un-globalize decor_state and allow ephemeral marks to be stored into a + // temporary DecorState. + DecorState saved_decor_start = decor_state; + linenr_T decor_lnum = -1; + decor_spell_nav_start(wp); + while (!got_int) { - line = ml_get_buf(wp->w_buffer, lnum, false); + char *line = ml_get_buf(wp->w_buffer, lnum, false); - len = STRLEN(line); + len = strlen(line); if (buflen < len + MAXWLEN + 2) { xfree(buf); buflen = len + MAXWLEN + 2; @@ -1279,10 +1300,10 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att // For checking first word with a capital skip white space. if (capcol == 0) { - capcol = (int)getwhitecols(line); + capcol = (colnr_T)getwhitecols(line); } else if (curline && wp == curwin) { // For spellbadword(): check if first word needs a capital. - col = (int)getwhitecols(line); + col = (colnr_T)getwhitecols(line); if (check_need_cap(lnum, col)) { capcol = col; } @@ -1298,12 +1319,12 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att bool empty_line = *skipwhite((const char *)line) == NUL; STRCPY(buf, line); if (lnum < wp->w_buffer->b_ml.ml_line_count) { - spell_cat_line(buf + STRLEN(buf), + spell_cat_line(buf + strlen(buf), ml_get_buf(wp->w_buffer, lnum + 1, false), MAXWLEN); } - p = buf + skip; - endp = buf + len; + char *p = buf + skip; + char *endp = buf + len; while (p < endp) { // When searching backward don't search after the cursor. Unless // we wrapped around the end of the buffer. @@ -1329,30 +1350,31 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att || ((colnr_T)(curline ? p - buf + (ptrdiff_t)len : p - buf) > wp->w_cursor.col)) { - if (has_syntax) { - col = (int)(p - buf); - (void)syn_get_id(wp, lnum, (colnr_T)col, - FALSE, &can_spell, FALSE); - if (!can_spell) { - attr = HLF_COUNT; - } - } else { - can_spell = true; + col = (colnr_T)(p - buf); + + bool can_spell = (!has_syntax && (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) == 0) + || decor_spell_nav_col(wp, lnum, &decor_lnum, col, &decor_error) + || (has_syntax && can_syn_spell(wp, lnum, col)); + + if (!can_spell) { + attr = HLF_COUNT; } if (can_spell) { found_one = true; - found_pos.lnum = lnum; - found_pos.col = (int)(p - buf); - found_pos.coladd = 0; + found_pos = (pos_T) { + .lnum = lnum, + .col = col, + .coladd = 0 + }; if (dir == FORWARD) { // No need to search further. wp->w_cursor = found_pos; - xfree(buf); if (attrp != NULL) { *attrp = attr; } - return len; + ret = len; + goto theend; } else if (curline) { // Insert mode completion: put cursor after // the bad word. @@ -1376,8 +1398,8 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att if (dir == BACKWARD && found_pos.lnum != 0) { // Use the last match in the line (before the cursor). wp->w_cursor = found_pos; - xfree(buf); - return found_len; + ret = found_len; + goto theend; } if (curline) { @@ -1447,43 +1469,45 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att line_breakcheck(); } +theend: + decor_state_free(&decor_state); + xfree(decor_error); + decor_state = saved_decor_start; xfree(buf); - return 0; + return ret; } // For spell checking: concatenate the start of the following line "line" into // "buf", blanking-out special characters. Copy less than "maxlen" bytes. // Keep the blanks at the start of the next line, this is used in win_line() // to skip those bytes if the word was OK. -void spell_cat_line(char_u *buf, char_u *line, int maxlen) +void spell_cat_line(char *buf, char *line, int maxlen) { - char_u *p; - int n; - - p = (char_u *)skipwhite((char *)line); - while (vim_strchr("*#/\"\t", *p) != NULL) { + char_u *p = (char_u *)skipwhite(line); + while (vim_strchr("*#/\"\t", (uint8_t)(*p)) != NULL) { p = (char_u *)skipwhite((char *)p + 1); } - if (*p != NUL) { - // Only worth concatenating if there is something else than spaces to - // concatenate. - n = (int)(p - line) + 1; - if (n < maxlen - 1) { - memset(buf, ' ', (size_t)n); - STRLCPY(buf + n, p, maxlen - n); - } + if (*p == NUL) { + return; + } + + // Only worth concatenating if there is something else than spaces to + // concatenate. + int n = (int)(p - (char_u *)line) + 1; + if (n < maxlen - 1) { + memset(buf, ' ', (size_t)n); + xstrlcpy(buf + n, (char *)p, (size_t)(maxlen - n)); } } // Load word list(s) for "lang" from Vim spell file(s). // "lang" must be the language without the region: e.g., "en". -static void spell_load_lang(char_u *lang) +static void spell_load_lang(char *lang) { char fname_enc[85]; int r; spelload_T sl; - int round; // Copy the language name to pass it to spell_load_cb() as a cookie. // It's truncated when an error is detected. @@ -1491,22 +1515,26 @@ static void spell_load_lang(char_u *lang) sl.sl_slang = NULL; sl.sl_nobreak = false; + // Disallow deleting the current buffer. Autocommands can do weird things + // and cause "lang" to be freed. + curbuf->b_locked++; + // We may retry when no spell file is found for the language, an // autocommand may load it then. - for (round = 1; round <= 2; ++round) { + for (int round = 1; round <= 2; round++) { // Find the first spell file for "lang" in 'runtimepath' and load it. - vim_snprintf((char *)fname_enc, sizeof(fname_enc) - 5, + vim_snprintf(fname_enc, sizeof(fname_enc) - 5, "spell/%s.%s.spl", lang, spell_enc()); - r = do_in_runtimepath((char *)fname_enc, 0, spell_load_cb, &sl); + r = do_in_runtimepath(fname_enc, 0, spell_load_cb, &sl); if (r == FAIL && *sl.sl_lang != NUL) { // Try loading the ASCII version. - vim_snprintf((char *)fname_enc, sizeof(fname_enc) - 5, + vim_snprintf(fname_enc, sizeof(fname_enc) - 5, "spell/%s.ascii.spl", lang); - r = do_in_runtimepath((char *)fname_enc, 0, spell_load_cb, &sl); + r = do_in_runtimepath(fname_enc, 0, spell_load_cb, &sl); if (r == FAIL && *sl.sl_lang != NUL && round == 1 - && apply_autocmds(EVENT_SPELLFILEMISSING, (char *)lang, + && apply_autocmds(EVENT_SPELLFILEMISSING, lang, curbuf->b_fname, false, curbuf)) { continue; } @@ -1530,38 +1558,40 @@ static void spell_load_lang(char_u *lang) } } else if (sl.sl_slang != NULL) { // At least one file was loaded, now load ALL the additions. - STRCPY(fname_enc + STRLEN(fname_enc) - 3, "add.spl"); - do_in_runtimepath((char *)fname_enc, DIP_ALL, spell_load_cb, &sl); + STRCPY(fname_enc + strlen(fname_enc) - 3, "add.spl"); + do_in_runtimepath(fname_enc, DIP_ALL, spell_load_cb, &sl); } + + curbuf->b_locked--; } // Return the encoding used for spell checking: Use 'encoding', except that we // use "latin1" for "latin9". And limit to 60 characters (just in case). -char_u *spell_enc(void) +char *spell_enc(void) { - if (STRLEN(p_enc) < 60 && STRCMP(p_enc, "iso-8859-15") != 0) { + if (strlen(p_enc) < 60 && strcmp(p_enc, "iso-8859-15") != 0) { return p_enc; } - return (char_u *)"latin1"; + return "latin1"; } // Get the name of the .spl file for the internal wordlist into // "fname[MAXPATHL]". -static void int_wordlist_spl(char_u *fname) +static void int_wordlist_spl(char *fname) { - vim_snprintf((char *)fname, MAXPATHL, SPL_FNAME_TMPL, + vim_snprintf(fname, MAXPATHL, SPL_FNAME_TMPL, int_wordlist, spell_enc()); } -// Allocate a new slang_T for language "lang". "lang" can be NULL. -// Caller must fill "sl_next". -slang_T *slang_alloc(char_u *lang) +/// Allocate a new slang_T for language "lang". "lang" can be NULL. +/// Caller must fill "sl_next". +slang_T *slang_alloc(char *lang) FUNC_ATTR_NONNULL_RET { slang_T *lp = xcalloc(1, sizeof(slang_T)); if (lang != NULL) { - lp->sl_name = vim_strsave(lang); + lp->sl_name = xstrdup(lang); } ga_init(&lp->sl_rep, sizeof(fromto_T), 10); ga_init(&lp->sl_repsal, sizeof(fromto_T), 10); @@ -1624,7 +1654,7 @@ void slang_clear(slang_T *lp) GA_DEEP_CLEAR(gap, salitem_T, free_salitem); } - for (int i = 0; i < lp->sl_prefixcnt; ++i) { + for (int i = 0; i < lp->sl_prefixcnt; i++) { vim_regfree(lp->sl_prefprog[i]); } lp->sl_prefixcnt = 0; @@ -1673,20 +1703,20 @@ void slang_clear_sug(slang_T *lp) static void spell_load_cb(char *fname, void *cookie) { spelload_T *slp = (spelload_T *)cookie; - slang_T *slang; - - slang = spell_load_file((char_u *)fname, slp->sl_lang, NULL, false); - if (slang != NULL) { - // When a previously loaded file has NOBREAK also use it for the - // ".add" files. - if (slp->sl_nobreak && slang->sl_add) { - slang->sl_nobreak = true; - } else if (slang->sl_nobreak) { - slp->sl_nobreak = true; - } + slang_T *slang = spell_load_file(fname, slp->sl_lang, NULL, false); + if (slang == NULL) { + return; + } - slp->sl_slang = slang; + // When a previously loaded file has NOBREAK also use it for the + // ".add" files. + if (slp->sl_nobreak && slang->sl_add) { + slang->sl_nobreak = true; + } else if (slang->sl_nobreak) { + slp->sl_nobreak = true; } + + slp->sl_slang = slang; } /// Add a word to the hashtable of common words. @@ -1696,31 +1726,29 @@ static void spell_load_cb(char *fname, void *cookie) /// @param[in] word added to common words hashtable /// @param[in] len length of word or -1 for NUL terminated /// @param[in] count 1 to count once, 10 to init -void count_common_word(slang_T *lp, char_u *word, int len, uint8_t count) +void count_common_word(slang_T *lp, char *word, int len, uint8_t count) { - hash_T hash; - hashitem_T *hi; - wordcount_T *wc; - char_u buf[MAXWLEN]; - char_u *p; + char buf[MAXWLEN]; + char *p; if (len == -1) { p = word; } else if (len >= MAXWLEN) { return; } else { - STRLCPY(buf, word, len + 1); + xstrlcpy(buf, word, (size_t)len + 1); p = buf; } - hash = hash_hash(p); - const size_t p_len = STRLEN(p); - hi = hash_lookup(&lp->sl_wordcount, (const char *)p, p_len, hash); + wordcount_T *wc; + hash_T hash = hash_hash(p); + const size_t p_len = strlen(p); + hashitem_T *hi = hash_lookup(&lp->sl_wordcount, (const char *)p, p_len, hash); if (HASHITEM_EMPTY(hi)) { wc = xmalloc(sizeof(wordcount_T) + p_len); memcpy(wc->wc_word, p, p_len + 1); wc->wc_count = count; - hash_add_item(&lp->sl_wordcount, hi, wc->wc_word, hash); + hash_add_item(&lp->sl_wordcount, hi, (char *)wc->wc_word, hash); } else { wc = HI2WC(hi); wc->wc_count = (uint16_t)(wc->wc_count + count); @@ -1732,11 +1760,9 @@ void count_common_word(slang_T *lp, char_u *word, int len, uint8_t count) // Returns true if byte "n" appears in "str". // Like strchr() but independent of locale. -bool byte_in_str(char_u *str, int n) +bool byte_in_str(uint8_t *str, int n) { - char_u *p; - - for (p = str; *p != NUL; ++p) { + for (uint8_t *p = str; *p != NUL; p++) { if (*p == n) { return true; } @@ -1748,21 +1774,18 @@ bool byte_in_str(char_u *str, int n) // in "slang->sl_syl_items". int init_syl_tab(slang_T *slang) { - char_u *p; - char_u *s; - int l; - ga_init(&slang->sl_syl_items, sizeof(syl_item_T), 4); - p = (char_u *)vim_strchr((char *)slang->sl_syllable, '/'); + char *p = vim_strchr((char *)slang->sl_syllable, '/'); while (p != NULL) { *p++ = NUL; if (*p == NUL) { // trailing slash break; } - s = p; - p = (char_u *)vim_strchr((char *)p, '/'); + char *s = p; + p = vim_strchr(p, '/'); + int l; if (p == NULL) { - l = (int)STRLEN(s); + l = (int)strlen(s); } else { l = (int)(p - s); } @@ -1771,7 +1794,7 @@ int init_syl_tab(slang_T *slang) } syl_item_T *syl = GA_APPEND_VIA_PTR(syl_item_T, &slang->sl_syl_items); - STRLCPY(syl->sy_chars, s, l + 1); + xstrlcpy(syl->sy_chars, s, (size_t)l + 1); syl->sy_len = l; } return OK; @@ -1780,20 +1803,18 @@ int init_syl_tab(slang_T *slang) // Count the number of syllables in "word". // When "word" contains spaces the syllables after the last space are counted. // Returns zero if syllables are not defines. -static int count_syllables(slang_T *slang, const char_u *word) +static int count_syllables(slang_T *slang, const char *word) FUNC_ATTR_NONNULL_ALL { int cnt = 0; bool skip = false; int len; - syl_item_T *syl; - int c; if (slang->sl_syllable == NULL) { return 0; } - for (const char_u *p = word; *p != NUL; p += len) { + for (const char *p = word; *p != NUL; p += len) { // When running into a space reset counter. if (*p == ' ') { len = 1; @@ -1803,10 +1824,10 @@ static int count_syllables(slang_T *slang, const char_u *word) // Find longest match of syllable items. len = 0; - for (int i = 0; i < slang->sl_syl_items.ga_len; ++i) { - syl = ((syl_item_T *)slang->sl_syl_items.ga_data) + i; + for (int i = 0; i < slang->sl_syl_items.ga_len; i++) { + syl_item_T *syl = ((syl_item_T *)slang->sl_syl_items.ga_data) + i; if (syl->sy_len > len - && STRNCMP(p, syl->sy_chars, syl->sy_len) == 0) { + && strncmp(p, syl->sy_chars, (size_t)syl->sy_len) == 0) { len = syl->sy_len; } } @@ -1815,12 +1836,12 @@ static int count_syllables(slang_T *slang, const char_u *word) skip = false; } else { // No recognized syllable item, at least a syllable char then? - c = utf_ptr2char((char *)p); - len = utfc_ptr2len((char *)p); + int c = utf_ptr2char(p); + len = utfc_ptr2len(p); if (vim_strchr((char *)slang->sl_syllable, c) == NULL) { skip = false; // No, search for next syllable } else if (!skip) { - ++cnt; // Yes, count it + cnt++; // Yes, count it skip = true; // don't count following syllable chars } } @@ -1828,31 +1849,31 @@ static int count_syllables(slang_T *slang, const char_u *word) return cnt; } -// Parse 'spelllang' and set w_s->b_langp accordingly. -// Returns NULL if it's OK, an error message otherwise. +/// Parse 'spelllang' and set w_s->b_langp accordingly. +/// @return NULL if it's OK, an untranslated error message otherwise. char *did_set_spelllang(win_T *wp) { garray_T ga; char *splp; - char_u *region; - char_u region_cp[3]; + char *region; + char region_cp[3]; bool filename; int region_mask; slang_T *slang; int c; - char_u lang[MAXWLEN + 1]; - char_u spf_name[MAXPATHL]; + char lang[MAXWLEN + 1]; + char spf_name[MAXPATHL]; int len; - char_u *p; + char *p; int round; char *spf; - char_u *use_region = NULL; + char *use_region = NULL; bool dont_use_region = false; bool nobreak = false; langp_T *lp, *lp2; static bool recursive = false; char *ret_msg = NULL; - char_u *spl_copy; + char *spl_copy; bufref_T bufref; set_bufref(&bufref, wp->w_buffer); @@ -1870,22 +1891,22 @@ char *did_set_spelllang(win_T *wp) // Make a copy of 'spelllang', the SpellFileMissing autocommands may change // it under our fingers. - spl_copy = vim_strsave(wp->w_s->b_p_spl); + spl_copy = xstrdup(wp->w_s->b_p_spl); wp->w_s->b_cjk = 0; // Loop over comma separated language names. - for (splp = (char *)spl_copy; *splp != NUL;) { + for (splp = spl_copy; *splp != NUL;) { // Get one language name. - copy_option_part(&splp, (char *)lang, MAXWLEN, ","); + copy_option_part(&splp, lang, MAXWLEN, ","); region = NULL; - len = (int)STRLEN(lang); + len = (int)strlen(lang); if (!valid_spelllang(lang)) { continue; } - if (STRCMP(lang, "cjk") == 0) { + if (strcmp(lang, "cjk") == 0) { wp->w_s->b_cjk = 1; continue; } @@ -1893,14 +1914,14 @@ char *did_set_spelllang(win_T *wp) // If the name ends in ".spl" use it as the name of the spell file. // If there is a region name let "region" point to it and remove it // from the name. - if (len > 4 && FNAMECMP(lang + len - 4, ".spl") == 0) { + if (len > 4 && path_fnamecmp(lang + len - 4, ".spl") == 0) { filename = true; // Locate a region and remove it from the file name. - p = (char_u *)vim_strchr(path_tail((char *)lang), '_'); + p = vim_strchr(path_tail(lang), '_'); if (p != NULL && ASCII_ISALPHA(p[1]) && ASCII_ISALPHA(p[2]) && !ASCII_ISALPHA(p[3])) { - STRLCPY(region_cp, p + 1, 3); + xstrlcpy(region_cp, p + 1, 3); memmove(p, p + 3, (size_t)(len - (p - lang) - 2)); region = region_cp; } else { @@ -1909,7 +1930,7 @@ char *did_set_spelllang(win_T *wp) // Check if we loaded this language before. for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)lang, (char *)slang->sl_fname, false, true) + if (path_full_compare(lang, slang->sl_fname, false, true) == kEqualFiles) { break; } @@ -1934,7 +1955,7 @@ char *did_set_spelllang(win_T *wp) if (region != NULL) { // If the region differs from what was used before then don't // use it for 'spellfile'. - if (use_region != NULL && STRCMP(region, use_region) != 0) { + if (use_region != NULL && strcmp(region, use_region) != 0) { dont_use_region = true; } use_region = region; @@ -1947,8 +1968,8 @@ char *did_set_spelllang(win_T *wp) } else { spell_load_lang(lang); // SpellFileMissing autocommands may do anything, including - // destroying the buffer we are using... - if (!bufref_valid(&bufref)) { + // destroying the buffer we are using or closing the window. + if (!bufref_valid(&bufref) || !win_valid_any_tab(wp)) { ret_msg = N_("E797: SpellFileMissing autocommand deleted buffer"); goto theend; } @@ -1958,7 +1979,7 @@ char *did_set_spelllang(win_T *wp) // Loop over the languages, there can be several files for "lang". for (slang = first_lang; slang != NULL; slang = slang->sl_next) { if (filename - ? path_full_compare((char *)lang, (char *)slang->sl_fname, false, true) == kEqualFiles + ? path_full_compare(lang, slang->sl_fname, false, true) == kEqualFiles : STRICMP(lang, slang->sl_name) == 0) { region_mask = REGION_ALL; if (!filename && region != NULL) { @@ -1999,7 +2020,7 @@ char *did_set_spelllang(win_T *wp) // round 1: load first name in 'spellfile'. // round 2: load second name in 'spellfile. // etc. - spf = (char *)curwin->w_s->b_p_spf; + spf = curwin->w_s->b_p_spf; for (round = 0; round == 0 || *spf != NUL; round++) { if (round == 0) { // Internal wordlist, if there is one. @@ -2009,14 +2030,14 @@ char *did_set_spelllang(win_T *wp) int_wordlist_spl(spf_name); } else { // One entry in 'spellfile'. - copy_option_part(&spf, (char *)spf_name, MAXPATHL - 5, ","); + copy_option_part(&spf, spf_name, MAXPATHL - 5, ","); STRCAT(spf_name, ".spl"); // If it was already found above then skip it. - for (c = 0; c < ga.ga_len; ++c) { + for (c = 0; c < ga.ga_len; c++) { p = LANGP_ENTRY(ga, c)->lp_slang->sl_fname; if (p != NULL - && path_full_compare((char *)spf_name, (char *)p, false, true) == kEqualFiles) { + && path_full_compare(spf_name, p, false, true) == kEqualFiles) { break; } } @@ -2027,7 +2048,7 @@ char *did_set_spelllang(win_T *wp) // Check if it was loaded already. for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)spf_name, (char *)slang->sl_fname, false, true) + if (path_full_compare(spf_name, slang->sl_fname, false, true) == kEqualFiles) { break; } @@ -2039,8 +2060,8 @@ char *did_set_spelllang(win_T *wp) if (round == 0) { STRCPY(lang, "internal wordlist"); } else { - STRLCPY(lang, path_tail((char *)spf_name), MAXWLEN + 1); - p = (char_u *)vim_strchr((char *)lang, '.'); + xstrlcpy(lang, path_tail(spf_name), MAXWLEN + 1); + p = vim_strchr(lang, '.'); if (p != NULL) { *p = NUL; // truncate at ".encoding.add" } @@ -2085,7 +2106,7 @@ char *did_set_spelllang(win_T *wp) // For each language figure out what language to use for sound folding and // REP items. If the language doesn't support it itself use another one // with the same name. E.g. for "en-math" use "en". - for (int i = 0; i < ga.ga_len; ++i) { + for (int i = 0; i < ga.ga_len; i++) { lp = LANGP_ENTRY(ga, i); // sound folding @@ -2094,10 +2115,10 @@ char *did_set_spelllang(win_T *wp) lp->lp_sallang = lp->lp_slang; } else { // find first similar language that does sound folding - for (int j = 0; j < ga.ga_len; ++j) { + for (int j = 0; j < ga.ga_len; j++) { lp2 = LANGP_ENTRY(ga, j); if (!GA_EMPTY(&lp2->lp_slang->sl_sal) - && STRNCMP(lp->lp_slang->sl_name, + && strncmp(lp->lp_slang->sl_name, lp2->lp_slang->sl_name, 2) == 0) { lp->lp_sallang = lp2->lp_slang; break; @@ -2111,10 +2132,10 @@ char *did_set_spelllang(win_T *wp) lp->lp_replang = lp->lp_slang; } else { // find first similar language that has REP items - for (int j = 0; j < ga.ga_len; ++j) { + for (int j = 0; j < ga.ga_len; j++) { lp2 = LANGP_ENTRY(ga, j); if (!GA_EMPTY(&lp2->lp_slang->sl_rep) - && STRNCMP(lp->lp_slang->sl_name, + && strncmp(lp->lp_slang->sl_name, lp2->lp_slang->sl_name, 2) == 0) { lp->lp_replang = lp2->lp_slang; break; @@ -2122,7 +2143,7 @@ char *did_set_spelllang(win_T *wp) } } } - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); theend: xfree(spl_copy); @@ -2137,8 +2158,8 @@ static void clear_midword(win_T *wp) XFREE_CLEAR(wp->w_s->b_spell_ismw_mb); } -// Use the "sl_midword" field of language "lp" for buffer "buf". -// They add up to any currently used midword characters. +/// Use the "sl_midword" field of language "lp" for buffer "buf". +/// They add up to any currently used midword characters. static void use_midword(slang_T *lp, win_T *wp) FUNC_ATTR_NONNULL_ALL { @@ -2146,21 +2167,21 @@ static void use_midword(slang_T *lp, win_T *wp) return; } - for (char_u *p = lp->sl_midword; *p != NUL;) { - const int c = utf_ptr2char((char *)p); - const int l = utfc_ptr2len((char *)p); + for (char *p = (char *)lp->sl_midword; *p != NUL;) { + const int c = utf_ptr2char(p); + const int l = utfc_ptr2len(p); if (c < 256 && l <= 2) { wp->w_s->b_spell_ismw[c] = true; } else if (wp->w_s->b_spell_ismw_mb == NULL) { // First multi-byte char in "b_spell_ismw_mb". - wp->w_s->b_spell_ismw_mb = vim_strnsave(p, (size_t)l); + wp->w_s->b_spell_ismw_mb = xstrnsave(p, (size_t)l); } else { // Append multi-byte chars to "b_spell_ismw_mb". - const int n = (int)STRLEN(wp->w_s->b_spell_ismw_mb); - char_u *bp = vim_strnsave(wp->w_s->b_spell_ismw_mb, (size_t)n + (size_t)l); + const int n = (int)strlen(wp->w_s->b_spell_ismw_mb); + char *bp = xstrnsave(wp->w_s->b_spell_ismw_mb, (size_t)n + (size_t)l); xfree(wp->w_s->b_spell_ismw_mb); wp->w_s->b_spell_ismw_mb = bp; - STRLCPY(bp + n, p, l + 1); + xstrlcpy(bp + n, p, (size_t)l + 1); } p += l; } @@ -2169,7 +2190,7 @@ static void use_midword(slang_T *lp, win_T *wp) // Find the region "region[2]" in "rp" (points to "sl_regions"). // Each region is simply stored as the two characters of its name. // Returns the index if found (first is 0), REGION_ALL if not found. -static int find_region(char_u *rp, char_u *region) +static int find_region(const char *rp, const char *region) { int i; @@ -2194,13 +2215,10 @@ static int find_region(char_u *rp, char_u *region) /// @param[in] end End of word or NULL for NUL delimited string /// /// @returns Case type of word -int captype(char_u *word, char_u *end) +int captype(char *word, const char *end) FUNC_ATTR_NONNULL_ARG(1) { - char_u *p; - int firstcap; - bool allcap; - bool past_second = false; // past second word char + char *p; // find first letter for (p = word; !spell_iswordp_nmw(p, curwin); MB_PTR_ADV(p)) { @@ -2208,14 +2226,16 @@ int captype(char_u *word, char_u *end) return 0; // only non-word characters, illegal word } } - int c = mb_ptr2char_adv((const char_u **)&p); - firstcap = allcap = SPELL_ISUPPER(c); + int c = mb_ptr2char_adv((const char **)&p); + bool allcap; + bool firstcap = allcap = SPELL_ISUPPER(c); + bool past_second = false; // past second word char // Need to check all letters to find a word with mixed upper/lower. // But a word with an upper char only at start is a ONECAP. for (; end == NULL ? *p != NUL : p < end; MB_PTR_ADV(p)) { if (spell_iswordp_nmw(p, curwin)) { - c = utf_ptr2char((char *)p); + c = utf_ptr2char(p); if (!SPELL_ISUPPER(c)) { // UUl -> KEEPCAP if (past_second && allcap) { @@ -2242,28 +2262,27 @@ int captype(char_u *word, char_u *end) // Delete the internal wordlist and its .spl file. void spell_delete_wordlist(void) { - char_u fname[MAXPATHL] = { 0 }; - - if (int_wordlist != NULL) { - os_remove((char *)int_wordlist); - int_wordlist_spl(fname); - os_remove((char *)fname); - XFREE_CLEAR(int_wordlist); + if (int_wordlist == NULL) { + return; } + + char fname[MAXPATHL] = { 0 }; + os_remove(int_wordlist); + int_wordlist_spl(fname); + os_remove(fname); + XFREE_CLEAR(int_wordlist); } // Free all languages. void spell_free_all(void) { - slang_T *slang; - // Go through all buffers and handle 'spelllang'. <VN> FOR_ALL_BUFFERS(buf) { ga_clear(&buf->b_s.b_langp); } while (first_lang != NULL) { - slang = first_lang; + slang_T *slang = first_lang; first_lang = slang->sl_next; slang_free(slang); } @@ -2319,37 +2338,37 @@ buf_T *open_spellbuf(void) // Close the buffer used for spell info. void close_spellbuf(buf_T *buf) { - if (buf != NULL) { - ml_close(buf, TRUE); - xfree(buf); + if (buf == NULL) { + return; } + + ml_close(buf, true); + xfree(buf); } // Init the chartab used for spelling for ASCII. void clear_spell_chartab(spelltab_T *sp) { - int i; - // Init everything to false (zero). CLEAR_FIELD(sp->st_isw); CLEAR_FIELD(sp->st_isu); - for (i = 0; i < 256; i++) { + for (int i = 0; i < 256; i++) { sp->st_fold[i] = (char_u)i; sp->st_upper[i] = (char_u)i; } // We include digits. A word shouldn't start with a digit, but handling // that is done separately. - for (i = '0'; i <= '9'; ++i) { + for (int i = '0'; i <= '9'; i++) { sp->st_isw[i] = true; } - for (i = 'A'; i <= 'Z'; ++i) { + for (int i = 'A'; i <= 'Z'; i++) { sp->st_isw[i] = true; sp->st_isu[i] = true; sp->st_fold[i] = (char_u)(i + 0x20); } - for (i = 'a'; i <= 'z'; ++i) { + for (int i = 'a'; i <= 'z'; i++) { sp->st_isw[i] = true; sp->st_upper[i] = (char_u)(i - 0x20); } @@ -2361,11 +2380,9 @@ void clear_spell_chartab(spelltab_T *sp) // locale. For utf-8 we don't use isalpha() but our own functions. void init_spell_chartab(void) { - int i; - did_set_spelltab = false; clear_spell_chartab(&spelltab); - for (i = 128; i < 256; i++) { + for (int i = 128; i < 256; i++) { int f = utf_fold(i); int u = mb_toupper(i); @@ -2385,29 +2402,27 @@ void init_spell_chartab(void) /// Thus this only works properly when past the first character of the word. /// /// @param wp Buffer used. -bool spell_iswordp(const char_u *p, const win_T *wp) +bool spell_iswordp(const char *p, const win_T *wp) FUNC_ATTR_NONNULL_ALL { - int c; - - const int l = utfc_ptr2len((char *)p); - const char_u *s = p; + const int l = utfc_ptr2len(p); + const char *s = p; if (l == 1) { // be quick for ASCII - if (wp->w_s->b_spell_ismw[*p]) { + if (wp->w_s->b_spell_ismw[(uint8_t)(*p)]) { s = p + 1; // skip a mid-word character } } else { - c = utf_ptr2char((char *)p); + int c = utf_ptr2char(p); if (c < 256 ? wp->w_s->b_spell_ismw[c] : (wp->w_s->b_spell_ismw_mb != NULL - && vim_strchr((char *)wp->w_s->b_spell_ismw_mb, c) != NULL)) { + && vim_strchr(wp->w_s->b_spell_ismw_mb, c) != NULL)) { s = p + l; } } - c = utf_ptr2char((char *)s); + int c = utf_ptr2char(s); if (c > 255) { return spell_mb_isword_class(mb_get_class(s), wp); } @@ -2416,9 +2431,9 @@ bool spell_iswordp(const char_u *p, const win_T *wp) // Returns true if "p" points to a word character. // Unlike spell_iswordp() this doesn't check for "midword" characters. -bool spell_iswordp_nmw(const char_u *p, win_T *wp) +bool spell_iswordp_nmw(const char *p, win_T *wp) { - int c = utf_ptr2char((char *)p); + int c = utf_ptr2char(p); if (c > 255) { return spell_mb_isword_class(mb_get_class(p), wp); } @@ -2448,7 +2463,7 @@ static bool spell_iswordp_w(const int *p, const win_T *wp) if (*p < 256 ? wp->w_s->b_spell_ismw[*p] : (wp->w_s->b_spell_ismw_mb != NULL - && vim_strchr((char *)wp->w_s->b_spell_ismw_mb, + && vim_strchr(wp->w_s->b_spell_ismw_mb, *p) != NULL)) { s = p + 1; } else { @@ -2465,7 +2480,7 @@ static bool spell_iswordp_w(const int *p, const win_T *wp) // Uses the character definitions from the .spl file. // When using a multi-byte 'encoding' the length may change! // Returns FAIL when something wrong. -int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf, int buflen) +int spell_casefold(const win_T *wp, char *str, int len, char *buf, int buflen) FUNC_ATTR_NONNULL_ALL { if (len >= buflen) { @@ -2476,12 +2491,12 @@ int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf, int bufle int outi = 0; // Fold one character at a time. - for (char_u *p = str; p < str + len;) { + for (char *p = str; p < str + len;) { if (outi + MB_MAXBYTES > buflen) { buf[outi] = NUL; return FAIL; } - int c = mb_cptr2char_adv((const char_u **)&p); + int c = mb_cptr2char_adv((const char **)&p); // Exception: greek capital sigma 0x03A3 folds to 0x03C3, except // when it is the last character in a word, then it folds to @@ -2496,7 +2511,7 @@ int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf, int bufle c = SPELL_TOFOLD(c); } - outi += utf_char2bytes(c, (char *)buf + outi); + outi += utf_char2bytes(c, buf + outi); } buf[outi] = NUL; @@ -2508,18 +2523,14 @@ int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf, int bufle bool check_need_cap(linenr_T lnum, colnr_T col) { bool need_cap = false; - char_u *line; - char_u *line_copy = NULL; - char_u *p; - colnr_T endcol; - regmatch_T regmatch; if (curwin->w_s->b_cap_prog == NULL) { return false; } - line = get_cursor_line_ptr(); - endcol = 0; + char *line = get_cursor_line_ptr(); + char *line_copy = NULL; + colnr_T endcol = 0; if (getwhitecols(line) >= (int)col) { // At start of line, check if previous line is empty or sentence // ends there. @@ -2527,13 +2538,13 @@ bool check_need_cap(linenr_T lnum, colnr_T col) need_cap = true; } else { line = ml_get(lnum - 1); - if (*skipwhite((char *)line) == NUL) { + if (*skipwhite(line) == NUL) { need_cap = true; } else { // Append a space in place of the line break. - line_copy = concat_str(line, (char_u *)" "); + line_copy = concat_str(line, " "); line = line_copy; - endcol = (colnr_T)STRLEN(line); + endcol = (colnr_T)strlen(line); } } } else { @@ -2542,15 +2553,17 @@ bool check_need_cap(linenr_T lnum, colnr_T col) if (endcol > 0) { // Check if sentence ends before the bad word. - regmatch.regprog = curwin->w_s->b_cap_prog; - regmatch.rm_ic = FALSE; - p = line + endcol; + regmatch_T regmatch = { + .regprog = curwin->w_s->b_cap_prog, + .rm_ic = false + }; + char *p = line + endcol; for (;;) { MB_PTR_BACK(line, p); if (p == line || spell_iswordp_nmw(p, curwin)) { break; } - if (vim_regexec(®match, (char *)p, 0) + if (vim_regexec(®match, p, 0) && regmatch.endp[0] == line + endcol) { need_cap = true; break; @@ -2568,10 +2581,6 @@ bool check_need_cap(linenr_T lnum, colnr_T col) void ex_spellrepall(exarg_T *eap) { pos_T pos = curwin->w_cursor; - char_u *frompat; - int addlen; - char_u *line; - char_u *p; bool save_ws = p_ws; linenr_T prev_lnum = 0; @@ -2579,10 +2588,11 @@ void ex_spellrepall(exarg_T *eap) emsg(_("E752: No previous spell replacement")); return; } - addlen = (int)(STRLEN(repl_to) - STRLEN(repl_from)); + int addlen = (int)(strlen(repl_to) - strlen(repl_from)); - frompat = xmalloc(STRLEN(repl_from) + 7); - sprintf((char *)frompat, "\\V\\<%s\\>", repl_from); + size_t frompatlen = strlen(repl_from) + 7; + char *frompat = xmalloc(frompatlen); + snprintf(frompat, frompatlen, "\\V\\<%s\\>", repl_from); p_ws = false; sub_nsubs = 0; @@ -2596,14 +2606,14 @@ void ex_spellrepall(exarg_T *eap) // Only replace when the right word isn't there yet. This happens // when changing "etc" to "etc.". - line = get_cursor_line_ptr(); - if (addlen <= 0 || STRNCMP(line + curwin->w_cursor.col, - repl_to, STRLEN(repl_to)) != 0) { - p = xmalloc(STRLEN(line) + (size_t)addlen + 1); + char *line = get_cursor_line_ptr(); + if (addlen <= 0 || strncmp(line + curwin->w_cursor.col, + repl_to, strlen(repl_to)) != 0) { + char *p = xmalloc(strlen(line) + (size_t)addlen + 1); memmove(p, line, (size_t)curwin->w_cursor.col); STRCPY(p + curwin->w_cursor.col, repl_to); - STRCAT(p, line + curwin->w_cursor.col + STRLEN(repl_from)); - ml_replace(curwin->w_cursor.lnum, (char *)p, false); + STRCAT(p, line + curwin->w_cursor.col + strlen(repl_from)); + ml_replace(curwin->w_cursor.lnum, p, false); changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); if (curwin->w_cursor.lnum != prev_lnum) { @@ -2612,7 +2622,7 @@ void ex_spellrepall(exarg_T *eap) } sub_nsubs++; } - curwin->w_cursor.col += (colnr_T)STRLEN(repl_to); + curwin->w_cursor.col += (colnr_T)strlen(repl_to); } p_ws = save_ws; @@ -2633,30 +2643,30 @@ void ex_spellrepall(exarg_T *eap) /// @param[in] word source string to copy /// @param[in,out] wcopy copied string, with case of first letter changed /// @param[in] upper True to upper case, otherwise lower case -void onecap_copy(char_u *word, char_u *wcopy, bool upper) +void onecap_copy(char *word, char *wcopy, bool upper) { - char_u *p = word; - int c = mb_cptr2char_adv((const char_u **)&p); + char *p = word; + int c = mb_cptr2char_adv((const char **)&p); if (upper) { c = SPELL_TOUPPER(c); } else { c = SPELL_TOFOLD(c); } - int l = utf_char2bytes(c, (char *)wcopy); - STRLCPY(wcopy + l, p, MAXWLEN - l); + int l = utf_char2bytes(c, wcopy); + xstrlcpy(wcopy + l, p, (size_t)(MAXWLEN - l)); } // Make a copy of "word" with all the letters upper cased into // "wcopy[MAXWLEN]". The result is NUL terminated. -void allcap_copy(char_u *word, char_u *wcopy) +void allcap_copy(char *word, char *wcopy) { - char_u *d = wcopy; - for (char_u *s = word; *s != NUL;) { - int c = mb_cptr2char_adv((const char_u **)&s); + char_u *d = (char_u *)wcopy; + for (char *s = word; *s != NUL;) { + int c = mb_cptr2char_adv((const char **)&s); if (c == 0xdf) { c = 'S'; - if (d - wcopy >= MAXWLEN - 1) { + if (d - (char_u *)wcopy >= MAXWLEN - 1) { break; } *d++ = (char_u)c; @@ -2664,7 +2674,7 @@ void allcap_copy(char_u *word, char_u *wcopy) c = SPELL_TOUPPER(c); } - if (d - wcopy >= MAXWLEN - MB_MAXBYTES) { + if (d - (char_u *)wcopy >= MAXWLEN - MB_MAXBYTES) { break; } d += utf_char2bytes(c, (char *)d); @@ -2674,9 +2684,9 @@ void allcap_copy(char_u *word, char_u *wcopy) // Case-folding may change the number of bytes: Count nr of chars in // fword[flen] and return the byte length of that many chars in "word". -int nofold_len(char_u *fword, int flen, char_u *word) +int nofold_len(char *fword, int flen, char *word) { - char_u *p; + char *p; int i = 0; for (p = fword; p < fword + flen; MB_PTR_ADV(p)) { @@ -2689,7 +2699,7 @@ int nofold_len(char_u *fword, int flen, char_u *word) } // Copy "fword" to "cword", fixing case according to "flags". -void make_case_word(char_u *fword, char_u *cword, int flags) +void make_case_word(char *fword, char *cword, int flags) { if (flags & WF_ALLCAP) { // Make it all upper-case @@ -2719,9 +2729,9 @@ char *eval_soundfold(const char *const word) langp_T *const lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); if (!GA_EMPTY(&lp->lp_slang->sl_sal)) { // soundfold the word - char_u sound[MAXWLEN]; - spell_soundfold(lp->lp_slang, (char_u *)word, false, sound); - return xstrdup((const char *)sound); + char sound[MAXWLEN]; + spell_soundfold(lp->lp_slang, (char *)word, false, sound); + return xstrdup(sound); } } } @@ -2745,20 +2755,19 @@ char *eval_soundfold(const char *const word) /// @param[in] inword word to soundfold /// @param[in] folded whether inword is already case-folded /// @param[in,out] res destination for soundfolded word -void spell_soundfold(slang_T *slang, char_u *inword, bool folded, char_u *res) +void spell_soundfold(slang_T *slang, char *inword, bool folded, char *res) { - char_u fword[MAXWLEN]; - char_u *word; - if (slang->sl_sofo) { // SOFOFROM and SOFOTO used spell_soundfold_sofo(slang, inword, res); } else { + char fword[MAXWLEN]; + char *word; // SAL items used. Requires the word to be case-folded. if (folded) { word = inword; } else { - (void)spell_casefold(curwin, inword, (int)STRLEN(inword), fword, MAXWLEN); + (void)spell_casefold(curwin, inword, (int)strlen(inword), fword, MAXWLEN); word = fword; } @@ -2768,7 +2777,7 @@ void spell_soundfold(slang_T *slang, char_u *inword, bool folded, char_u *res) // Perform sound folding of "inword" into "res" according to SOFOFROM and // SOFOTO lines. -static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) +static void spell_soundfold_sofo(slang_T *slang, char *inword, char *res) { int ri = 0; @@ -2776,8 +2785,8 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) // The sl_sal_first[] table contains the translation for chars up to // 255, sl_sal the rest. - for (char_u *s = inword; *s != NUL;) { - int c = mb_cptr2char_adv((const char_u **)&s); + for (char *s = inword; *s != NUL;) { + int c = mb_cptr2char_adv((const char **)&s); if (utf_class(c) == 0) { c = ' '; } else if (c < 256) { @@ -2802,7 +2811,7 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) } if (c != NUL && c != prevc) { - ri += utf_char2bytes(c, (char *)res + ri); + ri += utf_char2bytes(c, res + ri); if (ri + MB_MAXBYTES > MAXWLEN) { break; } @@ -2815,34 +2824,31 @@ static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) // Turn "inword" into its sound-a-like equivalent in "res[MAXWLEN]". // Multi-byte version of spell_soundfold(). -static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) +static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res) { salitem_T *smp = (salitem_T *)slang->sl_sal.ga_data; int word[MAXWLEN] = { 0 }; int wres[MAXWLEN] = { 0 }; - int l; int *ws; int *pf; - int i, j, z; + int j, z; int reslen; - int n, k = 0; + int k = 0; int z0; int k0; int n0; - int c; int pri; int p0 = -333; int c0; bool did_white = false; - int wordlen; // Convert the multi-byte string to a wide-character string. // Remove accents, if wanted. We actually remove all non-word characters. // But keep white space. - wordlen = 0; - for (const char_u *s = inword; *s != NUL;) { - const char_u *t = s; - c = mb_cptr2char_adv(&s); + int wordlen = 0; + for (const char *s = (char *)inword; *s != NUL;) { + const char_u *t = (char_u *)s; + int c = mb_cptr2char_adv(&s); if (slang->sl_rem_accents) { if (utf_class(c) == 0) { if (did_white) { @@ -2852,7 +2858,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) did_white = true; } else { did_white = false; - if (!spell_iswordp_nmw(t, curwin)) { + if (!spell_iswordp_nmw((char *)t, curwin)) { continue; } } @@ -2861,13 +2867,14 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) } word[wordlen] = NUL; + int c; // This algorithm comes from Aspell phonet.cpp. // Converted from C++ to C. Added support for multi-byte chars. // Changed to keep spaces. - i = reslen = z = 0; + int i = reslen = z = 0; while ((c = word[i]) != NUL) { // Start with the first rule that has the character in the word. - n = slang->sl_sal_first[c & 0xff]; + int n = slang->sl_sal_first[c & 0xff]; z0 = 0; if (n >= 0) { @@ -2875,7 +2882,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) // If c is 0x300 need extra check for the end of the array, as // (c & 0xff) is NUL. for (; ((ws = smp[n].sm_lead_w)[0] & 0xff) == (c & 0xff) - && ws[0] != NUL; ++n) { + && ws[0] != NUL; n++) { // Quickly skip entries that don't match the word. Most // entries are less than three chars, optimize for that. if (c != ws[0]) { @@ -2887,7 +2894,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) continue; } if (k > 2) { - for (j = 2; j < k; ++j) { + for (j = 2; j < k; j++) { if (word[i + j] != ws[j]) { break; } @@ -2908,7 +2915,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) } k++; } - char_u *s = smp[n].sm_rules; + char_u *s = (char_u *)smp[n].sm_rules; pri = 5; // default priority p0 = *s; @@ -2948,7 +2955,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) // Test follow-up rule for "word[i + k]"; loop over // all entries with the same index byte. for (; ((ws = smp[n0].sm_lead_w)[0] & 0xff) - == (c0 & 0xff); ++n0) { + == (c0 & 0xff); n0++) { // Quickly skip entries that don't match the word. if (c0 != ws[0]) { continue; @@ -2960,7 +2967,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) } if (k0 > 2) { pf = word + i + k + 1; - for (j = 2; j < k0; ++j) { + for (j = 2; j < k0; j++) { if (*pf++ != ws[j]) { break; } @@ -2985,7 +2992,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) } p0 = 5; - s = smp[n0].sm_rules; + s = (char_u *)smp[n0].sm_rules; while (*s == '-') { // "k0" gets NOT reduced because // "if (k0 == k)" @@ -3026,7 +3033,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) // replace string ws = smp[n].sm_to_w; - s = smp[n].sm_rules; + s = (char_u *)smp[n].sm_rules; p0 = (vim_strchr((char *)s, '<') != NULL) ? 1 : 0; if (p0 == 1 && z == 0) { // rule with '<' is used @@ -3102,9 +3109,9 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) } // Convert wide characters in "wres" to a multi-byte string in "res". - l = 0; - for (n = 0; n < reslen; n++) { - l += utf_char2bytes(wres[n], (char *)res + l); + int l = 0; + for (int n = 0; n < reslen; n++) { + l += utf_char2bytes(wres[n], res + l); if (l + MB_MAXBYTES > MAXWLEN) { break; } @@ -3143,20 +3150,19 @@ void ex_spellinfo(exarg_T *eap) // ":spelldump" void ex_spelldump(exarg_T *eap) { - char *spl; - long dummy; - if (no_spell_checking(curwin)) { return; } - (void)get_option_value("spl", &dummy, &spl, OPT_LOCAL); + char *spl; + long dummy; + (void)get_option_value("spl", &dummy, &spl, NULL, OPT_LOCAL); // Create a new empty buffer in a new window. do_cmdline_cmd("new"); // enable spelling locally in the new window - set_option_value("spell", true, "", OPT_LOCAL); - set_option_value("spl", dummy, spl, OPT_LOCAL); + set_option_value_give_err("spell", true, "", OPT_LOCAL); + set_option_value_give_err("spl", dummy, spl, OPT_LOCAL); xfree(spl); if (!buf_is_empty(curbuf)) { @@ -3169,7 +3175,7 @@ void ex_spelldump(exarg_T *eap) if (curbuf->b_ml.ml_line_count > 1) { ml_delete(curbuf->b_ml.ml_line_count, false); } - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } /// Go through all possible words and: @@ -3181,24 +3187,23 @@ void ex_spelldump(exarg_T *eap) /// @param ic ignore case /// @param dir direction for adding matches /// @param dumpflags_arg DUMPFLAG_* -void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) +void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg) { langp_T *lp; slang_T *slang; idx_T arridx[MAXWLEN]; int curi[MAXWLEN]; - char_u word[MAXWLEN]; + char word[MAXWLEN]; int c; - char_u *byts; + char *byts; idx_T *idxs; linenr_T lnum = 0; - int round; int depth; int n; int flags; - char_u *region_names = NULL; // region names being used + char *region_names = NULL; // region names being used bool do_region = true; // dump region names and numbers - char_u *p; + char *p; int dumpflags = dumpflags_arg; int patlen; @@ -3212,7 +3217,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) if (n == WF_ONECAP) { dumpflags |= DUMPFLAG_ONECAP; } else if (n == WF_ALLCAP - && (int)STRLEN(pat) > utfc_ptr2len((char *)pat)) { + && (int)strlen(pat) > utfc_ptr2len(pat)) { dumpflags |= DUMPFLAG_ALLCAP; } } @@ -3220,13 +3225,13 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) // Find out if we can support regions: All languages must support the same // regions or none at all. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); p = lp->lp_slang->sl_regions; if (p[0] != 0) { if (region_names == NULL) { // first language with regions region_names = p; - } else if (STRCMP(region_names, p) != 0) { + } else if (strcmp(region_names, p) != 0) { do_region = false; // region names are different break; } @@ -3235,15 +3240,15 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) if (do_region && region_names != NULL) { if (pat == NULL) { - vim_snprintf((char *)IObuff, IOSIZE, "/regions=%s", region_names); - ml_append(lnum++, (char *)IObuff, (colnr_T)0, false); + vim_snprintf(IObuff, IOSIZE, "/regions=%s", region_names); + ml_append(lnum++, IObuff, (colnr_T)0, false); } } else { do_region = false; } // Loop over all files loaded for the entries in 'spelllang'. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); slang = lp->lp_slang; if (slang->sl_fbyts == NULL) { // reloading failed @@ -3251,21 +3256,21 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) } if (pat == NULL) { - vim_snprintf((char *)IObuff, IOSIZE, "# file: %s", slang->sl_fname); - ml_append(lnum++, (char *)IObuff, (colnr_T)0, false); + vim_snprintf(IObuff, IOSIZE, "# file: %s", slang->sl_fname); + ml_append(lnum++, IObuff, (colnr_T)0, false); } // When matching with a pattern and there are no prefixes only use // parts of the tree that match "pat". if (pat != NULL && slang->sl_pbyts == NULL) { - patlen = (int)STRLEN(pat); + patlen = (int)strlen(pat); } else { patlen = -1; } // round 1: case-folded tree // round 2: keep-case tree - for (round = 1; round <= 2; ++round) { + for (int round = 1; round <= 2; round++) { if (round == 1) { dumpflags &= ~DUMPFLAG_KEEPCASE; byts = slang->sl_fbyts; @@ -3291,8 +3296,8 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) } else { // Do one more byte at this node. n = arridx[depth] + curi[depth]; - ++curi[depth]; - c = byts[n]; + curi[depth]++; + c = (uint8_t)byts[n]; if (c == 0 || depth >= MAXWLEN - 1) { // End of word or reached maximum length, deal with the // word. @@ -3315,8 +3320,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) // when it's the first one. c = (int)((unsigned)flags >> 24); if (c == 0 || curi[depth] == 2) { - dump_word(slang, word, pat, dir, - dumpflags, flags, lnum); + dump_word(slang, word, pat, dir, dumpflags, flags, lnum); if (pat == NULL) { lnum++; } @@ -3330,7 +3334,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) } } else { // Normal char, go one level deeper. - word[depth++] = (char_u)c; + word[depth++] = (char)c; arridx[depth] = idxs[n]; curi[depth] = 1; @@ -3352,17 +3356,15 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) } } -// Dumps one word: apply case modifications and append a line to the buffer. -// When "lnum" is zero add insert mode completion. -static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, int dumpflags, +/// Dumps one word: apply case modifications and append a line to the buffer. +/// When "lnum" is zero add insert mode completion. +static void dump_word(slang_T *slang, char *word, char *pat, Direction *dir, int dumpflags, int wordflags, linenr_T lnum) { bool keepcap = false; - char_u *p; - char_u *tw; - char_u cword[MAXWLEN]; - char_u badword[MAXWLEN + 10]; - int i; + char *p; + char cword[MAXWLEN]; + char badword[MAXWLEN + 10]; int flags = wordflags; if (dumpflags & DUMPFLAG_ONECAP) { @@ -3384,7 +3386,7 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, keepcap = true; } } - tw = p; + char *tw = p; if (pat == NULL) { // Add flags and regions after a slash. @@ -3400,10 +3402,10 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, STRCAT(badword, "?"); } if (flags & WF_REGION) { - for (i = 0; i < 7; i++) { + for (int i = 0; i < 7; i++) { if (flags & (0x10000 << i)) { - const size_t badword_len = STRLEN(badword); - snprintf((char *)badword + badword_len, + const size_t badword_len = strlen(badword); + snprintf(badword + badword_len, sizeof(badword) - badword_len, "%d", i + 1); } @@ -3416,19 +3418,19 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, hashitem_T *hi; // Include the word count for ":spelldump!". - hi = hash_find(&slang->sl_wordcount, (char *)tw); + hi = hash_find(&slang->sl_wordcount, tw); if (!HASHITEM_EMPTY(hi)) { - vim_snprintf((char *)IObuff, IOSIZE, "%s\t%d", + vim_snprintf(IObuff, IOSIZE, "%s\t%d", tw, HI2WC(hi)->wc_count); p = IObuff; } } - ml_append(lnum, (char *)p, (colnr_T)0, false); + ml_append(lnum, p, (colnr_T)0, false); } else if (((dumpflags & DUMPFLAG_ICASE) - ? mb_strnicmp(p, pat, STRLEN(pat)) == 0 - : STRNCMP(p, pat, STRLEN(pat)) == 0) - && ins_compl_add_infercase(p, (int)STRLEN(p), + ? mb_strnicmp(p, pat, strlen(pat)) == 0 + : strncmp(p, pat, strlen(pat)) == 0) + && ins_compl_add_infercase(p, (int)strlen(p), p_ic, NULL, *dir, false) == OK) { // if dir was BACKWARD then honor it just once *dir = FORWARD; @@ -3443,42 +3445,35 @@ static void dump_word(slang_T *slang, char_u *word, char_u *pat, Direction *dir, /// @param flags flags with prefix ID /// /// @return the updated line number. -static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Direction *dir, - int dumpflags, int flags, linenr_T startlnum) +static linenr_T dump_prefixes(slang_T *slang, char *word, char *pat, Direction *dir, int dumpflags, + int flags, linenr_T startlnum) { idx_T arridx[MAXWLEN]; int curi[MAXWLEN]; - char_u prefix[MAXWLEN]; - char_u word_up[MAXWLEN]; + char prefix[MAXWLEN]; + char word_up[MAXWLEN]; bool has_word_up = false; - int c; - char_u *byts; - idx_T *idxs; linenr_T lnum = startlnum; - int depth; - int n; - int len; - int i; // If the word starts with a lower-case letter make the word with an // upper-case letter in word_up[]. - c = utf_ptr2char((char *)word); + int c = utf_ptr2char(word); if (SPELL_TOUPPER(c) != c) { onecap_copy(word, word_up, true); has_word_up = true; } - byts = slang->sl_pbyts; - idxs = slang->sl_pidxs; + char_u *byts = (char_u *)slang->sl_pbyts; + idx_T *idxs = slang->sl_pidxs; if (byts != NULL) { // array not is empty // Loop over all prefixes, building them byte-by-byte in prefix[]. // When at the end of a prefix check that it supports "flags". - depth = 0; + int depth = 0; arridx[0] = 0; curi[0] = 1; while (depth >= 0 && !got_int) { - n = arridx[depth]; - len = byts[n]; + int n = arridx[depth]; + int len = byts[n]; if (curi[depth] > len) { // Done all bytes at this node, go up one level. depth--; @@ -3486,11 +3481,12 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi } else { // Do one more byte at this node. n += curi[depth]; - ++curi[depth]; + curi[depth]++; c = byts[n]; if (c == 0) { // End of prefix, find out how many IDs there are. - for (i = 1; i < len; ++i) { + int i; + for (i = 1; i < len; i++) { if (byts[n + i] != 0) { break; } @@ -3499,10 +3495,9 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi c = valid_word_prefix(i, n, flags, word, slang, false); if (c != 0) { - STRLCPY(prefix + depth, word, MAXWLEN - depth); + xstrlcpy(prefix + depth, word, (size_t)(MAXWLEN - depth)); dump_word(slang, prefix, pat, dir, dumpflags, - (c & WF_RAREPFX) ? (flags | WF_RARE) - : flags, lnum); + (c & WF_RAREPFX) ? (flags | WF_RARE) : flags, lnum); if (lnum != 0) { lnum++; } @@ -3512,13 +3507,11 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi // first letter is upper-case, but only if the prefix has // a condition. if (has_word_up) { - c = valid_word_prefix(i, n, flags, word_up, slang, - true); + c = valid_word_prefix(i, n, flags, word_up, slang, true); if (c != 0) { - STRLCPY(prefix + depth, word_up, MAXWLEN - depth); + xstrlcpy(prefix + depth, word_up, (size_t)(MAXWLEN - depth)); dump_word(slang, prefix, pat, dir, dumpflags, - (c & WF_RAREPFX) ? (flags | WF_RARE) - : flags, lnum); + (c & WF_RAREPFX) ? (flags | WF_RARE) : flags, lnum); if (lnum != 0) { lnum++; } @@ -3526,7 +3519,7 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi } } else { // Normal char, go one level deeper. - prefix[depth++] = (char_u)c; + prefix[depth++] = (char)c; arridx[depth] = idxs[n]; curi[depth] = 1; } @@ -3539,9 +3532,9 @@ static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, Directi // Move "p" to the end of word "start". // Uses the spell-checking word characters. -char_u *spell_to_word_end(char_u *start, win_T *win) +char *spell_to_word_end(char *start, win_T *win) { - char_u *p = start; + char *p = start; while (*p != NUL && spell_iswordp(p, win)) { MB_PTR_ADV(p); @@ -3556,16 +3549,14 @@ char_u *spell_to_word_end(char_u *start, win_T *win) // Returns the column number of the word. int spell_word_start(int startcol) { - char_u *line; - char_u *p; - int col = 0; - if (no_spell_checking(curwin)) { return startcol; } + char *line = get_cursor_line_ptr(); + char *p; + // Find a word character before "startcol". - line = get_cursor_line_ptr(); for (p = line + startcol; p > line;) { MB_PTR_BACK(line, p); if (spell_iswordp_nmw(p, curwin)) { @@ -3573,6 +3564,8 @@ int spell_word_start(int startcol) } } + int col = 0; + // Go back to start of the word. while (p > line) { col = (int)(p - line); @@ -3599,7 +3592,7 @@ void spell_expand_check_cap(colnr_T col) // Used for Insert mode completion CTRL-X ?. // Returns the number of matches. The matches are in "matchp[]", array of // allocated strings. -int expand_spelling(linenr_T lnum, char_u *pat, char ***matchp) +int expand_spelling(linenr_T lnum, char *pat, char ***matchp) { garray_T ga; @@ -3608,19 +3601,19 @@ int expand_spelling(linenr_T lnum, char_u *pat, char ***matchp) return ga.ga_len; } -/// Return true if "val" is a valid 'spelllang' value. -bool valid_spelllang(const char_u *val) +/// @return true if "val" is a valid 'spelllang' value. +bool valid_spelllang(const char *val) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return valid_name(val, ".-_,@"); } -/// Return true if "val" is a valid 'spellfile' value. -bool valid_spellfile(const char_u *val) +/// @return true if "val" is a valid 'spellfile' value. +bool valid_spellfile(const char *val) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - for (const char_u *s = val; *s != NUL; s++) { - if (!vim_isfilec(*s) && *s != ',' && *s != ' ') { + for (const char *s = val; *s != NUL; s++) { + if (!vim_isfilec((uint8_t)(*s)) && *s != ',' && *s != ' ') { return false; } } @@ -3632,22 +3625,23 @@ char *did_set_spell_option(bool is_spellfile) char *errmsg = NULL; if (is_spellfile) { - int l = (int)STRLEN(curwin->w_s->b_p_spf); + int l = (int)strlen(curwin->w_s->b_p_spf); if (l > 0 - && (l < 4 || STRCMP(curwin->w_s->b_p_spf + l - 4, ".add") != 0)) { + && (l < 4 || strcmp(curwin->w_s->b_p_spf + l - 4, ".add") != 0)) { errmsg = e_invarg; } } - if (errmsg == NULL) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == curbuf && wp->w_p_spell) { - errmsg = did_set_spelllang(wp); - break; - } - } + if (errmsg != NULL) { + return errmsg; } + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == curbuf && wp->w_p_spell) { + errmsg = did_set_spelllang(wp); + break; + } + } return errmsg; } @@ -3657,14 +3651,13 @@ char *compile_cap_prog(synblock_T *synblock) FUNC_ATTR_NONNULL_ALL { regprog_T *rp = synblock->b_cap_prog; - char_u *re; if (synblock->b_p_spc == NULL || *synblock->b_p_spc == NUL) { synblock->b_cap_prog = NULL; } else { // Prepend a ^ so that we only match at one column - re = concat_str((char_u *)"^", synblock->b_p_spc); - synblock->b_cap_prog = vim_regcomp((char *)re, RE_MAGIC); + char *re = concat_str("^", synblock->b_p_spc); + synblock->b_cap_prog = vim_regcomp(re, RE_MAGIC); xfree(re); if (synblock->b_cap_prog == NULL) { synblock->b_cap_prog = rp; // restore the previous program diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h index 61f722b9ee..726af7d698 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -72,19 +72,19 @@ typedef int idx_T; // si_repsal, sl_rep, and si_sal. Not for sl_sal! // One replacement: from "ft_from" to "ft_to". typedef struct fromto_S { - char_u *ft_from; - char_u *ft_to; + char *ft_from; + char *ft_to; } fromto_T; // Info from "SAL" entries in ".aff" file used in sl_sal. // The info is split for quick processing by spell_soundfold(). // Note that "sm_oneof" and "sm_rules" point into sm_lead. typedef struct salitem_S { - char_u *sm_lead; // leading letters - int sm_leadlen; // length of "sm_lead" - char_u *sm_oneof; // letters from () or NULL - char_u *sm_rules; // rules like ^, $, priority - char_u *sm_to; // replacement. + char *sm_lead; // leading letters + int sm_leadlen; // length of "sm_lead" + char_u *sm_oneof; // letters from () or NULL + char *sm_rules; // rules like ^, $, priority + char *sm_to; // replacement. int *sm_lead_w; // wide character copy of "sm_lead" int *sm_oneof_w; // wide character copy of "sm_oneof" int *sm_to_w; // wide character copy of "sm_to" @@ -115,21 +115,21 @@ typedef struct slang_S slang_T; struct slang_S { slang_T *sl_next; // next language - char_u *sl_name; // language name "en", "en.rare", "nl", etc. - char_u *sl_fname; // name of .spl file - bool sl_add; // true if it's a .add file. + char *sl_name; // language name "en", "en.rare", "nl", etc. + char *sl_fname; // name of .spl file + bool sl_add; // true if it's a .add file. - char_u *sl_fbyts; // case-folded word bytes + char *sl_fbyts; // case-folded word bytes long sl_fbyts_len; // length of sl_fbyts idx_T *sl_fidxs; // case-folded word indexes - char_u *sl_kbyts; // keep-case word bytes + char *sl_kbyts; // keep-case word bytes idx_T *sl_kidxs; // keep-case word indexes - char_u *sl_pbyts; // prefix tree word bytes + char *sl_pbyts; // prefix tree word bytes idx_T *sl_pidxs; // prefix tree word indexes char_u *sl_info; // infotext string or NULL - char_u sl_regions[MAXREGIONS * 2 + 1]; + char sl_regions[MAXREGIONS * 2 + 1]; // table with up to 8 region names plus NUL char_u *sl_midword; // MIDWORD string or NULL @@ -143,9 +143,9 @@ struct slang_S { garray_T sl_comppat; // CHECKCOMPOUNDPATTERN items regprog_T *sl_compprog; // COMPOUNDRULE turned into a regexp progrm // (NULL when no compounding) - char_u *sl_comprules; // all COMPOUNDRULE concatenated (or NULL) - char_u *sl_compstartflags; // flags for first compound word - char_u *sl_compallflags; // all flags for compound words + uint8_t *sl_comprules; // all COMPOUNDRULE concatenated (or NULL) + uint8_t *sl_compstartflags; // flags for first compound word + uint8_t *sl_compallflags; // all flags for compound words bool sl_nobreak; // When true: no spaces between words char_u *sl_syllable; // SYLLABLE repeatable chars or NULL garray_T sl_syl_items; // syllable items @@ -172,7 +172,7 @@ struct slang_S { // Info from the .sug file. Loaded on demand. time_t sl_sugtime; // timestamp for .sug file - char_u *sl_sbyts; // soundfolded word bytes + char *sl_sbyts; // soundfolded word bytes idx_T *sl_sidxs; // soundfolded word indexes buf_T *sl_sugbuf; // buffer with word number table bool sl_sugloaded; // true when .sug file was loaded or failed to @@ -218,8 +218,7 @@ typedef struct { // the "w" library function for characters above 255. #define SPELL_TOFOLD(c) ((c) >= 128 ? utf_fold(c) : (int)spelltab.st_fold[c]) -#define SPELL_TOUPPER(c) ((c) >= 128 ? mb_toupper(c) \ - : (int)spelltab.st_upper[c]) +#define SPELL_TOUPPER(c) ((c) >= 128 ? mb_toupper(c) : (int)spelltab.st_upper[c]) #define SPELL_ISUPPER(c) ((c) >= 128 ? mb_isupper(c) : spelltab.st_isu[c]) @@ -228,7 +227,7 @@ typedef struct { extern slang_T *first_lang; // file used for "zG" and "zW" -extern char_u *int_wordlist; +extern char *int_wordlist; extern spelltab_T spelltab; extern int did_set_spelltab; @@ -252,7 +251,7 @@ typedef struct wordcount_S { #define MAXWORDCOUNT 0xffff // Remember what "z?" replaced. -extern char_u *repl_from; -extern char_u *repl_to; +extern char *repl_from; +extern char *repl_to; #endif // NVIM_SPELL_DEFS_H diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index be1373f617..44414ca1a5 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -226,82 +226,101 @@ // stored as an offset to the previous number in as // few bytes as possible, see offset2bytes()) -#include <stdint.h> +#include <assert.h> +#include <ctype.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> #include <stdio.h> -#include <wctype.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include "auto/config.h" #include "nvim/arglist.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" -#include "nvim/ex_cmds2.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/fileio.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/time.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/spell.h" #include "nvim/spell_defs.h" #include "nvim/spellfile.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" -#ifndef UNIX // it's in os/unix_defs.h for Unix -# include <time.h> // for time_t -#endif - // Special byte values for <byte>. Some are only used in the tree for // postponed prefixes, some only in the other trees. This is a bit messy... -#define BY_NOFLAGS 0 // end of word without flags or region; for - // postponed prefix: no <pflags> -#define BY_INDEX 1 // child is shared, index follows -#define BY_FLAGS 2 // end of word, <flags> byte follows; for - // postponed prefix: <pflags> follows -#define BY_FLAGS2 3 // end of word, <flags> and <flags2> bytes - // follow; never used in prefix tree -#define BY_SPECIAL BY_FLAGS2 // highest special byte value +enum { + BY_NOFLAGS = 0, // end of word without flags or region; for postponed prefix: no <pflags> + BY_INDEX = 1, // child is shared, index follows + BY_FLAGS = 2, // end of word, <flags> byte follows; for postponed prefix: <pflags> follows + BY_FLAGS2 = 3, // end of word, <flags> and <flags2> bytes follow; never used in prefix tree + BY_SPECIAL = BY_FLAGS2, // highest special byte value +}; #define ZERO_FLAG 65009 // used when flag is zero: "0" // Flags used in .spl file for soundsalike flags. -#define SAL_F0LLOWUP 1 -#define SAL_COLLAPSE 2 -#define SAL_REM_ACCENTS 4 +enum { + SAL_F0LLOWUP = 1, + SAL_COLLAPSE = 2, + SAL_REM_ACCENTS = 4, +}; #define VIMSPELLMAGIC "VIMspell" // string at start of Vim spell file #define VIMSPELLMAGICL (sizeof(VIMSPELLMAGIC) - 1) #define VIMSPELLVERSION 50 // Section IDs. Only renumber them when VIMSPELLVERSION changes! -#define SN_REGION 0 // <regionname> section -#define SN_CHARFLAGS 1 // charflags section -#define SN_MIDWORD 2 // <midword> section -#define SN_PREFCOND 3 // <prefcond> section -#define SN_REP 4 // REP items section -#define SN_SAL 5 // SAL items section -#define SN_SOFO 6 // soundfolding section -#define SN_MAP 7 // MAP items section -#define SN_COMPOUND 8 // compound words section -#define SN_SYLLABLE 9 // syllable section -#define SN_NOBREAK 10 // NOBREAK section -#define SN_SUGFILE 11 // timestamp for .sug file -#define SN_REPSAL 12 // REPSAL items section -#define SN_WORDS 13 // common words -#define SN_NOSPLITSUGS 14 // don't split word for suggestions -#define SN_INFO 15 // info section -#define SN_NOCOMPOUNDSUGS 16 // don't compound for suggestions -#define SN_END 255 // end of sections +enum { + SN_REGION = 0, // <regionname> section + SN_CHARFLAGS = 1, // charflags section + SN_MIDWORD = 2, // <midword> section + SN_PREFCOND = 3, // <prefcond> section + SN_REP = 4, // REP items section + SN_SAL = 5, // SAL items section + SN_SOFO = 6, // soundfolding section + SN_MAP = 7, // MAP items section + SN_COMPOUND = 8, // compound words section + SN_SYLLABLE = 9, // syllable section + SN_NOBREAK = 10, // NOBREAK section + SN_SUGFILE = 11, // timestamp for .sug file + SN_REPSAL = 12, // REPSAL items section + SN_WORDS = 13, // common words + SN_NOSPLITSUGS = 14, // don't split word for suggestions + SN_INFO = 15, // info section + SN_NOCOMPOUNDSUGS = 16, // don't compound for suggestions + SN_END = 255, // end of sections +}; #define SNF_REQUIRED 1 // <sectionflags>: required section -#define CF_WORD 0x01 -#define CF_UPPER 0x02 +enum { + CF_WORD = 0x01, + CF_UPPER = 0x02, +}; static char *e_spell_trunc = N_("E758: Truncated spell file"); static char *e_illegal_character_in_word = N_("E1280: Illegal character in word"); @@ -313,7 +332,7 @@ static char *msg_compressing = N_("Compressing word tree..."); // and .dic file. // Main structure to store the contents of a ".aff" file. typedef struct afffile_S { - char_u *af_enc; // "SET", normalized, alloc'ed string or NULL + char *af_enc; // "SET", normalized, alloc'ed string or NULL int af_flagtype; // AFT_CHAR, AFT_LONG, AFT_NUM or AFT_CAPLONG unsigned af_rare; // RARE ID for rare word unsigned af_keepcase; // KEEPCASE ID for keep-case word @@ -341,12 +360,12 @@ typedef struct afffile_S { typedef struct affentry_S affentry_T; // Affix entry from ".aff" file. Used for prefixes and suffixes. struct affentry_S { - affentry_T *ae_next; // next affix with same name/number - char_u *ae_chop; // text to chop off basic word (can be NULL) - char_u *ae_add; // text to add to basic word (can be NULL) - char_u *ae_flags; // flags on the affix (can be NULL) - char_u *ae_cond; // condition (NULL for ".") - regprog_T *ae_prog; // regexp program for ae_cond or NULL + affentry_T *ae_next; // next affix with same name/number + char *ae_chop; // text to chop off basic word (can be NULL) + char *ae_add; // text to add to basic word (can be NULL) + char *ae_flags; // flags on the affix (can be NULL) + char *ae_cond; // condition (NULL for ".") + regprog_T *ae_prog; // regexp program for ae_cond or NULL char ae_compforbid; // COMPOUNDFORBIDFLAG found char ae_comppermit; // COMPOUNDPERMITFLAG found }; @@ -355,12 +374,12 @@ struct affentry_S { // Affix header from ".aff" file. Used for af_pref and af_suff. typedef struct affheader_S { - char_u ah_key[AH_KEY_LEN]; // key for hashtab == name of affix + char ah_key[AH_KEY_LEN]; // key for hashtab == name of affix unsigned ah_flag; // affix name as number, uses "af_flagtype" int ah_newID; // prefix ID after renumbering; 0 if not used int ah_combine; // suffix may combine with prefix int ah_follows; // another affix block should be following - affentry_T *ah_first; // first affix entry + affentry_T *ah_first; // first affix entry } affheader_T; #define HI2AH(hi) ((affheader_T *)(hi)->hi_key) @@ -415,7 +434,7 @@ struct wordnode_S { // "wn_region" the LSW of the wordnr. char_u wn_affixID; // supported/required prefix ID or 0 uint16_t wn_flags; // WF_ flags - short wn_region; // region mask + int16_t wn_region; // region mask #ifdef SPELL_PRINTTREE int wn_nr; // sequence nr for printing @@ -440,7 +459,7 @@ typedef struct spellinfo_S { sblock_T *si_blocks; // memory blocks used long si_blocks_cnt; // memory blocks allocated - int si_did_emsg; // TRUE when ran out of memory + int si_did_emsg; // true when ran out of memory long si_compress_cnt; // words to add before lowering // compression limit @@ -454,13 +473,13 @@ typedef struct spellinfo_S { int si_ascii; // handling only ASCII words int si_add; // addition file - int si_clear_chartab; // when TRUE clear char tables + int si_clear_chartab; // when true clear char tables int si_region; // region mask vimconv_T si_conv; // for conversion to 'encoding' int si_memtot; // runtime memory used int si_verbose; // verbose messages int si_msg_count; // number of words added since last message - char_u *si_info; // info text chars or NULL + char *si_info; // info text chars or NULL int si_region_count; // number of regions supported (1 when there // are no regions) char_u si_region_name[MAXREGIONS * 2 + 1]; @@ -470,8 +489,8 @@ typedef struct spellinfo_S { garray_T si_rep; // list of fromto_T entries from REP lines garray_T si_repsal; // list of fromto_T entries from REPSAL lines garray_T si_sal; // list of fromto_T entries from SAL lines - char_u *si_sofofr; // SOFOFROM text - char_u *si_sofoto; // SOFOTO text + char *si_sofofr; // SOFOFROM text + char *si_sofoto; // SOFOTO text int si_nosugfile; // NOSUGFILE item found int si_nosplitsugs; // NOSPLITSUGS item found int si_nocompoundsugs; // NOCOMPOUNDSUGS item found @@ -481,16 +500,16 @@ typedef struct spellinfo_S { time_t si_sugtime; // timestamp for .sug file int si_rem_accents; // soundsalike: remove accents garray_T si_map; // MAP info concatenated - char_u *si_midword; // MIDWORD chars or NULL + char *si_midword; // MIDWORD chars or NULL int si_compmax; // max nr of words for compounding int si_compminlen; // minimal length for compounding int si_compsylmax; // max nr of syllables for compounding int si_compoptions; // COMP_ flags garray_T si_comppat; // CHECKCOMPOUNDPATTERN items, each stored as // a string - char_u *si_compflags; // flags used for compounding + char *si_compflags; // flags used for compounding char_u si_nobreak; // NOBREAK - char_u *si_syllable; // syllable string + char *si_syllable; // syllable string garray_T si_prefcond; // table with conditions for postponed // prefixes, each stored as a string int si_newprefID; // current value for ah_newID @@ -552,7 +571,7 @@ static inline int spell_check_magic_string(FILE *const fd) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE { char buf[VIMSPELLMAGICL]; - SPELL_READ_BYTES(buf, VIMSPELLMAGICL, fd,; ); + SPELL_READ_BYTES(buf, VIMSPELLMAGICL, fd,; ); // NOLINT(whitespace/parens) if (memcmp(buf, VIMSPELLMAGIC, VIMSPELLMAGICL) != 0) { return SP_FORMERROR; } @@ -572,10 +591,10 @@ static inline int spell_check_magic_string(FILE *const fd) /// @param silent no error if file doesn't exist /// /// @return the slang_T the spell file was loaded into. NULL for error. -slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool silent) +slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent) { FILE *fd; - char_u *p; + char *p; int n; int len; slang_T *lp = NULL; @@ -583,7 +602,7 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile int res; bool did_estack_push = false; - fd = os_fopen((char *)fname, "r"); + fd = os_fopen(fname, "r"); if (fd == NULL) { if (!silent) { semsg(_(e_notopen), fname); @@ -604,16 +623,16 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile lp = slang_alloc(lang); // Remember the file name, used to reload the file when it's updated. - lp->sl_fname = vim_strsave(fname); + lp->sl_fname = xstrdup(fname); // Check for .add.spl. - lp->sl_add = strstr(path_tail((char *)fname), SPL_FNAME_ADD) != NULL; + lp->sl_add = strstr(path_tail(fname), SPL_FNAME_ADD) != NULL; } else { lp = old_lp; } // Set sourcing_name, so that error messages mention the file name. - estack_push(ETYPE_SPELL, (char *)fname, 0); + estack_push(ETYPE_SPELL, fname, 0); did_estack_push = true; // <HEADER>: <fileID> @@ -697,7 +716,7 @@ slang_T *spell_load_file(char_u *fname, char_u *lang, slang_T *old_lp, bool sile break; case SN_MAP: - p = READ_STRING(fd, len); // <mapstr> + p = (char *)READ_STRING(fd, len); // <mapstr> if (p == NULL) { goto endFAIL; } @@ -819,7 +838,7 @@ endOK: // Fill in the wordcount fields for a trie. // Returns the total number of words. -static void tree_count_words(char_u *byts, idx_T *idxs) +static void tree_count_words(const char_u *byts, idx_T *idxs) { int depth; idx_T arridx[MAXWLEN]; @@ -845,12 +864,12 @@ static void tree_count_words(char_u *byts, idx_T *idxs) } else { // Do one more byte at this node. n = arridx[depth] + curi[depth]; - ++curi[depth]; + curi[depth]++; c = byts[n]; if (c == 0) { // End of word, count it. - ++wordcount[depth]; + wordcount[depth]++; // Skip over any other NUL bytes (same word with different // flags). @@ -869,14 +888,14 @@ static void tree_count_words(char_u *byts, idx_T *idxs) } } -// Load the .sug files for languages that have one and weren't loaded yet. +/// Load the .sug files for languages that have one and weren't loaded yet. void suggest_load_files(void) { langp_T *lp; slang_T *slang; - char_u *dotp; + char *dotp; FILE *fd; - char_u buf[MAXWLEN]; + char buf[MAXWLEN]; int i; time_t timestamp; int wcount; @@ -885,7 +904,7 @@ void suggest_load_files(void) int c; // Do this for all languages that support sound folding. - for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) { + for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) { lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); slang = lp->lp_slang; if (slang->sl_sugtime != 0 && !slang->sl_sugloaded) { @@ -894,21 +913,21 @@ void suggest_load_files(void) // don't try again and again. slang->sl_sugloaded = true; - dotp = STRRCHR(slang->sl_fname, '.'); - if (dotp == NULL || FNAMECMP(dotp, ".spl") != 0) { + dotp = strrchr(slang->sl_fname, '.'); + if (dotp == NULL || path_fnamecmp(dotp, ".spl") != 0) { continue; } STRCPY(dotp, ".sug"); - fd = os_fopen((char *)slang->sl_fname, "r"); + fd = os_fopen(slang->sl_fname, "r"); if (fd == NULL) { goto nextone; } // <SUGHEADER>: <fileID> <versionnr> <timestamp> for (i = 0; i < VIMSUGMAGICL; i++) { - buf[i] = (char_u)getc(fd); // <fileID> + buf[i] = (char)getc(fd); // <fileID> } - if (STRNCMP(buf, VIMSUGMAGIC, VIMSUGMAGICL) != 0) { + if (strncmp(buf, VIMSUGMAGIC, VIMSUGMAGICL) != 0) { semsg(_("E778: This does not look like a .sug file: %s"), slang->sl_fname); goto nextone; @@ -960,7 +979,7 @@ someerror: // Read all the wordnr lists into the buffer, one NUL terminated // list per line. ga_init(&ga, 1, 100); - for (wordnr = 0; wordnr < wcount; ++wordnr) { + for (wordnr = 0; wordnr < wcount; wordnr++) { ga.ga_len = 0; for (;;) { c = getc(fd); // <sugline> @@ -981,8 +1000,8 @@ someerror: // Need to put word counts in the word tries, so that we can find // a word by its number. - tree_count_words(slang->sl_fbyts, slang->sl_fidxs); - tree_count_words(slang->sl_sbyts, slang->sl_sidxs); + tree_count_words((char_u *)slang->sl_fbyts, slang->sl_fidxs); + tree_count_words((char_u *)slang->sl_sbyts, slang->sl_sidxs); nextone: if (fd != NULL) { @@ -1031,7 +1050,7 @@ static int read_region_section(FILE *fd, slang_T *lp, int len) if (len > MAXREGIONS * 2) { return SP_FORMERROR; } - SPELL_READ_NONNUL_BYTES((char *)lp->sl_regions, (size_t)len, fd,; ); + SPELL_READ_NONNUL_BYTES(lp->sl_regions, (size_t)len, fd,; ); // NOLINT(whitespace/parens) lp->sl_regions[len] = NUL; return 0; } @@ -1041,12 +1060,12 @@ static int read_region_section(FILE *fd, slang_T *lp, int len) // Return SP_*ERROR flags. static int read_charflags_section(FILE *fd) { - char_u *flags; + char *flags; char_u *fol; int flagslen, follen; // <charflagslen> <charflags> - flags = read_cnt_string(fd, 1, &flagslen); + flags = (char *)read_cnt_string(fd, 1, &flagslen); if (flagslen < 0) { return flagslen; } @@ -1060,7 +1079,7 @@ static int read_charflags_section(FILE *fd) // Set the word-char flags and fill SPELL_ISUPPER() table. if (flags != NULL && fol != NULL) { - set_spell_charflags(flags, flagslen, fol); + set_spell_charflags((char_u *)flags, flagslen, (char *)fol); } xfree(flags); @@ -1098,7 +1117,7 @@ static int read_prefcond_section(FILE *fd, slang_T *lp) if (n > 0) { char buf[MAXWLEN + 1]; buf[0] = '^'; // always match at one position only - SPELL_READ_NONNUL_BYTES(buf + 1, (size_t)n, fd,; ); + SPELL_READ_NONNUL_BYTES(buf + 1, (size_t)n, fd,; ); // NOLINT(whitespace/parens) buf[n + 1] = NUL; lp->sl_prefprog[i] = vim_regcomp(buf, RE_MAGIC | RE_STRING); } @@ -1121,17 +1140,17 @@ static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first) ga_grow(gap, cnt); // <rep> : <repfromlen> <repfrom> <reptolen> <repto> - for (; gap->ga_len < cnt; ++gap->ga_len) { + for (; gap->ga_len < cnt; gap->ga_len++) { int c; ftp = &((fromto_T *)gap->ga_data)[gap->ga_len]; - ftp->ft_from = read_cnt_string(fd, 1, &c); + ftp->ft_from = (char *)read_cnt_string(fd, 1, &c); if (c < 0) { return c; } if (c == 0) { return SP_FORMERROR; } - ftp->ft_to = read_cnt_string(fd, 1, &c); + ftp->ft_to = (char *)read_cnt_string(fd, 1, &c); if (c <= 0) { xfree(ftp->ft_from); if (c < 0) { @@ -1142,13 +1161,13 @@ static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first) } // Fill the first-index table. - for (int i = 0; i < 256; ++i) { + for (int i = 0; i < 256; i++) { first[i] = -1; } - for (int i = 0; i < gap->ga_len; ++i) { + for (int i = 0; i < gap->ga_len; i++) { ftp = &((fromto_T *)gap->ga_data)[i]; - if (first[*ftp->ft_from] == -1) { - first[*ftp->ft_from] = (int16_t)i; + if (first[(uint8_t)(*ftp->ft_from)] == -1) { + first[(uint8_t)(*ftp->ft_from)] = (int16_t)i; } } return 0; @@ -1196,24 +1215,24 @@ static int read_sal_section(FILE *fd, slang_T *slang) return SP_TRUNCERROR; } p = xmalloc((size_t)ccnt + 2); - smp->sm_lead = p; + smp->sm_lead = (char *)p; // Read up to the first special char into sm_lead. int i = 0; - for (; i < ccnt; ++i) { + for (; i < ccnt; i++) { c = getc(fd); // <salfrom> if (vim_strchr("0123456789(-<^$", c) != NULL) { break; } *p++ = (char_u)c; } - smp->sm_leadlen = (int)(p - smp->sm_lead); + smp->sm_leadlen = (int)(p - (char_u *)smp->sm_lead); *p++ = NUL; // Put (abc) chars in sm_oneof, if any. if (c == '(') { smp->sm_oneof = p; - for (++i; i < ccnt; ++i) { + for (++i; i < ccnt; i++) { c = getc(fd); // <salfrom> if (c == ')') { break; @@ -1229,7 +1248,7 @@ static int read_sal_section(FILE *fd, slang_T *slang) } // Any following chars go in sm_rules. - smp->sm_rules = p; + smp->sm_rules = (char *)p; if (i < ccnt) { // store the char we got while checking for end of sm_lead *p++ = (char_u)c; @@ -1244,7 +1263,7 @@ static int read_sal_section(FILE *fd, slang_T *slang) *p++ = NUL; // <saltolen> <salto> - smp->sm_to = read_cnt_string(fd, 1, &ccnt); + smp->sm_to = (char *)read_cnt_string(fd, 1, &ccnt); if (ccnt < 0) { xfree(smp->sm_lead); return ccnt; @@ -1256,7 +1275,7 @@ static int read_sal_section(FILE *fd, slang_T *slang) if (smp->sm_oneof == NULL) { smp->sm_oneof_w = NULL; } else { - smp->sm_oneof_w = mb_str2wide(smp->sm_oneof); + smp->sm_oneof_w = mb_str2wide((char *)smp->sm_oneof); } if (smp->sm_to == NULL) { smp->sm_to_w = NULL; @@ -1271,12 +1290,12 @@ static int read_sal_section(FILE *fd, slang_T *slang) smp = &((salitem_T *)gap->ga_data)[gap->ga_len]; p = xmalloc(1); p[0] = NUL; - smp->sm_lead = p; + smp->sm_lead = (char *)p; smp->sm_lead_w = mb_str2wide(smp->sm_lead); smp->sm_leadlen = 0; smp->sm_oneof = NULL; smp->sm_oneof_w = NULL; - smp->sm_rules = p; + smp->sm_rules = (char *)p; smp->sm_to = NULL; smp->sm_to_w = NULL; gap->ga_len++; @@ -1299,7 +1318,7 @@ static int read_words_section(FILE *fd, slang_T *lp, int len) while (done < len) { // Read one word at a time. - for (i = 0;; ++i) { + for (i = 0;; i++) { c = getc(fd); if (c == EOF) { return SP_TRUNCERROR; @@ -1314,7 +1333,7 @@ static int read_words_section(FILE *fd, slang_T *lp, int len) } // Init the count to 10. - count_common_word(lp, word, -1, 10); + count_common_word(lp, (char *)word, -1, 10); done += i + 1; } return 0; @@ -1325,19 +1344,19 @@ static int read_words_section(FILE *fd, slang_T *lp, int len) static int read_sofo_section(FILE *fd, slang_T *slang) { int cnt; - char_u *from, *to; + char *from, *to; int res; slang->sl_sofo = true; // <sofofromlen> <sofofrom> - from = read_cnt_string(fd, 2, &cnt); + from = (char *)read_cnt_string(fd, 2, &cnt); if (cnt < 0) { return cnt; } // <sofotolen> <sofoto> - to = read_cnt_string(fd, 2, &cnt); + to = (char *)read_cnt_string(fd, 2, &cnt); if (cnt < 0) { xfree(from); return cnt; @@ -1407,7 +1426,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len) return SP_TRUNCERROR; } todo -= 2; - ga_init(gap, sizeof(char_u *), c); + ga_init(gap, sizeof(char *), c); ga_grow(gap, c); while (--c >= 0) { ((char **)(gap->ga_data))[gap->ga_len++] = (char *)read_cnt_string(fd, 1, &cnt); @@ -1428,25 +1447,25 @@ static int read_compound(FILE *fd, slang_T *slang, int len) // Conversion to utf-8 may double the size. c = todo * 2 + 7; c += todo * 2; - char_u *pat = xmalloc((size_t)c); + char *pat = xmalloc((size_t)c); // We also need a list of all flags that can appear at the start and one // for all flags. - char_u *cp = xmalloc((size_t)todo + 1); + uint8_t *cp = xmalloc((size_t)todo + 1); slang->sl_compstartflags = cp; *cp = NUL; - char_u *ap = xmalloc((size_t)todo + 1); + uint8_t *ap = xmalloc((size_t)todo + 1); slang->sl_compallflags = ap; *ap = NUL; // And a list of all patterns in their original form, for checking whether // compounding may work in match_compoundrule(). This is freed when we // encounter a wildcard, the check doesn't work then. - char_u *crp = xmalloc((size_t)todo + 1); + uint8_t *crp = xmalloc((size_t)todo + 1); slang->sl_comprules = crp; - char_u *pp = pat; + char_u *pp = (char_u *)pat; *pp++ = '^'; *pp++ = '\\'; *pp++ = '('; @@ -1515,7 +1534,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len) *crp = NUL; } - slang->sl_compprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING + RE_STRICT); + slang->sl_compprog = vim_regcomp(pat, RE_MAGIC + RE_STRING + RE_STRICT); xfree(pat); if (slang->sl_compprog == NULL) { return SP_FORMERROR; @@ -1526,10 +1545,10 @@ static int read_compound(FILE *fd, slang_T *slang, int len) // Set the SOFOFROM and SOFOTO items in language "lp". // Returns SP_*ERROR flags when there is something wrong. -static int set_sofo(slang_T *lp, char_u *from, char_u *to) +static int set_sofo(slang_T *lp, char *from, char *to) { - char_u *s; - char_u *p; + char *s; + char *p; // Use "sl_sal" as an array with 256 pointers to a list of wide // characters. The index is the low byte of the character. @@ -1544,7 +1563,7 @@ static int set_sofo(slang_T *lp, char_u *from, char_u *to) // First count the number of items for each list. Temporarily use // sl_sal_first[] for this. for (p = from, s = to; *p != NUL && *s != NUL;) { - const int c = mb_cptr2char_adv((const char_u **)&p); + const int c = mb_cptr2char_adv((const char **)&p); MB_CPTR_ADV(s); if (c >= 256) { lp->sl_sal_first[c & 0xff]++; @@ -1567,8 +1586,8 @@ static int set_sofo(slang_T *lp, char_u *from, char_u *to) // list. memset(lp->sl_sal_first, 0, sizeof(salfirst_T) * 256); for (p = from, s = to; *p != NUL && *s != NUL;) { - const int c = mb_cptr2char_adv((const char_u **)&p); - const int i = mb_cptr2char_adv((const char_u **)&s); + const int c = mb_cptr2char_adv((const char **)&p); + const int i = mb_cptr2char_adv((const char **)&s); if (c >= 256) { // Append the from-to chars at the end of the list with // the low byte. @@ -1597,7 +1616,7 @@ static void set_sal_first(slang_T *lp) garray_T *gap = &lp->sl_sal; sfirst = lp->sl_sal_first; - for (int i = 0; i < 256; ++i) { + for (int i = 0; i < 256; i++) { sfirst[i] = -1; } smp = (salitem_T *)gap->ga_data; @@ -1637,13 +1656,13 @@ static void set_sal_first(slang_T *lp) // Turn a multi-byte string into a wide character string. // Return it in allocated memory. -static int *mb_str2wide(char_u *s) +static int *mb_str2wide(char *s) { int i = 0; int *res = xmalloc(((size_t)mb_charlen(s) + 1) * sizeof(int)); - for (char_u *p = s; *p != NUL;) { - res[i++] = mb_ptr2char_adv((const char_u **)&p); + for (char *p = s; *p != NUL;) { + res[i++] = mb_ptr2char_adv((const char **)&p); } res[i] = NUL; @@ -1658,12 +1677,12 @@ static int *mb_str2wide(char_u *s) /// @param prefixcnt when "prefixtree" is true: prefix count /// /// @return zero when OK, SP_ value for an error. -static int spell_read_tree(FILE *fd, char_u **bytsp, long *bytsp_len, idx_T **idxsp, - bool prefixtree, int prefixcnt) +static int spell_read_tree(FILE *fd, char **bytsp, long *bytsp_len, idx_T **idxsp, bool prefixtree, + int prefixcnt) FUNC_ATTR_NONNULL_ARG(1, 2, 4) { int idx; - char_u *bp; + char *bp; idx_T *ip; // The tree size was computed when writing the file, so that we can @@ -1676,23 +1695,25 @@ static int spell_read_tree(FILE *fd, char_u **bytsp, long *bytsp_len, idx_T **id // Invalid length, multiply with sizeof(int) would overflow. return SP_FORMERROR; } - if (len > 0) { - // Allocate the byte array. - bp = xmalloc((size_t)len); - *bytsp = bp; - if (bytsp_len != NULL) { - *bytsp_len = len; - } + if (len <= 0) { + return 0; + } - // Allocate the index array. - ip = xcalloc((size_t)len, sizeof(*ip)); - *idxsp = ip; + // Allocate the byte array. + bp = xmalloc((size_t)len); + *bytsp = bp; + if (bytsp_len != NULL) { + *bytsp_len = len; + } - // Recursively read the tree and store it in the array. - idx = read_tree_node(fd, bp, ip, (int)len, 0, prefixtree, prefixcnt); - if (idx < 0) { - return idx; - } + // Allocate the index array. + ip = xcalloc((size_t)len, sizeof(*ip)); + *idxsp = ip; + + // Recursively read the tree and store it in the array. + idx = read_tree_node(fd, (char_u *)bp, ip, (int)len, 0, prefixtree, prefixcnt); + if (idx < 0) { + return idx; } return 0; } @@ -1732,7 +1753,7 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx byts[idx++] = (char_u)len; // Read the byte values, flag/region bytes and shared indexes. - for (i = 1; i <= len; ++i) { + for (i = 1; i <= len; i++) { c = getc(fd); // <byte> if (c < 0) { return SP_TRUNCERROR; @@ -1795,7 +1816,7 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx // Recursively read the children for non-shared siblings. // Skip the end-of-word ones (zero byte value) and the shared ones (and // remove SHARED_MASK) - for (i = 1; i <= len; ++i) { + for (i = 1; i <= len; i++) { if (byts[startidx + i] != 0) { if (idxs[startidx + i] & SHARED_MASK) { idxs[startidx + i] &= ~SHARED_MASK; @@ -1816,19 +1837,19 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx /// Reload the spell file "fname" if it's loaded. /// /// @param added_word invoked through "zg" -static void spell_reload_one(char_u *fname, bool added_word) +static void spell_reload_one(char *fname, bool added_word) { slang_T *slang; bool didit = false; for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)fname, (char *)slang->sl_fname, false, true) == kEqualFiles) { + if (path_full_compare(fname, slang->sl_fname, false, true) == kEqualFiles) { slang_clear(slang); if (spell_load_file(fname, NULL, slang, false) == NULL) { // reloading failed, clear the language slang_clear(slang); } - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); didit = true; } } @@ -1863,7 +1884,7 @@ static long compress_added = 500000; // word count // Sets "sps_flags". int spell_check_msm(void) { - char *p = (char *)p_msm; + char *p = p_msm; long start = 0; long incr = 0; long added = 0; @@ -1924,7 +1945,7 @@ static void spell_clear_flags(wordnode_T *node) wordnode_T *np; for (np = node; np != NULL; np = np->wn_sibling) { - np->wn_u1.index = FALSE; + np->wn_u1.index = false; spell_clear_flags(np->wn_child); } } @@ -1940,7 +1961,7 @@ static void spell_print_node(wordnode_T *node, int depth) msg((char_u *)line2); msg((char_u *)line3); } else { - node->wn_u1.index = TRUE; + node->wn_u1.index = true; if (node->wn_byte != NUL) { if (node->wn_child != NULL) { @@ -1984,38 +2005,39 @@ static void spell_print_node(wordnode_T *node, int depth) static void spell_print_tree(wordnode_T *root) { - if (root != NULL) { - // Clear the "wn_u1.index" fields, used to remember what has been - // done. - spell_clear_flags(root); - - // Recursively print the tree. - spell_print_node(root, 0); + if (root == NULL) { + return; } + + // Clear the "wn_u1.index" fields, used to remember what has been done. + spell_clear_flags(root); + + // Recursively print the tree. + spell_print_node(root, 0); } -#endif // SPELL_PRINTTREE +#endif // SPELL_PRINTTREE // Reads the affix file "fname". // Returns an afffile_T, NULL for complete failure. -static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) +static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname) { FILE *fd; - char_u rline[MAXLINELEN]; - char_u *line; - char_u *pc = NULL; + char rline[MAXLINELEN]; + char *line; + char *pc = NULL; #define MAXITEMCNT 30 - char_u *(items[MAXITEMCNT]); + char *(items[MAXITEMCNT]); int itemcnt; - char_u *p; + char *p; int lnum = 0; affheader_T *cur_aff = NULL; bool did_postpone_prefix = false; int aff_todo = 0; hashtab_T *tp; - char_u *low = NULL; - char_u *fol = NULL; - char_u *upp = NULL; + char *low = NULL; + char *fol = NULL; + char *upp = NULL; int do_rep; int do_repsal; int do_sal; @@ -2023,25 +2045,25 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) bool found_map = false; hashitem_T *hi; int l; - int compminlen = 0; // COMPOUNDMIN value - int compsylmax = 0; // COMPOUNDSYLMAX value - int compoptions = 0; // COMP_ flags - int compmax = 0; // COMPOUNDWORDMAX value - char_u *compflags = NULL; // COMPOUNDFLAG and COMPOUNDRULE + int compminlen = 0; // COMPOUNDMIN value + int compsylmax = 0; // COMPOUNDSYLMAX value + int compoptions = 0; // COMP_ flags + int compmax = 0; // COMPOUNDWORDMAX value + char *compflags = NULL; // COMPOUNDFLAG and COMPOUNDRULE // concatenated - char_u *midword = NULL; // MIDWORD value - char_u *syllable = NULL; // SYLLABLE value - char_u *sofofrom = NULL; // SOFOFROM value - char_u *sofoto = NULL; // SOFOTO value + char *midword = NULL; // MIDWORD value + char *syllable = NULL; // SYLLABLE value + char *sofofrom = NULL; // SOFOFROM value + char *sofoto = NULL; // SOFOTO value // Open the file. - fd = os_fopen((char *)fname, "r"); + fd = os_fopen(fname, "r"); if (fd == NULL) { semsg(_(e_notopen), fname); return NULL; } - vim_snprintf((char *)IObuff, IOSIZE, _("Reading affix file %s..."), fname); + vim_snprintf(IObuff, IOSIZE, _("Reading affix file %s..."), fname); spell_message(spin, IObuff); // Only do REP lines when not done in another .aff file already. @@ -2091,7 +2113,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // item. itemcnt = 0; for (p = line;;) { - while (*p != NUL && *p <= ' ') { // skip white space and CR/NL + while (*p != NUL && (uint8_t)(*p) <= ' ') { // skip white space and CR/NL p++; } if (*p == NUL) { @@ -2103,11 +2125,11 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) items[itemcnt++] = p; // A few items have arbitrary text argument, don't split them. if (itemcnt == 2 && spell_info_item(items[0])) { - while (*p >= ' ' || *p == TAB) { // skip until CR/NL + while ((uint8_t)(*p) >= ' ' || *p == TAB) { // skip until CR/NL p++; } } else { - while (*p > ' ') { // skip until white space or CR/NL + while ((uint8_t)(*p) > ' ') { // skip until white space or CR/NL p++; } } @@ -2121,21 +2143,20 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) if (itemcnt > 0) { if (is_aff_rule(items, itemcnt, "SET", 2) && aff->af_enc == NULL) { // Setup for conversion from "ENC" to 'encoding'. - aff->af_enc = enc_canonize(items[1]); + aff->af_enc = enc_canonize((char *)items[1]); if (!spin->si_ascii - && convert_setup(&spin->si_conv, aff->af_enc, - p_enc) == FAIL) { + && convert_setup(&spin->si_conv, aff->af_enc, p_enc) == FAIL) { smsg(_("Conversion in %s not supported: from %s to %s"), fname, aff->af_enc, p_enc); } spin->si_conv.vc_fail = true; } else if (is_aff_rule(items, itemcnt, "FLAG", 2) && aff->af_flagtype == AFT_CHAR) { - if (STRCMP(items[1], "long") == 0) { + if (strcmp(items[1], "long") == 0) { aff->af_flagtype = AFT_LONG; - } else if (STRCMP(items[1], "num") == 0) { + } else if (strcmp(items[1], "num") == 0) { aff->af_flagtype = AFT_NUM; - } else if (STRCMP(items[1], "caplong") == 0) { + } else if (strcmp(items[1], "caplong") == 0) { aff->af_flagtype = AFT_CAPLONG; } else { smsg(_("Invalid value for FLAG in %s line %d: %s"), @@ -2157,9 +2178,9 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } } else if (spell_info_item(items[0]) && itemcnt > 1) { p = getroom(spin, - (spin->si_info == NULL ? 0 : STRLEN(spin->si_info)) - + STRLEN(items[0]) - + STRLEN(items[1]) + 3, false); + (spin->si_info == NULL ? 0 : strlen(spin->si_info)) + + strlen(items[0]) + + strlen(items[1]) + 3, false); if (spin->si_info != NULL) { STRCPY(p, spin->si_info); STRCAT(p, "\n"); @@ -2168,63 +2189,48 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) STRCAT(p, " "); STRCAT(p, items[1]); spin->si_info = p; - } else if (is_aff_rule(items, itemcnt, "MIDWORD", 2) - && midword == NULL) { + } else if (is_aff_rule(items, itemcnt, "MIDWORD", 2) && midword == NULL) { midword = getroom_save(spin, items[1]); } else if (is_aff_rule(items, itemcnt, "TRY", 2)) { // ignored, we look in the tree for what chars may appear - } - // TODO: remove "RAR" later - else if ((is_aff_rule(items, itemcnt, "RAR", 2) - || is_aff_rule(items, itemcnt, "RARE", 2)) - && aff->af_rare == 0) { - aff->af_rare = affitem2flag(aff->af_flagtype, items[1], - fname, lnum); - } - // TODO: remove "KEP" later - else if ((is_aff_rule(items, itemcnt, "KEP", 2) - || is_aff_rule(items, itemcnt, "KEEPCASE", 2)) - && aff->af_keepcase == 0) { - aff->af_keepcase = affitem2flag(aff->af_flagtype, items[1], - fname, lnum); + } else if ((is_aff_rule(items, itemcnt, "RAR", 2) // TODO(vim): remove "RAR" later + || is_aff_rule(items, itemcnt, "RARE", 2)) + && aff->af_rare == 0) { + aff->af_rare = affitem2flag(aff->af_flagtype, items[1], fname, lnum); + } else if ((is_aff_rule(items, itemcnt, "KEP", 2) // TODO(vim): remove "KEP" later + || is_aff_rule(items, itemcnt, "KEEPCASE", 2)) + && aff->af_keepcase == 0) { + aff->af_keepcase = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if ((is_aff_rule(items, itemcnt, "BAD", 2) || is_aff_rule(items, itemcnt, "FORBIDDENWORD", 2)) && aff->af_bad == 0) { - aff->af_bad = affitem2flag(aff->af_flagtype, items[1], - fname, lnum); + aff->af_bad = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if (is_aff_rule(items, itemcnt, "NEEDAFFIX", 2) && aff->af_needaffix == 0) { - aff->af_needaffix = affitem2flag(aff->af_flagtype, items[1], - fname, lnum); + aff->af_needaffix = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if (is_aff_rule(items, itemcnt, "CIRCUMFIX", 2) && aff->af_circumfix == 0) { - aff->af_circumfix = affitem2flag(aff->af_flagtype, items[1], - fname, lnum); + aff->af_circumfix = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if (is_aff_rule(items, itemcnt, "NOSUGGEST", 2) && aff->af_nosuggest == 0) { - aff->af_nosuggest = affitem2flag(aff->af_flagtype, items[1], - fname, lnum); + aff->af_nosuggest = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if ((is_aff_rule(items, itemcnt, "NEEDCOMPOUND", 2) || is_aff_rule(items, itemcnt, "ONLYINCOMPOUND", 2)) && aff->af_needcomp == 0) { - aff->af_needcomp = affitem2flag(aff->af_flagtype, items[1], - fname, lnum); + aff->af_needcomp = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if (is_aff_rule(items, itemcnt, "COMPOUNDROOT", 2) && aff->af_comproot == 0) { - aff->af_comproot = affitem2flag(aff->af_flagtype, items[1], - fname, lnum); + aff->af_comproot = affitem2flag(aff->af_flagtype, items[1], fname, lnum); } else if (is_aff_rule(items, itemcnt, "COMPOUNDFORBIDFLAG", 2) && aff->af_compforbid == 0) { - aff->af_compforbid = affitem2flag(aff->af_flagtype, items[1], - fname, lnum); + aff->af_compforbid = affitem2flag(aff->af_flagtype, items[1], fname, lnum); if (aff->af_pref.ht_used > 0) { smsg(_("Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line %d"), fname, lnum); } } else if (is_aff_rule(items, itemcnt, "COMPOUNDPERMITFLAG", 2) && aff->af_comppermit == 0) { - aff->af_comppermit = affitem2flag(aff->af_flagtype, items[1], - fname, lnum); + aff->af_comppermit = affitem2flag(aff->af_flagtype, items[1], fname, lnum); if (aff->af_pref.ht_used > 0) { smsg(_("Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line %d"), fname, lnum); @@ -2233,7 +2239,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) && compflags == NULL) { // Turn flag "c" into COMPOUNDRULE compatible string "c+", // "Na" into "Na+", "1234" into "1234+". - p = getroom(spin, STRLEN(items[1]) + 2, false); + p = getroom(spin, strlen(items[1]) + 2, false); STRCPY(p, items[1]); STRCAT(p, "+"); compflags = p; @@ -2249,9 +2255,9 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) if (compflags != NULL || *skipdigits((char *)items[1]) != NUL) { // Concatenate this string to previously defined ones, // using a slash to separate them. - l = (int)STRLEN(items[1]) + 1; + l = (int)strlen(items[1]) + 1; if (compflags != NULL) { - l += (int)STRLEN(compflags) + 1; + l += (int)strlen(compflags) + 1; } p = getroom(spin, (size_t)l, false); if (compflags != NULL) { @@ -2301,15 +2307,15 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Only add the couple if it isn't already there. for (i = 0; i < gap->ga_len - 1; i += 2) { - if (STRCMP(((char **)(gap->ga_data))[i], items[1]) == 0 - && STRCMP(((char **)(gap->ga_data))[i + 1], items[2]) == 0) { + if (strcmp(((char **)(gap->ga_data))[i], items[1]) == 0 + && strcmp(((char **)(gap->ga_data))[i + 1], items[2]) == 0) { break; } } if (i >= gap->ga_len) { ga_grow(gap, 2); - ((char **)(gap->ga_data))[gap->ga_len++] = (char *)getroom_save(spin, items[1]); - ((char **)(gap->ga_data))[gap->ga_len++] = (char *)getroom_save(spin, items[2]); + ((char **)(gap->ga_data))[gap->ga_len++] = getroom_save(spin, items[1]); + ((char **)(gap->ga_data))[gap->ga_len++] = getroom_save(spin, items[2]); } } else if (is_aff_rule(items, itemcnt, "SYLLABLE", 2) && syllable == NULL) { @@ -2326,12 +2332,12 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) aff->af_pfxpostpone = true; } else if (is_aff_rule(items, itemcnt, "IGNOREEXTRA", 1)) { aff->af_ignoreextra = true; - } else if ((STRCMP(items[0], "PFX") == 0 - || STRCMP(items[0], "SFX") == 0) + } else if ((strcmp(items[0], "PFX") == 0 + || strcmp(items[0], "SFX") == 0) && aff_todo == 0 && itemcnt >= 4) { int lasti = 4; - char_u key[AH_KEY_LEN]; + char key[AH_KEY_LEN]; if (*items[0] == 'P') { tp = &aff->af_pref; @@ -2343,7 +2349,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // times. The affix files that do this have an undocumented // "S" flag on all but the last block, thus we check for that // and store it in ah_follows. - STRLCPY(key, items[1], AH_KEY_LEN); + xstrlcpy(key, items[1], AH_KEY_LEN); hi = hash_find(tp, (char *)key); if (!HASHITEM_EMPTY(hi)) { cur_aff = HI2AH(hi); @@ -2358,9 +2364,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } else { // New affix letter. cur_aff = getroom(spin, sizeof(*cur_aff), true); - cur_aff->ah_flag = affitem2flag(aff->af_flagtype, items[1], - fname, lnum); - if (cur_aff->ah_flag == 0 || STRLEN(items[1]) >= AH_KEY_LEN) { + cur_aff->ah_flag = affitem2flag(aff->af_flagtype, items[1], fname, lnum); + if (cur_aff->ah_flag == 0 || strlen(items[1]) >= AH_KEY_LEN) { break; } if (cur_aff->ah_flag == aff->af_bad @@ -2384,7 +2389,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Check for the "S" flag, which apparently means that another // block with the same affix name is following. - if (itemcnt > lasti && STRCMP(items[lasti], "S") == 0) { + if (itemcnt > lasti && strcmp(items[lasti], "S") == 0) { lasti++; cur_aff->ah_follows = true; } else { @@ -2400,7 +2405,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) smsg(_(e_afftrailing), fname, lnum, items[lasti]); } - if (STRCMP(items[2], "Y") != 0 && STRCMP(items[2], "N") != 0) { + if (strcmp(items[2], "Y") != 0 && strcmp(items[2], "N") != 0) { smsg(_("Expected Y or N in %s line %d: %s"), fname, lnum, items[2]); } @@ -2423,10 +2428,10 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } aff_todo = atoi((char *)items[3]); - } else if ((STRCMP(items[0], "PFX") == 0 - || STRCMP(items[0], "SFX") == 0) + } else if ((strcmp(items[0], "PFX") == 0 + || strcmp(items[0], "SFX") == 0) && aff_todo > 0 - && STRCMP(cur_aff->ah_key, items[1]) == 0 + && strcmp(cur_aff->ah_key, items[1]) == 0 && itemcnt >= 5) { affentry_T *aff_entry; bool upper = false; @@ -2436,7 +2441,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // mean mistakes go unnoticed. Require a comment-starter. // Hunspell uses a "-" item. if (itemcnt > lasti && *items[lasti] != '#' - && (STRCMP(items[lasti], "-") != 0 + && (strcmp(items[lasti], "-") != 0 || itemcnt != lasti + 1)) { smsg(_(e_afftrailing), fname, lnum, items[lasti]); } @@ -2445,14 +2450,14 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) aff_todo--; aff_entry = getroom(spin, sizeof(*aff_entry), true); - if (STRCMP(items[2], "0") != 0) { + if (strcmp(items[2], "0") != 0) { aff_entry->ae_chop = getroom_save(spin, items[2]); } - if (STRCMP(items[3], "0") != 0) { + if (strcmp(items[3], "0") != 0) { aff_entry->ae_add = getroom_save(spin, items[3]); // Recognize flags on the affix: abcd/XYZ - aff_entry->ae_flags = (char_u *)vim_strchr((char *)aff_entry->ae_add, '/'); + aff_entry->ae_flags = vim_strchr(aff_entry->ae_add, '/'); if (aff_entry->ae_flags != NULL) { *aff_entry->ae_flags++ = NUL; aff_process_flags(aff, aff_entry); @@ -2466,16 +2471,16 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) aff_entry->ae_next = cur_aff->ah_first; cur_aff->ah_first = aff_entry; - if (STRCMP(items[4], ".") != 0) { - char_u buf[MAXLINELEN]; + if (strcmp(items[4], ".") != 0) { + char buf[MAXLINELEN]; aff_entry->ae_cond = getroom_save(spin, items[4]); if (*items[0] == 'P') { - sprintf((char *)buf, "^%s", items[4]); + sprintf(buf, "^%s", items[4]); // NOLINT(runtime/printf) } else { - sprintf((char *)buf, "%s$", items[4]); + sprintf(buf, "%s$", items[4]); // NOLINT(runtime/printf) } - aff_entry->ae_prog = vim_regcomp((char *)buf, RE_MAGIC + RE_STRING + RE_STRICT); + aff_entry->ae_prog = vim_regcomp(buf, RE_MAGIC + RE_STRING + RE_STRICT); if (aff_entry->ae_prog == NULL) { smsg(_("Broken condition in %s line %d: %s"), fname, lnum, items[4]); @@ -2495,19 +2500,18 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // be empty or start with the same letter. if (aff_entry->ae_chop != NULL && aff_entry->ae_add != NULL - && aff_entry->ae_chop[utfc_ptr2len((char *)aff_entry->ae_chop)] == + && aff_entry->ae_chop[utfc_ptr2len(aff_entry->ae_chop)] == NUL) { int c, c_up; - c = utf_ptr2char((char *)aff_entry->ae_chop); + c = utf_ptr2char(aff_entry->ae_chop); c_up = SPELL_TOUPPER(c); if (c_up != c && (aff_entry->ae_cond == NULL - || utf_ptr2char((char *)aff_entry->ae_cond) == c)) { - p = aff_entry->ae_add - + STRLEN(aff_entry->ae_add); + || utf_ptr2char(aff_entry->ae_cond) == c)) { + p = aff_entry->ae_add + strlen(aff_entry->ae_add); MB_PTR_BACK(aff_entry->ae_add, p); - if (utf_ptr2char((char *)p) == c_up) { + if (utf_ptr2char(p) == c_up) { upper = true; aff_entry->ae_chop = NULL; *p = NUL; @@ -2516,14 +2520,13 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // actual word, thus must check for the // upper-case letter. if (aff_entry->ae_cond != NULL) { - char_u buf[MAXLINELEN]; + char buf[MAXLINELEN]; onecap_copy(items[4], buf, true); aff_entry->ae_cond = getroom_save(spin, buf); if (aff_entry->ae_cond != NULL) { - sprintf((char *)buf, "^%s", - aff_entry->ae_cond); + sprintf(buf, "^%s", aff_entry->ae_cond); // NOLINT(runtime/printf) vim_regfree(aff_entry->ae_prog); - aff_entry->ae_prog = vim_regcomp((char *)buf, RE_MAGIC + RE_STRING); + aff_entry->ae_prog = vim_regcomp(buf, RE_MAGIC + RE_STRING); } } } @@ -2536,9 +2539,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) int n; // Find a previously used condition. - for (idx = spin->si_prefcond.ga_len - 1; idx >= 0; - --idx) { - p = ((char_u **)spin->si_prefcond.ga_data)[idx]; + for (idx = spin->si_prefcond.ga_len - 1; idx >= 0; idx--) { + p = ((char **)spin->si_prefcond.ga_data)[idx]; if (str_equal(p, aff_entry->ae_cond)) { break; } @@ -2548,12 +2550,12 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) idx = spin->si_prefcond.ga_len; pp = GA_APPEND_VIA_PTR(char_u *, &spin->si_prefcond); *pp = (aff_entry->ae_cond == NULL) ? - NULL : getroom_save(spin, aff_entry->ae_cond); + NULL : (char_u *)getroom_save(spin, aff_entry->ae_cond); } // Add the prefix to the prefix tree. if (aff_entry->ae_add == NULL) { - p = (char_u *)""; + p = ""; } else { p = aff_entry->ae_add; } @@ -2573,33 +2575,33 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) if (aff_entry->ae_compforbid) { n |= WFP_COMPFORBID; } - tree_add_word(spin, p, spin->si_prefroot, n, + tree_add_word(spin, (char_u *)p, spin->si_prefroot, n, idx, cur_aff->ah_newID); did_postpone_prefix = true; } // Didn't actually use ah_newID, backup si_newprefID. if (aff_todo == 0 && !did_postpone_prefix) { - --spin->si_newprefID; + spin->si_newprefID--; cur_aff->ah_newID = 0; } } } } else if (is_aff_rule(items, itemcnt, "FOL", 2) && fol == NULL) { - fol = vim_strsave(items[1]); + fol = xstrdup(items[1]); } else if (is_aff_rule(items, itemcnt, "LOW", 2) && low == NULL) { - low = vim_strsave(items[1]); + low = xstrdup(items[1]); } else if (is_aff_rule(items, itemcnt, "UPP", 2) && upp == NULL) { - upp = vim_strsave(items[1]); + upp = xstrdup(items[1]); } else if (is_aff_rule(items, itemcnt, "REP", 2) || is_aff_rule(items, itemcnt, "REPSAL", 2)) { // Ignore REP/REPSAL count - if (!isdigit(*items[1])) { + if (!isdigit((uint8_t)(*items[1]))) { smsg(_("Expected REP(SAL) count in %s line %d"), fname, lnum); } - } else if ((STRCMP(items[0], "REP") == 0 - || STRCMP(items[0], "REPSAL") == 0) + } else if ((strcmp(items[0], "REP") == 0 + || strcmp(items[0], "REPSAL") == 0) && itemcnt >= 3) { // REP/REPSAL item // Myspell ignores extra arguments, we require it starts with @@ -2621,15 +2623,15 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } } add_fromto(spin, items[0][3] == 'S' - ? &spin->si_repsal - : &spin->si_rep, items[1], items[2]); + ? &spin->si_repsal + : &spin->si_rep, items[1], items[2]); } } else if (is_aff_rule(items, itemcnt, "MAP", 2)) { // MAP item or count if (!found_map) { // First line contains the count. found_map = true; - if (!isdigit(*items[1])) { + if (!isdigit((uint8_t)(*items[1]))) { smsg(_("Expected MAP count in %s line %d"), fname, lnum); } @@ -2638,11 +2640,11 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) // Check that every character appears only once. for (p = items[1]; *p != NUL;) { - c = mb_ptr2char_adv((const char_u **)&p); + c = mb_ptr2char_adv((const char **)&p); if ((!GA_EMPTY(&spin->si_map) && vim_strchr(spin->si_map.ga_data, c) != NULL) - || vim_strchr((char *)p, c) != NULL) { + || vim_strchr(p, c) != NULL) { smsg(_("Duplicate character in MAP in %s line %d"), fname, lnum); } @@ -2659,17 +2661,17 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) if (do_sal) { // SAL item (sounds-a-like) // Either one of the known keys or a from-to pair. - if (STRCMP(items[1], "followup") == 0) { + if (strcmp(items[1], "followup") == 0) { spin->si_followup = sal_to_bool(items[2]); - } else if (STRCMP(items[1], "collapse_result") == 0) { + } else if (strcmp(items[1], "collapse_result") == 0) { spin->si_collapse = sal_to_bool(items[2]); - } else if (STRCMP(items[1], "remove_accents") == 0) { + } else if (strcmp(items[1], "remove_accents") == 0) { spin->si_rem_accents = sal_to_bool(items[2]); } else { // when "to" is "_" it means empty add_fromto(spin, &spin->si_sal, items[1], - STRCMP(items[2], "_") == 0 ? (char_u *)"" - : items[2]); + strcmp(items[2], "_") == 0 ? "" + : items[2]); } } } else if (is_aff_rule(items, itemcnt, "SOFOFROM", 2) @@ -2678,12 +2680,12 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } else if (is_aff_rule(items, itemcnt, "SOFOTO", 2) && sofoto == NULL) { sofoto = getroom_save(spin, items[1]); - } else if (STRCMP(items[0], "COMMON") == 0) { + } else if (strcmp(items[0], "COMMON") == 0) { int i; for (i = 1; i < itemcnt; i++) { if (HASHITEM_EMPTY(hash_find(&spin->si_commonwords, (char *)items[i]))) { - p = vim_strsave(items[i]); + p = xstrdup(items[i]); hash_add(&spin->si_commonwords, p); } } @@ -2775,11 +2777,11 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) return aff; } -// Returns true when items[0] equals "rulename", there are "mincount" items or -// a comment is following after item "mincount". -static bool is_aff_rule(char_u **items, int itemcnt, char *rulename, int mincount) +/// @return true when items[0] equals "rulename", there are "mincount" items or +/// a comment is following after item "mincount". +static bool is_aff_rule(char **items, int itemcnt, char *rulename, int mincount) { - return STRCMP(items[0], rulename) == 0 + return strcmp(items[0], rulename) == 0 && (itemcnt == mincount || (itemcnt > mincount && items[mincount][0] == '#')); } @@ -2788,18 +2790,18 @@ static bool is_aff_rule(char_u **items, int itemcnt, char *rulename, int mincoun // ae_flags to ae_comppermit and ae_compforbid. static void aff_process_flags(afffile_T *affile, affentry_T *entry) { - char_u *p; + char *p; char_u *prevp; unsigned flag; if (entry->ae_flags != NULL && (affile->af_compforbid != 0 || affile->af_comppermit != 0)) { for (p = entry->ae_flags; *p != NUL;) { - prevp = p; + prevp = (char_u *)p; flag = get_affitem(affile->af_flagtype, &p); if (flag == affile->af_comppermit || flag == affile->af_compforbid) { - STRMOVE(prevp, p); - p = prevp; + STRMOVE(prevp, (char *)p); + p = (char *)prevp; if (flag == affile->af_comppermit) { entry->ae_comppermit = true; } else { @@ -2816,23 +2818,23 @@ static void aff_process_flags(afffile_T *affile, affentry_T *entry) } } -// Returns true if "s" is the name of an info item in the affix file. -static bool spell_info_item(char_u *s) +/// @return true if "s" is the name of an info item in the affix file. +static bool spell_info_item(char *s) { - return STRCMP(s, "NAME") == 0 - || STRCMP(s, "HOME") == 0 - || STRCMP(s, "VERSION") == 0 - || STRCMP(s, "AUTHOR") == 0 - || STRCMP(s, "EMAIL") == 0 - || STRCMP(s, "COPYRIGHT") == 0; + return strcmp(s, "NAME") == 0 + || strcmp(s, "HOME") == 0 + || strcmp(s, "VERSION") == 0 + || strcmp(s, "AUTHOR") == 0 + || strcmp(s, "EMAIL") == 0 + || strcmp(s, "COPYRIGHT") == 0; } // Turn an affix flag name into a number, according to the FLAG type. // returns zero for failure. -static unsigned affitem2flag(int flagtype, char_u *item, char_u *fname, int lnum) +static unsigned affitem2flag(int flagtype, char *item, char *fname, int lnum) { unsigned res; - char_u *p = item; + char *p = item; res = get_affitem(flagtype, &p); if (res == 0) { @@ -2855,54 +2857,54 @@ static unsigned affitem2flag(int flagtype, char_u *item, char_u *fname, int lnum // Get one affix name from "*pp" and advance the pointer. // Returns ZERO_FLAG for "0". // Returns zero for an error, still advances the pointer then. -static unsigned get_affitem(int flagtype, char_u **pp) +static unsigned get_affitem(int flagtype, char **pp) { int res; if (flagtype == AFT_NUM) { if (!ascii_isdigit(**pp)) { - ++*pp; // always advance, avoid getting stuck + (*pp)++; // always advance, avoid getting stuck return 0; } - res = getdigits_int((char **)pp, true, 0); + res = getdigits_int(pp, true, 0); if (res == 0) { res = ZERO_FLAG; } } else { - res = mb_ptr2char_adv((const char_u **)pp); + res = mb_ptr2char_adv((const char **)pp); if (flagtype == AFT_LONG || (flagtype == AFT_CAPLONG && res >= 'A' && res <= 'Z')) { if (**pp == NUL) { return 0; } - res = mb_ptr2char_adv((const char_u **)pp) + (res << 16); + res = mb_ptr2char_adv((const char **)pp) + (res << 16); } } return (unsigned)res; } -// Process the "compflags" string used in an affix file and append it to -// spin->si_compflags. -// The processing involves changing the affix names to ID numbers, so that -// they fit in one byte. -static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compflags) +/// Process the "compflags" string used in an affix file and append it to +/// spin->si_compflags. +/// The processing involves changing the affix names to ID numbers, so that +/// they fit in one byte. +static void process_compflags(spellinfo_T *spin, afffile_T *aff, char *compflags) { - char_u *p; - char_u *prevp; + char *p; + char *prevp; unsigned flag; compitem_T *ci; int id; int len; char_u *tp; - char_u key[AH_KEY_LEN]; + char key[AH_KEY_LEN]; hashitem_T *hi; // Make room for the old and the new compflags, concatenated with a / in // between. Processing it makes it shorter, but we don't know by how // much, thus allocate the maximum. - len = (int)STRLEN(compflags) + 1; + len = (int)strlen(compflags) + 1; if (spin->si_compflags != NULL) { - len += (int)STRLEN(spin->si_compflags) + 1; + len += (int)strlen(spin->si_compflags) + 1; } p = getroom(spin, (size_t)len, false); if (spin->si_compflags != NULL) { @@ -2910,12 +2912,12 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compfla STRCAT(p, "/"); } spin->si_compflags = p; - tp = p + STRLEN(p); + tp = (char_u *)p + strlen(p); for (p = compflags; *p != NUL;) { - if (vim_strchr("/?*+[]", *p) != NULL) { + if (vim_strchr("/?*+[]", (uint8_t)(*p)) != NULL) { // Copy non-flag characters directly. - *tp++ = *p++; + *tp++ = (char_u)(*p++); } else { // First get the flag number, also checks validity. prevp = p; @@ -2923,7 +2925,7 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compfla if (flag != 0) { // Find the flag in the hashtable. If it was used before, use // the existing ID. Otherwise add a new entry. - STRLCPY(key, prevp, p - prevp + 1); + xstrlcpy(key, prevp, (size_t)(p - prevp) + 1); hi = hash_find(&aff->af_comp, (char *)key); if (!HASHITEM_EMPTY(hi)) { id = HI2CI(hi)->ci_newID; @@ -2938,7 +2940,7 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char_u *compfla id = spin->si_newcompID--; } while (vim_strchr("/?*+[]\\-^", id) != NULL); ci->ci_newID = id; - hash_add(&aff->af_comp, ci->ci_key); + hash_add(&aff->af_comp, (char *)ci->ci_key); } *tp++ = (char_u)id; } @@ -2964,22 +2966,22 @@ static void check_renumber(spellinfo_T *spin) } // Returns true if flag "flag" appears in affix list "afflist". -static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag) +static bool flag_in_afflist(int flagtype, char *afflist, unsigned flag) { char *p; unsigned n; switch (flagtype) { case AFT_CHAR: - return vim_strchr((char *)afflist, (int)flag) != NULL; + return vim_strchr(afflist, (int)flag) != NULL; case AFT_CAPLONG: case AFT_LONG: - for (p = (char *)afflist; *p != NUL;) { - n = (unsigned)mb_ptr2char_adv((const char_u **)&p); + for (p = afflist; *p != NUL;) { + n = (unsigned)mb_ptr2char_adv((const char **)&p); if ((flagtype == AFT_LONG || (n >= 'A' && n <= 'Z')) && *p != NUL) { - n = (unsigned)mb_ptr2char_adv((const char_u **)&p) + (n << 16); + n = (unsigned)mb_ptr2char_adv((const char **)&p) + (n << 16); } if (n == flag) { return true; @@ -2988,7 +2990,7 @@ static bool flag_in_afflist(int flagtype, char_u *afflist, unsigned flag) break; case AFT_NUM: - for (p = (char *)afflist; *p != NUL;) { + for (p = afflist; *p != NUL;) { int digits = getdigits_int(&p, true, 0); assert(digits >= 0); n = (unsigned int)digits; @@ -3016,42 +3018,42 @@ static void aff_check_number(int spinval, int affval, char *name) } } -// Give a warning when "spinval" and "affval" strings are set and not the same. -static void aff_check_string(char_u *spinval, char_u *affval, char *name) +/// Give a warning when "spinval" and "affval" strings are set and not the same. +static void aff_check_string(char *spinval, char *affval, char *name) { - if (spinval != NULL && STRCMP(spinval, affval) != 0) { + if (spinval != NULL && strcmp(spinval, affval) != 0) { smsg(_("%s value differs from what is used in another .aff file"), name); } } -// Returns true if strings "s1" and "s2" are equal. Also consider both being -// NULL as equal. -static bool str_equal(char_u *s1, char_u *s2) +/// @return true if strings "s1" and "s2" are equal. Also consider both being +/// NULL as equal. +static bool str_equal(char *s1, char *s2) { if (s1 == NULL || s2 == NULL) { return s1 == s2; } - return STRCMP(s1, s2) == 0; + return strcmp(s1, s2) == 0; } -// Add a from-to item to "gap". Used for REP and SAL items. -// They are stored case-folded. -static void add_fromto(spellinfo_T *spin, garray_T *gap, char_u *from, char_u *to) +/// Add a from-to item to "gap". Used for REP and SAL items. +/// They are stored case-folded. +static void add_fromto(spellinfo_T *spin, garray_T *gap, char *from, char *to) { - char_u word[MAXWLEN]; + char word[MAXWLEN]; fromto_T *ftp = GA_APPEND_VIA_PTR(fromto_T, gap); - (void)spell_casefold(curwin, from, (int)STRLEN(from), word, MAXWLEN); + (void)spell_casefold(curwin, from, (int)strlen(from), word, MAXWLEN); ftp->ft_from = getroom_save(spin, word); - (void)spell_casefold(curwin, to, (int)STRLEN(to), word, MAXWLEN); + (void)spell_casefold(curwin, to, (int)strlen(to), word, MAXWLEN); ftp->ft_to = getroom_save(spin, word); } -// Converts a boolean argument in a SAL line to true or false; -static bool sal_to_bool(char_u *s) +/// Converts a boolean argument in a SAL line to true or false; +static bool sal_to_bool(char *s) { - return STRCMP(s, "1") == 0 || STRCMP(s, "true") == 0; + return strcmp(s, "1") == 0 || strcmp(s, "true") == 0; } // Free the structure filled by spell_read_aff(). @@ -3068,7 +3070,7 @@ static void spell_free_aff(afffile_T *aff) // All this trouble to free the "ae_prog" items... for (ht = &aff->af_pref;; ht = &aff->af_suff) { todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { + for (hi = ht->ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { todo--; ah = HI2AH(hi); @@ -3089,16 +3091,16 @@ static void spell_free_aff(afffile_T *aff) // Read dictionary file "fname". // Returns OK or FAIL; -static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) +static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile) { hashtab_T ht; - char_u line[MAXLINELEN]; + char line[MAXLINELEN]; char_u *p; char_u *afflist; char_u store_afflist[MAXWLEN]; int pfxlen; bool need_affix; - char_u *dw; + char *dw; char_u *pc; char_u *w; int l; @@ -3114,7 +3116,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) Timestamp last_msg_time = 0; // Open the file. - fd = os_fopen((char *)fname, "r"); + fd = os_fopen(fname, "r"); if (fd == NULL) { semsg(_(e_notopen), fname); return FAIL; @@ -3123,7 +3125,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) // The hashtable is only used to detect duplicated words. hash_init(&ht); - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Reading dictionary file %s..."), fname); spell_message(spin, IObuff); @@ -3131,14 +3133,14 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) spin->si_msg_count = 999999; // Read and ignore the first line: word count. - if (vim_fgets(line, MAXLINELEN, fd) || !ascii_isdigit(*skipwhite((char *)line))) { + if (vim_fgets((char *)line, MAXLINELEN, fd) || !ascii_isdigit(*skipwhite((char *)line))) { semsg(_("E760: No word count in %s"), fname); } // Read all the lines in the file one by one. // The words are converted to 'encoding' here, before being added to // the hashtable. - while (!vim_fgets(line, MAXLINELEN, fd) && !got_int) { + while (!vim_fgets((char *)line, MAXLINELEN, fd) && !got_int) { line_breakcheck(); lnum++; if (line[0] == '#' || line[0] == '/') { @@ -3146,7 +3148,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) } // Remove CR, LF and white space from the end. White space halfway through // the word is kept to allow multi-word terms like "et al.". - l = (int)STRLEN(line); + l = (int)strlen(line); while (l > 0 && line[l - 1] <= ' ') { l--; } @@ -3157,7 +3159,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) // Convert from "SET" to 'encoding' when needed. if (spin->si_conv.vc_type != CONV_NONE) { - pc = string_convert(&spin->si_conv, line, NULL); + pc = (char_u *)string_convert(&spin->si_conv, (char *)line, NULL); if (pc == NULL) { smsg(_("Conversion failure for word in %s line %d: %s"), fname, lnum, line); @@ -3166,7 +3168,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) w = pc; } else { pc = NULL; - w = line; + w = (char_u *)line; } // Truncate the word at the "/", set "afflist" to what follows. @@ -3174,7 +3176,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) afflist = NULL; for (p = w; *p != NUL; MB_PTR_ADV(p)) { if (*p == '\\' && (p[1] == '\\' || p[1] == '/')) { - STRMOVE(p, p + 1); + STRMOVE(p, (char *)p + 1); } else if (*p == '/') { *p = NUL; afflist = p + 1; @@ -3183,7 +3185,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) } // Skip non-ASCII words when "spin->si_ascii" is true. - if (spin->si_ascii && has_non_ascii(w)) { + if (spin->si_ascii && has_non_ascii((char *)w)) { non_ascii++; xfree(pc); continue; @@ -3199,7 +3201,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) _("line %6d, word %6ld - %s"), lnum, spin->si_foldwcount + spin->si_keepwcount, w); msg_start(); - msg_outtrans_long_attr(message, 0); + msg_outtrans_long_attr((char *)message, 0); msg_clr_eos(); msg_didout = false; msg_col = 0; @@ -3208,7 +3210,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) } // Store the word in the hashtable to be able to find duplicates. - dw = getroom_save(spin, w); + dw = getroom_save(spin, (char *)w); if (dw == NULL) { retval = FAIL; xfree(pc); @@ -3216,7 +3218,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) } hash = hash_hash(dw); - hi = hash_lookup(&ht, (const char *)dw, STRLEN(dw), hash); + hi = hash_lookup(&ht, (const char *)dw, strlen(dw), hash); if (!HASHITEM_EMPTY(hi)) { if (p_verbose > 0) { smsg(_("Duplicate word in %s line %d: %s"), @@ -3236,45 +3238,45 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) need_affix = false; if (afflist != NULL) { // Extract flags from the affix list. - flags |= get_affix_flags(affile, afflist); + flags |= get_affix_flags(affile, (char *)afflist); if (affile->af_needaffix != 0 - && flag_in_afflist(affile->af_flagtype, afflist, + && flag_in_afflist(affile->af_flagtype, (char *)afflist, affile->af_needaffix)) { need_affix = true; } if (affile->af_pfxpostpone) { // Need to store the list of prefix IDs with the word. - pfxlen = get_pfxlist(affile, afflist, store_afflist); + pfxlen = get_pfxlist(affile, (char *)afflist, store_afflist); } if (spin->si_compflags != NULL) { // Need to store the list of compound flags with the word. // Concatenate them to the list of prefix IDs. - get_compflags(affile, afflist, store_afflist + pfxlen); + get_compflags(affile, (char *)afflist, store_afflist + pfxlen); } } // Add the word to the word tree(s). if (store_word(spin, dw, flags, spin->si_region, - store_afflist, need_affix) == FAIL) { + (char *)store_afflist, need_affix) == FAIL) { retval = FAIL; } if (afflist != NULL) { // Find all matching suffixes and add the resulting words. // Additionally do matching prefixes that combine. - if (store_aff_word(spin, dw, afflist, affile, + if (store_aff_word(spin, dw, (char *)afflist, affile, &affile->af_suff, &affile->af_pref, - CONDIT_SUF, flags, store_afflist, pfxlen) == FAIL) { + CONDIT_SUF, flags, (char *)store_afflist, pfxlen) == FAIL) { retval = FAIL; } // Find all matching prefixes and add the resulting words. - if (store_aff_word(spin, dw, afflist, affile, + if (store_aff_word(spin, dw, (char *)afflist, affile, &affile->af_pref, NULL, - CONDIT_SUF, flags, store_afflist, pfxlen) == FAIL) { + CONDIT_SUF, flags, (char *)store_afflist, pfxlen) == FAIL) { retval = FAIL; } } @@ -3297,7 +3299,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) // Check for affix flags in "afflist" that are turned into word flags. // Return WF_ flags. -static int get_affix_flags(afffile_T *affile, char_u *afflist) +static int get_affix_flags(afffile_T *affile, char *afflist) { int flags = 0; @@ -3336,13 +3338,13 @@ static int get_affix_flags(afffile_T *affile, char_u *afflist) // Used for PFXPOSTPONE. // Put the resulting flags in "store_afflist[MAXWLEN]" with a terminating NUL // and return the number of affixes. -static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist) +static int get_pfxlist(afffile_T *affile, char *afflist, char_u *store_afflist) { - char_u *p; - char_u *prevp; + char *p; + char *prevp; int cnt = 0; int id; - char_u key[AH_KEY_LEN]; + char key[AH_KEY_LEN]; hashitem_T *hi; for (p = afflist; *p != NUL;) { @@ -3350,7 +3352,7 @@ static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist if (get_affitem(affile->af_flagtype, &p) != 0) { // A flag is a postponed prefix flag if it appears in "af_pref" // and its ID is not zero. - STRLCPY(key, prevp, p - prevp + 1); + xstrlcpy(key, prevp, (size_t)(p - prevp) + 1); hi = hash_find(&affile->af_pref, (char *)key); if (!HASHITEM_EMPTY(hi)) { id = HI2AH(hi)->ah_newID; @@ -3371,19 +3373,19 @@ static int get_pfxlist(afffile_T *affile, char_u *afflist, char_u *store_afflist // Get the list of compound IDs from the affix list "afflist" that are used // for compound words. // Puts the flags in "store_afflist[]". -static void get_compflags(afffile_T *affile, char_u *afflist, char_u *store_afflist) +static void get_compflags(afffile_T *affile, char *afflist, char_u *store_afflist) { - char_u *p; - char_u *prevp; + char *p; + char *prevp; int cnt = 0; - char_u key[AH_KEY_LEN]; + char key[AH_KEY_LEN]; hashitem_T *hi; for (p = afflist; *p != NUL;) { prevp = p; if (get_affitem(affile->af_flagtype, &p) != 0) { // A flag is a compound flag if it appears in "af_comp". - STRLCPY(key, prevp, p - prevp + 1); + xstrlcpy(key, prevp, (size_t)(p - prevp) + 1); hi = hash_find(&affile->af_comp, (char *)key); if (!HASHITEM_EMPTY(hi)) { store_afflist[cnt++] = (char_u)HI2CI(hi)->ci_newID; @@ -3412,29 +3414,29 @@ static void get_compflags(afffile_T *affile, char_u *afflist, char_u *store_affl /// @param pfxlen nr of flags in "pfxlist" for prefixes, rest is compound flags /// /// @return FAIL when out of memory. -static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afffile_T *affile, - hashtab_T *ht, hashtab_T *xht, int condit, int flags, char_u *pfxlist, +static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_T *affile, + hashtab_T *ht, hashtab_T *xht, int condit, int flags, char *pfxlist, int pfxlen) { int todo; hashitem_T *hi; affheader_T *ah; affentry_T *ae; - char_u newword[MAXWLEN]; + char newword[MAXWLEN]; int retval = OK; int i, j; - char_u *p; + char *p; int use_flags; - char_u *use_pfxlist; + char *use_pfxlist; int use_pfxlen; bool need_affix; char_u store_afflist[MAXWLEN]; - char_u pfx_pfxlist[MAXWLEN]; - size_t wordlen = STRLEN(word); + char pfx_pfxlist[MAXWLEN]; + size_t wordlen = strlen(word); int use_condit; todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0 && retval == OK; ++hi) { + for (hi = ht->ht_array; todo > 0 && retval == OK; hi++) { if (!HASHITEM_EMPTY(hi)) { todo--; ah = HI2AH(hi); @@ -3460,7 +3462,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff || ae->ae_chop != NULL || ae->ae_flags != NULL) && (ae->ae_chop == NULL - || STRLEN(ae->ae_chop) < wordlen) + || strlen(ae->ae_chop) < wordlen) && (ae->ae_prog == NULL || vim_regexec_prog(&ae->ae_prog, false, word, (colnr_T)0)) && (((condit & CONDIT_CFIX) == 0) @@ -3474,7 +3476,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff if (ae->ae_add == NULL) { *newword = NUL; } else { - STRLCPY(newword, ae->ae_add, MAXWLEN); + xstrlcpy(newword, ae->ae_add, MAXWLEN); } p = word; if (ae->ae_chop != NULL) { @@ -3487,10 +3489,10 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff STRCAT(newword, p); } else { // suffix: chop/add at the end of the word - STRLCPY(newword, word, MAXWLEN); + xstrlcpy(newword, word, MAXWLEN); if (ae->ae_chop != NULL) { // Remove chop string. - p = newword + STRLEN(newword); + p = newword + strlen(newword); i = mb_charlen(ae->ae_chop); for (; i > 0; i--) { MB_PTR_BACK(newword, p); @@ -3511,16 +3513,18 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff // Extract flags from the affix list. use_flags |= get_affix_flags(affile, ae->ae_flags); - if (affile->af_needaffix != 0 && flag_in_afflist(affile->af_flagtype, ae->ae_flags, - affile->af_needaffix)) { + if (affile->af_needaffix != 0 + && flag_in_afflist(affile->af_flagtype, ae->ae_flags, + affile->af_needaffix)) { need_affix = true; } // When there is a CIRCUMFIX flag the other affix // must also have it and we don't add the word // with one affix. - if (affile->af_circumfix != 0 && flag_in_afflist(affile->af_flagtype, ae->ae_flags, - affile->af_circumfix)) { + if (affile->af_circumfix != 0 + && flag_in_afflist(affile->af_flagtype, ae->ae_flags, + affile->af_circumfix)) { use_condit |= CONDIT_CFIX; if ((condit & CONDIT_CFIX) == 0) { need_affix = true; @@ -3531,17 +3535,16 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff || spin->si_compflags != NULL) { if (affile->af_pfxpostpone) { // Get prefix IDS from the affix list. - use_pfxlen = get_pfxlist(affile, - ae->ae_flags, store_afflist); + use_pfxlen = get_pfxlist(affile, ae->ae_flags, store_afflist); } else { use_pfxlen = 0; } - use_pfxlist = store_afflist; + use_pfxlist = (char *)store_afflist; // Combine the prefix IDs. Avoid adding the // same ID twice. - for (i = 0; i < pfxlen; ++i) { - for (j = 0; j < use_pfxlen; ++j) { + for (i = 0; i < pfxlen; i++) { + for (j = 0; j < use_pfxlen; j++) { if (pfxlist[i] == use_pfxlist[j]) { break; } @@ -3554,7 +3557,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff if (spin->si_compflags != NULL) { // Get compound IDS from the affix list. get_compflags(affile, ae->ae_flags, - use_pfxlist + use_pfxlen); + (char_u *)use_pfxlist + use_pfxlen); } else { use_pfxlist[use_pfxlen] = NUL; } @@ -3562,9 +3565,8 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff // Combine the list of compound flags. // Concatenate them to the prefix IDs list. // Avoid adding the same ID twice. - for (i = pfxlen; pfxlist[i] != NUL; ++i) { - for (j = use_pfxlen; - use_pfxlist[j] != NUL; ++j) { + for (i = pfxlen; pfxlist[i] != NUL; i++) { + for (j = use_pfxlen; use_pfxlist[j] != NUL; j++) { if (pfxlist[i] == use_pfxlist[j]) { break; } @@ -3580,7 +3582,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff // Obey a "COMPOUNDFORBIDFLAG" of the affix: don't // use the compound flags. if (use_pfxlist != NULL && ae->ae_compforbid) { - STRLCPY(pfx_pfxlist, use_pfxlist, use_pfxlen + 1); + xstrlcpy(pfx_pfxlist, use_pfxlist, (size_t)use_pfxlen + 1); use_pfxlist = pfx_pfxlist; } @@ -3622,7 +3624,7 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff if (store_aff_word(spin, newword, ae->ae_flags, affile, &affile->af_suff, xht, use_condit & (xht == NULL - ? ~0 : ~CONDIT_SUF), + ? ~0 : ~CONDIT_SUF), use_flags, use_pfxlist, pfxlen) == FAIL) { retval = FAIL; } @@ -3656,12 +3658,12 @@ static int store_aff_word(spellinfo_T *spin, char_u *word, char_u *afflist, afff } // Read a file with a list of words. -static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) +static int spell_read_wordfile(spellinfo_T *spin, char *fname) { FILE *fd; long lnum = 0; - char_u rline[MAXLINELEN]; - char_u *line; + char rline[MAXLINELEN]; + char *line; char_u *pc = NULL; char_u *p; int l; @@ -3672,13 +3674,13 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) int regionmask; // Open the file. - fd = os_fopen((char *)fname, "r"); + fd = os_fopen(fname, "r"); if (fd == NULL) { semsg(_(e_notopen), fname); return FAIL; } - vim_snprintf((char *)IObuff, IOSIZE, _("Reading word file %s..."), fname); + vim_snprintf(IObuff, IOSIZE, _("Reading word file %s..."), fname); spell_message(spin, IObuff); // Read all the lines in the file one by one. @@ -3692,8 +3694,8 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) } // Remove CR, LF and white space from the end. - l = (int)STRLEN(rline); - while (l > 0 && rline[l - 1] <= ' ') { + l = (int)strlen(rline); + while (l > 0 && (uint8_t)rline[l - 1] <= ' ') { l--; } if (l == 0) { @@ -3704,13 +3706,13 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) // Convert from "/encoding={encoding}" to 'encoding' when needed. xfree(pc); if (spin->si_conv.vc_type != CONV_NONE) { - pc = string_convert(&spin->si_conv, rline, NULL); + pc = (char_u *)string_convert(&spin->si_conv, rline, NULL); if (pc == NULL) { smsg(_("Conversion failure for word in %s line %ld: %s"), fname, lnum, rline); continue; } - line = pc; + line = (char *)pc; } else { pc = NULL; line = rline; @@ -3718,7 +3720,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) if (*line == '/') { line++; - if (STRNCMP(line, "encoding=", 9) == 0) { + if (strncmp(line, "encoding=", 9) == 0) { if (spin->si_conv.vc_type != CONV_NONE) { smsg(_("Duplicate /encoding= line ignored in %s line %ld: %s"), fname, lnum, line - 1); @@ -3726,14 +3728,13 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) smsg(_("/encoding= line after word ignored in %s line %ld: %s"), fname, lnum, line - 1); } else { - char_u *enc; + char *enc; // Setup for conversion to 'encoding'. line += 9; enc = enc_canonize(line); if (!spin->si_ascii - && convert_setup(&spin->si_conv, enc, - p_enc) == FAIL) { + && convert_setup(&spin->si_conv, enc, p_enc) == FAIL) { smsg(_("Conversion in %s not supported: from %s to %s"), fname, line, p_enc); } @@ -3743,17 +3744,17 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) continue; } - if (STRNCMP(line, "regions=", 8) == 0) { + if (strncmp(line, "regions=", 8) == 0) { if (spin->si_region_count > 1) { smsg(_("Duplicate /regions= line ignored in %s line %ld: %s"), fname, lnum, line); } else { line += 8; - if (STRLEN(line) > MAXREGIONS * 2) { + if (strlen(line) > MAXREGIONS * 2) { smsg(_("Too many regions in %s line %ld: %s"), fname, lnum, line); } else { - spin->si_region_count = (int)STRLEN(line) / 2; + spin->si_region_count = (int)strlen(line) / 2; STRCPY(spin->si_region_name, line); // Adjust the mask for a word valid in all regions. @@ -3772,7 +3773,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) regionmask = spin->si_region; // Check for flags and region after a slash. - p = (char_u *)vim_strchr((char *)line, '/'); + p = (char_u *)vim_strchr(line, '/'); if (p != NULL) { *p++ = NUL; while (*p != NUL) { @@ -3822,7 +3823,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) fclose(fd); if (spin->si_ascii && non_ascii > 0) { - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Ignored %d words with non-ASCII characters"), non_ascii); spell_message(spin, IObuff); } @@ -3858,7 +3859,7 @@ static void *getroom(spellinfo_T *spin, size_t len, bool align) bl->sb_next = spin->si_blocks; spin->si_blocks = bl; bl->sb_used = 0; - ++spin->si_blocks_cnt; + spin->si_blocks_cnt++; } p = bl->sb_data + bl->sb_used; @@ -3867,11 +3868,12 @@ static void *getroom(spellinfo_T *spin, size_t len, bool align) return p; } -// Make a copy of a string into memory allocated with getroom(). -// Returns NULL when out of memory. -static char_u *getroom_save(spellinfo_T *spin, char_u *s) +/// Make a copy of a string into memory allocated with getroom(). +/// +/// @return NULL when out of memory. +static char *getroom_save(spellinfo_T *spin, char *s) { - const size_t s_size = STRLEN(s) + 1; + const size_t s_size = strlen(s) + 1; return memcpy(getroom(spin, s_size, false), s, s_size); } @@ -3897,13 +3899,13 @@ static wordnode_T *wordtree_alloc(spellinfo_T *spin) /// Return true if "word" contains valid word characters. /// Control characters and trailing '/' are invalid. Space is OK. -static bool valid_spell_word(const char_u *word, const char_u *end) +static bool valid_spell_word(const char *word, const char *end) { - if (!utf_valid_string(word, end)) { + if (!utf_valid_string((char_u *)word, (char_u *)end)) { return false; } - for (const char_u *p = word; *p != NUL && p < end; p += utfc_ptr2len((const char *)p)) { - if (*p < ' ' || (p[0] == '/' && p[1] == NUL)) { + for (const char *p = word; *p != NUL && p < end; p += utfc_ptr2len(p)) { + if ((uint8_t)(*p) < ' ' || (p[0] == '/' && p[1] == NUL)) { return false; } } @@ -3922,10 +3924,10 @@ static bool valid_spell_word(const char_u *word, const char_u *end) /// @param region supported region(s) /// @param pfxlist list of prefix ids or null /// @param need_affix only store word with affix id -static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, const char_u *pfxlist, +static int store_word(spellinfo_T *spin, char *word, int flags, int region, const char *pfxlist, bool need_affix) { - int len = (int)STRLEN(word); + int len = (int)strlen(word); int ct = captype(word, word + len); char_u foldword[MAXWLEN]; int res = OK; @@ -3935,8 +3937,8 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co return FAIL; } - (void)spell_casefold(curwin, word, len, foldword, MAXWLEN); - for (const char_u *p = pfxlist; res == OK; p++) { + (void)spell_casefold(curwin, word, len, (char *)foldword, MAXWLEN); + for (const char_u *p = (char_u *)pfxlist; res == OK; p++) { if (!need_affix || (p != NULL && *p != NUL)) { res = tree_add_word(spin, foldword, spin->si_foldroot, ct | flags, region, p == NULL ? 0 : *p); @@ -3945,19 +3947,19 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co break; } } - ++spin->si_foldwcount; + spin->si_foldwcount++; if (res == OK && (ct == WF_KEEPCAP || (flags & WF_KEEPCAP))) { - for (const char_u *p = pfxlist; res == OK; p++) { + for (const char_u *p = (char_u *)pfxlist; res == OK; p++) { if (!need_affix || (p != NULL && *p != NUL)) { - res = tree_add_word(spin, word, spin->si_keeproot, flags, + res = tree_add_word(spin, (char_u *)word, spin->si_keeproot, flags, region, p == NULL ? 0 : *p); } if (p == NULL || *p == NUL) { break; } } - ++spin->si_keepwcount; + spin->si_keepwcount++; } return res; } @@ -3966,8 +3968,8 @@ static int store_word(spellinfo_T *spin, char_u *word, int flags, int region, co // When "flags" < 0 we are adding to the prefix tree where "flags" is used for // "rare" and "region" is the condition nr. // Returns FAIL when out of memory. -static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int flags, int region, - int affixID) +static int tree_add_word(spellinfo_T *spin, const char_u *word, wordnode_T *root, int flags, + int region, int affixID) { wordnode_T *node = root; wordnode_T *np; @@ -3976,12 +3978,12 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int int i; // Add each byte of the word to the tree, including the NUL at the end. - for (i = 0;; ++i) { + for (i = 0;; i++) { // When there is more than one reference to this node we need to make // a copy, so that we can modify it. Copy the whole list of siblings // (we don't optimize for a partly shared list of siblings). if (node != NULL && node->wn_refs > 1) { - --node->wn_refs; + node->wn_refs--; copyprev = prev; for (copyp = node; copyp != NULL; copyp = copyp->wn_sibling) { // Allocate a new node and copy the info. @@ -3991,7 +3993,7 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int } np->wn_child = copyp->wn_child; if (np->wn_child != NULL) { - ++np->wn_child->wn_refs; // child gets extra ref + np->wn_child->wn_refs++; // child gets extra ref } np->wn_byte = copyp->wn_byte; if (np->wn_byte == NUL) { @@ -4078,7 +4080,7 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int #endif // count nr of words added since last message - ++spin->si_msg_count; + spin->si_msg_count++; if (spin->si_compress_cnt > 1) { if (--spin->si_compress_cnt == 1) { @@ -4097,7 +4099,7 @@ static int tree_add_word(spellinfo_T *spin, char_u *word, wordnode_T *root, int // 3. When compressed before, added "compress_added" words // (si_compress_cnt == 1) and the number of free nodes drops below the // maximum word length. -#ifndef SPELL_COMPRESS_ALLWAYS +#ifndef SPELL_COMPRESS_ALWAYS if (spin->si_compress_cnt == 1 // NOLINT(readability/braces) ? spin->si_free_count < MAXWLEN : spin->si_blocks_cnt >= compress_start) @@ -4173,7 +4175,7 @@ static int deref_wordnode(spellinfo_T *spin, wordnode_T *node) free_wordnode(spin, np); cnt++; } - ++cnt; // length field + cnt++; // length field } return cnt; } @@ -4185,7 +4187,7 @@ static void free_wordnode(spellinfo_T *spin, wordnode_T *n) { n->wn_child = spin->si_first_free; spin->si_first_free = n; - ++spin->si_free_count; + spin->si_free_count++; } // Compress a tree: find tails that are identical and can be shared. @@ -4198,31 +4200,33 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n // Skip the root itself, it's not actually used. The first sibling is the // start of the tree. - if (root->wn_sibling != NULL) { - hash_init(&ht); - const long n = node_compress(spin, root->wn_sibling, &ht, &tot); + if (root->wn_sibling == NULL) { + return; + } + + hash_init(&ht); + const long n = node_compress(spin, root->wn_sibling, &ht, &tot); #ifndef SPELL_PRINTTREE - if (spin->si_verbose || p_verbose > 2) + if (spin->si_verbose || p_verbose > 2) #endif - { - if (tot > 1000000) { - perc = (tot - n) / (tot / 100); - } else if (tot == 0) { - perc = 0; - } else { - perc = (tot - n) * 100 / tot; - } - vim_snprintf((char *)IObuff, IOSIZE, - _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"), - name, tot, tot - n, perc); - spell_message(spin, IObuff); + { + if (tot > 1000000) { + perc = (tot - n) / (tot / 100); + } else if (tot == 0) { + perc = 0; + } else { + perc = (tot - n) * 100 / tot; } + vim_snprintf(IObuff, IOSIZE, + _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"), + name, tot, tot - n, perc); + spell_message(spin, IObuff); + } #ifdef SPELL_PRINTTREE - spell_print_tree(root->wn_sibling); + spell_print_tree(root->wn_sibling); #endif - hash_clear(&ht); - } + hash_clear(&ht); } /// Compress a node, its siblings and its children, depth first. @@ -4252,9 +4256,9 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo compressed += node_compress(spin, child, ht, tot); // Try to find an identical child. - hash = hash_hash(child->wn_u1.hashkey); + hash = hash_hash((char *)child->wn_u1.hashkey); hi = hash_lookup(ht, (const char *)child->wn_u1.hashkey, - STRLEN(child->wn_u1.hashkey), hash); + strlen((char *)child->wn_u1.hashkey), hash); if (!HASHITEM_EMPTY(hi)) { // There are children we encountered before with a hash value // identical to the current child. Now check if there is one @@ -4264,7 +4268,7 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo // Found one! Now use that child in place of the // current one. This means the current child and all // its siblings is unlinked from the tree. - ++tp->wn_refs; + tp->wn_refs++; compressed += deref_wordnode(spin, child); np->wn_child = tp; break; @@ -4281,7 +4285,7 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo } else { // No other child has this hash value, add it to the // hashtable. - hash_add_item(ht, hi, child->wn_u1.hashkey, hash); + hash_add_item(ht, hi, (char *)child->wn_u1.hashkey, hash); } } } @@ -4341,23 +4345,24 @@ static bool node_equal(wordnode_T *n1, wordnode_T *n2) return p1 == NULL && p2 == NULL; } -// Function given to qsort() to sort the REP items on "from" string. +/// Function given to qsort() to sort the REP items on "from" string. static int rep_compare(const void *s1, const void *s2) { fromto_T *p1 = (fromto_T *)s1; fromto_T *p2 = (fromto_T *)s2; - return STRCMP(p1->ft_from, p2->ft_from); + return strcmp(p1->ft_from, p2->ft_from); } -// Write the Vim .spl file "fname". -// Return OK/FAIL. -static int write_vim_spell(spellinfo_T *spin, char_u *fname) +/// Write the Vim .spl file "fname". +/// +/// @return OK/FAIL. +static int write_vim_spell(spellinfo_T *spin, char *fname) { int retval = OK; int regionmask; - FILE *fd = os_fopen((char *)fname, "w"); + FILE *fd = os_fopen(fname, "w"); if (fd == NULL) { semsg(_(e_notopen), fname); return FAIL; @@ -4379,7 +4384,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) if (spin->si_info != NULL) { putc(SN_INFO, fd); // <sectionID> putc(0, fd); // <sectionflags> - size_t i = STRLEN(spin->si_info); + size_t i = strlen(spin->si_info); put_bytes(fd, i, 4); // <sectionlen> fwv &= fwrite(spin->si_info, i, 1, fd); // <infotext> } @@ -4422,7 +4427,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) put_bytes(fd, 1 + 128 + 2 + l, 4); // <sectionlen> fputc(128, fd); // <charflagslen> - for (size_t i = 128; i < 256; ++i) { + for (size_t i = 128; i < 256; i++) { flags = 0; if (spelltab.st_isw[i]) { flags |= CF_WORD; @@ -4442,7 +4447,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) putc(SN_MIDWORD, fd); // <sectionID> putc(SNF_REQUIRED, fd); // <sectionflags> - size_t i = STRLEN(spin->si_midword); + size_t i = strlen(spin->si_midword); put_bytes(fd, i, 4); // <sectionlen> fwv &= fwrite(spin->si_midword, i, 1, fd); // <midword> @@ -4466,7 +4471,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // round 1: SN_REP section // round 2: SN_SAL section (unless SN_SOFO is used) // round 3: SN_REPSAL section - for (unsigned int round = 1; round <= 3; ++round) { + for (unsigned int round = 1; round <= 3; round++) { garray_T *gap; if (round == 1) { gap = &spin->si_rep; @@ -4500,13 +4505,13 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // Compute the length of what follows. size_t l = 2; // count <repcount> or <salcount> assert(gap->ga_len >= 0); - for (size_t i = 0; i < (size_t)gap->ga_len; ++i) { + for (size_t i = 0; i < (size_t)gap->ga_len; i++) { fromto_T *ftp = &((fromto_T *)gap->ga_data)[i]; - l += 1 + STRLEN(ftp->ft_from); // count <*fromlen> and <*from> - l += 1 + STRLEN(ftp->ft_to); // count <*tolen> and <*to> + l += 1 + strlen(ftp->ft_from); // count <*fromlen> and <*from> + l += 1 + strlen(ftp->ft_to); // count <*tolen> and <*to> } if (round == 2) { - ++l; // count <salflags> + l++; // count <salflags> } put_bytes(fd, l, 4); // <sectionlen> @@ -4525,13 +4530,13 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) } put_bytes(fd, (uintmax_t)gap->ga_len, 2); // <repcount> or <salcount> - for (size_t i = 0; i < (size_t)gap->ga_len; ++i) { + for (size_t i = 0; i < (size_t)gap->ga_len; i++) { // <rep> : <repfromlen> <repfrom> <reptolen> <repto> // <sal> : <salfromlen> <salfrom> <saltolen> <salto> fromto_T *ftp = &((fromto_T *)gap->ga_data)[i]; - for (unsigned int rr = 1; rr <= 2; ++rr) { - char_u *p = rr == 1 ? ftp->ft_from : ftp->ft_to; - l = STRLEN(p); + for (unsigned int rr = 1; rr <= 2; rr++) { + char *p = rr == 1 ? ftp->ft_from : ftp->ft_to; + l = strlen(p); assert(l < INT_MAX); putc((int)l, fd); if (l > 0) { @@ -4547,13 +4552,13 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) putc(SN_SOFO, fd); // <sectionID> putc(0, fd); // <sectionflags> - size_t l = STRLEN(spin->si_sofofr); - put_bytes(fd, l + STRLEN(spin->si_sofoto) + 4, 4); // <sectionlen> + size_t l = strlen(spin->si_sofofr); + put_bytes(fd, l + strlen(spin->si_sofoto) + 4, 4); // <sectionlen> put_bytes(fd, l, 2); // <sofofromlen> fwv &= fwrite(spin->si_sofofr, l, 1, fd); // <sofofrom> - l = STRLEN(spin->si_sofoto); + l = strlen(spin->si_sofoto); put_bytes(fd, l, 2); // <sofotolen> fwv &= fwrite(spin->si_sofoto, l, 1, fd); // <sofoto> } @@ -4566,15 +4571,15 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // round 1: count the bytes // round 2: write the bytes - for (unsigned int round = 1; round <= 2; ++round) { + for (unsigned int round = 1; round <= 2; round++) { size_t todo; size_t len = 0; hashitem_T *hi; todo = spin->si_commonwords.ht_used; - for (hi = spin->si_commonwords.ht_array; todo > 0; ++hi) { + for (hi = spin->si_commonwords.ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { - size_t l = STRLEN(hi->hi_key) + 1; + size_t l = strlen(hi->hi_key) + 1; len += l; if (round == 2) { // <word> fwv &= fwrite(hi->hi_key, l, 1, fd); @@ -4640,10 +4645,10 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) putc(SN_COMPOUND, fd); // <sectionID> putc(0, fd); // <sectionflags> - size_t l = STRLEN(spin->si_compflags); + size_t l = strlen(spin->si_compflags); assert(spin->si_comppat.ga_len >= 0); for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; i++) { - l += STRLEN(((char **)(spin->si_comppat.ga_data))[i]) + 1; + l += strlen(((char **)(spin->si_comppat.ga_data))[i]) + 1; } put_bytes(fd, l + 7, 4); // <sectionlen> @@ -4655,12 +4660,12 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) put_bytes(fd, (uintmax_t)spin->si_comppat.ga_len, 2); // <comppatcount> for (size_t i = 0; i < (size_t)spin->si_comppat.ga_len; i++) { char *p = ((char **)(spin->si_comppat.ga_data))[i]; - assert(STRLEN(p) < INT_MAX); - putc((int)STRLEN(p), fd); // <comppatlen> - fwv &= fwrite(p, STRLEN(p), 1, fd); // <comppattext> + assert(strlen(p) < INT_MAX); + putc((int)strlen(p), fd); // <comppatlen> + fwv &= fwrite(p, strlen(p), 1, fd); // <comppattext> } // <compflags> - fwv &= fwrite(spin->si_compflags, STRLEN(spin->si_compflags), 1, fd); + fwv &= fwrite(spin->si_compflags, strlen(spin->si_compflags), 1, fd); } // SN_NOBREAK: NOBREAK flag @@ -4679,7 +4684,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) putc(SN_SYLLABLE, fd); // <sectionID> putc(0, fd); // <sectionflags> - size_t l = STRLEN(spin->si_syllable); + size_t l = strlen(spin->si_syllable); put_bytes(fd, l, 4); // <sectionlen> fwv &= fwrite(spin->si_syllable, l, 1, fd); // <syllable> } @@ -4689,7 +4694,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // <LWORDTREE> <KWORDTREE> <PREFIXTREE> spin->si_memtot = 0; - for (unsigned int round = 1; round <= 3; ++round) { + for (unsigned int round = 1; round <= 3; round++) { wordnode_T *tree; if (round == 1) { tree = spin->si_foldroot->wn_sibling; @@ -4878,27 +4883,29 @@ void ex_mkspell(exarg_T *eap) { int fcount; char **fnames; - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; bool ascii = false; - if (STRNCMP(arg, "-ascii", 6) == 0) { + if (strncmp(arg, "-ascii", 6) == 0) { ascii = true; - arg = (char_u *)skipwhite((char *)arg + 6); + arg = skipwhite(arg + 6); } // Expand all the remaining arguments (e.g., $VIMRUNTIME). - if (get_arglist_exp(arg, &fcount, &fnames, false) == OK) { - mkspell(fcount, fnames, ascii, eap->forceit, false); - FreeWild(fcount, fnames); + if (get_arglist_exp(arg, &fcount, &fnames, false) != OK) { + return; } + + mkspell(fcount, fnames, ascii, eap->forceit, false); + FreeWild(fcount, fnames); } // Create the .sug file. // Uses the soundfold info in "spin". // Writes the file with the name "wfname", with ".spl" changed to ".sug". -static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) +static void spell_make_sugfile(spellinfo_T *spin, char *wfname) { - char_u *fname = NULL; + char *fname = NULL; int len; slang_T *slang; bool free_slang = false; @@ -4909,13 +4916,13 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) // of the code for the soundfolding stuff. // It might have been done already by spell_reload_one(). for (slang = first_lang; slang != NULL; slang = slang->sl_next) { - if (path_full_compare((char *)wfname, (char *)slang->sl_fname, false, true) + if (path_full_compare(wfname, slang->sl_fname, false, true) == kEqualFiles) { break; } } if (slang == NULL) { - spell_message(spin, (char_u *)_("Reading back spell file...")); + spell_message(spin, _("Reading back spell file...")); slang = spell_load_file(wfname, NULL, NULL, false); if (slang == NULL) { return; @@ -4933,7 +4940,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) // Go through the trie of good words, soundfold each word and add it to // the soundfold trie. - spell_message(spin, (char_u *)_("Performing soundfolding...")); + spell_message(spin, _("Performing soundfolding...")); if (sug_filltree(spin, slang) == FAIL) { goto theend; } @@ -4950,14 +4957,14 @@ static void spell_make_sugfile(spellinfo_T *spin, char_u *wfname) (int64_t)spin->si_spellbuf->b_ml.ml_line_count); // Compress the soundfold trie. - spell_message(spin, (char_u *)_(msg_compressing)); + spell_message(spin, _(msg_compressing)); wordtree_compress(spin, spin->si_foldroot, "case-folded"); // Write the .sug file. // Make the file name by changing ".spl" to ".sug". fname = xmalloc(MAXPATHL); - STRLCPY(fname, wfname, MAXPATHL); - len = (int)STRLEN(fname); + xstrlcpy(fname, wfname, MAXPATHL); + len = (int)strlen(fname); fname[len - 2] = 'u'; fname[len - 1] = 'g'; sug_write(spin, fname); @@ -4994,7 +5001,7 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang) // Go through the whole case-folded tree, soundfold each word and put it // in the trie. - byts = slang->sl_fbyts; + byts = (char_u *)slang->sl_fbyts; idxs = slang->sl_fidxs; arridx[0] = 0; @@ -5015,13 +5022,13 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang) } else { // Do one more byte at this node. n = arridx[depth] + curi[depth]; - ++curi[depth]; + curi[depth]++; c = byts[n]; if (c == 0) { // Sound-fold the word. tword[depth] = NUL; - spell_soundfold(slang, tword, true, tsalword); + spell_soundfold(slang, (char *)tword, true, (char *)tsalword); // We use the "flags" field for the MSB of the wordnr, // "region" for the LSB of the wordnr. @@ -5185,16 +5192,16 @@ static int offset2bytes(int nr, char_u *buf) } // Write the .sug file in "fname". -static void sug_write(spellinfo_T *spin, char_u *fname) +static void sug_write(spellinfo_T *spin, char *fname) { // Create the file. Note that an existing file is silently overwritten! - FILE *fd = os_fopen((char *)fname, "w"); + FILE *fd = os_fopen(fname, "w"); if (fd == NULL) { semsg(_(e_notopen), fname); return; } - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Writing suggestion file %s..."), fname); spell_message(spin, IObuff); @@ -5233,10 +5240,10 @@ static void sug_write(spellinfo_T *spin, char_u *fname) assert(wcount >= 0); put_bytes(fd, (uintmax_t)wcount, 4); // <sugwcount> - for (linenr_T lnum = 1; lnum <= wcount; ++lnum) { + for (linenr_T lnum = 1; lnum <= wcount; lnum++) { // <sugline>: <sugnr> ... NUL - char_u *line = ml_get_buf(spin->si_spellbuf, lnum, false); - size_t len = STRLEN(line) + 1; + char *line = ml_get_buf(spin->si_spellbuf, lnum, false); + size_t len = strlen(line) + 1; if (fwrite(line, len, 1, fd) == 0) { emsg(_(e_write)); goto theend; @@ -5250,7 +5257,7 @@ static void sug_write(spellinfo_T *spin, char_u *fname) emsg(_(e_write)); } - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Estimated runtime memory use: %d bytes"), spin->si_memtot); spell_message(spin, IObuff); @@ -5270,8 +5277,7 @@ theend: /// @param added_word invoked through "zg" static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool added_word) { - char_u *fname = NULL; - char_u *wfname; + char *fname = NULL; char **innames; int incount; afffile_T *(afile[MAXREGIONS]); @@ -5288,9 +5294,9 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool ga_init(&spin.si_rep, (int)sizeof(fromto_T), 20); ga_init(&spin.si_repsal, (int)sizeof(fromto_T), 20); ga_init(&spin.si_sal, (int)sizeof(fromto_T), 20); - ga_init(&spin.si_map, (int)sizeof(char_u), 100); - ga_init(&spin.si_comppat, (int)sizeof(char_u *), 20); - ga_init(&spin.si_prefcond, (int)sizeof(char_u *), 50); + ga_init(&spin.si_map, (int)sizeof(char), 100); + ga_init(&spin.si_comppat, (int)sizeof(char *), 20); + ga_init(&spin.si_prefcond, (int)sizeof(char *), 50); hash_init(&spin.si_commonwords); spin.si_newcompID = 127; // start compound ID at first maximum @@ -5299,43 +5305,43 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool innames = &fnames[fcount == 1 ? 0 : 1]; incount = fcount - 1; - wfname = xmalloc(MAXPATHL); + char *wfname = xmalloc(MAXPATHL); if (fcount >= 1) { - len = (int)STRLEN(fnames[0]); - if (fcount == 1 && len > 4 && STRCMP(fnames[0] + len - 4, ".add") == 0) { + len = (int)strlen(fnames[0]); + if (fcount == 1 && len > 4 && strcmp(fnames[0] + len - 4, ".add") == 0) { // For ":mkspell path/en.latin1.add" output file is // "path/en.latin1.add.spl". incount = 1; - vim_snprintf((char *)wfname, MAXPATHL, "%s.spl", fnames[0]); + vim_snprintf(wfname, MAXPATHL, "%s.spl", fnames[0]); } else if (fcount == 1) { // For ":mkspell path/vim" output file is "path/vim.latin1.spl". incount = 1; - vim_snprintf((char *)wfname, MAXPATHL, SPL_FNAME_TMPL, - fnames[0], spin.si_ascii ? (char_u *)"ascii" : spell_enc()); - } else if (len > 4 && STRCMP(fnames[0] + len - 4, ".spl") == 0) { + vim_snprintf(wfname, MAXPATHL, SPL_FNAME_TMPL, + fnames[0], spin.si_ascii ? "ascii" : spell_enc()); + } else if (len > 4 && strcmp(fnames[0] + len - 4, ".spl") == 0) { // Name ends in ".spl", use as the file name. - STRLCPY(wfname, fnames[0], MAXPATHL); + xstrlcpy(wfname, fnames[0], MAXPATHL); } else { // Name should be language, make the file name from it. - vim_snprintf((char *)wfname, MAXPATHL, SPL_FNAME_TMPL, - fnames[0], spin.si_ascii ? (char_u *)"ascii" : spell_enc()); + vim_snprintf(wfname, MAXPATHL, SPL_FNAME_TMPL, + fnames[0], spin.si_ascii ? "ascii" : spell_enc()); } // Check for .ascii.spl. - if (strstr(path_tail((char *)wfname), SPL_FNAME_ASCII) != NULL) { + if (strstr(path_tail(wfname), SPL_FNAME_ASCII) != NULL) { spin.si_ascii = true; } // Check for .add.spl. - if (strstr(path_tail((char *)wfname), SPL_FNAME_ADD) != NULL) { + if (strstr(path_tail(wfname), SPL_FNAME_ADD) != NULL) { spin.si_add = true; } } if (incount <= 0) { emsg(_(e_invarg)); // need at least output and input names - } else if (vim_strchr(path_tail((char *)wfname), '_') != NULL) { + } else if (vim_strchr(path_tail(wfname), '_') != NULL) { emsg(_("E751: Output file name must not have region name")); } else if (incount > MAXREGIONS) { semsg(_("E754: Only up to %d regions supported"), MAXREGIONS); @@ -5355,12 +5361,12 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool // Init the aff and dic pointers. // Get the region names if there are more than 2 arguments. - for (i = 0; i < incount; ++i) { + for (i = 0; i < incount; i++) { afile[i] = NULL; if (incount > 1) { - len = (int)STRLEN(innames[i]); - if (STRLEN(path_tail((char *)innames[i])) < 5 + len = (int)strlen(innames[i]); + if (strlen(path_tail(innames[i])) < 5 || innames[i][len - 3] != '_') { semsg(_("E755: Invalid region in %s"), innames[i]); goto theend; @@ -5387,11 +5393,11 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool // Read all the .aff and .dic files. // Text is converted to 'encoding'. // Words are stored in the case-folded and keep-case trees. - for (i = 0; i < incount && !error; ++i) { + for (i = 0; i < incount && !error; i++) { spin.si_conv.vc_type = CONV_NONE; spin.si_region = 1 << i; - vim_snprintf((char *)fname, MAXPATHL, "%s.aff", innames[i]); + vim_snprintf(fname, MAXPATHL, "%s.aff", innames[i]); if (os_path_exists(fname)) { // Read the .aff file. Will init "spin->si_conv" based on the // "SET" line. @@ -5400,8 +5406,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool error = true; } else { // Read the .dic file and store the words in the trees. - vim_snprintf((char *)fname, MAXPATHL, "%s.dic", - innames[i]); + vim_snprintf(fname, MAXPATHL, "%s.dic", innames[i]); if (spell_read_dic(&spin, fname, afile[i]) == FAIL) { error = true; } @@ -5409,7 +5414,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool } else { // No .aff file, try reading the file as a word list. Store // the words in the trees. - if (spell_read_wordfile(&spin, (char_u *)innames[i]) == FAIL) { + if (spell_read_wordfile(&spin, innames[i]) == FAIL) { error = true; } } @@ -5424,7 +5429,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool if (!error && !got_int) { // Combine tails in the tree. - spell_message(&spin, (char_u *)_(msg_compressing)); + spell_message(&spin, _(msg_compressing)); wordtree_compress(&spin, spin.si_foldroot, "case-folded"); wordtree_compress(&spin, spin.si_keeproot, "keep-case"); wordtree_compress(&spin, spin.si_prefroot, "prefixes"); @@ -5432,14 +5437,14 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool if (!error && !got_int) { // Write the info in the spell file. - vim_snprintf((char *)IObuff, IOSIZE, + vim_snprintf(IObuff, IOSIZE, _("Writing spell file %s..."), wfname); spell_message(&spin, IObuff); error = write_vim_spell(&spin, wfname) == FAIL; - spell_message(&spin, (char_u *)_("Done!")); - vim_snprintf((char *)IObuff, IOSIZE, + spell_message(&spin, _("Done!")); + vim_snprintf(IObuff, IOSIZE, _("Estimated runtime memory use: %d bytes"), spin.si_memtot); spell_message(&spin, IObuff); @@ -5459,7 +5464,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool hash_clear_all(&spin.si_commonwords, 0); // Free the .aff file structures. - for (i = 0; i < incount; ++i) { + for (i = 0; i < incount; i++) { if (afile[i] != NULL) { spell_free_aff(afile[i]); } @@ -5482,14 +5487,14 @@ theend: // Display a message for spell file processing when 'verbose' is set or using // ":mkspell". "str" can be IObuff. -static void spell_message(const spellinfo_T *spin, char_u *str) +static void spell_message(const spellinfo_T *spin, char *str) FUNC_ATTR_NONNULL_ALL { if (spin->si_verbose || p_verbose > 2) { if (!spin->si_verbose) { verbose_enter(); } - msg((char *)str); + msg(str); ui_flush(); if (!spin->si_verbose) { verbose_leave(); @@ -5503,7 +5508,7 @@ static void spell_message(const spellinfo_T *spin, char_u *str) // ":[count]spellrare {word}" void ex_spell(exarg_T *eap) { - spell_add_word((char_u *)eap->arg, (int)STRLEN(eap->arg), + spell_add_word(eap->arg, (int)strlen(eap->arg), eap->cmdidx == CMD_spellwrong ? SPELL_ADD_BAD : eap->cmdidx == CMD_spellrare ? SPELL_ADD_RARE : SPELL_ADD_GOOD, eap->forceit ? 0 : (int)eap->line2, @@ -5515,17 +5520,17 @@ void ex_spell(exarg_T *eap) /// @param what SPELL_ADD_ values /// @param idx "zG" and "zW": zero, otherwise index in 'spellfile' /// @param bool // true for "zug", "zuG", "zuw" and "zuW" -void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo) +void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo) { FILE *fd = NULL; buf_T *buf = NULL; bool new_spf = false; char *fname; - char_u *fnamebuf = NULL; - char_u line[MAXWLEN * 2]; + char *fnamebuf = NULL; + char line[MAXWLEN * 2]; long fpos, fpos_next = 0; int i; - char_u *spf; + char *spf; if (!valid_spell_word(word, word + len)) { emsg(_(e_illegal_character_in_word)); @@ -5539,7 +5544,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo return; } } - fname = (char *)int_wordlist; + fname = int_wordlist; } else { // If 'spellfile' isn't set figure out a good default value. if (*curwin->w_s->b_p_spf == NUL) { @@ -5554,7 +5559,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo fnamebuf = xmalloc(MAXPATHL); for (spf = curwin->w_s->b_p_spf, i = 1; *spf != NUL; i++) { - copy_option_part((char **)&spf, (char *)fnamebuf, MAXPATHL, ","); + copy_option_part(&spf, fnamebuf, MAXPATHL, ","); if (i == idx) { break; } @@ -5566,7 +5571,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo } // Check that the user isn't editing the .add file somewhere. - buf = buflist_findname_exp((char *)fnamebuf); + buf = buflist_findname_exp(fnamebuf); if (buf != NULL && buf->b_ml.ml_mfp == NULL) { buf = NULL; } @@ -5576,7 +5581,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo return; } - fname = (char *)fnamebuf; + fname = fnamebuf; } if (what == SPELL_ADD_BAD || undo) { @@ -5584,14 +5589,14 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo // since its flags sort before the one with WF_BANNED. fd = os_fopen(fname, "r"); if (fd != NULL) { - while (!vim_fgets(line, MAXWLEN * 2, fd)) { + while (!vim_fgets((char *)line, MAXWLEN * 2, fd)) { fpos = fpos_next; fpos_next = ftell(fd); if (fpos_next < 0) { break; // should never happen } - if (STRNCMP(word, line, len) == 0 - && (line[len] == '/' || line[len] < ' ')) { + if (strncmp(word, line, (size_t)len) == 0 + && (line[len] == '/' || (uint8_t)line[len] < ' ')) { // Found duplicate word. Remove it by writing a '#' at // the start of the line. Mixing reading and writing // doesn't work for all systems, close the file first. @@ -5603,7 +5608,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo if (fseek(fd, fpos, SEEK_SET) == 0) { fputc('#', fd); if (undo) { - home_replace(NULL, fname, (char *)NameBuff, MAXPATHL, true); + home_replace(NULL, fname, NameBuff, MAXPATHL, true); smsg(_("Word '%.*s' removed from %s"), len, word, NameBuff); } } @@ -5628,7 +5633,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo // file. We may need to create the "spell" directory first. We // already checked the runtime directory is writable in // init_spellfile(). - if (!dir_of_file_exists((char_u *)fname) + if (!dir_of_file_exists(fname) && (p = (char_u *)path_tail_with_sep(fname)) != (char_u *)fname) { int c = *p; @@ -5653,7 +5658,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo } fclose(fd); - home_replace(NULL, fname, (char *)NameBuff, MAXPATHL, true); + home_replace(NULL, fname, NameBuff, MAXPATHL, true); smsg(_("Word '%.*s' added to %s"), len, word, NameBuff); } } @@ -5667,7 +5672,7 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo buf_reload(buf, buf->b_orig_mode, false); } - redraw_all_later(SOME_VALID); + redraw_all_later(UPD_SOME_VALID); } xfree(fnamebuf); } @@ -5675,98 +5680,98 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo // Initialize 'spellfile' for the current buffer. static void init_spellfile(void) { - char_u *buf; + char *buf; int l; - char_u *fname; - char_u *rtp; - char_u *lend; + char *fname; + char *rtp; + char *lend; bool aspath = false; - char_u *lstart = curbuf->b_s.b_p_spl; - - if (*curwin->w_s->b_p_spl != NUL && !GA_EMPTY(&curwin->w_s->b_langp)) { - buf = xmalloc(MAXPATHL); - - // Find the end of the language name. Exclude the region. If there - // is a path separator remember the start of the tail. - for (lend = curwin->w_s->b_p_spl; *lend != NUL - && vim_strchr(",._", *lend) == NULL; lend++) { - if (vim_ispathsep(*lend)) { - aspath = true; - lstart = lend + 1; - } + char *lstart = curbuf->b_s.b_p_spl; + + if (*curwin->w_s->b_p_spl == NUL || GA_EMPTY(&curwin->w_s->b_langp)) { + return; + } + + buf = xmalloc(MAXPATHL); + + // Find the end of the language name. Exclude the region. If there + // is a path separator remember the start of the tail. + for (lend = curwin->w_s->b_p_spl; *lend != NUL + && vim_strchr(",._", (uint8_t)(*lend)) == NULL; lend++) { + if (vim_ispathsep(*lend)) { + aspath = true; + lstart = lend + 1; } + } - // Loop over all entries in 'runtimepath'. Use the first one where we - // are allowed to write. - rtp = p_rtp; - while (*rtp != NUL) { + // Loop over all entries in 'runtimepath'. Use the first one where we + // are allowed to write. + rtp = p_rtp; + while (*rtp != NUL) { + if (aspath) { + // Use directory of an entry with path, e.g., for + // "/dir/lg.utf-8.spl" use "/dir". + xstrlcpy(buf, curbuf->b_s.b_p_spl, (size_t)(lstart - curbuf->b_s.b_p_spl)); + } else { + // Copy the path from 'runtimepath' to buf[]. + copy_option_part(&rtp, buf, MAXPATHL, ","); + } + if (os_file_is_writable(buf) == 2) { + // Use the first language name from 'spelllang' and the + // encoding used in the first loaded .spl file. if (aspath) { - // Use directory of an entry with path, e.g., for - // "/dir/lg.utf-8.spl" use "/dir". - STRLCPY(buf, curbuf->b_s.b_p_spl, - lstart - curbuf->b_s.b_p_spl); + xstrlcpy(buf, curbuf->b_s.b_p_spl, (size_t)(lend - curbuf->b_s.b_p_spl + 1)); } else { - // Copy the path from 'runtimepath' to buf[]. - copy_option_part((char **)&rtp, (char *)buf, MAXPATHL, ","); - } - if (os_file_is_writable((char *)buf) == 2) { - // Use the first language name from 'spelllang' and the - // encoding used in the first loaded .spl file. - if (aspath) { - STRLCPY(buf, curbuf->b_s.b_p_spl, - lend - curbuf->b_s.b_p_spl + 1); - } else { - // Create the "spell" directory if it doesn't exist yet. - l = (int)STRLEN(buf); - vim_snprintf((char *)buf + l, MAXPATHL - (size_t)l, "/spell"); - if (os_file_is_writable((char *)buf) != 2) { - os_mkdir((char *)buf, 0755); - } - - l = (int)STRLEN(buf); - vim_snprintf((char *)buf + l, MAXPATHL - (size_t)l, - "/%.*s", (int)(lend - lstart), lstart); + // Create the "spell" directory if it doesn't exist yet. + l = (int)strlen(buf); + vim_snprintf(buf + l, MAXPATHL - (size_t)l, "/spell"); + if (os_file_is_writable(buf) != 2) { + os_mkdir(buf, 0755); } - l = (int)STRLEN(buf); - fname = LANGP_ENTRY(curwin->w_s->b_langp, 0) - ->lp_slang->sl_fname; - vim_snprintf((char *)buf + l, MAXPATHL - (size_t)l, ".%s.add", - ((fname != NULL - && strstr(path_tail((char *)fname), ".ascii.") != NULL) - ? "ascii" - : (const char *)spell_enc())); - set_option_value("spellfile", 0L, (const char *)buf, OPT_LOCAL); - break; + + l = (int)strlen(buf); + vim_snprintf(buf + l, MAXPATHL - (size_t)l, + "/%.*s", (int)(lend - lstart), lstart); } - aspath = false; + l = (int)strlen(buf); + fname = LANGP_ENTRY(curwin->w_s->b_langp, 0) + ->lp_slang->sl_fname; + vim_snprintf(buf + l, MAXPATHL - (size_t)l, ".%s.add", + ((fname != NULL + && strstr(path_tail(fname), ".ascii.") != NULL) + ? "ascii" + : (const char *)spell_enc())); + set_option_value_give_err("spellfile", 0L, buf, OPT_LOCAL); + break; } - - xfree(buf); + aspath = false; } + + xfree(buf); } /// Set the spell character tables from strings in the .spl file. /// /// @param cnt length of "flags" -static void set_spell_charflags(char_u *flags, int cnt, char_u *fol) +static void set_spell_charflags(const char_u *flags, int cnt, char *fol) { // We build the new tables here first, so that we can compare with the // previous one. spelltab_T new_st; int i; - char_u *p = fol; + char *p = fol; int c; clear_spell_chartab(&new_st); - for (i = 0; i < 128; ++i) { + for (i = 0; i < 128; i++) { if (i < cnt) { new_st.st_isw[i + 128] = (flags[i] & CF_WORD) != 0; new_st.st_isu[i + 128] = (flags[i] & CF_UPPER) != 0; } if (*p != NUL) { - c = mb_ptr2char_adv((const char_u **)&p); + c = mb_ptr2char_adv((const char **)&p); new_st.st_fold[i + 128] = (char_u)c; if (i + 128 != c && new_st.st_isu[i + 128] && c < 256) { new_st.st_upper[c] = (char_u)(i + 128); @@ -5783,7 +5788,7 @@ static int set_spell_finish(spelltab_T *new_st) if (did_set_spelltab) { // check that it's the same table - for (i = 0; i < 256; ++i) { + for (i = 0; i < 256; i++) { if (spelltab.st_isw[i] != new_st->st_isw[i] || spelltab.st_isu[i] != new_st->st_isu[i] || spelltab.st_fold[i] != new_st->st_fold[i] @@ -5815,7 +5820,7 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap, size_t *fwv) // <prefcond> : <condlen> <condstr> char *p = ((char **)gap->ga_data)[i]; if (p != NULL) { - size_t len = STRLEN(p); + size_t len = strlen(p); if (fd != NULL) { assert(len <= INT_MAX); fputc((int)len, fd); @@ -5832,9 +5837,9 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap, size_t *fwv) } // Use map string "map" for languages "lp". -static void set_map_str(slang_T *lp, char_u *map) +static void set_map_str(slang_T *lp, char *map) { - char_u *p; + char *p; int headc = 0; int c; int i; @@ -5846,7 +5851,7 @@ static void set_map_str(slang_T *lp, char_u *map) lp->sl_has_map = true; // Init the array and hash tables empty. - for (i = 0; i < 256; ++i) { + for (i = 0; i < 256; i++) { lp->sl_map_array[i] = 0; } hash_init(&lp->sl_map_hash); @@ -5855,7 +5860,7 @@ static void set_map_str(slang_T *lp, char_u *map) // "aaa/bbb/ccc/". Fill sl_map_array[c] with the character before c and // before the same slash. For characters above 255 sl_map_hash is used. for (p = map; *p != NUL;) { - c = mb_cptr2char_adv((const char_u **)&p); + c = mb_cptr2char_adv((const char **)&p); if (c == '/') { headc = 0; } else { @@ -5877,10 +5882,10 @@ static void set_map_str(slang_T *lp, char_u *map) b[cl] = NUL; utf_char2bytes(headc, b + cl + 1); b[cl + 1 + headcl] = NUL; - hash = hash_hash((char_u *)b); - hi = hash_lookup(&lp->sl_map_hash, (const char *)b, STRLEN(b), hash); + hash = hash_hash(b); + hi = hash_lookup(&lp->sl_map_hash, (const char *)b, strlen(b), hash); if (HASHITEM_EMPTY(hi)) { - hash_add_item(&lp->sl_map_hash, hi, (char_u *)b, hash); + hash_add_item(&lp->sl_map_hash, hi, b, hash); } else { // This should have been checked when generating the .spl // file. diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index b4a9bed437..22add418a0 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -3,30 +3,48 @@ // spellsuggest.c: functions for spelling suggestions +#include <assert.h> +#include <inttypes.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/fileio.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/hashtab.h" +#include "nvim/highlight_defs.h" #include "nvim/input.h" +#include "nvim/macros.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/os/fs.h" #include "nvim/os/input.h" +#include "nvim/os/os_defs.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/screen.h" #include "nvim/spell.h" -#include "nvim/spell_defs.h" #include "nvim/spellfile.h" #include "nvim/spellsuggest.h" #include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -53,11 +71,11 @@ typedef struct suginfo_S { int su_maxscore; ///< maximum score for adding to su_ga int su_sfmaxscore; ///< idem, for when doing soundfold words garray_T su_sga; ///< like su_ga, sound-folded scoring - char_u *su_badptr; ///< start of bad word in line + char *su_badptr; ///< start of bad word in line int su_badlen; ///< length of detected bad word in line int su_badflags; ///< caps flags for bad word char_u su_badword[MAXWLEN]; ///< bad word truncated at su_badlen - char_u su_fbadword[MAXWLEN]; ///< su_badword case-folded + char su_fbadword[MAXWLEN]; ///< su_badword case-folded char_u su_sal_badword[MAXWLEN]; ///< su_badword soundfolded hashtab_T su_banned; ///< table with banned words slang_T *su_sallang; ///< default language for sound folding @@ -65,8 +83,8 @@ typedef struct suginfo_S { /// One word suggestion. Used in "si_ga". typedef struct { - char_u *st_word; ///< suggested word, allocated string - int st_wordlen; ///< STRLEN(st_word) + char *st_word; ///< suggested word, allocated string + int st_wordlen; ///< strlen(st_word) int st_orglen; ///< length of replaced text int st_score; ///< lower is better int st_altscore; ///< used when st_score compares equal @@ -91,45 +109,55 @@ typedef struct { #define SUG_MAX_COUNT(su) (SUG_CLEAN_COUNT(su) + 50) // score for various changes -#define SCORE_SPLIT 149 // split bad word -#define SCORE_SPLIT_NO 249 // split bad word with NOSPLITSUGS -#define SCORE_ICASE 52 // slightly different case -#define SCORE_REGION 200 // word is for different region -#define SCORE_RARE 180 // rare word -#define SCORE_SWAP 75 // swap two characters -#define SCORE_SWAP3 110 // swap two characters in three -#define SCORE_REP 65 // REP replacement -#define SCORE_SUBST 93 // substitute a character -#define SCORE_SIMILAR 33 // substitute a similar character -#define SCORE_SUBCOMP 33 // substitute a composing character -#define SCORE_DEL 94 // delete a character -#define SCORE_DELDUP 66 // delete a duplicated character -#define SCORE_DELCOMP 28 // delete a composing character -#define SCORE_INS 96 // insert a character -#define SCORE_INSDUP 67 // insert a duplicate character -#define SCORE_INSCOMP 30 // insert a composing character -#define SCORE_NONWORD 103 // change non-word to word char - -#define SCORE_FILE 30 // suggestion from a file -#define SCORE_MAXINIT 350 // Initial maximum score: higher == slower. - // 350 allows for about three changes. - -#define SCORE_COMMON1 30 // subtracted for words seen before -#define SCORE_COMMON2 40 // subtracted for words often seen -#define SCORE_COMMON3 50 // subtracted for words very often seen -#define SCORE_THRES2 10 // word count threshold for COMMON2 -#define SCORE_THRES3 100 // word count threshold for COMMON3 +enum { + SCORE_SPLIT = 149, // split bad word + SCORE_SPLIT_NO = 249, // split bad word with NOSPLITSUGS + SCORE_ICASE = 52, // slightly different case + SCORE_REGION = 200, // word is for different region + SCORE_RARE = 180, // rare word + SCORE_SWAP = 75, // swap two characters + SCORE_SWAP3 = 110, // swap two characters in three + SCORE_REP = 65, // REP replacement + SCORE_SUBST = 93, // substitute a character + SCORE_SIMILAR = 33, // substitute a similar character + SCORE_SUBCOMP = 33, // substitute a composing character + SCORE_DEL = 94, // delete a character + SCORE_DELDUP = 66, // delete a duplicated character + SCORE_DELCOMP = 28, // delete a composing character + SCORE_INS = 96, // insert a character + SCORE_INSDUP = 67, // insert a duplicate character + SCORE_INSCOMP = 30, // insert a composing character + SCORE_NONWORD = 103, // change non-word to word char +}; + +enum { + SCORE_FILE = 30, // suggestion from a file + SCORE_MAXINIT = 350, // Initial maximum score: higher == slower. + // 350 allows for about three changes. +}; + +enum { + SCORE_COMMON1 = 30, // subtracted for words seen before + SCORE_COMMON2 = 40, // subtracted for words often seen + SCORE_COMMON3 = 50, // subtracted for words very often seen + SCORE_THRES2 = 10, // word count threshold for COMMON2 + SCORE_THRES3 = 100, // word count threshold for COMMON3 +}; // When trying changed soundfold words it becomes slow when trying more than // two changes. With less than two changes it's slightly faster but we miss a // few good suggestions. In rare cases we need to try three of four changes. -#define SCORE_SFMAX1 200 // maximum score for first try -#define SCORE_SFMAX2 300 // maximum score for second try -#define SCORE_SFMAX3 400 // maximum score for third try +enum { + SCORE_SFMAX1 = 200, // maximum score for first try + SCORE_SFMAX2 = 300, // maximum score for second try + SCORE_SFMAX3 = 400, // maximum score for third try +}; #define SCORE_BIG (SCORE_INS * 3) // big difference -#define SCORE_MAXMAX 999999 // accept any score -#define SCORE_LIMITMAX 350 // for spell_edit_score_limit() +enum { + SCORE_MAXMAX = 999999, // accept any score + SCORE_LIMITMAX = 350, // for spell_edit_score_limit() +}; // for spell_edit_score_limit() we need to know the minimum value of // SCORE_ICASE, SCORE_SWAP, SCORE_DEL, SCORE_SIMILAR and SCORE_INS @@ -186,19 +214,25 @@ typedef struct trystate_S { } trystate_T; // values for ts_isdiff -#define DIFF_NONE 0 // no different byte (yet) -#define DIFF_YES 1 // different byte found -#define DIFF_INSERT 2 // inserting character +enum { + DIFF_NONE = 0, // no different byte (yet) + DIFF_YES = 1, // different byte found + DIFF_INSERT = 2, // inserting character +}; // values for ts_flags -#define TSF_PREFIXOK 1 // already checked that prefix is OK -#define TSF_DIDSPLIT 2 // tried split at this point -#define TSF_DIDDEL 4 // did a delete, "ts_delidx" has index +enum { + TSF_PREFIXOK = 1, // already checked that prefix is OK + TSF_DIDSPLIT = 2, // tried split at this point + TSF_DIDDEL = 4, // did a delete, "ts_delidx" has index +}; // special values ts_prefixdepth -#define PFD_NOPREFIX 0xff // not using prefixes -#define PFD_PREFIXTREE 0xfe // walking through the prefix tree -#define PFD_NOTSPECIAL 0xfd // highest value that's not special +enum { + PFD_NOPREFIX = 0xff, // not using prefixes + PFD_PREFIXTREE = 0xfe, // walking through the prefix tree + PFD_NOTSPECIAL = 0xfd, // highest value that's not special +}; static long spell_suggest_timeout = 5000; @@ -242,26 +276,27 @@ static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool spl int newscore; hashitem_T *hi = hash_find(&slang->sl_wordcount, (char *)word); - if (!HASHITEM_EMPTY(hi)) { - wc = HI2WC(hi); - if (wc->wc_count < SCORE_THRES2) { - bonus = SCORE_COMMON1; - } else if (wc->wc_count < SCORE_THRES3) { - bonus = SCORE_COMMON2; - } else { - bonus = SCORE_COMMON3; - } - if (split) { - newscore = score - bonus / 2; - } else { - newscore = score - bonus; - } - if (newscore < 0) { - return 0; - } - return newscore; + if (HASHITEM_EMPTY(hi)) { + return score; } - return score; + + wc = HI2WC(hi); + if (wc->wc_count < SCORE_THRES2) { + bonus = SCORE_COMMON1; + } else if (wc->wc_count < SCORE_THRES3) { + bonus = SCORE_COMMON2; + } else { + bonus = SCORE_COMMON3; + } + if (split) { + newscore = score - bonus / 2; + } else { + newscore = score - bonus; + } + if (newscore < 0) { + return 0; + } + return newscore; } /// Like captype() but for a KEEPCAP word add ONECAP if the word starts with a @@ -270,42 +305,45 @@ static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool spl static int badword_captype(char_u *word, char_u *end) FUNC_ATTR_NONNULL_ALL { - int flags = captype(word, end); + int flags = captype((char *)word, (char *)end); int c; int l, u; bool first; char_u *p; - if (flags & WF_KEEPCAP) { - // Count the number of UPPER and lower case letters. - l = u = 0; - first = false; - for (p = word; p < end; MB_PTR_ADV(p)) { - c = utf_ptr2char((char *)p); - if (SPELL_ISUPPER(c)) { - u++; - if (p == word) { - first = true; - } - } else { - l++; + if (!(flags & WF_KEEPCAP)) { + return flags; + } + + // Count the number of UPPER and lower case letters. + l = u = 0; + first = false; + for (p = word; p < end; MB_PTR_ADV(p)) { + c = utf_ptr2char((char *)p); + if (SPELL_ISUPPER(c)) { + u++; + if (p == word) { + first = true; } + } else { + l++; } + } - // If there are more UPPER than lower case letters suggest an - // ALLCAP word. Otherwise, if the first letter is UPPER then - // suggest ONECAP. Exception: "ALl" most likely should be "All", - // require three upper case letters. - if (u > l && u > 2) { - flags |= WF_ALLCAP; - } else if (first) { - flags |= WF_ONECAP; - } + // If there are more UPPER than lower case letters suggest an + // ALLCAP word. Otherwise, if the first letter is UPPER then + // suggest ONECAP. Exception: "ALl" most likely should be "All", + // require three upper case letters. + if (u > l && u > 2) { + flags |= WF_ALLCAP; + } else if (first) { + flags |= WF_ONECAP; + } - if (u >= 2 && l >= 2) { // maCARONI maCAroni - flags |= WF_MIXCAP; - } + if (u >= 2 && l >= 2) { // maCARONI maCAroni + flags |= WF_MIXCAP; } + return flags; } @@ -341,9 +379,11 @@ static int bytes2offset(char_u **pp) } // values for sps_flags -#define SPS_BEST 1 -#define SPS_FAST 2 -#define SPS_DOUBLE 4 +enum { + SPS_BEST = 1, + SPS_FAST = 2, + SPS_DOUBLE = 4, +}; static int sps_flags = SPS_BEST; ///< flags from 'spellsuggest' static int sps_limit = 9999; ///< max nr of suggestions given @@ -354,13 +394,13 @@ int spell_check_sps(void) { char *p; char *s; - char_u buf[MAXPATHL]; + char buf[MAXPATHL]; int f; sps_flags = 0; sps_limit = 9999; - for (p = (char *)p_sps; *p != NUL;) { + for (p = p_sps; *p != NUL;) { copy_option_part(&p, (char *)buf, MAXPATHL, ","); f = 0; @@ -370,15 +410,15 @@ int spell_check_sps(void) if (*s != NUL && !ascii_isdigit(*s)) { f = -1; } - } else if (STRCMP(buf, "best") == 0) { + } else if (strcmp(buf, "best") == 0) { f = SPS_BEST; - } else if (STRCMP(buf, "fast") == 0) { + } else if (strcmp(buf, "fast") == 0) { f = SPS_FAST; - } else if (STRCMP(buf, "double") == 0) { + } else if (strcmp(buf, "double") == 0) { f = SPS_DOUBLE; - } else if (STRNCMP(buf, "expr:", 5) != 0 - && STRNCMP(buf, "file:", 5) != 0 - && (STRNCMP(buf, "timeout:", 8) != 0 + } else if (strncmp(buf, "expr:", 5) != 0 + && strncmp(buf, "file:", 5) != 0 + && (strncmp(buf, "timeout:", 8) != 0 || (!ascii_isdigit(buf[8]) && !(buf[8] == '-' && ascii_isdigit(buf[9]))))) { f = -1; @@ -407,9 +447,9 @@ int spell_check_sps(void) /// When "count" is non-zero use that suggestion. void spell_suggest(int count) { - char_u *line; + char *line; pos_T prev_cursor = curwin->w_cursor; - char_u wcopy[MAXWLEN + 2]; + char wcopy[MAXWLEN + 2]; char_u *p; int c; suginfo_T sug; @@ -447,6 +487,11 @@ void spell_suggest(int count) } badlen++; end_visual_mode(); + // make sure we don't include the NUL at the end of the line + line = get_cursor_line_ptr(); + if (badlen > (int)strlen(line) - (int)curwin->w_cursor.col) { + badlen = (int)strlen(line) - (int)curwin->w_cursor.col; + } // Find the start of the badly spelled word. } else if (spell_move_to(curwin, FORWARD, true, true, NULL) == 0 || curwin->w_cursor.col > prev_cursor.col) { @@ -454,21 +499,21 @@ void spell_suggest(int count) // cursor. curwin->w_cursor = prev_cursor; line = get_cursor_line_ptr(); - p = line + curwin->w_cursor.col; + p = (char_u *)line + curwin->w_cursor.col; // Backup to before start of word. - while (p > line && spell_iswordp_nmw(p, curwin)) { + while (p > (char_u *)line && spell_iswordp_nmw((char *)p, curwin)) { MB_PTR_BACK(line, p); } // Forward to start of word. - while (*p != NUL && !spell_iswordp_nmw(p, curwin)) { + while (*p != NUL && !spell_iswordp_nmw((char *)p, curwin)) { MB_PTR_ADV(p); } - if (!spell_iswordp_nmw(p, curwin)) { // No word found. + if (!spell_iswordp_nmw((char *)p, curwin)) { // No word found. beep_flush(); return; } - curwin->w_cursor.col = (colnr_T)(p - line); + curwin->w_cursor.col = (colnr_T)(p - (char_u *)line); } // Get the word and its length. @@ -477,7 +522,7 @@ void spell_suggest(int count) need_cap = check_need_cap(curwin->w_cursor.lnum, curwin->w_cursor.col); // Make a copy of current line since autocommands may free the line. - line = vim_strsave(get_cursor_line_ptr()); + line = xstrdup(get_cursor_line_ptr()); spell_suggest_timeout = 5000; // Get the list of suggestions. Limit to 'lines' - 2 or the number in @@ -487,7 +532,7 @@ void spell_suggest(int count) } else { limit = sps_limit; } - spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit, + spell_find_suggest((char_u *)line + curwin->w_cursor.col, badlen, &sug, limit, true, need_cap, true); if (GA_EMPTY(&sug.su_ga)) { @@ -508,12 +553,12 @@ void spell_suggest(int count) msg_start(); msg_row = Rows - 1; // for when 'cmdheight' > 1 lines_left = Rows; // avoid more prompt - vim_snprintf((char *)IObuff, IOSIZE, _("Change \"%.*s\" to:"), + vim_snprintf(IObuff, IOSIZE, _("Change \"%.*s\" to:"), sug.su_badlen, sug.su_badptr); - if (cmdmsg_rl && STRNCMP(IObuff, "Change", 6) == 0) { + if (cmdmsg_rl && strncmp(IObuff, "Change", 6) == 0) { // And now the rabbit from the high hat: Avoid showing the // untranslated message rightleft. - vim_snprintf((char *)IObuff, IOSIZE, ":ot \"%.*s\" egnahC", + vim_snprintf(IObuff, IOSIZE, ":ot \"%.*s\" egnahC", sug.su_badlen, sug.su_badptr); } msg_puts((const char *)IObuff); @@ -526,23 +571,23 @@ void spell_suggest(int count) // The suggested word may replace only part of the bad word, add // the not replaced part. But only when it's not getting too long. - STRLCPY(wcopy, stp->st_word, MAXWLEN + 1); + xstrlcpy(wcopy, stp->st_word, MAXWLEN + 1); int el = sug.su_badlen - stp->st_orglen; if (el > 0 && stp->st_wordlen + el <= MAXWLEN) { - STRLCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen, el + 1); + xstrlcpy(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen, (size_t)el + 1); } - vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1); + vim_snprintf(IObuff, IOSIZE, "%2d", i + 1); if (cmdmsg_rl) { rl_mirror(IObuff); } msg_puts((const char *)IObuff); - vim_snprintf((char *)IObuff, IOSIZE, " \"%s\"", wcopy); + vim_snprintf(IObuff, IOSIZE, " \"%s\"", wcopy); msg_puts((const char *)IObuff); // The word may replace more than "su_badlen". if (sug.su_badlen < stp->st_orglen) { - vim_snprintf((char *)IObuff, IOSIZE, _(" < \"%.*s\""), + vim_snprintf(IObuff, IOSIZE, _(" < \"%.*s\""), stp->st_orglen, sug.su_badptr); msg_puts((const char *)IObuff); } @@ -550,11 +595,11 @@ void spell_suggest(int count) if (p_verbose > 0) { // Add the score. if (sps_flags & (SPS_DOUBLE | SPS_BEST)) { - vim_snprintf((char *)IObuff, IOSIZE, " (%s%d - %d)", + vim_snprintf(IObuff, IOSIZE, " (%s%d - %d)", stp->st_salscore ? "s " : "", stp->st_score, stp->st_altscore); } else { - vim_snprintf((char *)IObuff, IOSIZE, " (%d)", + vim_snprintf(IObuff, IOSIZE, " (%d)", stp->st_score); } if (cmdmsg_rl) { @@ -593,19 +638,19 @@ void spell_suggest(int count) if (sug.su_badlen > stp->st_orglen) { // Replacing less than "su_badlen", append the remainder to // repl_to. - repl_from = vim_strnsave(sug.su_badptr, (size_t)sug.su_badlen); - vim_snprintf((char *)IObuff, IOSIZE, "%s%.*s", stp->st_word, + repl_from = xstrnsave(sug.su_badptr, (size_t)sug.su_badlen); + vim_snprintf(IObuff, IOSIZE, "%s%.*s", stp->st_word, sug.su_badlen - stp->st_orglen, sug.su_badptr + stp->st_orglen); - repl_to = vim_strsave(IObuff); + repl_to = xstrdup(IObuff); } else { // Replacing su_badlen or more, use the whole word. - repl_from = vim_strnsave(sug.su_badptr, (size_t)stp->st_orglen); - repl_to = vim_strsave(stp->st_word); + repl_from = xstrnsave(sug.su_badptr, (size_t)stp->st_orglen); + repl_to = xstrdup(stp->st_word); } // Replace the word. - p = xmalloc(STRLEN(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1); + p = xmalloc(strlen(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1); c = (int)(sug.su_badptr - line); memmove(p, line, (size_t)c); STRCPY(p + c, stp->st_word); @@ -637,13 +682,13 @@ void spell_suggest(int count) /// /// @param maxcount maximum nr of suggestions /// @param need_cap 'spellcapcheck' matched -void spell_suggest_list(garray_T *gap, char_u *word, int maxcount, bool need_cap, bool interactive) +void spell_suggest_list(garray_T *gap, char *word, int maxcount, bool need_cap, bool interactive) { suginfo_T sug; suggest_T *stp; char_u *wcopy; - spell_find_suggest(word, 0, &sug, maxcount, false, need_cap, interactive); + spell_find_suggest((char_u *)word, 0, &sug, maxcount, false, need_cap, interactive); // Make room in "gap". ga_init(gap, sizeof(char_u *), sug.su_ga.ga_len + 1); @@ -653,7 +698,7 @@ void spell_suggest_list(garray_T *gap, char_u *word, int maxcount, bool need_cap // The suggested word may replace only part of "word", add the not // replaced part. - wcopy = xmalloc((size_t)stp->st_wordlen + STRLEN(sug.su_badptr + stp->st_orglen) + 1); + wcopy = xmalloc((size_t)stp->st_wordlen + strlen(sug.su_badptr + stp->st_orglen) + 1); STRCPY(wcopy, stp->st_word); STRCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen); ((char_u **)gap->ga_data)[gap->ga_len++] = wcopy; @@ -675,10 +720,10 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma bool banbadword, bool need_cap, bool interactive) { hlf_T attr = HLF_COUNT; - char_u buf[MAXPATHL]; + char buf[MAXPATHL]; char *p; bool do_combine = false; - char_u *sps_copy; + char *sps_copy; static bool expr_busy = false; int c; langp_T *lp; @@ -693,7 +738,7 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma } hash_init(&su->su_banned); - su->su_badptr = badptr; + su->su_badptr = (char *)badptr; if (badlen != 0) { su->su_badlen = badlen; } else { @@ -707,7 +752,7 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma if (su->su_badlen >= MAXWLEN) { su->su_badlen = MAXWLEN - 1; // just in case } - STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); + xstrlcpy((char *)su->su_badword, su->su_badptr, (size_t)su->su_badlen + 1); (void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword, MAXWLEN); @@ -717,8 +762,8 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma su->su_fbadword[su->su_badlen] = NUL; // get caps flags for bad word - su->su_badflags = badword_captype(su->su_badptr, - su->su_badptr + su->su_badlen); + su->su_badflags = badword_captype((char_u *)su->su_badptr, + (char_u *)su->su_badptr + su->su_badlen); if (need_cap) { su->su_badflags |= WF_ONECAP; } @@ -738,44 +783,44 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma // Soundfold the bad word with the default sound folding, so that we don't // have to do this many times. if (su->su_sallang != NULL) { - spell_soundfold(su->su_sallang, su->su_fbadword, true, - su->su_sal_badword); + spell_soundfold(su->su_sallang, (char *)su->su_fbadword, true, + (char *)su->su_sal_badword); } // If the word is not capitalised and spell_check() doesn't consider the // word to be bad then it might need to be capitalised. Add a suggestion // for that. - c = utf_ptr2char((char *)su->su_badptr); + c = utf_ptr2char(su->su_badptr); if (!SPELL_ISUPPER(c) && attr == HLF_COUNT) { - make_case_word(su->su_badword, buf, WF_ONECAP); - add_suggestion(su, &su->su_ga, buf, su->su_badlen, SCORE_ICASE, + make_case_word((char *)su->su_badword, buf, WF_ONECAP); + add_suggestion(su, &su->su_ga, (char *)buf, su->su_badlen, SCORE_ICASE, 0, true, su->su_sallang, false); } // Ban the bad word itself. It may appear in another region. if (banbadword) { - add_banned(su, su->su_badword); + add_banned(su, (char *)su->su_badword); } // Make a copy of 'spellsuggest', because the expression may change it. - sps_copy = vim_strsave(p_sps); + sps_copy = xstrdup(p_sps); // Loop over the items in 'spellsuggest'. - for (p = (char *)sps_copy; *p != NUL;) { + for (p = sps_copy; *p != NUL;) { copy_option_part(&p, (char *)buf, MAXPATHL, ","); - if (STRNCMP(buf, "expr:", 5) == 0) { + if (strncmp(buf, "expr:", 5) == 0) { // Evaluate an expression. Skip this when called recursively, // when using spellsuggest() in the expression. if (!expr_busy) { expr_busy = true; - spell_suggest_expr(su, buf + 5); + spell_suggest_expr(su, (char_u *)buf + 5); expr_busy = false; } - } else if (STRNCMP(buf, "file:", 5) == 0) { + } else if (strncmp(buf, "file:", 5) == 0) { // Use list of suggestions in a file. - spell_suggest_file(su, buf + 5); - } else if (STRNCMP(buf, "timeout:", 8) == 0) { + spell_suggest_file(su, (char_u *)buf + 5); + } else if (strncmp(buf, "timeout:", 8) == 0) { // Limit the time searching for suggestions. spell_suggest_timeout = atol((char *)buf + 8); } else if (!did_intern) { @@ -814,7 +859,7 @@ static void spell_suggest_expr(suginfo_T *su, char_u *expr) // Get the word and the score from the items. score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p); if (score >= 0 && score <= su->su_maxscore) { - add_suggestion(su, &su->su_ga, (const char_u *)p, su->su_badlen, + add_suggestion(su, &su->su_ga, p, su->su_badlen, score, 0, true, su->su_sallang, false); } } @@ -844,7 +889,7 @@ static void spell_suggest_file(suginfo_T *su, char_u *fname) } // Read it line by line. - while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int) { + while (!vim_fgets((char *)line, MAXWLEN * 2, fd) && !got_int) { line_breakcheck(); p = (char_u *)vim_strchr((char *)line, '/'); @@ -859,12 +904,12 @@ static void spell_suggest_file(suginfo_T *su, char_u *fname) // If the suggestion doesn't have specific case duplicate the case // of the bad word. - if (captype(p, NULL) == 0) { - make_case_word(p, cword, su->su_badflags); + if (captype((char *)p, NULL) == 0) { + make_case_word((char *)p, (char *)cword, su->su_badflags); p = cword; } - add_suggestion(su, &su->su_ga, p, su->su_badlen, + add_suggestion(su, &su->su_ga, (char *)p, su->su_badlen, SCORE_FILE, 0, true, su->su_sallang, false); } } @@ -968,24 +1013,24 @@ static void spell_find_cleanup(suginfo_T *su) /// Try finding suggestions by recognizing specific situations. static void suggest_try_special(suginfo_T *su) { - int c; + char c; char_u word[MAXWLEN]; // Recognize a word that is repeated: "the the". - char_u *p = skiptowhite(su->su_fbadword); - size_t len = (size_t)(p - su->su_fbadword); - p = (char_u *)skipwhite((char *)p); - if (STRLEN(p) == len && STRNCMP(su->su_fbadword, p, len) == 0) { + char *p = skiptowhite((char *)su->su_fbadword); + size_t len = (size_t)(p - (char *)su->su_fbadword); + p = skipwhite(p); + if (strlen(p) == len && strncmp(su->su_fbadword, p, len) == 0) { // Include badflags: if the badword is onecap or allcap // use that for the goodword too: "The the" -> "The". c = su->su_fbadword[len]; su->su_fbadword[len] = NUL; - make_case_word(su->su_fbadword, word, su->su_badflags); - su->su_fbadword[len] = (char_u)c; + make_case_word(su->su_fbadword, (char *)word, su->su_badflags); + su->su_fbadword[len] = c; // Give a soundalike score of 0, compute the score as if deleting one // character. - add_suggestion(su, &su->su_ga, word, su->su_badlen, + add_suggestion(su, &su->su_ga, (char *)word, su->su_badlen, RESCORE(SCORE_REP, 0), 0, true, su->su_sallang, false); } } @@ -1038,21 +1083,21 @@ static void prof_report(char *name) /// Try finding suggestions by adding/removing/swapping letters. static void suggest_try_change(suginfo_T *su) { - char_u fword[MAXWLEN]; // copy of the bad word, case-folded + char fword[MAXWLEN]; // copy of the bad word, case-folded int n; - char_u *p; + char *p; langp_T *lp; // We make a copy of the case-folded bad word, so that we can modify it // to find matches (esp. REP items). Append some more text, changing // chars after the bad word may help. STRCPY(fword, su->su_fbadword); - n = (int)STRLEN(fword); + n = (int)strlen(fword); p = su->su_badptr + su->su_badlen; - (void)spell_casefold(curwin, p, (int)STRLEN(p), fword + n, MAXWLEN - n); + (void)spell_casefold(curwin, p, (int)strlen(p), fword + n, MAXWLEN - n); // Make sure the resulting text is not longer than the original text. - n = (int)STRLEN(su->su_badptr); + n = (int)strlen(su->su_badptr); if (n < MAXWLEN) { fword[n] = NUL; } @@ -1110,11 +1155,11 @@ static void suggest_try_change(suginfo_T *su) /// word splitting for now /// "similar_chars()" /// use "slang->sl_repsal" instead of "lp->lp_replang->sl_rep" -static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool soundfold) +static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soundfold) { - char_u tword[MAXWLEN]; // good word collected so far + char tword[MAXWLEN]; // good word collected so far trystate_T stack[MAXWLEN]; - char_u preword[MAXWLEN * 3] = { 0 }; // word found with proper case; + char preword[MAXWLEN * 3] = { 0 }; // word found with proper case; // concatenation of prefix compound // words and split word. NUL terminated // when going deeper but not when coming @@ -1132,7 +1177,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so garray_T *gap; idx_T arridx; int len; - char_u *p; + char *p; fromto_T *ftp; int fl = 0, tl; int repextra = 0; // extra bytes in fword[] from REP item @@ -1157,7 +1202,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if (soundfold) { // Going through the soundfold tree. - byts = fbyts = slang->sl_sbyts; + byts = fbyts = (char_u *)slang->sl_sbyts; idxs = fidxs = slang->sl_sidxs; pbyts = NULL; pidxs = NULL; @@ -1166,9 +1211,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so } else { // When there are postponed prefixes we need to use these first. At // the end of the prefix we continue in the case-fold tree. - fbyts = slang->sl_fbyts; + fbyts = (char_u *)slang->sl_fbyts; fidxs = slang->sl_fidxs; - pbyts = slang->sl_pbyts; + pbyts = (char_u *)slang->sl_pbyts; pidxs = slang->sl_pidxs; if (pbyts != NULL) { byts = pbyts; @@ -1223,9 +1268,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Set su->su_badflags to the caps type at this position. // Use the caps type until here for the prefix itself. n = nofold_len(fword, sp->ts_fidx, su->su_badptr); - flags = badword_captype(su->su_badptr, su->su_badptr + n); - su->su_badflags = badword_captype(su->su_badptr + n, - su->su_badptr + su->su_badlen); + flags = badword_captype((char_u *)su->su_badptr, (char_u *)su->su_badptr + n); + su->su_badflags = badword_captype((char_u *)su->su_badptr + n, + (char_u *)su->su_badptr + su->su_badlen); #ifdef DEBUG_TRIEWALK sprintf(changename[depth], "prefix"); // NOLINT(runtime/printf) #endif @@ -1242,7 +1287,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so tword[sp->ts_twordlen] = NUL; make_case_word(tword + sp->ts_splitoff, preword + sp->ts_prewordlen, flags); - sp->ts_prewordlen = (char_u)STRLEN(preword); + sp->ts_prewordlen = (char_u)strlen(preword); sp->ts_splitoff = sp->ts_twordlen; } break; @@ -1321,16 +1366,16 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // need to check if a correct word follows. if (sp->ts_fidx - sp->ts_splitfidx == sp->ts_twordlen - sp->ts_splitoff - && STRNCMP(fword + sp->ts_splitfidx, + && strncmp(fword + sp->ts_splitfidx, tword + sp->ts_splitoff, - sp->ts_fidx - sp->ts_splitfidx) == 0) { + (size_t)(sp->ts_fidx - sp->ts_splitfidx)) == 0) { preword[sp->ts_prewordlen] = NUL; newscore = score_wordcount_adj(slang, sp->ts_score, - preword + sp->ts_prewordlen, + (char_u *)preword + sp->ts_prewordlen, sp->ts_prewordlen > 0); // Add the suggestion if the score isn't too bad. if (newscore <= su->su_maxscore) { - add_suggestion(su, &su->su_ga, preword, + add_suggestion(su, &su->su_ga, (char *)preword, sp->ts_splitfidx - repextra, newscore, 0, false, lp->lp_sallang, false); @@ -1357,9 +1402,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24); compflags[sp->ts_complen + 1] = NUL; - STRLCPY(preword + sp->ts_prewordlen, - tword + sp->ts_splitoff, - sp->ts_twordlen - sp->ts_splitoff + 1); + xstrlcpy(preword + sp->ts_prewordlen, + tword + sp->ts_splitoff, + (size_t)(sp->ts_twordlen - sp->ts_splitoff) + 1); // Verify CHECKCOMPOUNDPATTERN rules. if (match_checkcompoundpattern(preword, sp->ts_prewordlen, @@ -1370,10 +1415,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if (compound_ok) { p = preword; while (*skiptowhite(p) != NUL) { - p = (char_u *)skipwhite((char *)skiptowhite(p)); + p = skipwhite(skiptowhite(p)); } - if (fword_ends && !can_compound(slang, p, - compflags + sp->ts_compsplit)) { + if (fword_ends && !can_compound(slang, p, compflags + sp->ts_compsplit)) { // Compound is not allowed. But it may still be // possible if we add another (short) word. compound_ok = false; @@ -1393,16 +1437,13 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so STRCPY(preword + sp->ts_prewordlen, tword + sp->ts_splitoff); } else if (flags & WF_KEEPCAP) { // Must find the word in the keep-case tree. - find_keepcap_word(slang, tword + sp->ts_splitoff, - preword + sp->ts_prewordlen); + find_keepcap_word(slang, (char *)tword + sp->ts_splitoff, preword + sp->ts_prewordlen); } else { // Include badflags: If the badword is onecap or allcap // use that for the goodword too. But if the badword is // allcap and it's only one char long use onecap. c = su->su_badflags; - if ((c & WF_ALLCAP) - && su->su_badlen == - utfc_ptr2len((char *)su->su_badptr)) { + if ((c & WF_ALLCAP) && su->su_badlen == utfc_ptr2len(su->su_badptr)) { c = WF_ONECAP; } c |= flags; @@ -1457,7 +1498,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so && compound_ok) { // The badword also ends: add suggestions. #ifdef DEBUG_TRIEWALK - if (soundfold && STRCMP(preword, "smwrd") == 0) { + if (soundfold && strcmp(preword, "smwrd") == 0) { int j; // print the stack of changes that brought us here @@ -1477,7 +1518,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so p = fword + sp->ts_fidx; MB_PTR_BACK(fword, p); if (!spell_iswordp(p, curwin) && *preword != NUL) { - p = preword + STRLEN(preword); + p = preword + strlen(preword); MB_PTR_BACK(preword, p); if (spell_iswordp(p, curwin)) { newscore += SCORE_NONWORD; @@ -1487,12 +1528,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Give a bonus to words seen before. score = score_wordcount_adj(slang, sp->ts_score + newscore, - preword + sp->ts_prewordlen, + (char_u *)preword + sp->ts_prewordlen, sp->ts_prewordlen > 0); // Add the suggestion if the score isn't too bad. if (score <= su->su_maxscore) { - add_suggestion(su, &su->su_ga, preword, + add_suggestion(su, &su->su_ga, (char *)preword, sp->ts_fidx - repextra, score, 0, false, lp->lp_sallang, false); @@ -1505,7 +1546,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so preword + sp->ts_prewordlen, c == 0 ? WF_ALLCAP : 0); - add_suggestion(su, &su->su_ga, preword, + add_suggestion(su, &su->su_ga, (char *)preword, sp->ts_fidx - repextra, score + SCORE_ICASE, 0, false, lp->lp_sallang, false); @@ -1591,11 +1632,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so } p = preword; while (*skiptowhite(p) != NUL) { - p = (char_u *)skipwhite((char *)skiptowhite(p)); + p = skipwhite(skiptowhite(p)); } if (sp->ts_complen > sp->ts_compsplit - && !can_compound(slang, p, - compflags + sp->ts_compsplit)) { + && !can_compound(slang, p, compflags + sp->ts_compsplit)) { break; } @@ -1607,7 +1647,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Give a bonus to words seen before. newscore = score_wordcount_adj(slang, newscore, - preword + sp->ts_prewordlen, true); + (char_u *)preword + sp->ts_prewordlen, true); } if (TRY_DEEPER(su, stack, depth, newscore)) { @@ -1633,7 +1673,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if (!try_compound && !fword_ends) { STRCAT(preword, " "); } - sp->ts_prewordlen = (char_u)STRLEN(preword); + sp->ts_prewordlen = (char_u)strlen(preword); sp->ts_splitoff = sp->ts_twordlen; sp->ts_splitfidx = sp->ts_fidx; @@ -1650,7 +1690,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so && goodword_ends) { int l; - l = utfc_ptr2len((char *)fword + sp->ts_fidx); + l = utfc_ptr2len(fword + sp->ts_fidx); if (fword_ends) { // Copy the skipped character to preword. memmove(preword + sp->ts_prewordlen, fword + sp->ts_fidx, (size_t)l); @@ -1675,8 +1715,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // set su->su_badflags to the caps type at this // position n = nofold_len(fword, sp->ts_fidx, su->su_badptr); - su->su_badflags = badword_captype(su->su_badptr + n, - su->su_badptr + su->su_badlen); + su->su_badflags = badword_captype((char_u *)su->su_badptr + n, + (char_u *)su->su_badptr + su->su_badlen); // Restart at top of the tree. sp->ts_arridx = 0; @@ -1743,7 +1783,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // when the byte was already changed. And don't try when we // just deleted this byte, accepting it is always cheaper than // delete + substitute. - if (c == fword[sp->ts_fidx] + if (c == (uint8_t)fword[sp->ts_fidx] || (sp->ts_tcharlen > 0 && sp->ts_isdiff != DIFF_NONE)) { newscore = 0; @@ -1753,7 +1793,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if ((newscore == 0 || (sp->ts_fidx >= sp->ts_fidxtry && ((sp->ts_flags & TSF_DIDDEL) == 0 - || c != fword[sp->ts_delidx]))) + || c != (uint8_t)fword[sp->ts_delidx]))) && TRY_DEEPER(su, stack, depth, newscore)) { go_deeper(stack, depth, newscore); #ifdef DEBUG_TRIEWALK @@ -1772,7 +1812,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if (fword[sp->ts_fidx] != NUL) { sp->ts_fidx++; } - tword[sp->ts_twordlen++] = (char_u)c; + tword[sp->ts_twordlen++] = (char)c; sp->ts_arridx = idxs[arridx]; if (newscore == SCORE_SUBST) { sp->ts_isdiff = DIFF_YES; @@ -1798,14 +1838,14 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Correct ts_fidx for the byte length of the // character (we didn't check that before). sp->ts_fidx = (char_u)(sp->ts_fcharstart - + utfc_ptr2len((char *)fword + sp->ts_fcharstart)); + + utfc_ptr2len(fword + sp->ts_fcharstart)); // For changing a composing character adjust // the score from SCORE_SUBST to // SCORE_SUBCOMP. if (utf_iscomposing(utf_ptr2char((char *)tword + sp->ts_twordlen - sp->ts_tcharlen)) - && utf_iscomposing(utf_ptr2char((char *)fword + && utf_iscomposing(utf_ptr2char(fword + sp->ts_fcharstart))) { sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP; } else if (!soundfold @@ -1813,15 +1853,15 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so && similar_chars(slang, utf_ptr2char((char *)tword + sp->ts_twordlen - sp->ts_tcharlen), - utf_ptr2char((char *)fword + sp->ts_fcharstart))) { + utf_ptr2char(fword + sp->ts_fcharstart))) { // For a similar character adjust score from // SCORE_SUBST to SCORE_SIMILAR. sp->ts_score -= SCORE_SUBST - SCORE_SIMILAR; } } else if (sp->ts_isdiff == DIFF_INSERT && sp->ts_twordlen > sp->ts_tcharlen) { - p = tword + sp->ts_twordlen - sp->ts_tcharlen; - c = utf_ptr2char((char *)p); + p = (char *)tword + sp->ts_twordlen - sp->ts_tcharlen; + c = utf_ptr2char(p); if (utf_iscomposing(c)) { // Inserting a composing char doesn't // count that much. @@ -1833,7 +1873,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // tree (might seem illogical but does // give better scores). MB_PTR_BACK(tword, p); - if (c == utf_ptr2char((char *)p)) { + if (c == utf_ptr2char(p)) { sp->ts_score -= SCORE_INS - SCORE_INSDUP; } } @@ -1884,12 +1924,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // score if the same character is following "nn" -> "n". It's // a bit illogical for soundfold tree but it does give better // results. - c = utf_ptr2char((char *)fword + sp->ts_fidx); + c = utf_ptr2char(fword + sp->ts_fidx); stack[depth].ts_fidx = - (char_u)(stack[depth].ts_fidx + utfc_ptr2len((char *)fword + sp->ts_fidx)); + (char_u)(stack[depth].ts_fidx + utfc_ptr2len(fword + sp->ts_fidx)); if (utf_iscomposing(c)) { stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP; - } else if (c == utf_ptr2char((char *)fword + stack[depth].ts_fidx)) { + } else if (c == utf_ptr2char(fword + stack[depth].ts_fidx)) { stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP; } @@ -1949,7 +1989,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so } else { newscore = SCORE_INS; } - if (c != fword[sp->ts_fidx] + if (c != (uint8_t)fword[sp->ts_fidx] && TRY_DEEPER(su, stack, depth, newscore)) { go_deeper(stack, depth, newscore); #ifdef DEBUG_TRIEWALK @@ -1959,7 +1999,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so #endif depth++; sp = &stack[depth]; - tword[sp->ts_twordlen++] = (char_u)c; + tword[sp->ts_twordlen++] = (char)c; sp->ts_arridx = idxs[n]; fl = MB_BYTE2LEN(c); if (fl > 1) { @@ -1976,7 +2016,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // soundfold words (illogical but does give a better // score). if (sp->ts_twordlen >= 2 - && tword[sp->ts_twordlen - 2] == c) { + && (uint8_t)tword[sp->ts_twordlen - 2] == c) { sp->ts_score -= SCORE_INS - SCORE_INSDUP; } } @@ -1988,7 +2028,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // We change "fword" here, it's changed back afterwards at // STATE_UNSWAP. p = fword + sp->ts_fidx; - c = *p; + c = (uint8_t)(*p); if (c == NUL) { // End of word, can't swap or replace. PROF_STORE(sp->ts_state) @@ -2004,14 +2044,14 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so break; } - n = utf_ptr2len((char *)p); - c = utf_ptr2char((char *)p); + n = utf_ptr2len(p); + c = utf_ptr2char(p); if (p[n] == NUL) { c2 = NUL; } else if (!soundfold && !spell_iswordp(p + n, curwin)) { c2 = c; // don't swap non-word char } else { - c2 = utf_ptr2char((char *)p + n); + c2 = utf_ptr2char(p + n); } // When the second character is NUL we can't swap. @@ -2041,7 +2081,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so depth++; fl = utf_char2len(c2); memmove(p, p + n, (size_t)fl); - utf_char2bytes(c, (char *)p + fl); + utf_char2bytes(c, p + fl); stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl); } else { // If this swap doesn't work then SWAP3 won't either. @@ -2053,10 +2093,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so case STATE_UNSWAP: // Undo the STATE_SWAP swap: "21" -> "12". p = fword + sp->ts_fidx; - n = utfc_ptr2len((char *)p); - c = utf_ptr2char((char *)p + n); - memmove(p + utfc_ptr2len((char *)p + n), p, (size_t)n); - utf_char2bytes(c, (char *)p); + n = utfc_ptr2len(p); + c = utf_ptr2char(p + n); + memmove(p + utfc_ptr2len(p + n), p, (size_t)n); + utf_char2bytes(c, p); FALLTHROUGH; @@ -2064,14 +2104,14 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Swap two bytes, skipping one: "123" -> "321". We change // "fword" here, it's changed back afterwards at STATE_UNSWAP3. p = fword + sp->ts_fidx; - n = utf_ptr2len((char *)p); - c = utf_ptr2char((char *)p); - fl = utf_ptr2len((char *)p + n); - c2 = utf_ptr2char((char *)p + n); + n = utf_ptr2len(p); + c = utf_ptr2char(p); + fl = utf_ptr2len(p + n); + c2 = utf_ptr2char(p + n); if (!soundfold && !spell_iswordp(p + n + fl, curwin)) { c3 = c; // don't swap non-word char } else { - c3 = utf_ptr2char((char *)p + n + fl); + c3 = utf_ptr2char(p + n + fl); } // When characters are identical: "121" then SWAP3 result is @@ -2097,8 +2137,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so depth++; tl = utf_char2len(c3); memmove(p, p + n + fl, (size_t)tl); - utf_char2bytes(c2, (char *)p + tl); - utf_char2bytes(c, (char *)p + fl + tl); + utf_char2bytes(c2, p + tl); + utf_char2bytes(c, p + fl + tl); stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl + tl); } else { PROF_STORE(sp->ts_state) @@ -2109,14 +2149,14 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so case STATE_UNSWAP3: // Undo STATE_SWAP3: "321" -> "123" p = fword + sp->ts_fidx; - n = utfc_ptr2len((char *)p); - c2 = utf_ptr2char((char *)p + n); - fl = utfc_ptr2len((char *)p + n); - c = utf_ptr2char((char *)p + n + fl); - tl = utfc_ptr2len((char *)p + n + fl); + n = utfc_ptr2len(p); + c2 = utf_ptr2char(p + n); + fl = utfc_ptr2len(p + n); + c = utf_ptr2char(p + n + fl); + tl = utfc_ptr2len(p + n + fl); memmove(p + fl + tl, p, (size_t)n); - utf_char2bytes(c, (char *)p); - utf_char2bytes(c2, (char *)p + tl); + utf_char2bytes(c, p); + utf_char2bytes(c2, p + tl); p = p + tl; if (!soundfold && !spell_iswordp(p, curwin)) { @@ -2141,12 +2181,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so sp->ts_state = STATE_UNROT3L; depth++; p = fword + sp->ts_fidx; - n = utf_ptr2len((char *)p); - c = utf_ptr2char((char *)p); - fl = utf_ptr2len((char *)p + n); - fl += utf_ptr2len((char *)p + n + fl); + n = utf_ptr2len(p); + c = utf_ptr2char(p); + fl = utf_ptr2len(p + n); + fl += utf_ptr2len(p + n + fl); memmove(p, p + n, (size_t)fl); - utf_char2bytes(c, (char *)p + fl); + utf_char2bytes(c, p + fl); stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl); } else { PROF_STORE(sp->ts_state) @@ -2157,12 +2197,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so case STATE_UNROT3L: // Undo ROT3L: "231" -> "123" p = fword + sp->ts_fidx; - n = utfc_ptr2len((char *)p); - n += utfc_ptr2len((char *)p + n); - c = utf_ptr2char((char *)p + n); - tl = utfc_ptr2len((char *)p + n); + n = utfc_ptr2len(p); + n += utfc_ptr2len(p + n); + c = utf_ptr2char(p + n); + tl = utfc_ptr2len(p + n); memmove(p + tl, p, (size_t)n); - utf_char2bytes(c, (char *)p); + utf_char2bytes(c, p); // Rotate three bytes right: "123" -> "312". We change "fword" // here, it's changed back afterwards at STATE_UNROT3R. @@ -2178,12 +2218,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so sp->ts_state = STATE_UNROT3R; depth++; p = fword + sp->ts_fidx; - n = utf_ptr2len((char *)p); - n += utf_ptr2len((char *)p + n); - c = utf_ptr2char((char *)p + n); - tl = utf_ptr2len((char *)p + n); + n = utf_ptr2len(p); + n += utf_ptr2len(p + n); + c = utf_ptr2char(p + n); + tl = utf_ptr2len(p + n); memmove(p + tl, p, (size_t)n); - utf_char2bytes(c, (char *)p); + utf_char2bytes(c, p); stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + tl); } else { PROF_STORE(sp->ts_state) @@ -2194,12 +2234,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so case STATE_UNROT3R: // Undo ROT3R: "312" -> "123" p = fword + sp->ts_fidx; - c = utf_ptr2char((char *)p); - tl = utfc_ptr2len((char *)p); - n = utfc_ptr2len((char *)p + tl); - n += utfc_ptr2len((char *)p + tl + n); + c = utf_ptr2char(p); + tl = utfc_ptr2len(p); + n = utfc_ptr2len(p + tl); + n += utfc_ptr2len(p + tl + n); memmove(p, p + tl, (size_t)n); - utf_char2bytes(c, (char *)p + n); + utf_char2bytes(c, p + n); FALLTHROUGH; @@ -2220,9 +2260,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Use the first byte to quickly find the first entry that may // match. If the index is -1 there is none. if (soundfold) { - sp->ts_curi = slang->sl_repsal_first[fword[sp->ts_fidx]]; + sp->ts_curi = slang->sl_repsal_first[(uint8_t)fword[sp->ts_fidx]]; } else { - sp->ts_curi = lp->lp_replang->sl_rep_first[fword[sp->ts_fidx]]; + sp->ts_curi = lp->lp_replang->sl_rep_first[(uint8_t)fword[sp->ts_fidx]]; } if (sp->ts_curi < 0) { @@ -2250,10 +2290,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so ftp = (fromto_T *)gap->ga_data + sp->ts_curi++; if (*ftp->ft_from != *p) { // past possible matching entries - sp->ts_curi = (char_u)gap->ga_len; + sp->ts_curi = (int16_t)gap->ga_len; break; } - if (STRNCMP(ftp->ft_from, p, STRLEN(ftp->ft_from)) == 0 + if (strncmp(ftp->ft_from, p, strlen(ftp->ft_from)) == 0 && TRY_DEEPER(su, stack, depth, SCORE_REP)) { go_deeper(stack, depth, SCORE_REP); #ifdef DEBUG_TRIEWALK @@ -2267,10 +2307,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Change the "from" to the "to" string. depth++; - fl = (int)STRLEN(ftp->ft_from); - tl = (int)STRLEN(ftp->ft_to); + fl = (int)strlen(ftp->ft_from); + tl = (int)strlen(ftp->ft_to); if (fl != tl) { - STRMOVE(p + tl, p + fl); + STRMOVE(p + tl, (char *)p + fl); repextra += tl - fl; } memmove(p, ftp->ft_to, (size_t)tl); @@ -2296,11 +2336,11 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so gap = &lp->lp_replang->sl_rep; } ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1; - fl = (int)STRLEN(ftp->ft_from); - tl = (int)STRLEN(ftp->ft_to); + fl = (int)strlen(ftp->ft_from); + tl = (int)strlen(ftp->ft_to); p = fword + sp->ts_fidx; if (fl != tl) { - STRMOVE(p + fl, p + tl); + STRMOVE(p + fl, (char *)p + tl); repextra -= tl - fl; } memmove(p, ftp->ft_from, (size_t)fl); @@ -2344,9 +2384,9 @@ static void go_deeper(trystate_T *stack, int depth, int score_add) /// words and put it in "kword". /// Theoretically there could be several keep-case words that result in the /// same case-folded word, but we only find one... -static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword) +static void find_keepcap_word(slang_T *slang, char *fword, char *kword) { - char_u uword[MAXWLEN]; // "fword" in upper-case + char uword[MAXWLEN]; // "fword" in upper-case int depth; idx_T tryidx; @@ -2363,7 +2403,7 @@ static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword) int c; idx_T lo, hi, m; char_u *p; - char_u *byts = slang->sl_kbyts; // array with bytes of the words + char_u *byts = (char_u *)slang->sl_kbyts; // array with bytes of the words idx_T *idxs = slang->sl_kidxs; // array with indexes if (byts == NULL) { @@ -2402,13 +2442,13 @@ static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword) } else { // round[depth] == 1: Try using the folded-case character. // round[depth] == 2: Try using the upper-case character. - flen = utf_ptr2len((char *)fword + fwordidx[depth]); + flen = utf_ptr2len(fword + fwordidx[depth]); ulen = utf_ptr2len((char *)uword + uwordidx[depth]); if (round[depth] == 1) { - p = fword + fwordidx[depth]; + p = (char_u *)fword + fwordidx[depth]; l = flen; } else { - p = uword + uwordidx[depth]; + p = (char_u *)uword + uwordidx[depth]; l = ulen; } @@ -2443,12 +2483,14 @@ static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword) // Found the matching char. Copy it to "kword" and go a // level deeper. if (round[depth] == 1) { - STRNCPY(kword + kwordlen[depth], fword + fwordidx[depth], // NOLINT(runtime/printf) - flen); + strncpy(kword + kwordlen[depth], // NOLINT(runtime/printf) + fword + fwordidx[depth], + (size_t)flen); kwordlen[depth + 1] = kwordlen[depth] + flen; } else { - STRNCPY(kword + kwordlen[depth], uword + uwordidx[depth], // NOLINT(runtime/printf) - ulen); + strncpy(kword + kwordlen[depth], // NOLINT(runtime/printf) + uword + uwordidx[depth], + (size_t)ulen); kwordlen[depth + 1] = kwordlen[depth] + ulen; } fwordidx[depth + 1] = fwordidx[depth] + flen; @@ -2483,7 +2525,7 @@ static void score_comp_sal(suginfo_T *su) lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); if (!GA_EMPTY(&lp->lp_slang->sl_sal)) { // soundfold the bad word - spell_soundfold(lp->lp_slang, su->su_fbadword, true, badsound); + spell_soundfold(lp->lp_slang, (char *)su->su_fbadword, true, (char *)badsound); for (i = 0; i < su->su_ga.ga_len; i++) { stp = &SUG(su->su_ga, i); @@ -2494,7 +2536,7 @@ static void score_comp_sal(suginfo_T *su) if (score < SCORE_MAXMAX) { // Add the suggestion. sstp = &SUG(su->su_sga, su->su_sga.ga_len); - sstp->st_word = vim_strsave(stp->st_word); + sstp->st_word = xstrdup(stp->st_word); sstp->st_wordlen = stp->st_wordlen; sstp->st_score = score; sstp->st_altscore = 0; @@ -2515,8 +2557,8 @@ static void score_combine(suginfo_T *su) garray_T *gap; langp_T *lp; suggest_T *stp; - char_u *p; - char_u badsound[MAXWLEN]; + char *p; + char badsound[MAXWLEN]; int round; slang_T *slang = NULL; @@ -2526,11 +2568,11 @@ static void score_combine(suginfo_T *su) if (!GA_EMPTY(&lp->lp_slang->sl_sal)) { // soundfold the bad word slang = lp->lp_slang; - spell_soundfold(slang, su->su_fbadword, true, badsound); + spell_soundfold(slang, (char *)su->su_fbadword, true, badsound); for (int i = 0; i < su->su_ga.ga_len; i++) { stp = &SUG(su->su_ga, i); - stp->st_altscore = stp_sal_score(stp, su, slang, badsound); + stp->st_altscore = stp_sal_score(stp, su, slang, (char_u *)badsound); if (stp->st_altscore == SCORE_MAXMAX) { stp->st_score = (stp->st_score * 3 + SCORE_BIG) / 4; } else { @@ -2551,8 +2593,7 @@ static void score_combine(suginfo_T *su) // Add the alternate score to su_sga. for (int i = 0; i < su->su_sga.ga_len; i++) { stp = &SUG(su->su_sga, i); - stp->st_altscore = spell_edit_score(slang, - su->su_badword, stp->st_word); + stp->st_altscore = spell_edit_score(slang, (char_u *)su->su_badword, (char_u *)stp->st_word); if (stp->st_score == SCORE_MAXMAX) { stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8; } else { @@ -2582,7 +2623,7 @@ static void score_combine(suginfo_T *su) p = SUG(*gap, i).st_word; int j; for (j = 0; j < ga.ga_len; j++) { - if (STRCMP(stp[j].st_word, p) == 0) { + if (strcmp(stp[j].st_word, p) == 0) { break; } } @@ -2621,7 +2662,7 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u * char_u badsound2[MAXWLEN]; char_u fword[MAXWLEN]; char_u goodsound[MAXWLEN]; - char_u goodword[MAXWLEN]; + char goodword[MAXWLEN]; int lendiff; lendiff = su->su_badlen - stp->st_orglen; @@ -2629,7 +2670,7 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u * pbad = badsound; } else { // soundfold the bad word with more characters following - (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, fword, MAXWLEN); + (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, (char *)fword, MAXWLEN); // When joining two words the sound often changes a lot. E.g., "t he" // sounds like "t h" while "the" sounds like "@". Avoid that by @@ -2637,12 +2678,12 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u * // space. if (ascii_iswhite(su->su_badptr[su->su_badlen]) && *skiptowhite(stp->st_word) == NUL) { - for (p = fword; *(p = skiptowhite(p)) != NUL;) { - STRMOVE(p, p + 1); + for (p = fword; *(p = (char_u *)skiptowhite((char *)p)) != NUL;) { + STRMOVE(p, (char *)p + 1); } } - spell_soundfold(slang, fword, true, badsound2); + spell_soundfold(slang, (char *)fword, true, (char *)badsound2); pbad = badsound2; } @@ -2650,17 +2691,17 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u * // Add part of the bad word to the good word, so that we soundfold // what replaces the bad word. STRCPY(goodword, stp->st_word); - STRLCPY(goodword + stp->st_wordlen, - su->su_badptr + su->su_badlen - lendiff, lendiff + 1); - pgood = goodword; + xstrlcpy(goodword + stp->st_wordlen, + su->su_badptr + su->su_badlen - lendiff, (size_t)lendiff + 1); + pgood = (char_u *)goodword; } else { - pgood = stp->st_word; + pgood = (char_u *)stp->st_word; } // Sound-fold the word and compute the score for the difference. - spell_soundfold(slang, pgood, false, goodsound); + spell_soundfold(slang, (char *)pgood, false, (char *)goodsound); - return soundalike_score(goodsound, pbad); + return soundalike_score((char *)goodsound, (char *)pbad); } /// structure used to store soundfolded words that add_sound_suggest() has @@ -2707,7 +2748,7 @@ static void suggest_try_soundalike(suginfo_T *su) slang = lp->lp_slang; if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) { // soundfold the bad word - spell_soundfold(slang, su->su_fbadword, true, salword); + spell_soundfold(slang, (char *)su->su_fbadword, true, (char *)salword); // try all kinds of inserts/deletes/swaps/etc. // TODO(vim): also soundfold the next words, so that we can try joining @@ -2715,7 +2756,7 @@ static void suggest_try_soundalike(suginfo_T *su) #ifdef SUGGEST_PROFILE prof_init(); #endif - suggest_trie_walk(su, lp, salword, true); + suggest_trie_walk(su, lp, (char *)salword, true); #ifdef SUGGEST_PROFILE prof_report("soundalike"); #endif @@ -2757,7 +2798,7 @@ static void suggest_try_soundalike_finish(void) /// produce this soundfolded word. /// /// @param score soundfold score -static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_T *lp) +static void add_sound_suggest(suginfo_T *su, char *goodword, int score, langp_T *lp) { slang_T *slang = lp->lp_slang; // language for sound folding int sfwordnr; @@ -2783,14 +2824,14 @@ static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_ // the words that have a better score than before. Use a hashtable to // remember the words that have been done. hash = hash_hash(goodword); - const size_t goodword_len = STRLEN(goodword); + const size_t goodword_len = strlen(goodword); hi = hash_lookup(&slang->sl_sounddone, (const char *)goodword, goodword_len, hash); if (HASHITEM_EMPTY(hi)) { sft = xmalloc(sizeof(sftword_T) + goodword_len); sft->sft_score = (int16_t)score; memcpy(sft->sft_word, goodword, goodword_len + 1); - hash_add_item(&slang->sl_sounddone, hi, sft->sft_word, hash); + hash_add_item(&slang->sl_sounddone, hi, (char *)sft->sft_word, hash); } else { sft = HI2SFT(hi); if (score >= sft->sft_score) { @@ -2800,21 +2841,21 @@ static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_ } // Find the word nr in the soundfold tree. - sfwordnr = soundfold_find(slang, goodword); + sfwordnr = soundfold_find(slang, (char_u *)goodword); if (sfwordnr < 0) { internal_error("add_sound_suggest()"); return; } // Go over the list of good words that produce this soundfold word - nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1, false); + nrline = (char_u *)ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1, false); orgnr = 0; while (*nrline != NUL) { // The wordnr was stored in a minimal nr of bytes as an offset to the // previous wordnr. orgnr += bytes2offset(&nrline); - byts = slang->sl_fbyts; + byts = (char_u *)slang->sl_fbyts; idxs = slang->sl_fidxs; // Lookup the word "orgnr" one of the two tries. @@ -2866,13 +2907,13 @@ badword: if (flags & WF_KEEPCAP) { // Must find the word in the keep-case tree. - find_keepcap_word(slang, theword, cword); + find_keepcap_word(slang, (char *)theword, (char *)cword); p = cword; } else { flags |= su->su_badflags; if ((flags & WF_CAPMASK) != 0) { // Need to fix case according to "flags". - make_case_word(theword, cword, flags); + make_case_word((char *)theword, (char *)cword, flags); p = cword; } else { p = theword; @@ -2883,7 +2924,7 @@ badword: if (sps_flags & SPS_DOUBLE) { // Add the suggestion if the score isn't too bad. if (score <= su->su_maxscore) { - add_suggestion(su, &su->su_sga, p, su->su_badlen, + add_suggestion(su, &su->su_sga, (char *)p, su->su_badlen, score, 0, false, slang, false); } } else { @@ -2917,9 +2958,9 @@ badword: // inefficient, using an array is quicker. limit = MAXSCORE(su->su_sfmaxscore - goodscore, score); if (limit > SCORE_LIMITMAX) { - goodscore += spell_edit_score(slang, su->su_badword, p); + goodscore += spell_edit_score(slang, (char_u *)su->su_badword, p); } else { - goodscore += spell_edit_score_limit(slang, su->su_badword, + goodscore += spell_edit_score_limit(slang, (char_u *)su->su_badword, p, limit); } @@ -2931,7 +2972,7 @@ badword: // Add the suggestion if the score isn't too bad. goodscore = RESCORE(goodscore, score); if (goodscore <= su->su_sfmaxscore) { - add_suggestion(su, &su->su_ga, p, su->su_badlen, + add_suggestion(su, &su->su_ga, (char *)p, su->su_badlen, goodscore, score, true, slang, true); } } @@ -2952,7 +2993,7 @@ static int soundfold_find(slang_T *slang, char_u *word) idx_T *idxs; int wordnr = 0; - byts = slang->sl_sbyts; + byts = (char_u *)slang->sl_sbyts; idxs = slang->sl_sidxs; for (;;) { @@ -3029,7 +3070,7 @@ static bool similar_chars(slang_T *slang, int c1, int c2) if (HASHITEM_EMPTY(hi)) { m1 = 0; } else { - m1 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1); + m1 = utf_ptr2char(hi->hi_key + strlen(hi->hi_key) + 1); } } else { m1 = slang->sl_map_array[c1]; @@ -3044,7 +3085,7 @@ static bool similar_chars(slang_T *slang, int c1, int c2) if (HASHITEM_EMPTY(hi)) { m2 = 0; } else { - m2 = utf_ptr2char((char *)hi->hi_key + STRLEN(hi->hi_key) + 1); + m2 = utf_ptr2char(hi->hi_key + strlen(hi->hi_key) + 1); } } else { m2 = slang->sl_map_array[c2]; @@ -3061,7 +3102,7 @@ static bool similar_chars(slang_T *slang, int c1, int c2) /// @param had_bonus value for st_had_bonus /// @param slang language for sound folding /// @param maxsf su_maxscore applies to soundfold score, su_sfmaxscore to the total score. -static void add_suggestion(suginfo_T *su, garray_T *gap, const char_u *goodword, int badlenarg, +static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, int badlenarg, int score, int altscore, bool had_bonus, slang_T *slang, bool maxsf) { int goodlen; // len of goodword changed @@ -3071,8 +3112,8 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char_u *goodword, // Minimize "badlen" for consistency. Avoids that changing "the the" to // "thee the" is added next to changing the first "the" the "thee". - const char_u *pgood = goodword + STRLEN(goodword); - char_u *pbad = su->su_badptr + badlenarg; + const char *pgood = goodword + strlen(goodword); + char *pbad = su->su_badptr + badlenarg; for (;;) { goodlen = (int)(pgood - goodword); badlen = (int)(pbad - su->su_badptr); @@ -3081,7 +3122,7 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char_u *goodword, } MB_PTR_BACK(goodword, pgood); MB_PTR_BACK(su->su_badptr, pbad); - if (utf_ptr2char((char *)pgood) != utf_ptr2char((char *)pbad)) { + if (utf_ptr2char(pgood) != utf_ptr2char(pbad)) { break; } } @@ -3100,10 +3141,10 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char_u *goodword, // being replaced "thes," -> "these" is a different suggestion from // "thes" -> "these". stp = &SUG(*gap, 0); - for (i = gap->ga_len; --i >= 0; ++stp) { + for (i = gap->ga_len; --i >= 0; stp++) { if (stp->st_wordlen == goodlen && stp->st_orglen == badlen - && STRNCMP(stp->st_word, goodword, goodlen) == 0) { + && strncmp(stp->st_word, goodword, (size_t)goodlen) == 0) { // Found it. Remember the word with the lowest score. if (stp->st_slang == NULL) { stp->st_slang = slang; @@ -3144,7 +3185,7 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char_u *goodword, if (i < 0) { // Add a suggestion. stp = GA_APPEND_VIA_PTR(suggest_T, gap); - stp->st_word = vim_strnsave(goodword, (size_t)goodlen); + stp->st_word = xstrnsave(goodword, (size_t)goodlen); stp->st_wordlen = goodlen; stp->st_score = score; stp->st_altscore = altscore; @@ -3173,7 +3214,7 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char_u *goodword, static void check_suggestions(suginfo_T *su, garray_T *gap) { suggest_T *stp; - char_u longword[MAXWLEN + 1]; + char longword[MAXWLEN + 1]; int len; hlf_T attr; @@ -3183,10 +3224,10 @@ static void check_suggestions(suginfo_T *su, garray_T *gap) stp = &SUG(*gap, 0); for (int i = gap->ga_len - 1; i >= 0; i--) { // Need to append what follows to check for "the the". - STRLCPY(longword, stp[i].st_word, MAXWLEN + 1); + xstrlcpy(longword, stp[i].st_word, MAXWLEN + 1); len = stp[i].st_wordlen; - STRLCPY(longword + len, su->su_badptr + stp[i].st_orglen, - MAXWLEN - len + 1); + xstrlcpy(longword + len, su->su_badptr + stp[i].st_orglen, + (size_t)(MAXWLEN - len + 1)); attr = HLF_COUNT; (void)spell_check(curwin, longword, &attr, NULL, false); if (attr != HLF_COUNT) { @@ -3201,19 +3242,20 @@ static void check_suggestions(suginfo_T *su, garray_T *gap) } /// Add a word to be banned. -static void add_banned(suginfo_T *su, char_u *word) +static void add_banned(suginfo_T *su, char *word) { char_u *s; hash_T hash; hashitem_T *hi; hash = hash_hash(word); - const size_t word_len = STRLEN(word); - hi = hash_lookup(&su->su_banned, (const char *)word, word_len, hash); - if (HASHITEM_EMPTY(hi)) { - s = xmemdupz(word, word_len); - hash_add_item(&su->su_banned, hi, s, hash); + const size_t word_len = strlen(word); + hi = hash_lookup(&su->su_banned, word, word_len, hash); + if (!HASHITEM_EMPTY(hi)) { // already present + return; } + s = xmemdupz(word, word_len); + hash_add_item(&su->su_banned, hi, (char *)s, hash); } /// Recompute the score for all suggestions if sound-folding is possible. This @@ -3240,7 +3282,7 @@ static void rescore_one(suginfo_T *su, suggest_T *stp) if (slang == su->su_sallang) { p = su->su_sal_badword; } else { - spell_soundfold(slang, su->su_fbadword, true, sal_badword); + spell_soundfold(slang, (char *)su->su_fbadword, true, (char *)sal_badword); p = sal_badword; } @@ -3280,21 +3322,23 @@ static int sug_compare(const void *s1, const void *s2) static int cleanup_suggestions(garray_T *gap, int maxscore, int keep) FUNC_ATTR_NONNULL_ALL { - if (gap->ga_len > 0) { - // Sort the list. - qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare); + if (gap->ga_len <= 0) { + return maxscore; + } - // Truncate the list to the number of suggestions that will be displayed. - if (gap->ga_len > keep) { - suggest_T *const stp = &SUG(*gap, 0); + // Sort the list. + qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare); - for (int i = keep; i < gap->ga_len; i++) { - xfree(stp[i].st_word); - } - gap->ga_len = keep; - if (keep >= 1) { - return stp[keep - 1].st_score; - } + // Truncate the list to the number of suggestions that will be displayed. + if (gap->ga_len > keep) { + suggest_T *const stp = &SUG(*gap, 0); + + for (int i = keep; i < gap->ga_len; i++) { + xfree(stp[i].st_word); + } + gap->ga_len = keep; + if (keep >= 1) { + return stp[keep - 1].st_score; } } return maxscore; @@ -3307,15 +3351,15 @@ static int cleanup_suggestions(garray_T *gap, int maxscore, int keep) /// /// @param goodstart sound-folded good word /// @param badstart sound-folded bad word -static int soundalike_score(char_u *goodstart, char_u *badstart) +static int soundalike_score(char *goodstart, char *badstart) { - char_u *goodsound = goodstart; - char_u *badsound = badstart; + char *goodsound = goodstart; + char *badsound = badstart; int goodlen; int badlen; int n; - char_u *pl, *ps; - char_u *pl2, *ps2; + char *pl, *ps; + char *pl2, *ps2; int score = 0; // Adding/inserting "*" at the start (word starts with vowel) shouldn't be @@ -3346,8 +3390,8 @@ static int soundalike_score(char_u *goodstart, char_u *badstart) } } - goodlen = (int)STRLEN(goodsound); - badlen = (int)STRLEN(badsound); + goodlen = (int)strlen(goodsound); + badlen = (int)strlen(badsound); // Return quickly if the lengths are too different to be fixed by two // changes. @@ -3380,7 +3424,7 @@ static int soundalike_score(char_u *goodstart, char_u *badstart) ps++; } // strings must be equal after second delete - if (STRCMP(pl + 1, ps) == 0) { + if (strcmp(pl + 1, ps) == 0) { return score + SCORE_DEL * 2; } @@ -3404,12 +3448,12 @@ static int soundalike_score(char_u *goodstart, char_u *badstart) // 2: delete then swap, then rest must be equal if (pl2[0] == ps2[1] && pl2[1] == ps2[0] - && STRCMP(pl2 + 2, ps2 + 2) == 0) { + && strcmp(pl2 + 2, ps2 + 2) == 0) { return score + SCORE_DEL + SCORE_SWAP; } // 3: delete then substitute, then the rest must be equal - if (STRCMP(pl2 + 1, ps2 + 1) == 0) { + if (strcmp(pl2 + 1, ps2 + 1) == 0) { return score + SCORE_DEL + SCORE_SUBST; } @@ -3422,7 +3466,7 @@ static int soundalike_score(char_u *goodstart, char_u *badstart) ps2++; } // delete a char and then strings must be equal - if (STRCMP(pl2 + 1, ps2) == 0) { + if (strcmp(pl2 + 1, ps2) == 0) { return score + SCORE_SWAP + SCORE_DEL; } } @@ -3435,7 +3479,7 @@ static int soundalike_score(char_u *goodstart, char_u *badstart) ps2++; } // delete a char and then strings must be equal - if (STRCMP(pl2 + 1, ps2) == 0) { + if (strcmp(pl2 + 1, ps2) == 0) { return score + SCORE_SUBST + SCORE_DEL; } @@ -3463,12 +3507,12 @@ static int soundalike_score(char_u *goodstart, char_u *badstart) } // 3: swap and swap again if (pl2[0] == ps2[1] && pl2[1] == ps2[0] - && STRCMP(pl2 + 2, ps2 + 2) == 0) { + && strcmp(pl2 + 2, ps2 + 2) == 0) { return score + SCORE_SWAP + SCORE_SWAP; } // 4: swap and substitute - if (STRCMP(pl2 + 1, ps2 + 1) == 0) { + if (strcmp(pl2 + 1, ps2 + 1) == 0) { return score + SCORE_SWAP + SCORE_SUBST; } } @@ -3486,12 +3530,12 @@ static int soundalike_score(char_u *goodstart, char_u *badstart) // 6: substitute and swap if (pl2[0] == ps2[1] && pl2[1] == ps2[0] - && STRCMP(pl2 + 2, ps2 + 2) == 0) { + && strcmp(pl2 + 2, ps2 + 2) == 0) { return score + SCORE_SUBST + SCORE_SWAP; } // 7: substitute and substitute - if (STRCMP(pl2 + 1, ps2 + 1) == 0) { + if (strcmp(pl2 + 1, ps2 + 1) == 0) { return score + SCORE_SUBST + SCORE_SUBST; } @@ -3502,7 +3546,7 @@ static int soundalike_score(char_u *goodstart, char_u *badstart) pl2++; ps2++; } - if (STRCMP(pl2 + 1, ps2) == 0) { + if (strcmp(pl2 + 1, ps2) == 0) { return score + SCORE_INS + SCORE_DEL; } @@ -3513,7 +3557,7 @@ static int soundalike_score(char_u *goodstart, char_u *badstart) pl2++; ps2++; } - if (STRCMP(pl2, ps2 + 1) == 0) { + if (strcmp(pl2, ps2 + 1) == 0) { return score + SCORE_INS + SCORE_DEL; } @@ -3531,7 +3575,7 @@ static int soundalike_score(char_u *goodstart, char_u *badstart) /// The implementation of the algorithm comes from Aspell editdist.cpp, /// edit_distance(). It has been converted from C++ to C and modified to /// support multi-byte characters. -static int spell_edit_score(slang_T *slang, char_u *badword, char_u *goodword) +static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u *goodword) { int *cnt; int j, i; @@ -3548,12 +3592,12 @@ static int spell_edit_score(slang_T *slang, char_u *badword, char_u *goodword) // Get the characters from the multi-byte strings and put them in an // int array for easy access. badlen = 0; - for (const char_u *p = badword; *p != NUL;) { + for (const char *p = (char *)badword; *p != NUL;) { wbadword[badlen++] = mb_cptr2char_adv(&p); } wbadword[badlen++] = 0; goodlen = 0; - for (const char_u *p = goodword; *p != NUL;) { + for (const char *p = (char *)goodword; *p != NUL;) { wgoodword[goodlen++] = mb_cptr2char_adv(&p); } wgoodword[goodlen++] = 0; @@ -3636,7 +3680,8 @@ static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodw /// Multi-byte version of spell_edit_score_limit(). /// Keep it in sync with the above! -static int spell_edit_score_limit_w(slang_T *slang, char_u *badword, char_u *goodword, int limit) +static int spell_edit_score_limit_w(slang_T *slang, const char_u *badword, const char_u *goodword, + int limit) { limitscore_T stack[10]; // allow for over 3 * 2 edits int stackidx; @@ -3653,12 +3698,12 @@ static int spell_edit_score_limit_w(slang_T *slang, char_u *badword, char_u *goo // Get the characters from the multi-byte strings and put them in an // int array for easy access. bi = 0; - for (const char_u *p = badword; *p != NUL;) { + for (const char *p = (char *)badword; *p != NUL;) { wbadword[bi++] = mb_cptr2char_adv(&p); } wbadword[bi++] = 0; gi = 0; - for (const char_u *p = goodword; *p != NUL;) { + for (const char *p = (char *)goodword; *p != NUL;) { wgoodword[gi++] = mb_cptr2char_adv(&p); } wgoodword[gi++] = 0; diff --git a/src/nvim/state.c b/src/nvim/state.c index 61740800a1..9ba5f81776 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -1,27 +1,38 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <assert.h> +#include <stdbool.h> +#include <stddef.h> +#include <string.h> #include "nvim/ascii.h" #include "nvim/autocmd.h" +#include "nvim/buffer_defs.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" -#include "nvim/ex_docmd.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/event/defs.h" +#include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/insexpand.h" -#include "nvim/lib/kvec.h" +#include "nvim/keycodes.h" #include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/main.h" #include "nvim/option.h" -#include "nvim/option_defs.h" #include "nvim/os/input.h" +#include "nvim/screen.h" #include "nvim/state.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "state.c.generated.h" +# include "state.c.generated.h" // IWYU pragma: export #endif void state_enter(VimState *s) @@ -57,7 +68,7 @@ getkey: // Duplicate display updating logic in vgetorpeek() if (((State & MODE_INSERT) != 0 || p_lz) && (State & MODE_CMDLINE) == 0 && must_redraw != 0 && !need_wait_return) { - update_screen(0); + update_screen(); setcursor(); // put cursor back where it belongs } // Flush screen updates before blocking @@ -65,7 +76,7 @@ getkey: // Call `os_inchar` directly to block for events or user input without // consuming anything from `input_buffer`(os/input.c) or calling the // mapping engine. - (void)os_inchar(NULL, 0, -1, 0, main_loop.events); + (void)os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events); // If an event was put into the queue, we send K_EVENT directly. if (!multiqueue_empty(main_loop.events)) { key = K_EVENT; @@ -229,7 +240,9 @@ void get_mode(char *buf) /// Fires a ModeChanged autocmd if appropriate. void may_trigger_modechanged(void) { - if (!has_event(EVENT_MODECHANGED)) { + // Skip this when got_int is set, the autocommand will not be executed. + // Better trigger it next time. + if (!has_event(EVENT_MODECHANGED) || got_int) { return; } @@ -237,7 +250,7 @@ void may_trigger_modechanged(void) char pattern_buf[2 * MODE_MAX_LENGTH]; get_mode(curr_mode); - if (STRCMP(curr_mode, last_mode) == 0) { + if (strcmp(curr_mode, last_mode) == 0) { return; } diff --git a/src/nvim/state.h b/src/nvim/state.h index b93d57d035..76a38b0dab 100644 --- a/src/nvim/state.h +++ b/src/nvim/state.h @@ -3,6 +3,8 @@ #include <stddef.h> +struct vim_state; + typedef struct vim_state VimState; typedef int (*state_check_callback)(VimState *state); diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c index e868a79b9a..6ad1f31143 100644 --- a/src/nvim/statusline.c +++ b/src/nvim/statusline.c @@ -5,22 +5,43 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> -#include "nvim/assert.h" -#include "nvim/autocmd.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/move.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" +#include "nvim/os/os.h" +#include "nvim/path.h" +#include "nvim/pos.h" +#include "nvim/screen.h" +#include "nvim/sign_defs.h" #include "nvim/statusline.h" +#include "nvim/strings.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" @@ -43,11 +64,10 @@ void win_redr_status(win_T *wp) { int row; int col; - char_u *p; + char *p; int len; int fillchar; int attr; - int width; int this_ru_col; bool is_stl_global = global_stl_height() > 0; static bool busy = false; @@ -74,53 +94,54 @@ void win_redr_status(win_T *wp) redraw_custom_statusline(wp); } else { fillchar = fillchar_status(&attr, wp); - width = is_stl_global ? Columns : wp->w_width; + const int stl_width = is_stl_global ? Columns : wp->w_width; get_trans_bufname(wp->w_buffer); p = NameBuff; - len = (int)STRLEN(p); + len = (int)strlen(p); - if (bt_help(wp->w_buffer) - || wp->w_p_pvw - || bufIsChanged(wp->w_buffer) - || wp->w_buffer->b_p_ro) { + if ((bt_help(wp->w_buffer) + || wp->w_p_pvw + || bufIsChanged(wp->w_buffer) + || wp->w_buffer->b_p_ro) + && len < MAXPATHL - 1) { *(p + len++) = ' '; } if (bt_help(wp->w_buffer)) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Help]")); - len += (int)STRLEN(p + len); + snprintf(p + len, MAXPATHL - (size_t)len, "%s", _("[Help]")); + len += (int)strlen(p + len); } if (wp->w_p_pvw) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]")); - len += (int)STRLEN(p + len); + snprintf(p + len, MAXPATHL - (size_t)len, "%s", _("[Preview]")); + len += (int)strlen(p + len); } if (bufIsChanged(wp->w_buffer)) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", "[+]"); - len += (int)STRLEN(p + len); + snprintf(p + len, MAXPATHL - (size_t)len, "%s", "[+]"); + len += (int)strlen(p + len); } if (wp->w_buffer->b_p_ro) { - snprintf((char *)p + len, MAXPATHL - (size_t)len, "%s", _("[RO]")); - // len += (int)STRLEN(p + len); // dead assignment + snprintf(p + len, MAXPATHL - (size_t)len, "%s", _("[RO]")); + // len += (int)strlen(p + len); // dead assignment } - this_ru_col = ru_col - (Columns - width); - if (this_ru_col < (width + 1) / 2) { - this_ru_col = (width + 1) / 2; + this_ru_col = ru_col - (Columns - stl_width); + if (this_ru_col < (stl_width + 1) / 2) { + this_ru_col = (stl_width + 1) / 2; } if (this_ru_col <= 1) { - p = (char_u *)"<"; // No room for file name! + p = "<"; // No room for file name! len = 1; } else { int clen = 0, i; // Count total number of display cells. - clen = (int)mb_string2cells((char *)p); + clen = (int)mb_string2cells(p); // Find first character that will fit. // Going from start to end is much faster for DBCS. for (i = 0; p[i] != NUL && clen >= this_ru_col - 1; - i += utfc_ptr2len((char *)p + i)) { - clen -= utf_ptr2cells((char *)p + i); + i += utfc_ptr2len(p + i)) { + clen -= utf_ptr2cells(p + i); } len = clen; if (i > 0) { @@ -136,13 +157,23 @@ void win_redr_status(win_T *wp) grid_fill(&default_grid, row, row + 1, len + col, this_ru_col + col, fillchar, fillchar, attr); - if (get_keymap_str(wp, "<%s>", (char *)NameBuff, MAXPATHL) - && this_ru_col - len > (int)(STRLEN(NameBuff) + 1)) { + if (get_keymap_str(wp, "<%s>", NameBuff, MAXPATHL) + && this_ru_col - len > (int)(strlen(NameBuff) + 1)) { grid_puts(&default_grid, NameBuff, row, - (int)((size_t)this_ru_col - STRLEN(NameBuff) - 1), attr); + (int)((size_t)this_ru_col - strlen(NameBuff) - 1), attr); } win_redr_ruler(wp, true); + + // Draw the 'showcmd' information if 'showcmdloc' == "statusline". + if (p_sc && *p_sloc == 's') { + const int sc_width = MIN(10, this_ru_col - len - 2); + + if (sc_width > 0) { + grid_puts_len(&default_grid, showcmd_buf, sc_width, row, + wp->w_wincol + this_ru_col - sc_width - 1, attr); + } + } } // May need to draw the character below the vertical separator. @@ -157,6 +188,252 @@ void win_redr_status(win_T *wp) busy = false; } +/// Clear status line, window bar or tab page line click definition table +/// +/// @param[out] tpcd Table to clear. +/// @param[in] tpcd_size Size of the table. +void stl_clear_click_defs(StlClickDefinition *const click_defs, const size_t click_defs_size) +{ + if (click_defs != NULL) { + for (size_t i = 0; i < click_defs_size; i++) { + if (i == 0 || click_defs[i].func != click_defs[i - 1].func) { + xfree(click_defs[i].func); + } + } + memset(click_defs, 0, click_defs_size * sizeof(click_defs[0])); + } +} + +/// Allocate or resize the click definitions array if needed. +StlClickDefinition *stl_alloc_click_defs(StlClickDefinition *cdp, long width, size_t *size) +{ + if (*size < (size_t)width) { + xfree(cdp); + *size = (size_t)width; + cdp = xcalloc(*size, sizeof(StlClickDefinition)); + } + return cdp; +} + +/// Fill the click definitions array if needed. +void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_recs, char *buf, + int width, bool tabline) +{ + if (click_defs == NULL) { + return; + } + + int col = 0; + int len = 0; + + StlClickDefinition cur_click_def = { + .type = kStlClickDisabled, + }; + for (int i = 0; click_recs[i].start != NULL; i++) { + len += vim_strnsize(buf, (int)(click_recs[i].start - buf)); + if (col < len) { + while (col < len) { + click_defs[col++] = cur_click_def; + } + } else { + xfree(cur_click_def.func); + } + buf = (char *)click_recs[i].start; + cur_click_def = click_recs[i].def; + if (!tabline && !(cur_click_def.type == kStlClickDisabled + || cur_click_def.type == kStlClickFuncRun)) { + // window bar and status line only support click functions + cur_click_def.type = kStlClickDisabled; + } + } + if (col < width) { + while (col < width) { + click_defs[col++] = cur_click_def; + } + } else { + xfree(cur_click_def.func); + } +} + +/// Redraw the status line, window bar or ruler of window "wp". +/// When "wp" is NULL redraw the tab pages line from 'tabline'. +static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) +{ + static bool entered = false; + int attr; + int curattr; + int row; + int col = 0; + int maxwidth; + int width; + int n; + int len; + int fillchar; + char buf[MAXPATHL]; + char *stl; + char *p; + char *opt_name; + int opt_scope = 0; + stl_hlrec_t *hltab; + StlClickRecord *tabtab; + win_T *ewp; + int p_crb_save; + bool is_stl_global = global_stl_height() > 0; + + ScreenGrid *grid = &default_grid; + + // There is a tiny chance that this gets called recursively: When + // redrawing a status line triggers redrawing the ruler or tabline. + // Avoid trouble by not allowing recursion. + if (entered) { + return; + } + entered = true; + + // setup environment for the task at hand + if (wp == NULL) { + // Use 'tabline'. Always at the first line of the screen. + stl = p_tal; + row = 0; + fillchar = ' '; + attr = HL_ATTR(HLF_TPF); + maxwidth = Columns; + opt_name = "tabline"; + } else if (draw_winbar) { + opt_name = "winbar"; + stl = ((*wp->w_p_wbr != NUL) ? wp->w_p_wbr : p_wbr); + opt_scope = ((*wp->w_p_wbr != NUL) ? OPT_LOCAL : 0); + row = -1; // row zero is first row of text + col = 0; + grid = &wp->w_grid; + grid_adjust(&grid, &row, &col); + + if (row < 0) { + return; + } + + fillchar = wp->w_p_fcs_chars.wbr; + attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC); + maxwidth = wp->w_width_inner; + stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size); + wp->w_winbar_click_defs = stl_alloc_click_defs(wp->w_winbar_click_defs, maxwidth, + &wp->w_winbar_click_defs_size); + } else { + row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); + fillchar = fillchar_status(&attr, wp); + maxwidth = is_stl_global ? Columns : wp->w_width; + stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size); + wp->w_status_click_defs = stl_alloc_click_defs(wp->w_status_click_defs, maxwidth, + &wp->w_status_click_defs_size); + + if (draw_ruler) { + stl = p_ruf; + opt_name = "rulerformat"; + // advance past any leading group spec - implicit in ru_col + if (*stl == '%') { + if (*++stl == '-') { + stl++; + } + if (atoi(stl)) { + while (ascii_isdigit(*stl)) { + stl++; + } + } + if (*stl++ != '(') { + stl = p_ruf; + } + } + col = ru_col - (Columns - maxwidth); + if (col < (maxwidth + 1) / 2) { + col = (maxwidth + 1) / 2; + } + maxwidth = maxwidth - col; + if (!wp->w_status_height && !is_stl_global) { + grid = &msg_grid_adj; + row = Rows - 1; + maxwidth--; // writing in last column may cause scrolling + fillchar = ' '; + attr = HL_ATTR(HLF_MSG); + } + } else { + opt_name = "statusline"; + stl = ((*wp->w_p_stl != NUL) ? wp->w_p_stl : p_stl); + opt_scope = ((*wp->w_p_stl != NUL) ? OPT_LOCAL : 0); + } + + col += is_stl_global ? 0 : wp->w_wincol; + } + + if (maxwidth <= 0) { + goto theend; + } + + // Temporarily reset 'cursorbind', we don't want a side effect from moving + // the cursor away and back. + ewp = wp == NULL ? curwin : wp; + p_crb_save = ewp->w_p_crb; + ewp->w_p_crb = false; + + // Make a copy, because the statusline may include a function call that + // might change the option value and free the memory. + stl = xstrdup(stl); + width = build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_name, opt_scope, + fillchar, maxwidth, &hltab, &tabtab, NULL); + + xfree(stl); + ewp->w_p_crb = p_crb_save; + + // Make all characters printable. + p = transstr(buf, true); + len = (int)xstrlcpy(buf, p, sizeof(buf)); + len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1; + xfree(p); + + // fill up with "fillchar" + while (width < maxwidth && len < (int)sizeof(buf) - 1) { + len += utf_char2bytes(fillchar, buf + len); + width++; + } + buf[len] = NUL; + + // Draw each snippet with the specified highlighting. + grid_puts_line_start(grid, row); + + curattr = attr; + p = buf; + for (n = 0; hltab[n].start != NULL; n++) { + int textlen = (int)(hltab[n].start - p); + grid_puts_len(grid, p, textlen, row, col, curattr); + col += vim_strnsize(p, textlen); + p = hltab[n].start; + + if (hltab[n].userhl == 0) { + curattr = attr; + } else if (hltab[n].userhl < 0) { + curattr = syn_id2attr(-hltab[n].userhl); + } else if (wp != NULL && wp != curwin && wp->w_status_height != 0) { + curattr = highlight_stlnc[hltab[n].userhl - 1]; + } else { + curattr = highlight_user[hltab[n].userhl - 1]; + } + } + // Make sure to use an empty string instead of p, if p is beyond buf + len. + grid_puts(grid, p >= buf + len ? "" : p, row, col, curattr); + + grid_puts_line_flush(false); + + // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking + // in the tab page line, status line or window bar + StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs + : draw_winbar ? wp->w_winbar_click_defs + : wp->w_status_click_defs; + + stl_fill_click_defs(click_defs, tabtab, buf, maxwidth, wp == NULL); + +theend: + entered = false; +} + void win_redr_winbar(win_T *wp) { static bool entered = false; @@ -171,19 +448,7 @@ void win_redr_winbar(win_T *wp) if (wp->w_winbar_height == 0 || !redrawing()) { // Do nothing. } else if (*p_wbr != NUL || *wp->w_p_wbr != NUL) { - int saved_did_emsg = did_emsg; - - did_emsg = false; win_redr_custom(wp, true, false); - if (did_emsg) { - // When there is an error disable the winbar, otherwise the - // display is messed up with errors and a redraw triggers the problem - // again and again. - set_string_option_direct("winbar", -1, "", - OPT_FREE | (*wp->w_p_stl != NUL - ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); - } - did_emsg |= saved_did_emsg; } entered = false; } @@ -213,11 +478,7 @@ void win_redr_ruler(win_T *wp, bool always) } if (*p_ruf && p_ch > 0 && !ui_has(kUIMessages)) { - const int called_emsg_before = called_emsg; win_redr_custom(wp, false, true); - if (called_emsg > called_emsg_before) { - set_string_option_direct("rulerformat", -1, "", OPT_FREE, SID_ERROR); - } return; } @@ -266,7 +527,7 @@ void win_redr_ruler(win_T *wp, bool always) off = 0; } - if (!part_of_status && !ui_has_messages()) { + if (!part_of_status && p_ch == 0 && !ui_has(kUIMessages)) { return; } @@ -286,7 +547,7 @@ void win_redr_ruler(win_T *wp, bool always) vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",", (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? (int64_t)0L : (int64_t)wp->w_cursor.lnum); - size_t len = STRLEN(buffer); + size_t len = strlen(buffer); col_print(buffer + len, RULER_BUF_LEN - len, empty_line ? 0 : (int)wp->w_cursor.col + 1, (int)virtcol + 1); @@ -294,7 +555,7 @@ void win_redr_ruler(win_T *wp, bool always) // Add a "50%" if there is room for it. // On the last line, don't print in the last column (scrolls the // screen up on some terminals). - int i = (int)STRLEN(buffer); + int i = (int)strlen(buffer); get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1); int o = i + vim_strsize(buffer + i + 1); if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen @@ -342,9 +603,9 @@ void win_redr_ruler(win_T *wp, bool always) } ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj; - grid_puts(grid, (char_u *)buffer, row, this_ru_col + off, attr); + grid_puts(grid, buffer, row, this_ru_col + off, attr); grid_fill(grid, row, row + 1, - this_ru_col + off + (int)STRLEN(buffer), off + width, fillchar, + this_ru_col + off + (int)strlen(buffer), off + width, fillchar, fillchar, attr); } @@ -388,7 +649,6 @@ int fillchar_status(int *attr, win_T *wp) void redraw_custom_statusline(win_T *wp) { static bool entered = false; - int saved_did_emsg = did_emsg; // When called recursively return. This can happen when the statusline // contains an expression that triggers a redraw. @@ -397,235 +657,262 @@ void redraw_custom_statusline(win_T *wp) } entered = true; - did_emsg = false; win_redr_custom(wp, false, false); - if (did_emsg) { - // When there is an error disable the statusline, otherwise the - // display is messed up with errors and a redraw triggers the problem - // again and again. - set_string_option_direct("statusline", -1, "", - OPT_FREE | (*wp->w_p_stl != NUL - ? OPT_LOCAL : OPT_GLOBAL), SID_ERROR); - } - did_emsg |= saved_did_emsg; entered = false; } -/// Redraw the status line, window bar or ruler of window "wp". -/// When "wp" is NULL redraw the tab pages line from 'tabline'. -void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler) +static void ui_ext_tabline_update(void) { - static bool entered = false; - int attr; - int curattr; - int row; + Arena arena = ARENA_EMPTY; + + size_t n_tabs = 0; + FOR_ALL_TABS(tp) { + n_tabs++; + } + + Array tabs = arena_array(&arena, n_tabs); + FOR_ALL_TABS(tp) { + Dictionary tab_info = arena_dict(&arena, 2); + PUT_C(tab_info, "tab", TABPAGE_OBJ(tp->handle)); + + win_T *cwp = (tp == curtab) ? curwin : tp->tp_curwin; + get_trans_bufname(cwp->w_buffer); + PUT_C(tab_info, "name", STRING_OBJ(arena_string(&arena, cstr_as_string(NameBuff)))); + + ADD_C(tabs, DICTIONARY_OBJ(tab_info)); + } + + size_t n_buffers = 0; + FOR_ALL_BUFFERS(buf) { + n_buffers += buf->b_p_bl ? 1 : 0; + } + + Array buffers = arena_array(&arena, n_buffers); + FOR_ALL_BUFFERS(buf) { + // Do not include unlisted buffers + if (!buf->b_p_bl) { + continue; + } + + Dictionary buffer_info = arena_dict(&arena, 2); + PUT_C(buffer_info, "buffer", BUFFER_OBJ(buf->handle)); + + get_trans_bufname(buf); + PUT_C(buffer_info, "name", STRING_OBJ(arena_string(&arena, cstr_as_string(NameBuff)))); + + ADD_C(buffers, DICTIONARY_OBJ(buffer_info)); + } + + ui_call_tabline_update(curtab->handle, tabs, curbuf->handle, buffers); + arena_mem_free(arena_finish(&arena)); +} + +/// Draw the tab pages line at the top of the Vim window. +void draw_tabline(void) +{ + int tabcount = 0; + int tabwidth = 0; int col = 0; - int maxwidth; - int width; - int n; + int scol = 0; + int attr; + win_T *wp; + win_T *cwp; + int wincount; + int modified; + int c; int len; - int fillchar; - char buf[MAXPATHL]; - char_u *stl; + int attr_nosel = HL_ATTR(HLF_TP); + int attr_fill = HL_ATTR(HLF_TPF); char *p; - stl_hlrec_t *hltab; - StlClickRecord *tabtab; - int use_sandbox = false; - win_T *ewp; - int p_crb_save; - bool is_stl_global = global_stl_height() > 0; + int room; + int use_sep_chars = (t_colors < 8); - ScreenGrid *grid = &default_grid; + if (default_grid.chars == NULL) { + return; + } + redraw_tabline = false; - // There is a tiny chance that this gets called recursively: When - // redrawing a status line triggers redrawing the ruler or tabline. - // Avoid trouble by not allowing recursion. - if (entered) { + if (ui_has(kUITabline)) { + ui_ext_tabline_update(); return; } - entered = true; - // setup environment for the task at hand - if (wp == NULL) { - // Use 'tabline'. Always at the first line of the screen. - stl = p_tal; - row = 0; - fillchar = ' '; - attr = HL_ATTR(HLF_TPF); - maxwidth = Columns; - use_sandbox = was_set_insecurely(wp, "tabline", 0); - } else if (draw_winbar) { - stl = (char_u *)((*wp->w_p_wbr != NUL) ? wp->w_p_wbr : p_wbr); - row = -1; // row zero is first row of text - col = 0; - grid = &wp->w_grid; - grid_adjust(&grid, &row, &col); + if (tabline_height() < 1) { + return; + } - if (row < 0) { - return; - } + // Clear tab_page_click_defs: Clicking outside of tabs has no effect. + assert(tab_page_click_defs_size >= (size_t)Columns); + stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size); - fillchar = wp->w_p_fcs_chars.wbr; - attr = (wp == curwin) ? win_hl_attr(wp, HLF_WBR) : win_hl_attr(wp, HLF_WBRNC); - maxwidth = wp->w_width_inner; - use_sandbox = was_set_insecurely(wp, "winbar", 0); + // Use the 'tabline' option if it's set. + if (*p_tal != NUL) { + win_redr_custom(NULL, false, false); + } else { + FOR_ALL_TABS(tp) { + tabcount++; + } - stl_clear_click_defs(wp->w_winbar_click_defs, (long)wp->w_winbar_click_defs_size); - // Allocate / resize the click definitions array for winbar if needed. - if (wp->w_winbar_height && wp->w_winbar_click_defs_size < (size_t)maxwidth) { - xfree(wp->w_winbar_click_defs); - wp->w_winbar_click_defs_size = (size_t)maxwidth; - wp->w_winbar_click_defs = xcalloc(wp->w_winbar_click_defs_size, sizeof(StlClickRecord)); + if (tabcount > 0) { + tabwidth = (Columns - 1 + tabcount / 2) / tabcount; } - } else { - row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp); - fillchar = fillchar_status(&attr, wp); - maxwidth = is_stl_global ? Columns : wp->w_width; - stl_clear_click_defs(wp->w_status_click_defs, (long)wp->w_status_click_defs_size); - // Allocate / resize the click definitions array for statusline if needed. - if (wp->w_status_click_defs_size < (size_t)maxwidth) { - xfree(wp->w_status_click_defs); - wp->w_status_click_defs_size = (size_t)maxwidth; - wp->w_status_click_defs = xcalloc(wp->w_status_click_defs_size, sizeof(StlClickRecord)); + if (tabwidth < 6) { + tabwidth = 6; } - if (draw_ruler) { - stl = p_ruf; - // advance past any leading group spec - implicit in ru_col - if (*stl == '%') { - if (*++stl == '-') { - stl++; - } - if (atoi((char *)stl)) { - while (ascii_isdigit(*stl)) { - stl++; - } - } - if (*stl++ != '(') { - stl = p_ruf; - } - } - col = ru_col - (Columns - maxwidth); - if (col < (maxwidth + 1) / 2) { - col = (maxwidth + 1) / 2; - } - maxwidth = maxwidth - col; - if (!wp->w_status_height && !is_stl_global) { - grid = &msg_grid_adj; - row = Rows - 1; - maxwidth--; // writing in last column may cause scrolling - fillchar = ' '; - attr = HL_ATTR(HLF_MSG); + attr = attr_nosel; + tabcount = 0; + + FOR_ALL_TABS(tp) { + if (col >= Columns - 4) { + break; } - use_sandbox = was_set_insecurely(wp, "rulerformat", 0); - } else { - if (*wp->w_p_stl != NUL) { - stl = wp->w_p_stl; + scol = col; + + if (tp == curtab) { + cwp = curwin; + wp = firstwin; } else { - stl = p_stl; + cwp = tp->tp_curwin; + wp = tp->tp_firstwin; } - use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); - } - col += is_stl_global ? 0 : wp->w_wincol; - } + if (tp->tp_topframe == topframe) { + attr = win_hl_attr(cwp, HLF_TPS); + } + if (use_sep_chars && col > 0) { + grid_putchar(&default_grid, '|', 0, col++, attr); + } - if (maxwidth <= 0) { - goto theend; - } + if (tp->tp_topframe != topframe) { + attr = win_hl_attr(cwp, HLF_TP); + } - // Temporarily reset 'cursorbind', we don't want a side effect from moving - // the cursor away and back. - ewp = wp == NULL ? curwin : wp; - p_crb_save = ewp->w_p_crb; - ewp->w_p_crb = false; + grid_putchar(&default_grid, ' ', 0, col++, attr); - // Make a copy, because the statusline may include a function call that - // might change the option value and free the memory. - stl = vim_strsave(stl); - width = - build_stl_str_hl(ewp, buf, sizeof(buf), (char *)stl, use_sandbox, - fillchar, maxwidth, &hltab, &tabtab); - xfree(stl); - ewp->w_p_crb = p_crb_save; + modified = false; - // Make all characters printable. - p = transstr(buf, true); - len = (int)STRLCPY(buf, p, sizeof(buf)); - len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1; - xfree(p); + for (wincount = 0; wp != NULL; wp = wp->w_next, wincount++) { + if (bufIsChanged(wp->w_buffer)) { + modified = true; + } + } - // fill up with "fillchar" - while (width < maxwidth && len < (int)sizeof(buf) - 1) { - len += utf_char2bytes(fillchar, buf + len); - width++; - } - buf[len] = NUL; + if (modified || wincount > 1) { + if (wincount > 1) { + vim_snprintf(NameBuff, MAXPATHL, "%d", wincount); + len = (int)strlen(NameBuff); + if (col + len >= Columns - 3) { + break; + } + grid_puts_len(&default_grid, NameBuff, len, 0, col, + hl_combine_attr(attr, win_hl_attr(cwp, HLF_T))); + col += len; + } + if (modified) { + grid_puts_len(&default_grid, "+", 1, 0, col++, attr); + } + grid_putchar(&default_grid, ' ', 0, col++, attr); + } - // Draw each snippet with the specified highlighting. - grid_puts_line_start(grid, row); + room = scol - col + tabwidth - 1; + if (room > 0) { + // Get buffer name in NameBuff[] + get_trans_bufname(cwp->w_buffer); + shorten_dir(NameBuff); + len = vim_strsize(NameBuff); + p = NameBuff; + while (len > room) { + len -= ptr2cells(p); + MB_PTR_ADV(p); + } + if (len > Columns - col - 1) { + len = Columns - col - 1; + } - curattr = attr; - p = buf; - for (n = 0; hltab[n].start != NULL; n++) { - int textlen = (int)(hltab[n].start - p); - grid_puts_len(grid, (char_u *)p, textlen, row, col, curattr); - col += vim_strnsize((char_u *)p, textlen); - p = hltab[n].start; + grid_puts_len(&default_grid, p, (int)strlen(p), 0, col, attr); + col += len; + } + grid_putchar(&default_grid, ' ', 0, col++, attr); + + // Store the tab page number in tab_page_click_defs[], so that + // jump_to_mouse() knows where each one is. + tabcount++; + while (scol < col) { + tab_page_click_defs[scol++] = (StlClickDefinition) { + .type = kStlClickTabSwitch, + .tabnr = tabcount, + .func = NULL, + }; + } + } - if (hltab[n].userhl == 0) { - curattr = attr; - } else if (hltab[n].userhl < 0) { - curattr = syn_id2attr(-hltab[n].userhl); - } else if (wp != NULL && wp != curwin && wp->w_status_height != 0) { - curattr = highlight_stlnc[hltab[n].userhl - 1]; + if (use_sep_chars) { + c = '_'; } else { - curattr = highlight_user[hltab[n].userhl - 1]; + c = ' '; } - } - // Make sure to use an empty string instead of p, if p is beyond buf + len. - grid_puts(grid, p >= buf + len ? (char_u *)"" : (char_u *)p, row, col, - curattr); + grid_fill(&default_grid, 0, 1, col, Columns, c, c, attr_fill); - grid_puts_line_flush(false); + // Draw the 'showcmd' information if 'showcmdloc' == "tabline". + if (p_sc && *p_sloc == 't') { + const int sc_width = MIN(10, (int)Columns - col - (tabcount > 1) * 3); - // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking - // in the tab page line, status line or window bar - StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs - : draw_winbar ? wp->w_winbar_click_defs - : wp->w_status_click_defs; + if (sc_width > 0) { + grid_puts_len(&default_grid, showcmd_buf, sc_width, 0, + Columns - sc_width - (tabcount > 1) * 2, attr_nosel); + } + } - if (click_defs == NULL) { - goto theend; + // Put an "X" for closing the current tab if there are several. + if (tabcount > 1) { + grid_putchar(&default_grid, 'X', 0, Columns - 1, attr_nosel); + tab_page_click_defs[Columns - 1] = (StlClickDefinition) { + .type = kStlClickTabClose, + .tabnr = 999, + .func = NULL, + }; + } } - col = 0; - len = 0; - p = buf; - StlClickDefinition cur_click_def = { - .type = kStlClickDisabled, - }; - for (n = 0; tabtab[n].start != NULL; n++) { - len += vim_strnsize((char_u *)p, (int)(tabtab[n].start - p)); - while (col < len) { - click_defs[col++] = cur_click_def; - } - p = (char *)tabtab[n].start; - cur_click_def = tabtab[n].def; - if ((wp != NULL) && !(cur_click_def.type == kStlClickDisabled - || cur_click_def.type == kStlClickFuncRun)) { - // window bar and status line only support click functions - cur_click_def.type = kStlClickDisabled; - } + // Reset the flag here again, in case evaluating 'tabline' causes it to be + // set. + redraw_tabline = false; +} + +/// Build the 'statuscolumn' string for line "lnum". When "relnum" == -1, +/// the v:lnum and v:relnum variables don't have to be updated. +/// +/// @param hlrec HL attributes (can be NULL) +/// @param stcp Status column attributes (can be NULL) +/// @return The width of the built status column string for line "lnum" +int build_statuscol_str(win_T *wp, linenr_T lnum, long relnum, int maxwidth, int fillchar, + char *buf, stl_hlrec_t **hlrec, statuscol_T *stcp) +{ + bool fillclick = relnum >= 0 && lnum == wp->w_topline; + + if (relnum >= 0) { + set_vim_var_nr(VV_LNUM, lnum); + set_vim_var_nr(VV_RELNUM, relnum); } - while (col < maxwidth) { - click_defs[col++] = cur_click_def; + + StlClickRecord *clickrec; + char *stc = xstrdup(wp->w_p_stc); + int width = build_stl_str_hl(wp, buf, MAXPATHL, stc, "statuscolumn", OPT_LOCAL, fillchar, + maxwidth, hlrec, fillclick ? &clickrec : NULL, stcp); + xfree(stc); + + // Only update click definitions once per window per redraw + if (fillclick) { + stl_clear_click_defs(wp->w_statuscol_click_defs, wp->w_statuscol_click_defs_size); + wp->w_statuscol_click_defs = stl_alloc_click_defs(wp->w_statuscol_click_defs, width, + &wp->w_statuscol_click_defs_size); + stl_fill_click_defs(wp->w_statuscol_click_defs, clickrec, buf, width, false); } -theend: - entered = false; + return width; } /// Build a string from the status line items in "fmt". @@ -646,15 +933,18 @@ theend: /// Note: This should not be NameBuff /// @param outlen The length of the output buffer /// @param fmt The statusline format string -/// @param use_sandbox Use a sandboxed environment when evaluating fmt +/// @param opt_name The option name corresponding to "fmt" +/// @param opt_scope The scope corresponding to "opt_name" /// @param fillchar Character to use when filling empty space in the statusline /// @param maxwidth The maximum width to make the statusline /// @param hltab HL attributes (can be NULL) -/// @param tabtab Tab clicks definition (can be NULL). +/// @param tabtab Tab clicks definition (can be NULL) +/// @param stcp Status column attributes (can be NULL) /// /// @return The final width of the statusline -int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_sandbox, int fillchar, - int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab) +int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_name, int opt_scope, + int fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab, + statuscol_T *stcp) { static size_t stl_items_len = 20; // Initial value, grows as needed. static stl_item_t *stl_items = NULL; @@ -669,6 +959,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san char *usefmt = fmt; const int save_must_redraw = must_redraw; const int save_redr_type = curwin->w_redr_type; + const bool save_KeyTyped = KeyTyped; + // TODO(Bram): find out why using called_emsg_before makes tests fail, does it + // matter? + // const int called_emsg_before = called_emsg; + const int did_emsg_before = did_emsg; if (stl_items == NULL) { stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len); @@ -682,6 +977,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san stl_separator_locations = xmalloc(sizeof(int) * stl_items_len); } + // if "fmt" was set insecurely it needs to be evaluated in the sandbox + // "opt_name" will be NULL when caller is nvim_eval_statusline() + const int use_sandbox = opt_name ? was_set_insecurely(wp, opt_name, opt_scope) + : false; + // When the format starts with "%!" then evaluate it as an expression and // use the result as the actual format string. if (fmt[0] == '%' && fmt[1] == '!') { @@ -712,13 +1012,13 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } // Get line & check if empty (cursorpos will show "0-1"). - const char *line_ptr = (char *)ml_get_buf(wp->w_buffer, lnum, false); + const char *line_ptr = ml_get_buf(wp->w_buffer, lnum, false); bool empty_line = (*line_ptr == NUL); // Get the byte value now, in case we need it below. This is more // efficient than making a copy of the line. int byteval; - const size_t len = STRLEN(line_ptr); + const size_t len = strlen(line_ptr); if (wp->w_cursor.col > (colnr_T)len) { // Line may have changed since checking the cursor column, or the lnum // was adjusted above. @@ -829,7 +1129,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // so `vim_strsize` will work. char *t = stl_items[stl_groupitems[groupdepth]].start; *out_p = NUL; - long group_len = vim_strsize(t); + ptrdiff_t group_len = vim_strsize(t); // If the group contained internal items // and the group did not have a minimum width, @@ -915,7 +1215,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } // If the group is shorter than the minimum width, add padding characters. } else if (abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) { - long min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; + ptrdiff_t min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; // If the group is left-aligned, add characters to the right. if (min_group_width < 0) { min_group_width = 0 - min_group_width; @@ -929,7 +1229,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san group_len = (min_group_width - group_len) * utf_char2len(fillchar); memmove(t + group_len, t, (size_t)(out_p - t)); if (out_p + group_len >= (out_end_p + 1)) { - group_len = (long)(out_end_p - out_p); + group_len = out_end_p - out_p; } out_p += group_len; // } @@ -1037,7 +1337,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } stl_items[curitem].type = ClickFunc; stl_items[curitem].start = out_p; - stl_items[curitem].cmd = xmemdupz(t, (size_t)(fmt_p - t)); + stl_items[curitem].cmd = tabtab ? xmemdupz(t, (size_t)(fmt_p - t)) : NULL; stl_items[curitem].minwid = minwid; fmt_p++; curitem++; @@ -1078,7 +1378,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // An invalid item was specified. // Continue processing on the next character of the format string. - if (vim_strchr(STL_ALL, *fmt_p) == NULL) { + if (vim_strchr(STL_ALL, (uint8_t)(*fmt_p)) == NULL) { fmt_p++; continue; } @@ -1100,17 +1400,17 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // get replaced with the fillchar fillable = false; if (buf_spname(wp->w_buffer) != NULL) { - STRLCPY(NameBuff, buf_spname(wp->w_buffer), MAXPATHL); + xstrlcpy(NameBuff, buf_spname(wp->w_buffer), MAXPATHL); } else { char *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname : wp->w_buffer->b_fname; - home_replace(wp->w_buffer, t, (char *)NameBuff, MAXPATHL, true); + home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, true); } - trans_characters((char *)NameBuff, MAXPATHL); + trans_characters(NameBuff, MAXPATHL); if (opt != STL_FILENAME) { - str = (char *)NameBuff; + str = NameBuff; } else { - str = path_tail((char *)NameBuff); + str = path_tail(NameBuff); } break; case STL_VIM_EXPR: // '{' @@ -1191,8 +1491,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san && strchr((const char *)str, '%') != NULL && evaldepth < MAX_STL_EVAL_DEPTH) { size_t parsed_usefmt = (size_t)(block_start - usefmt); - size_t str_length = STRLEN(str); - size_t fmt_length = STRLEN(fmt_p); + size_t str_length = strlen(str); + size_t fmt_length = strlen(fmt_p); size_t new_fmt_len = parsed_usefmt + str_length + fmt_length + 3; char *new_fmt = xmalloc(new_fmt_len * sizeof(char)); char *new_fmt_p = new_fmt; @@ -1217,8 +1517,14 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } case STL_LINE: - num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) - ? 0L : (long)(wp->w_cursor.lnum); + // Overload %l with v:lnum for 'statuscolumn' + if (opt_name != NULL && strcmp(opt_name, "statuscolumn") == 0) { + if (wp->w_p_nu && !get_vim_var_nr(VV_VIRTNUM)) { + num = get_vim_var_nr(VV_LNUM); + } + } else { + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? 0L : (long)(wp->w_cursor.lnum); + } break; case STL_NUMLINES: @@ -1255,6 +1561,12 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san str = buf_tmp; break; + case STL_SHOWCMD: + if (p_sc && (opt_name == NULL || strcmp(opt_name, p_sloc) == 0)) { + str = showcmd_buf; + } + break; + case STL_ARGLISTSTAT: fillable = false; @@ -1278,7 +1590,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } break; case STL_PAGENUM: - num = printer_page_num; + num = 0; break; case STL_BUFNO: @@ -1310,9 +1622,16 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san case STL_ROFLAG: case STL_ROFLAG_ALT: - itemisflag = true; - if (wp->w_buffer->b_p_ro) { - str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); + // Overload %r with v:relnum for 'statuscolumn' + if (opt_name != NULL && strcmp(opt_name, "statuscolumn") == 0) { + if (wp->w_p_rnu && !get_vim_var_nr(VV_VIRTNUM)) { + num = get_vim_var_nr(VV_RELNUM); + } + } else { + itemisflag = true; + if (wp->w_buffer->b_p_ro) { + str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]"); + } } break; @@ -1324,12 +1643,39 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san } break; + case STL_FOLDCOL: // 'C' for 'statuscolumn' + case STL_SIGNCOL: { // 's' for 'statuscolumn' + if (stcp == NULL) { + break; + } + + bool fold = opt == STL_FOLDCOL; + *buf_tmp = NUL; + for (int i = 0; i <= SIGN_SHOW_MAX; i++) { + char *p = fold ? stcp->fold_text : stcp->sign_text[i]; + if ((!p || !*p) && *buf_tmp == NUL) { + break; + } + stl_items[curitem].type = Highlight; + stl_items[curitem].start = out_p + strlen(buf_tmp); + stl_items[curitem].minwid = !p || (fold && i) ? 0 : fold ? stcp->fold_attr + : stcp->sign_attr[i]; + curitem++; + if (!p || (fold && i)) { + str = buf_tmp; + break; + } + STRCAT(buf_tmp, p); + } + break; + } + case STL_FILETYPE: // Copy the filetype if it is not null and the formatted string will fit // in the temporary buffer // (including the brackets and null terminating character) if (*wp->w_buffer->b_p_ft != NUL - && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) { + && strlen(wp->w_buffer->b_p_ft) < TMPLEN - 3) { vim_snprintf(buf_tmp, sizeof(buf_tmp), "[%s]", wp->w_buffer->b_p_ft); str = buf_tmp; @@ -1342,11 +1688,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // in the temporary buffer // (including the comma and null terminating character) if (*wp->w_buffer->b_p_ft != NUL - && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) { + && strlen(wp->w_buffer->b_p_ft) < TMPLEN - 2) { vim_snprintf(buf_tmp, sizeof(buf_tmp), ",%s", wp->w_buffer->b_p_ft); // Uppercase the file extension for (char *t = buf_tmp; *t != 0; t++) { - *t = (char)TOUPPER_LOC(*t); + *t = (char)TOUPPER_LOC((uint8_t)(*t)); } str = buf_tmp; } @@ -1562,7 +1908,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // Advance the output buffer position to the end of the // number we just printed - out_p += STRLEN(out_p); + out_p += strlen(out_p); // Otherwise, there was nothing to print so mark the item as empty } else { @@ -1595,6 +1941,10 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // What follows is post-processing to handle alignment and highlighting. int width = vim_strsize(out); + // Return truncated width for 'statuscolumn' + if (stcp != NULL && width > maxwidth) { + stcp->truncate = width - maxwidth; + } if (maxwidth > 0 && width > maxwidth) { // Result is too long, must truncate somewhere. int item_idx = 0; @@ -1642,6 +1992,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // the truncation point for (int i = 0; i < itemcnt; i++) { if (stl_items[i].start > trunc_p) { + for (int j = i; j < itemcnt; j++) { + if (stl_items[j].type == ClickFunc) { + XFREE_CLEAR(stl_items[j].cmd); + } + } itemcnt = i; break; } @@ -1670,7 +2025,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san if (width + 1 < maxwidth) { // Advance the pointer to the end of the string - trunc_p = trunc_p + STRLEN(trunc_p); + trunc_p = trunc_p + strlen(trunc_p); } // Fill up for half a double-wide character. @@ -1706,7 +2061,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san // add characters at the separate marker (if there is one) to // fill up the available space. } else if (width < maxwidth - && STRLEN(out) + (size_t)(maxwidth - width) + 1 < outlen) { + && strlen(out) + (size_t)(maxwidth - width) + 1 < outlen) { // Find how many separators there are, which we will use when // figuring out how many groups there are. int num_separators = 0; @@ -1804,5 +2159,18 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, int use_san curwin->w_redr_type = save_redr_type; } + // Check for an error. If there is one the display will be messed up and + // might loop redrawing. Avoid that by making the corresponding option + // empty. + // TODO(Bram): find out why using called_emsg_before makes tests fail, does it + // matter? + // if (called_emsg > called_emsg_before) + if (opt_name && did_emsg > did_emsg_before) { + set_string_option_direct(opt_name, -1, "", OPT_FREE | opt_scope, SID_ERROR); + } + + // A user function may reset KeyTyped, restore it. + KeyTyped = save_KeyTyped; + return width; } diff --git a/src/nvim/statusline.h b/src/nvim/statusline.h index 357a9a821f..f7e36f138c 100644 --- a/src/nvim/statusline.h +++ b/src/nvim/statusline.h @@ -1,7 +1,16 @@ #ifndef NVIM_STATUSLINE_H #define NVIM_STATUSLINE_H +#include <stddef.h> + #include "nvim/buffer_defs.h" +#include "nvim/macros.h" +#include "nvim/statusline_defs.h" + +/// Array defining what should be done when tabline is clicked +EXTERN StlClickDefinition *tab_page_click_defs INIT(= NULL); +/// Size of the tab_page_click_defs array +EXTERN size_t tab_page_click_defs_size INIT(= 0); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "statusline.h.generated.h" diff --git a/src/nvim/statusline_defs.h b/src/nvim/statusline_defs.h new file mode 100644 index 0000000000..eac9dfd690 --- /dev/null +++ b/src/nvim/statusline_defs.h @@ -0,0 +1,26 @@ +#ifndef NVIM_STATUSLINE_DEFS_H +#define NVIM_STATUSLINE_DEFS_H + +#include <stddef.h> + +#include "nvim/macros.h" + +/// Status line click definition +typedef struct { + enum { + kStlClickDisabled = 0, ///< Clicks to this area are ignored. + kStlClickTabSwitch, ///< Switch to the given tab. + kStlClickTabClose, ///< Close given tab. + kStlClickFuncRun, ///< Run user function. + } type; ///< Type of the click. + int tabnr; ///< Tab page number. + char *func; ///< Function to run. +} StlClickDefinition; + +/// Used for tabline clicks +typedef struct { + StlClickDefinition def; ///< Click definition. + const char *start; ///< Location where region starts. +} StlClickRecord; + +#endif // NVIM_STATUSLINE_DEFS_H diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 78312c738c..34b3c38103 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -6,121 +6,80 @@ #include <math.h> #include <stdarg.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include "auto/config.h" #include "nvim/ascii.h" #include "nvim/assert.h" -#include "nvim/buffer.h" #include "nvim/charset.h" -#include "nvim/diff.h" -#include "nvim/edit.h" -#include "nvim/eval.h" #include "nvim/eval/encode.h" -#include "nvim/ex_cmds.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" -#include "nvim/file_search.h" -#include "nvim/fileio.h" -#include "nvim/fold.h" -#include "nvim/func_attr.h" -#include "nvim/getchar.h" -#include "nvim/mark.h" +#include "nvim/gettext.h" +#include "nvim/macros.h" #include "nvim/math.h" #include "nvim/mbyte.h" -#include "nvim/memfile.h" -#include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/move.h" -#include "nvim/ops.h" #include "nvim/option.h" -#include "nvim/os/os.h" -#include "nvim/os/shell.h" -#include "nvim/os_unix.h" -#include "nvim/path.h" -#include "nvim/quickfix.h" -#include "nvim/regexp.h" -#include "nvim/screen.h" -#include "nvim/search.h" -#include "nvim/spell.h" #include "nvim/strings.h" -#include "nvim/syntax.h" -#include "nvim/tag.h" +#include "nvim/types.h" #include "nvim/vim.h" -#include "nvim/window.h" - -/// Copy "string" into newly allocated memory. -char_u *vim_strsave(const char_u *string) - FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL -{ - return (char_u *)xstrdup((char *)string); -} /// Copy up to `len` bytes of `string` into newly allocated memory and /// terminate with a NUL. The allocated memory always has size `len + 1`, even /// when `string` is shorter. -char_u *vim_strnsave(const char_u *string, size_t len) - FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL -{ - // strncpy is intentional: some parts of Vim use `string` shorter than `len` - // and expect the remainder to be zeroed out. - return (char_u *)strncpy(xmallocz(len), (char *)string, len); -} - -/// A clone of vim_strnsave() that uses char* instead of char_u* char *xstrnsave(const char *string, size_t len) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { return strncpy(xmallocz(len), string, len); // NOLINT(runtime/printf) } -/* - * Same as vim_strsave(), but any characters found in esc_chars are preceded - * by a backslash. - */ -char_u *vim_strsave_escaped(const char_u *string, const char_u *esc_chars) +// Same as vim_strsave(), but any characters found in esc_chars are preceded +// by a backslash. +char *vim_strsave_escaped(const char *string, const char *esc_chars) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { return vim_strsave_escaped_ext(string, esc_chars, '\\', false); } -/* - * Same as vim_strsave_escaped(), but when "bsl" is true also escape - * characters where rem_backslash() would remove the backslash. - * Escape the characters with "cc". - */ -char_u *vim_strsave_escaped_ext(const char_u *string, const char_u *esc_chars, char_u cc, bool bsl) +// Same as vim_strsave_escaped(), but when "bsl" is true also escape +// characters where rem_backslash() would remove the backslash. +// Escape the characters with "cc". +char *vim_strsave_escaped_ext(const char *string, const char *esc_chars, char cc, bool bsl) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - /* - * First count the number of backslashes required. - * Then allocate the memory and insert them. - */ + // First count the number of backslashes required. + // Then allocate the memory and insert them. size_t length = 1; // count the trailing NUL - for (const char_u *p = string; *p; p++) { - const size_t l = (size_t)(utfc_ptr2len((char *)p)); + for (const char *p = string; *p; p++) { + const size_t l = (size_t)(utfc_ptr2len(p)); if (l > 1) { length += l; // count a multibyte char p += l - 1; continue; } - if (vim_strchr((char *)esc_chars, *p) != NULL || (bsl && rem_backslash(p))) { + if (vim_strchr(esc_chars, (uint8_t)(*p)) != NULL || (bsl && rem_backslash(p))) { length++; // count a backslash } - ++length; // count an ordinary char + length++; // count an ordinary char } - char_u *escaped_string = xmalloc(length); - char_u *p2 = escaped_string; - for (const char_u *p = string; *p; p++) { - const size_t l = (size_t)(utfc_ptr2len((char *)p)); + char *escaped_string = xmalloc(length); + char *p2 = escaped_string; + for (const char *p = string; *p; p++) { + const size_t l = (size_t)(utfc_ptr2len(p)); if (l > 1) { memcpy(p2, p, l); p2 += l; p += l - 1; // skip multibyte char continue; } - if (vim_strchr((char *)esc_chars, *p) != NULL || (bsl && rem_backslash(p))) { + if (vim_strchr(esc_chars, (uint8_t)(*p)) != NULL || (bsl && rem_backslash(p))) { *p2++ = cc; } *p2++ = *p; @@ -178,29 +137,28 @@ char *vim_strnsave_unquoted(const char *const string, const size_t length) return ret; } -/* - * Escape "string" for use as a shell argument with system(). - * This uses single quotes, except when we know we need to use double quotes - * (MS-Windows without 'shellslash' set). - * Escape a newline, depending on the 'shell' option. - * When "do_special" is true also replace "!", "%", "#" and things starting - * with "<" like "<cfile>". - * When "do_newline" is false do not escape newline unless it is csh shell. - * Returns the result in allocated memory. - */ -char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_newline) +/// Escape "string" for use as a shell argument with system(). +/// This uses single quotes, except when we know we need to use double quotes +/// (MS-Windows without 'shellslash' set). +/// Escape a newline, depending on the 'shell' option. +/// When "do_special" is true also replace "!", "%", "#" and things starting +/// with "<" like "<cfile>". +/// When "do_newline" is false do not escape newline unless it is csh shell. +/// +/// @return the result in allocated memory. +char *vim_strsave_shellescape(const char *string, bool do_special, bool do_newline) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { char *d; - char_u *escaped_string; + char *escaped_string; size_t l; int csh_like; bool fish_like; - /* Only csh and similar shells expand '!' within single quotes. For sh and - * the like we must not put a backslash before it, it will be taken - * literally. If do_special is set the '!' will be escaped twice. - * Csh also needs to have "\n" escaped twice when do_special is set. */ + // Only csh and similar shells expand '!' within single quotes. For sh and + // the like we must not put a backslash before it, it will be taken + // literally. If do_special is set the '!' will be escaped twice. + // Csh also needs to have "\n" escaped twice when do_special is set. csh_like = csh_like_shell(); // Fish shell uses '\' as an escape character within single quotes, so '\' @@ -208,9 +166,9 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n fish_like = fish_like_shell(); // First count the number of extra bytes required. - size_t length = STRLEN(string) + 3; // two quotes and a trailing NUL - for (const char_u *p = string; *p != NUL; MB_PTR_ADV(p)) { -#ifdef WIN32 + size_t length = strlen(string) + 3; // two quotes and a trailing NUL + for (const char *p = string; *p != NUL; MB_PTR_ADV(p)) { +#ifdef MSWIN if (!p_ssl) { if (*p == '"') { length++; // " -> "" @@ -222,13 +180,13 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n } if ((*p == '\n' && (csh_like || do_newline)) || (*p == '!' && (csh_like || do_special))) { - ++length; // insert backslash + length++; // insert backslash if (csh_like && do_special) { - ++length; // insert backslash + length++; // insert backslash } } if (do_special && find_cmdline_var(p, &l) >= 0) { - ++length; // insert backslash + length++; // insert backslash p += l - 1; } if (*p == '\\' && fish_like) { @@ -238,18 +196,18 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n // Allocate memory for the result and fill it. escaped_string = xmalloc(length); - d = (char *)escaped_string; + d = escaped_string; // add opening quote -#ifdef WIN32 +#ifdef MSWIN if (!p_ssl) { *d++ = '"'; } else #endif *d++ = '\''; - for (const char *p = (char *)string; *p != NUL;) { -#ifdef WIN32 + for (const char *p = string; *p != NUL;) { +#ifdef MSWIN if (!p_ssl) { if (*p == '"') { *d++ = '"'; @@ -276,7 +234,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n *d++ = *p++; continue; } - if (do_special && find_cmdline_var((char_u *)p, &l) >= 0) { + if (do_special && find_cmdline_var(p, &l) >= 0) { *d++ = '\\'; // insert backslash while (--l != SIZE_MAX) { // copy the var *d++ = *p++; @@ -293,7 +251,7 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n } // add terminating quote and finish with a NUL -#ifdef WIN32 +#ifdef MSWIN if (!p_ssl) { *d++ = '"'; } else @@ -304,41 +262,35 @@ char_u *vim_strsave_shellescape(const char_u *string, bool do_special, bool do_n return escaped_string; } -/* - * Like vim_strsave(), but make all characters uppercase. - * This uses ASCII lower-to-upper case translation, language independent. - */ -char_u *vim_strsave_up(const char_u *string) +// Like vim_strsave(), but make all characters uppercase. +// This uses ASCII lower-to-upper case translation, language independent. +char *vim_strsave_up(const char *string) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - char_u *p1; + char *p1; - p1 = vim_strsave(string); + p1 = xstrdup(string); vim_strup(p1); return p1; } -/* - * Like vim_strnsave(), but make all characters uppercase. - * This uses ASCII lower-to-upper case translation, language independent. - */ -char_u *vim_strnsave_up(const char_u *string, size_t len) +/// Like xstrnsave(), but make all characters uppercase. +/// This uses ASCII lower-to-upper case translation, language independent. +char *vim_strnsave_up(const char *string, size_t len) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - char_u *p1 = vim_strnsave(string, len); + char *p1 = xstrnsave(string, len); vim_strup(p1); return p1; } -/* - * ASCII lower-to-upper case translation, language independent. - */ -void vim_strup(char_u *p) +// ASCII lower-to-upper case translation, language independent. +void vim_strup(char *p) FUNC_ATTR_NONNULL_ALL { - char_u c; - while ((c = *p) != NUL) { - *p++ = (char_u)(c < 'a' || c > 'z' ? c : c - 0x20); + uint8_t c; + while ((c = (uint8_t)(*p)) != NUL) { + *p++ = (char)(uint8_t)(c < 'a' || c > 'z' ? c : c - 0x20); } } @@ -361,7 +313,7 @@ char *strcase_save(const char *const orig, bool upper) int l = utf_ptr2len(p); if (c == 0) { // overlong sequence, use only the first byte - c = (char_u)(*p); + c = (uint8_t)(*p); l = 1; } int uc = upper ? mb_toupper(c) : mb_tolower(c); @@ -371,7 +323,7 @@ char *strcase_save(const char *const orig, bool upper) int newl = utf_char2len(uc); if (newl != l) { // TODO(philix): use xrealloc() in strcase_save() - char *s = xmalloc(STRLEN(res) + (size_t)(1 + newl - l)); + char *s = xmalloc(strlen(res) + (size_t)(1 + newl - l)); memcpy(s, res, (size_t)(p - res)); STRCPY(s + (p - res) + newl, p + l); p = s + (p - res); @@ -386,15 +338,13 @@ char *strcase_save(const char *const orig, bool upper) return res; } -/* - * delete spaces at the end of a string - */ -void del_trailing_spaces(char_u *ptr) +// delete spaces at the end of a string +void del_trailing_spaces(char *ptr) FUNC_ATTR_NONNULL_ALL { - char_u *q; + char *q; - q = ptr + STRLEN(ptr); + q = ptr + strlen(ptr); while (--q > ptr && ascii_iswhite(q[0]) && q[-1] != '\\' && q[-1] != Ctrl_V) { *q = NUL; } @@ -413,18 +363,16 @@ size_t xstrnlen(const char *s, size_t n) #endif #if (!defined(HAVE_STRCASECMP) && !defined(HAVE_STRICMP)) -/* - * Compare two strings, ignoring case, using current locale. - * Doesn't work for multi-byte characters. - * return 0 for match, < 0 for smaller, > 0 for bigger - */ +// Compare two strings, ignoring case, using current locale. +// Doesn't work for multi-byte characters. +// return 0 for match, < 0 for smaller, > 0 for bigger int vim_stricmp(const char *s1, const char *s2) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE { int i; for (;;) { - i = (int)TOLOWER_LOC(*s1) - (int)TOLOWER_LOC(*s2); + i = (int)TOLOWER_LOC((uint8_t)(*s1)) - (int)TOLOWER_LOC((uint8_t)(*s2)); if (i != 0) { return i; // this character different } @@ -439,18 +387,16 @@ int vim_stricmp(const char *s1, const char *s2) #endif #if (!defined(HAVE_STRNCASECMP) && !defined(HAVE_STRNICMP)) -/* - * Compare two strings, for length "len", ignoring case, using current locale. - * Doesn't work for multi-byte characters. - * return 0 for match, < 0 for smaller, > 0 for bigger - */ +// Compare two strings, for length "len", ignoring case, using current locale. +// Doesn't work for multi-byte characters. +// return 0 for match, < 0 for smaller, > 0 for bigger int vim_strnicmp(const char *s1, const char *s2, size_t len) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE { int i; while (len > 0) { - i = (int)TOLOWER_LOC(*s1) - (int)TOLOWER_LOC(*s2); + i = (int)TOLOWER_LOC((uint8_t)(*s1)) - (int)TOLOWER_LOC((uint8_t)(*s2)); if (i != 0) { return i; // this character different } @@ -488,9 +434,7 @@ char *vim_strchr(const char *const string, const int c) } } -/* - * Sort an array of strings. - */ +// Sort an array of strings. #ifdef INCLUDE_GENERATED_DECLARATIONS # include "strings.c.generated.h" @@ -498,26 +442,24 @@ char *vim_strchr(const char *const string, const int c) static int sort_compare(const void *s1, const void *s2) FUNC_ATTR_NONNULL_ALL { - return STRCMP(*(char **)s1, *(char **)s2); + return strcmp(*(char **)s1, *(char **)s2); } void sort_strings(char **files, int count) { - qsort((void *)files, (size_t)count, sizeof(char_u *), sort_compare); + qsort((void *)files, (size_t)count, sizeof(char *), sort_compare); } -/* - * Return true if string "s" contains a non-ASCII character (128 or higher). - * When "s" is NULL false is returned. - */ -bool has_non_ascii(const char_u *s) +// Return true if string "s" contains a non-ASCII character (128 or higher). +// When "s" is NULL false is returned. +bool has_non_ascii(const char *s) FUNC_ATTR_PURE { - const char_u *p; + const char *p; if (s != NULL) { - for (p = s; *p != NUL; ++p) { - if (*p >= 128) { + for (p = s; *p != NUL; p++) { + if ((uint8_t)(*p) >= 128) { return true; } } @@ -540,14 +482,12 @@ bool has_non_ascii_len(const char *const s, const size_t len) return false; } -/* - * Concatenate two strings and return the result in allocated memory. - */ -char_u *concat_str(const char_u *restrict str1, const char_u *restrict str2) +/// Concatenate two strings and return the result in allocated memory. +char *concat_str(const char *restrict str1, const char *restrict str2) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - size_t l = STRLEN(str1); - char_u *dest = xmalloc(l + STRLEN(str2) + 1); + size_t l = strlen(str1); + char *dest = xmalloc(l + strlen(str2) + 1); STRCPY(dest, str1); STRCPY(dest + l, str2); return dest; @@ -646,10 +586,9 @@ static const void *tv_ptr(const typval_T *const tvs, int *const idxp) if (tvs[idx].v_type == VAR_UNKNOWN) { emsg(_(e_printf)); return NULL; - } else { - (*idxp)++; - return tvs[idx].vval.v_string; } + (*idxp)++; + return tvs[idx].vval.v_string; } /// Get float argument from idxp entry in tvs @@ -1007,18 +946,18 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t - str_arg); } if (fmt_spec == 'S') { - char_u *p1; + char *p1; size_t i; - for (i = 0, p1 = (char_u *)str_arg; *p1; p1 += utfc_ptr2len((char *)p1)) { - size_t cell = (size_t)utf_ptr2cells((char *)p1); + for (i = 0, p1 = (char *)str_arg; *p1; p1 += utfc_ptr2len(p1)) { + size_t cell = (size_t)utf_ptr2cells(p1); if (precision_specified && i + cell > precision) { break; } i += cell; } - str_arg_l = (size_t)(p1 - (char_u *)str_arg); + str_arg_l = (size_t)(p1 - str_arg); if (min_field_width != 0) { min_field_width += str_arg_l - i; } @@ -1077,13 +1016,11 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t : va_arg(ap, long long)); // NOLINT (runtime/int) break; case 'z': - arg = (tvs - ? (ptrdiff_t)tv_nr(tvs, &arg_idx) - : va_arg(ap, ptrdiff_t)); + arg = (tvs ? (ptrdiff_t)tv_nr(tvs, &arg_idx) : va_arg(ap, ptrdiff_t)); break; } if (arg > 0) { - arg_sign = 1; + arg_sign = 1; } else if (arg < 0) { arg_sign = -1; } @@ -1091,19 +1028,13 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t // unsigned switch (length_modifier) { case '\0': - uarg = (unsigned int)(tvs - ? tv_nr(tvs, &arg_idx) - : va_arg(ap, unsigned int)); + uarg = (unsigned int)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned int)); break; case 'h': - uarg = (uint16_t)(tvs - ? tv_nr(tvs, &arg_idx) - : va_arg(ap, unsigned int)); + uarg = (uint16_t)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned int)); break; case 'l': - uarg = (tvs - ? (unsigned long)tv_nr(tvs, &arg_idx) - : va_arg(ap, unsigned long)); + uarg = (tvs ? (unsigned long)tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned long)); break; case '2': uarg = (uintmax_t)(unsigned long long)( // NOLINT (runtime/int) @@ -1113,9 +1044,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t : va_arg(ap, unsigned long long)); // NOLINT (runtime/int) break; case 'z': - uarg = (tvs - ? (size_t)tv_nr(tvs, &arg_idx) - : va_arg(ap, size_t)); + uarg = (tvs ? (size_t)tv_nr(tvs, &arg_idx) : va_arg(ap, size_t)); break; } arg_sign = (uarg != 0); @@ -1511,15 +1440,15 @@ int kv_do_printf(StringBuilder *str, const char *fmt, ...) /// Reverse text into allocated memory. /// /// @return the allocated string. -char_u *reverse_text(char_u *s) +char *reverse_text(char *s) FUNC_ATTR_NONNULL_RET { // Reverse the pattern. - size_t len = STRLEN(s); - char_u *rev = xmalloc(len + 1); + size_t len = strlen(s); + char *rev = xmalloc(len + 1); size_t rev_i = len; for (size_t s_i = 0; s_i < len; s_i++) { - const int mb_len = utfc_ptr2len((char *)s + s_i); + const int mb_len = utfc_ptr2len(s + s_i); rev_i -= (size_t)mb_len; memmove(rev + rev_i, s + s_i, (size_t)mb_len); s_i += (size_t)mb_len - 1; @@ -1540,7 +1469,7 @@ char_u *reverse_text(char_u *s) char *strrep(const char *src, const char *what, const char *rep) { char *pos = (char *)src; - size_t whatlen = STRLEN(what); + size_t whatlen = strlen(what); // Count occurrences size_t count = 0; @@ -1553,8 +1482,8 @@ char *strrep(const char *src, const char *what, const char *rep) return NULL; } - size_t replen = STRLEN(rep); - char *ret = xmalloc(STRLEN(src) + count * (replen - whatlen) + 1); + size_t replen = strlen(rep); + char *ret = xmalloc(strlen(src) + count * (replen - whatlen) + 1); char *ptr = ret; while ((pos = strstr(src, what)) != NULL) { size_t idx = (size_t)(pos - src); diff --git a/src/nvim/strings.h b/src/nvim/strings.h index 9ef1eb5816..6ad9daf5bf 100644 --- a/src/nvim/strings.h +++ b/src/nvim/strings.h @@ -5,8 +5,8 @@ #include <stdbool.h> #include <string.h> +#include "klib/kvec.h" #include "nvim/eval/typval.h" -#include "nvim/lib/kvec.h" #include "nvim/types.h" /// Append string to string and return pointer to the next byte diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 2a3ec56451..05c570e52f 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -1,56 +1,49 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * syntax.c: code for syntax highlighting - */ +// syntax.c: code for syntax highlighting #include <assert.h> -#include <ctype.h> #include <inttypes.h> #include <stdbool.h> #include <stdlib.h> #include <string.h> -#include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" -#include "nvim/cursor_shape.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/hashtab.h" -#include "nvim/highlight.h" +#include "nvim/highlight_defs.h" #include "nvim/highlight_group.h" #include "nvim/indent_c.h" -#include "nvim/keycodes.h" -#include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/option.h" +#include "nvim/option_defs.h" #include "nvim/optionstr.h" #include "nvim/os/input.h" -#include "nvim/os/os.h" -#include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/profile.h" #include "nvim/regexp.h" -#include "nvim/sign.h" +#include "nvim/runtime.h" #include "nvim/strings.h" #include "nvim/syntax.h" -#include "nvim/syntax_defs.h" -#include "nvim/terminal.h" -#include "nvim/ui.h" +#include "nvim/types.h" #include "nvim/vim.h" static bool did_syntax_onoff = false; @@ -93,22 +86,20 @@ typedef struct syn_pattern { int16_t *sp_cont_list; // cont. group IDs, if non-zero int16_t *sp_next_list; // next group IDs, if non-zero struct sp_syn sp_syn; // struct passed to in_id_list() - char_u *sp_pattern; // regexp to match, pattern + char *sp_pattern; // regexp to match, pattern regprog_T *sp_prog; // regexp to match, program syn_time_T sp_time; } synpat_T; typedef struct syn_cluster_S { - char_u *scl_name; // syntax cluster name - char_u *scl_name_u; // uppercase of scl_name + char *scl_name; // syntax cluster name + char *scl_name_u; // uppercase of scl_name int16_t *scl_list; // IDs in this syntax cluster } syn_cluster_T; -/* - * For the current state we need to remember more than just the idx. - * When si_m_endpos.lnum is 0, the items other than si_idx are unknown. - * (The end positions have the column number of the next char) - */ +// For the current state we need to remember more than just the idx. +// When si_m_endpos.lnum is 0, the items other than si_idx are unknown. +// (The end positions have the column number of the next char) typedef struct state_item { int si_idx; // index of syntax pattern or // KEYWORD_IDX @@ -133,10 +124,8 @@ typedef struct state_item { // pattern } stateitem_T; -/* - * Struct to reduce the number of arguments to get_syn_options(), it's used - * very often. - */ +// Struct to reduce the number of arguments to get_syn_options(), it's used +// very often. typedef struct { int flags; // flags for contained and transparent bool keyword; // true for ":syn keyword" @@ -155,7 +144,7 @@ typedef struct { proftime_T slowest; proftime_T average; int id; - char_u *pattern; + char *pattern; } time_entry_T; struct name_list { @@ -184,9 +173,7 @@ static char *(spo_name_tab[SPO_COUNT]) = #define NONE_IDX (-2) // value of sp_sync_idx for "NONE" -/* - * Flags for b_syn_sync_flags: - */ +// Flags for b_syn_sync_flags: #define SF_CCOMMENT 0x01 // sync on a C-style comment #define SF_MATCH 0x02 // sync by matching a pattern @@ -194,9 +181,7 @@ static char *(spo_name_tab[SPO_COUNT]) = #define MAXKEYWLEN 80 // maximum length of a keyword -/* - * The attributes of the syntax item that has been recognized. - */ +// The attributes of the syntax item that has been recognized. static int current_attr = 0; // attr of current syntax word static int current_id = 0; // ID of current char for syn_get_id() static int current_trans_id = 0; // idem, transparency removed @@ -204,23 +189,19 @@ static int current_flags = 0; static int current_seqnr = 0; static int current_sub_char = 0; -/* - * Methods of combining two clusters - */ +// Methods of combining two clusters #define CLUSTER_REPLACE 1 // replace first list with second #define CLUSTER_ADD 2 // add second list to first #define CLUSTER_SUBTRACT 3 // subtract second list from first #define SYN_CLSTR(buf) ((syn_cluster_T *)((buf)->b_syn_clusters.ga_data)) -/* - * Syntax group IDs have different types: - * 0 - 19999 normal syntax groups - * 20000 - 20999 ALLBUT indicator (current_syn_inc_tag added) - * 21000 - 21999 TOP indicator (current_syn_inc_tag added) - * 22000 - 22999 CONTAINED indicator (current_syn_inc_tag added) - * 23000 - 32767 cluster IDs (subtract SYNID_CLUSTER for the cluster ID) - */ +// Syntax group IDs have different types: +// 0 - 19999 normal syntax groups +// 20000 - 20999 ALLBUT indicator (current_syn_inc_tag added) +// 21000 - 21999 TOP indicator (current_syn_inc_tag added) +// 22000 - 22999 CONTAINED indicator (current_syn_inc_tag added) +// 23000 - 32767 cluster IDs (subtract SYNID_CLUSTER for the cluster ID) #define SYNID_ALLBUT MAX_HL_ID // syntax group ID for contains=ALLBUT #define SYNID_TOP 21000 // syntax group ID for contains=TOP #define SYNID_CONTAINED 22000 // syntax group ID for contains=CONTAINED @@ -234,33 +215,27 @@ static int current_sub_char = 0; // instead of passing it to them, we stow it here. static char **syn_cmdlinep; -/* - * Another Annoying Hack(TM): To prevent rules from other ":syn include"'d - * files from leaking into ALLBUT lists, we assign a unique ID to the - * rules in each ":syn include"'d file. - */ +// Another Annoying Hack(TM): To prevent rules from other ":syn include"'d +// files from leaking into ALLBUT lists, we assign a unique ID to the +// rules in each ":syn include"'d file. static int current_syn_inc_tag = 0; static int running_syn_inc_tag = 0; -/* - * In a hashtable item "hi_key" points to "keyword" in a keyentry. - * This avoids adding a pointer to the hashtable item. - * KE2HIKEY() converts a var pointer to a hashitem key pointer. - * HIKEY2KE() converts a hashitem key pointer to a var pointer. - * HI2KE() converts a hashitem pointer to a var pointer. - */ +// In a hashtable item "hi_key" points to "keyword" in a keyentry. +// This avoids adding a pointer to the hashtable item. +// KE2HIKEY() converts a var pointer to a hashitem key pointer. +// HIKEY2KE() converts a hashitem key pointer to a var pointer. +// HI2KE() converts a hashitem pointer to a var pointer. static keyentry_T dumkey; #define KE2HIKEY(kp) ((kp)->keyword) -#define HIKEY2KE(p) ((keyentry_T *)((p) - (dumkey.keyword - (char_u *)&dumkey))) +#define HIKEY2KE(p) ((keyentry_T *)((p) - (dumkey.keyword - (char *)&dumkey))) #define HI2KE(hi) HIKEY2KE((hi)->hi_key) // -V:HI2KE:782 -/* - * To reduce the time spent in keepend(), remember at which level in the state - * stack the first item with "keepend" is present. When "-1", there is no - * "keepend" on the stack. - */ +// To reduce the time spent in keepend(), remember at which level in the state +// stack the first item with "keepend" is present. When "-1", there is no +// "keepend" on the stack. static int keepend_level = -1; static char msg_no_items[] = N_("No Syntax items defined for this buffer"); @@ -272,13 +247,11 @@ static char msg_no_items[] = N_("No Syntax items defined for this buffer"); static int next_seqnr = 1; // value to use for si_seqnr -/* - * The next possible match in the current line for any pattern is remembered, - * to avoid having to try for a match in each column. - * If next_match_idx == -1, not tried (in this line) yet. - * If next_match_col == MAXCOL, no match found in this line. - * (All end positions have the column of the char after the end) - */ +// The next possible match in the current line for any pattern is remembered, +// to avoid having to try for a match in each column. +// If next_match_idx == -1, not tried (in this line) yet. +// If next_match_col == MAXCOL, no match found in this line. +// (All end positions have the column of the char after the end) static int next_match_col; // column for start of next match static lpos_T next_match_m_endpos; // position for end of next match static lpos_T next_match_h_startpos; // pos. for highl. start of next match @@ -290,17 +263,13 @@ static lpos_T next_match_eoe_pos; // pos. for end of end pattern static int next_match_end_idx; // ID of group for end pattn or zero static reg_extmatch_T *next_match_extmatch = NULL; -/* - * A state stack is an array of integers or stateitem_T, stored in a - * garray_T. A state stack is invalid if its itemsize entry is zero. - */ +// A state stack is an array of integers or stateitem_T, stored in a +// garray_T. A state stack is invalid if its itemsize entry is zero. #define INVALID_STATE(ssp) ((ssp)->ga_itemsize == 0) #define VALID_STATE(ssp) ((ssp)->ga_itemsize != 0) -/* - * The current state (within the line) of the recognition engine. - * When current_state.ga_itemsize is 0 the current state is invalid. - */ +// The current state (within the line) of the recognition engine. +// When current_state.ga_itemsize is 0 the current state is invalid. static win_T *syn_win; // current window for highlighting static buf_T *syn_buf; // current buffer for highlighting static synblock_T *syn_block; // current buffer for highlighting @@ -328,13 +297,11 @@ void syn_set_timeout(proftime_T *tm) syn_tm = tm; } -/* - * Start the syntax recognition for a line. This function is normally called - * from the screen updating, once for each displayed line. - * The buffer is remembered in syn_buf, because get_syntax_attr() doesn't get - * it. Careful: curbuf and curwin are likely to point to another buffer and - * window. - */ +// Start the syntax recognition for a line. This function is normally called +// from the screen updating, once for each displayed line. +// The buffer is remembered in syn_buf, because get_syntax_attr() doesn't get +// it. Careful: curbuf and curwin are likely to point to another buffer and +// window. void syntax_start(win_T *wp, linenr_T lnum) { synstate_T *p; @@ -348,11 +315,9 @@ void syntax_start(win_T *wp, linenr_T lnum) current_sub_char = NUL; - /* - * After switching buffers, invalidate current_state. - * Also do this when a change was made, the current state may be invalid - * then. - */ + // After switching buffers, invalidate current_state. + // Also do this when a change was made, the current state may be invalid + // then. if (syn_block != wp->w_s || syn_buf != wp->w_buffer || changedtick != buf_get_changedtick(syn_buf)) { @@ -363,18 +328,14 @@ void syntax_start(win_T *wp, linenr_T lnum) changedtick = buf_get_changedtick(syn_buf); syn_win = wp; - /* - * Allocate syntax stack when needed. - */ + // Allocate syntax stack when needed. syn_stack_alloc(); if (syn_block->b_sst_array == NULL) { return; // out of memory } syn_block->b_sst_lasttick = display_tick; - /* - * If the state of the end of the previous line is useful, store it. - */ + // If the state of the end of the previous line is useful, store it. if (VALID_STATE(¤t_state) && current_lnum < lnum && current_lnum < syn_buf->b_ml.ml_line_count) { @@ -384,11 +345,9 @@ void syntax_start(win_T *wp, linenr_T lnum) (void)store_current_state(); } - /* - * If the current_lnum is now the same as "lnum", keep the current - * state (this happens very often!). Otherwise invalidate - * current_state and figure it out below. - */ + // If the current_lnum is now the same as "lnum", keep the current + // state (this happens very often!). Otherwise invalidate + // current_state and figure it out below. if (current_lnum != lnum) { invalidate_current_state(); } @@ -396,10 +355,8 @@ void syntax_start(win_T *wp, linenr_T lnum) invalidate_current_state(); } - /* - * Try to synchronize from a saved state in b_sst_array[]. - * Only do this if lnum is not before and not to far beyond a saved state. - */ + // Try to synchronize from a saved state in b_sst_array[]. + // Only do this if lnum is not before and not to far beyond a saved state. if (INVALID_STATE(¤t_state) && syn_block->b_sst_array != NULL) { // Find last valid saved state before start_lnum. for (p = syn_block->b_sst_first; p != NULL; p = p->sst_next) { @@ -418,10 +375,8 @@ void syntax_start(win_T *wp, linenr_T lnum) } } - /* - * If "lnum" is before or far beyond a line with a saved state, need to - * re-synchronize. - */ + // If "lnum" is before or far beyond a line with a saved state, need to + // re-synchronize. if (INVALID_STATE(¤t_state)) { syn_sync(wp, lnum, last_valid); if (current_lnum == 1) { @@ -436,10 +391,8 @@ void syntax_start(win_T *wp, linenr_T lnum) first_stored = current_lnum; } - /* - * Advance from the sync point or saved state until the current line. - * Save some entries for syncing with later on. - */ + // Advance from the sync point or saved state until the current line. + // Save some entries for syncing with later on. if (syn_block->b_sst_len <= Rows) { dist = 999999; } else { @@ -506,10 +459,8 @@ void syntax_start(win_T *wp, linenr_T lnum) syn_start_line(); } -/* - * We cannot simply discard growarrays full of state_items or buf_states; we - * have to manually release their extmatch pointers first. - */ +// We cannot simply discard growarrays full of state_items or buf_states; we +// have to manually release their extmatch pointers first. static void clear_syn_state(synstate_T *p) { if (p->sst_stacksize > SST_FIX_STATES) { @@ -522,24 +473,20 @@ static void clear_syn_state(synstate_T *p) } } -/* - * Cleanup the current_state stack. - */ +// Cleanup the current_state stack. static void clear_current_state(void) { #define UNREF_STATEITEM_EXTMATCH(si) unref_extmatch((si)->si_extmatch) GA_DEEP_CLEAR(¤t_state, stateitem_T, UNREF_STATEITEM_EXTMATCH); } -/* - * Try to find a synchronisation point for line "lnum". - * - * This sets current_lnum and the current state. One of three methods is - * used: - * 1. Search backwards for the end of a C-comment. - * 2. Search backwards for given sync patterns. - * 3. Simply start on a given number of lines above "lnum". - */ +// Try to find a synchronisation point for line "lnum". +// +// This sets current_lnum and the current state. One of three methods is +// used: +// 1. Search backwards for the end of a C-comment. +// 2. Search backwards for given sync patterns. +// 3. Simply start on a given number of lines above "lnum". static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) { buf_T *curbuf_save; @@ -552,7 +499,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) bool had_sync_point; stateitem_T *cur_si; synpat_T *spp; - char_u *line; + char *line; int found_flags = 0; int found_match_idx = 0; linenr_T found_current_lnum = 0; @@ -560,19 +507,15 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) lpos_T found_m_endpos; colnr_T prev_current_col; - /* - * Clear any current state that might be hanging around. - */ + // Clear any current state that might be hanging around. invalidate_current_state(); - /* - * Start at least "minlines" back. Default starting point for parsing is - * there. - * Start further back, to avoid that scrolling backwards will result in - * resyncing for every line. Now it resyncs only one out of N lines, - * where N is minlines * 1.5, or minlines * 2 if minlines is small. - * Watch out for overflow when minlines is MAXLNUM. - */ + // Start at least "minlines" back. Default starting point for parsing is + // there. + // Start further back, to avoid that scrolling backwards will result in + // resyncing for every line. Now it resyncs only one out of N lines, + // where N is minlines * 1.5, or minlines * 2 if minlines is small. + // Watch out for overflow when minlines is MAXLNUM. if (syn_block->b_syn_sync_minlines > start_lnum) { start_lnum = 1; } else { @@ -595,9 +538,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) } current_lnum = start_lnum; - /* - * 1. Search backwards for the end of a C-style comment. - */ + // 1. Search backwards for the end of a C-style comment. if (syn_block->b_syn_sync_flags & SF_CCOMMENT) { // Need to make syn_buf the current buffer for a moment, to be able to // use find_start_comment(). @@ -606,12 +547,10 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) curbuf_save = curbuf; curbuf = syn_buf; - /* - * Skip lines that end in a backslash. - */ - for (; start_lnum > 1; --start_lnum) { + // Skip lines that end in a backslash. + for (; start_lnum > 1; start_lnum--) { line = ml_get(start_lnum - 1); - if (*line == NUL || *(line + STRLEN(line) - 1) != '\\') { + if (*line == NUL || *(line + strlen(line) - 1) != '\\') { break; } } @@ -622,11 +561,9 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) wp->w_cursor.lnum = start_lnum; wp->w_cursor.col = 0; - /* - * If the line is inside a comment, need to find the syntax item that - * defines the comment. - * Restrict the search for the end of a comment to b_syn_sync_maxlines. - */ + // If the line is inside a comment, need to find the syntax item that + // defines the comment. + // Restrict the search for the end of a comment to b_syn_sync_maxlines. if (find_start_comment((int)syn_block->b_syn_sync_maxlines) != NULL) { for (idx = syn_block->b_syn_patterns.ga_len; --idx >= 0;) { if (SYN_ITEMS(syn_block)[idx].sp_syn.id @@ -644,11 +581,8 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) wp->w_cursor = cursor_save; curwin = curwin_save; curbuf = curbuf_save; - } - /* - * 2. Search backwards for given sync patterns. - */ - else if (syn_block->b_syn_sync_flags & SF_MATCH) { + } else if (syn_block->b_syn_sync_flags & SF_MATCH) { + // 2. Search backwards for given sync patterns. if (syn_block->b_syn_sync_maxlines != 0 && start_lnum > syn_block->b_syn_sync_maxlines) { break_lnum = start_lnum - syn_block->b_syn_sync_maxlines; @@ -675,19 +609,15 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) break; } - /* - * Check if the previous line has the line-continuation pattern. - */ + // Check if the previous line has the line-continuation pattern. if (lnum > 1 && syn_match_linecont(lnum - 1)) { continue; } - /* - * Start with nothing on the state stack - */ + // Start with nothing on the state stack validate_current_state(); - for (current_lnum = lnum; current_lnum < end_lnum; ++current_lnum) { + for (current_lnum = lnum; current_lnum < end_lnum; current_lnum++) { syn_start_line(); for (;;) { had_sync_point = syn_finish_line(true); @@ -712,10 +642,8 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) found_current_lnum = current_lnum; found_current_col = current_col; found_m_endpos = cur_si->si_m_endpos; - /* - * Continue after the match (be aware of a zero-length - * match). - */ + // Continue after the match (be aware of a zero-length + // match). if (found_m_endpos.lnum > current_lnum) { current_lnum = found_m_endpos.lnum; current_col = found_m_endpos.col; @@ -743,27 +671,21 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) } } - /* - * If a sync point was encountered, break here. - */ + // If a sync point was encountered, break here. if (found_flags) { - /* - * Put the item that was specified by the sync point on the - * state stack. If there was no item specified, make the - * state stack empty. - */ + // Put the item that was specified by the sync point on the + // state stack. If there was no item specified, make the + // state stack empty. clear_current_state(); if (found_match_idx >= 0) { push_current_state(found_match_idx); update_si_attr(current_state.ga_len - 1); } - /* - * When using "grouphere", continue from the sync point - * match, until the end of the line. Parsing starts at - * the next line. - * For "groupthere" the parsing starts at start_lnum. - */ + // When using "grouphere", continue from the sync point + // match, until the end of the line. Parsing starts at + // the next line. + // For "groupthere" the parsing starts at start_lnum. if (found_flags & HL_SYNC_HERE) { if (!GA_EMPTY(¤t_state)) { cur_si = &CUR_STATE(current_state.ga_len - 1); @@ -797,56 +719,53 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid) validate_current_state(); } -static void save_chartab(char_u *chartab) +static void save_chartab(char *chartab) { - if (syn_block->b_syn_isk != empty_option) { - memmove(chartab, syn_buf->b_chartab, (size_t)32); - memmove(syn_buf->b_chartab, syn_win->w_s->b_syn_chartab, (size_t)32); + if (syn_block->b_syn_isk == empty_option) { + return; } + + memmove(chartab, syn_buf->b_chartab, (size_t)32); + memmove(syn_buf->b_chartab, syn_win->w_s->b_syn_chartab, (size_t)32); } -static void restore_chartab(char_u *chartab) +static void restore_chartab(char *chartab) { if (syn_win->w_s->b_syn_isk != empty_option) { memmove(syn_buf->b_chartab, chartab, (size_t)32); } } -/* - * Return TRUE if the line-continuation pattern matches in line "lnum". - */ +/// Return true if the line-continuation pattern matches in line "lnum". static int syn_match_linecont(linenr_T lnum) { - if (syn_block->b_syn_linecont_prog != NULL) { - regmmatch_T regmatch; - // chartab array for syn iskeyword - char_u buf_chartab[32]; - save_chartab(buf_chartab); - - regmatch.rmm_ic = syn_block->b_syn_linecont_ic; - regmatch.regprog = syn_block->b_syn_linecont_prog; - int r = syn_regexec(®match, lnum, (colnr_T)0, - IF_SYN_TIME(&syn_block->b_syn_linecont_time)); - syn_block->b_syn_linecont_prog = regmatch.regprog; - - restore_chartab(buf_chartab); - return r; - } - return FALSE; + if (syn_block->b_syn_linecont_prog == NULL) { + return false; + } + + regmmatch_T regmatch; + // chartab array for syn iskeyword + char buf_chartab[32]; + save_chartab(buf_chartab); + + regmatch.rmm_ic = syn_block->b_syn_linecont_ic; + regmatch.regprog = syn_block->b_syn_linecont_prog; + int r = syn_regexec(®match, lnum, (colnr_T)0, + IF_SYN_TIME(&syn_block->b_syn_linecont_time)); + syn_block->b_syn_linecont_prog = regmatch.regprog; + + restore_chartab(buf_chartab); + return r; } -/* - * Prepare the current state for the start of a line. - */ +// Prepare the current state for the start of a line. static void syn_start_line(void) { current_finished = false; current_col = 0; - /* - * Need to update the end of a start/skip/end that continues from the - * previous line and regions that have "keepend". - */ + // Need to update the end of a start/skip/end that continues from the + // previous line and regions that have "keepend". if (!GA_EMPTY(¤t_state)) { syn_update_ends(true); check_state_ends(); @@ -878,23 +797,21 @@ static void syn_update_ends(bool startofline) cur_si->si_m_endpos.lnum = 0; cur_si->si_m_endpos.col = 0; cur_si->si_h_endpos = cur_si->si_m_endpos; - cur_si->si_ends = TRUE; + cur_si->si_ends = true; } } } - /* - * Need to update the end of a start/skip/end that continues from the - * previous line. And regions that have "keepend", because they may - * influence contained items. If we've just removed "extend" - * (startofline == 0) then we should update ends of normal regions - * contained inside "keepend" because "extend" could have extended - * these "keepend" regions as well as contained normal regions. - * Then check for items ending in column 0. - */ + // Need to update the end of a start/skip/end that continues from the + // previous line. And regions that have "keepend", because they may + // influence contained items. If we've just removed "extend" + // (startofline == 0) then we should update ends of normal regions + // contained inside "keepend" because "extend" could have extended + // these "keepend" regions as well as contained normal regions. + // Then check for items ending in column 0. int i = current_state.ga_len - 1; if (keepend_level >= 0) { - for (; i > keepend_level; --i) { + for (; i > keepend_level; i--) { if (CUR_STATE(i).si_flags & HL_EXTEND) { break; } @@ -925,55 +842,53 @@ static void syn_update_ends(bool startofline) ///////////////////////////////////////// // Handling of the state stack cache. -/* - * EXPLANATION OF THE SYNTAX STATE STACK CACHE - * - * To speed up syntax highlighting, the state stack for the start of some - * lines is cached. These entries can be used to start parsing at that point. - * - * The stack is kept in b_sst_array[] for each buffer. There is a list of - * valid entries. b_sst_first points to the first one, then follow sst_next. - * The entries are sorted on line number. The first entry is often for line 2 - * (line 1 always starts with an empty stack). - * There is also a list for free entries. This construction is used to avoid - * having to allocate and free memory blocks too often. - * - * When making changes to the buffer, this is logged in b_mod_*. When calling - * update_screen() to update the display, it will call - * syn_stack_apply_changes() for each displayed buffer to adjust the cached - * entries. The entries which are inside the changed area are removed, - * because they must be recomputed. Entries below the changed have their line - * number adjusted for deleted/inserted lines, and have their sst_change_lnum - * set to indicate that a check must be made if the changed lines would change - * the cached entry. - * - * When later displaying lines, an entry is stored for each line. Displayed - * lines are likely to be displayed again, in which case the state at the - * start of the line is needed. - * For not displayed lines, an entry is stored for every so many lines. These - * entries will be used e.g., when scrolling backwards. The distance between - * entries depends on the number of lines in the buffer. For small buffers - * the distance is fixed at SST_DIST, for large buffers there is a fixed - * number of entries SST_MAX_ENTRIES, and the distance is computed. - */ +// EXPLANATION OF THE SYNTAX STATE STACK CACHE +// +// To speed up syntax highlighting, the state stack for the start of some +// lines is cached. These entries can be used to start parsing at that point. +// +// The stack is kept in b_sst_array[] for each buffer. There is a list of +// valid entries. b_sst_first points to the first one, then follow sst_next. +// The entries are sorted on line number. The first entry is often for line 2 +// (line 1 always starts with an empty stack). +// There is also a list for free entries. This construction is used to avoid +// having to allocate and free memory blocks too often. +// +// When making changes to the buffer, this is logged in b_mod_*. When calling +// update_screen() to update the display, it will call +// syn_stack_apply_changes() for each displayed buffer to adjust the cached +// entries. The entries which are inside the changed area are removed, +// because they must be recomputed. Entries below the changed have their line +// number adjusted for deleted/inserted lines, and have their sst_change_lnum +// set to indicate that a check must be made if the changed lines would change +// the cached entry. +// +// When later displaying lines, an entry is stored for each line. Displayed +// lines are likely to be displayed again, in which case the state at the +// start of the line is needed. +// For not displayed lines, an entry is stored for every so many lines. These +// entries will be used e.g., when scrolling backwards. The distance between +// entries depends on the number of lines in the buffer. For small buffers +// the distance is fixed at SST_DIST, for large buffers there is a fixed +// number of entries SST_MAX_ENTRIES, and the distance is computed. static void syn_stack_free_block(synblock_T *block) { synstate_T *p; - if (block->b_sst_array != NULL) { - for (p = block->b_sst_first; p != NULL; p = p->sst_next) { - clear_syn_state(p); - } - XFREE_CLEAR(block->b_sst_array); - block->b_sst_first = NULL; - block->b_sst_len = 0; + if (block->b_sst_array == NULL) { + return; + } + + for (p = block->b_sst_first; p != NULL; p = p->sst_next) { + clear_syn_state(p); } + XFREE_CLEAR(block->b_sst_array); + block->b_sst_first = NULL; + block->b_sst_len = 0; } -/* - * Free b_sst_array[] for buffer "buf". - * Used when syntax items changed to force resyncing everywhere. - */ +// Free b_sst_array[] for buffer "buf". +// Used when syntax items changed to force resyncing everywhere. void syn_stack_free_all(synblock_T *block) { syn_stack_free_block(block); @@ -986,12 +901,10 @@ void syn_stack_free_all(synblock_T *block) } } -/* - * Allocate the syntax state stack for syn_buf when needed. - * If the number of entries in b_sst_array[] is much too big or a bit too - * small, reallocate it. - * Also used to allocate b_sst_array[] for the first time. - */ +// Allocate the syntax state stack for syn_buf when needed. +// If the number of entries in b_sst_array[] is much too big or a bit too +// small, reallocate it. +// Also used to allocate b_sst_array[] for the first time. static void syn_stack_alloc(void) { synstate_T *to, *from; @@ -1058,12 +971,10 @@ static void syn_stack_alloc(void) } } -/* - * Check for changes in a buffer to affect stored syntax states. Uses the - * b_mod_* fields. - * Called from update_screen(), before screen is being updated, once for each - * displayed buffer. - */ +// Check for changes in a buffer to affect stored syntax states. Uses the +// b_mod_* fields. +// Called from update_screen(), before screen is being updated, once for each +// displayed buffer. void syn_stack_apply_changes(buf_T *buf) { syn_stack_apply_changes_block(&buf->b_s, buf); @@ -1139,11 +1050,9 @@ static bool syn_stack_cleanup(void) dist = syn_buf->b_ml.ml_line_count / (syn_block->b_sst_len - Rows) + 1; } - /* - * Go through the list to find the "tick" for the oldest entry that can - * be removed. Set "above" when the "tick" for the oldest entry is above - * "b_sst_lasttick" (the display tick wraps around). - */ + // Go through the list to find the "tick" for the oldest entry that can + // be removed. Set "above" when the "tick" for the oldest entry is above + // "b_sst_lasttick" (the display tick wraps around). tick = syn_block->b_sst_lasttick; bool above = false; prev = syn_block->b_sst_first; @@ -1160,10 +1069,8 @@ static bool syn_stack_cleanup(void) } } - /* - * Go through the list to make the entries for the oldest tick at an - * interval of several lines. - */ + // Go through the list to make the entries for the oldest tick at an + // interval of several lines. prev = syn_block->b_sst_first; for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) { if (p->sst_tick == tick && prev->sst_lnum + dist > p->sst_lnum) { @@ -1177,22 +1084,18 @@ static bool syn_stack_cleanup(void) return retval; } -/* - * Free the allocated memory for a syn_state item. - * Move the entry into the free list. - */ +// Free the allocated memory for a syn_state item. +// Move the entry into the free list. static void syn_stack_free_entry(synblock_T *block, synstate_T *p) { clear_syn_state(p); p->sst_next = block->b_sst_firstfree; block->b_sst_firstfree = p; - ++block->b_sst_freecount; + block->b_sst_freecount++; } -/* - * Find an entry in the list of state stacks at or before "lnum". - * Returns NULL when there is no entry or the first entry is after "lnum". - */ +// Find an entry in the list of state stacks at or before "lnum". +// Returns NULL when there is no entry or the first entry is after "lnum". static synstate_T *syn_stack_find_entry(linenr_T lnum) { synstate_T *p, *prev; @@ -1209,10 +1112,8 @@ static synstate_T *syn_stack_find_entry(linenr_T lnum) return prev; } -/* - * Try saving the current state in b_sst_array[]. - * The current state must be valid for the start of the current_lnum line! - */ +// Try saving the current state in b_sst_array[]. +// The current state must be valid for the start of the current_lnum line! static synstate_T *store_current_state(void) { int i; @@ -1221,11 +1122,9 @@ static synstate_T *store_current_state(void) stateitem_T *cur_si; synstate_T *sp = syn_stack_find_entry(current_lnum); - /* - * If the current state contains a start or end pattern that continues - * from the previous line, we can't use it. Don't store it then. - */ - for (i = current_state.ga_len - 1; i >= 0; --i) { + // If the current state contains a start or end pattern that continues + // from the previous line, we can't use it. Don't store it then. + for (i = current_state.ga_len - 1; i >= 0; i--) { cur_si = &CUR_STATE(i); if (cur_si->si_h_startpos.lnum >= current_lnum || cur_si->si_m_endpos.lnum >= current_lnum @@ -1256,9 +1155,7 @@ static synstate_T *store_current_state(void) sp = NULL; } } else if (sp == NULL || sp->sst_lnum != current_lnum) { - /* - * Add a new entry - */ + // Add a new entry // If no free items, cleanup the array first. if (syn_block->b_sst_freecount == 0) { (void)syn_stack_cleanup(); @@ -1273,7 +1170,7 @@ static synstate_T *store_current_state(void) // list, after *sp p = syn_block->b_sst_firstfree; syn_block->b_sst_firstfree = p->sst_next; - --syn_block->b_sst_freecount; + syn_block->b_sst_freecount--; if (sp == NULL) { // Insert in front of the list p->sst_next = syn_block->b_sst_first; @@ -1302,7 +1199,7 @@ static synstate_T *store_current_state(void) } else { bp = sp->sst_union.sst_stack; } - for (i = 0; i < sp->sst_stacksize; ++i) { + for (i = 0; i < sp->sst_stacksize; i++) { bp[i].bs_idx = CUR_STATE(i).si_idx; bp[i].bs_flags = (int)CUR_STATE(i).si_flags; bp[i].bs_seqnr = CUR_STATE(i).si_seqnr; @@ -1318,9 +1215,7 @@ static synstate_T *store_current_state(void) return sp; } -/* - * Copy a state stack from "from" in b_sst_array[] to current_state; - */ +// Copy a state stack from "from" in b_sst_array[] to current_state; static void load_current_state(synstate_T *from) { int i; @@ -1336,7 +1231,7 @@ static void load_current_state(synstate_T *from) } else { bp = from->sst_union.sst_stack; } - for (i = 0; i < from->sst_stacksize; ++i) { + for (i = 0; i < from->sst_stacksize; i++) { CUR_STATE(i).si_idx = bp[i].bs_idx; CUR_STATE(i).si_flags = bp[i].bs_flags; CUR_STATE(i).si_seqnr = bp[i].bs_seqnr; @@ -1345,7 +1240,7 @@ static void load_current_state(synstate_T *from) if (keepend_level < 0 && (CUR_STATE(i).si_flags & HL_KEEPEND)) { keepend_level = i; } - CUR_STATE(i).si_ends = FALSE; + CUR_STATE(i).si_ends = false; CUR_STATE(i).si_m_lnum = 0; if (CUR_STATE(i).si_idx >= 0) { CUR_STATE(i).si_next_list = @@ -1424,20 +1319,21 @@ static bool syn_stack_equal(synstate_T *sp) return i < 0 ? true : false; } -/* - * We stop parsing syntax above line "lnum". If the stored state at or below - * this line depended on a change before it, it now depends on the line below - * the last parsed line. - * The window looks like this: - * line which changed - * displayed line - * displayed line - * lnum -> line below window - */ -void syntax_end_parsing(linenr_T lnum) +// We stop parsing syntax above line "lnum". If the stored state at or below +// this line depended on a change before it, it now depends on the line below +// the last parsed line. +// The window looks like this: +// line which changed +// displayed line +// displayed line +// lnum -> line below window +void syntax_end_parsing(win_T *wp, linenr_T lnum) { synstate_T *sp; + if (syn_block != wp->w_s) { + return; // not the right window + } sp = syn_stack_find_entry(lnum); if (sp != NULL && sp->sst_lnum < lnum) { sp = sp->sst_next; @@ -1448,9 +1344,8 @@ void syntax_end_parsing(linenr_T lnum) } } -/* - * End of handling of the state stack. - ****************************************/ +// End of handling of the state stack. +// ************************************** static void invalidate_current_state(void) { @@ -1475,33 +1370,25 @@ bool syntax_check_changed(linenr_T lnum) bool retval = true; synstate_T *sp; - /* - * Check the state stack when: - * - lnum is just below the previously syntaxed line. - * - lnum is not before the lines with saved states. - * - lnum is not past the lines with saved states. - * - lnum is at or before the last changed line. - */ + // Check the state stack when: + // - lnum is just below the previously syntaxed line. + // - lnum is not before the lines with saved states. + // - lnum is not past the lines with saved states. + // - lnum is at or before the last changed line. if (VALID_STATE(¤t_state) && lnum == current_lnum + 1) { sp = syn_stack_find_entry(lnum); if (sp != NULL && sp->sst_lnum == lnum) { - /* - * finish the previous line (needed when not all of the line was - * drawn) - */ + // finish the previous line (needed when not all of the line was + // drawn) (void)syn_finish_line(false); - /* - * Compare the current state with the previously saved state of - * the line. - */ + // Compare the current state with the previously saved state of + // the line. if (syn_stack_equal(sp)) { retval = false; } - /* - * Store the current state in b_sst_array[] for later use. - */ + // Store the current state in b_sst_array[] for later use. current_lnum++; (void)store_current_state(); } @@ -1551,7 +1438,7 @@ static bool syn_finish_line(const bool syncing) /// "col" is normally 0 for the first use in a line, and increments by one each /// time. It's allowed to skip characters and to stop before the end of the /// line. But only a "col" after a previously used column is allowed. -/// When "can_spell" is not NULL set it to TRUE when spell-checking should be +/// When "can_spell" is not NULL set it to true when spell-checking should be /// done. /// /// @param keep_state keep state of char at "col" @@ -1589,9 +1476,7 @@ int get_syntax_attr(const colnr_T col, bool *const can_spell, const bool keep_st validate_current_state(); } - /* - * Skip from the current column to "col", get the attributes for "col". - */ + // Skip from the current column to "col", get the attributes for "col". while (current_col <= col) { attr = syn_current_attr(false, true, can_spell, current_col == col ? keep_state : false); @@ -1610,8 +1495,8 @@ int get_syntax_attr(const colnr_T col, bool *const can_spell, const bool keep_st static int syn_current_attr(const bool syncing, const bool displaying, bool *const can_spell, const bool keep_state) { - lpos_T endpos; // was: char_u *endp; - lpos_T hl_startpos; // was: int hl_startcol; + lpos_T endpos; + lpos_T hl_startpos; lpos_T hl_endpos; lpos_T eos_pos; // end-of-start match (start region) lpos_T eoe_pos; // end-of-end pattern @@ -1627,8 +1512,8 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con regmmatch_T regmatch; lpos_T pos; reg_extmatch_T *cur_extmatch = NULL; - char_u buf_chartab[32]; // chartab array for syn iskeyword - char_u *line; // current line. NOTE: becomes invalid after + char buf_chartab[32]; // chartab array for syn iskeyword + char *line; // current line. NOTE: becomes invalid after // looking for a pattern match! // variables for zero-width matches that have a "nextgroup" argument @@ -1636,15 +1521,11 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con bool zero_width_next_list = false; garray_T zero_width_next_ga; - /* - * No character, no attributes! Past end of line? - * Do try matching with an empty line (could be the start of a region). - */ + // No character, no attributes! Past end of line? + // Do try matching with an empty line (could be the start of a region). line = syn_getcurline(); if (line[current_col] == NUL && current_col != 0) { - /* - * If we found a match after the last column, use it. - */ + // If we found a match after the last column, use it. if (next_match_idx >= 0 && next_match_col >= (int)current_col && next_match_col != MAXCOL) { (void)push_next_match(); @@ -1661,11 +1542,9 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con current_state_stored = false; } - /* - * When in the previous column there was a match but it could not be used - * (empty match or already matched in this column) need to try again in - * the next column. - */ + // When in the previous column there was a match but it could not be used + // (empty match or already matched in this column) need to try again in + // the next column. if (try_next_column) { next_match_idx = -1; try_next_column = false; @@ -1683,23 +1562,19 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con // use syntax iskeyword option save_chartab(buf_chartab); - /* - * Repeat matching keywords and patterns, to find contained items at the - * same column. This stops when there are no extra matches at the current - * column. - */ + // Repeat matching keywords and patterns, to find contained items at the + // same column. This stops when there are no extra matches at the current + // column. do { found_match = false; keep_next_list = false; int syn_id = 0; - /* - * 1. Check for a current state. - * Only when there is no current state, or if the current state may - * contain other things, we need to check for keywords and patterns. - * Always need to check for contained items if some item has the - * "containedin" argument (takes extra time!). - */ + // 1. Check for a current state. + // Only when there is no current state, or if the current state may + // contain other things, we need to check for keywords and patterns. + // Always need to check for contained items if some item has the + // "containedin" argument (takes extra time!). if (current_state.ga_len) { cur_si = &CUR_STATE(current_state.ga_len - 1); } else { @@ -1708,16 +1583,15 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con if (syn_block->b_syn_containedin || cur_si == NULL || cur_si->si_cont_list != NULL) { - /* - * 2. Check for keywords, if on a keyword char after a non-keyword - * char. Don't do this when syncing. - */ + // 2. Check for keywords, if on a keyword char after a non-keyword + // char. Don't do this when syncing. if (do_keywords) { line = syn_getcurline(); - const char_u *cur_pos = line + current_col; + const char *cur_pos = line + current_col; if (vim_iswordp_buf(cur_pos, syn_buf) && (current_col == 0 - || !vim_iswordp_buf(cur_pos - 1 - utf_head_off(line, cur_pos - 1), + || !vim_iswordp_buf(cur_pos - 1 - + utf_head_off(line, cur_pos - 1), syn_buf))) { syn_id = check_keyword_id(line, (int)current_col, &endcol, &flags, &next_list, cur_si, &cchar); @@ -1732,7 +1606,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con cur_si->si_m_endpos.col = endcol; cur_si->si_h_endpos.lnum = current_lnum; cur_si->si_h_endpos.col = endcol; - cur_si->si_ends = TRUE; + cur_si->si_ends = true; cur_si->si_end_idx = 0; cur_si->si_flags = flags; cur_si->si_seqnr = next_seqnr++; @@ -1763,21 +1637,15 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con } } - /* - * 3. Check for patterns (only if no keyword found). - */ + // 3. Check for patterns (only if no keyword found). if (syn_id == 0 && syn_block->b_syn_patterns.ga_len) { - /* - * If we didn't check for a match yet, or we are past it, check - * for any match with a pattern. - */ + // If we didn't check for a match yet, or we are past it, check + // for any match with a pattern. if (next_match_idx < 0 || next_match_col < (int)current_col) { - /* - * Check all relevant patterns for a match at this - * position. This is complicated, because matching with a - * pattern takes quite a bit of time, thus we want to - * avoid doing it when it's not needed. - */ + // Check all relevant patterns for a match at this + // position. This is complicated, because matching with a + // pattern takes quite a bit of time, thus we want to + // avoid doing it when it's not needed. next_match_idx = 0; // no match in this line yet next_match_col = MAXCOL; for (int idx = syn_block->b_syn_patterns.ga_len; --idx >= 0;) { @@ -1787,13 +1655,12 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con && (spp->sp_type == SPTYPE_MATCH || spp->sp_type == SPTYPE_START) && (current_next_list != NULL - ? in_id_list(NULL, current_next_list, - &spp->sp_syn, 0) - : (cur_si == NULL - ? !(spp->sp_flags & HL_CONTAINED) - : in_id_list(cur_si, - cur_si->si_cont_list, &spp->sp_syn, - spp->sp_flags & HL_CONTAINED)))) { + ? in_id_list(NULL, current_next_list, &spp->sp_syn, 0) + : (cur_si == NULL + ? !(spp->sp_flags & HL_CONTAINED) + : in_id_list(cur_si, + cur_si->si_cont_list, &spp->sp_syn, + spp->sp_flags & HL_CONTAINED)))) { // If we already tried matching in this line, and // there isn't a match before next_match_col, skip // this item. @@ -1819,9 +1686,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con continue; } - /* - * Compute the first column of the match. - */ + // Compute the first column of the match. syn_add_start_off(&pos, ®match, spp, SPO_MS_OFF, -1); if (pos.lnum > current_lnum) { @@ -1836,19 +1701,15 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con // matches in the current line spp->sp_startcol = startcol; - /* - * If a previously found match starts at a lower - * column number, don't use this one. - */ + // If a previously found match starts at a lower + // column number, don't use this one. if (startcol >= next_match_col) { continue; } - /* - * If we matched this pattern at this position - * before, skip it. Must retry in the next - * column, because it may match from there. - */ + // If we matched this pattern at this position + // before, skip it. Must retry in the next + // column, because it may match from there. if (did_match_already(idx, &zero_width_next_ga)) { try_next_column = true; continue; @@ -1866,10 +1727,8 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con syn_add_end_off(&eos_pos, ®match, spp, SPO_RS_OFF, 0); - /* - * Grab the external submatches before they get - * overwritten. Reference count doesn't change. - */ + // Grab the external submatches before they get + // overwritten. Reference count doesn't change. unref_extmatch(cur_extmatch); cur_extmatch = re_extmatch_out; re_extmatch_out = NULL; @@ -1880,12 +1739,10 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con end_idx = 0; hl_endpos.lnum = 0; - /* - * For a "oneline" the end must be found in the - * same line too. Search for it after the end of - * the match with the start pattern. Set the - * resulting end positions at the same time. - */ + // For a "oneline" the end must be found in the + // same line too. Search for it after the end of + // the match with the start pattern. Set the + // resulting end positions at the same time. if (spp->sp_type == SPTYPE_START && (spp->sp_flags & HL_ONELINE)) { lpos_T startpos; @@ -1896,23 +1753,18 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con if (endpos.lnum == 0) { continue; // not found } - } - /* - * For a "match" the size must be > 0 after the - * end offset needs has been added. Except when - * syncing. - */ - else if (spp->sp_type == SPTYPE_MATCH) { + } else if (spp->sp_type == SPTYPE_MATCH) { + // For a "match" the size must be > 0 after the + // end offset needs has been added. Except when + // syncing. syn_add_end_off(&hl_endpos, ®match, spp, SPO_HE_OFF, 0); syn_add_end_off(&endpos, ®match, spp, SPO_ME_OFF, 0); if (endpos.lnum == current_lnum && (int)endpos.col + syncing < startcol) { - /* - * If an empty string is matched, may need - * to try matching again at next column. - */ + // If an empty string is matched, may need + // to try matching again at next column. if (regmatch.startpos[0].col == regmatch.endpos[0].col) { try_next_column = true; } @@ -1920,9 +1772,8 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con } } - /* - * keep the best match so far in next_match_* - */ + // keep the best match so far in next_match_* + // Highlighting must start after startpos and end // before endpos. if (hl_startpos.lnum == current_lnum @@ -1947,9 +1798,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con } } - /* - * If we found a match at the current column, use it. - */ + // If we found a match at the current column, use it. if (next_match_idx >= 0 && next_match_col == (int)current_col) { synpat_T *lspp; @@ -1977,15 +1826,11 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con } } - /* - * Handle searching for nextgroup match. - */ + // Handle searching for nextgroup match. if (current_next_list != NULL && !keep_next_list) { - /* - * If a nextgroup was not found, continue looking for one if: - * - this is an empty line and the "skipempty" option was given - * - we are on white space and the "skipwhite" option was given - */ + // If a nextgroup was not found, continue looking for one if: + // - this is an empty line and the "skipempty" option was given + // - we are on white space and the "skipwhite" option was given if (!found_match) { line = syn_getcurline(); if (((current_next_flags & HL_SKIPWHITE) @@ -1996,14 +1841,12 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con } } - /* - * If a nextgroup was found: Use it, and continue looking for - * contained matches. - * If a nextgroup was not found: Continue looking for a normal - * match. - * When did set current_next_list for a zero-width item and no - * match was found don't loop (would get stuck). - */ + // If a nextgroup was found: Use it, and continue looking for + // contained matches. + // If a nextgroup was not found: Continue looking for a normal + // match. + // When did set current_next_list for a zero-width item and no + // match was found don't loop (would get stuck). current_next_list = NULL; next_match_idx = -1; if (!zero_width_next_list) { @@ -2014,17 +1857,15 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con restore_chartab(buf_chartab); - /* - * Use attributes from the current state, if within its highlighting. - * If not, use attributes from the current-but-one state, etc. - */ + // Use attributes from the current state, if within its highlighting. + // If not, use attributes from the current-but-one state, etc. current_attr = 0; current_id = 0; current_trans_id = 0; current_flags = 0; current_seqnr = 0; if (cur_si != NULL) { - for (int idx = current_state.ga_len - 1; idx >= 0; --idx) { + for (int idx = current_state.ga_len - 1; idx >= 0; idx--) { sip = &CUR_STATE(idx); if ((current_lnum > sip->si_h_startpos.lnum || (current_lnum == sip->si_h_startpos.lnum @@ -2046,10 +1887,8 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con if (can_spell != NULL) { struct sp_syn sps; - /* - * set "can_spell" to TRUE if spell checking is supposed to be - * done in the current item. - */ + // set "can_spell" to true if spell checking is supposed to be + // done in the current item. if (syn_block->b_spell_cluster_id == 0) { // There is no @Spell cluster: Do spelling for items without // @NoSpell cluster. @@ -2085,14 +1924,12 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con } } - /* - * Check for end of current state (and the states before it) at the - * next column. Don't do this for syncing, because we would miss a - * single character match. - * First check if the current state ends at the current column. It - * may be for an empty match and a containing item might end in the - * current column. - */ + // Check for end of current state (and the states before it) at the + // next column. Don't do this for syncing, because we would miss a + // single character match. + // First check if the current state ends at the current column. It + // may be for an empty match and a containing item might end in the + // current column. if (!syncing && !keep_state) { check_state_ends(); if (!GA_EMPTY(¤t_state) @@ -2152,9 +1989,7 @@ static bool did_match_already(int idx, garray_T *gap) return false; } -/* - * Push the next match onto the stack. - */ +// Push the next match onto the stack. static stateitem_T *push_next_match(void) { stateitem_T *cur_si; @@ -2163,15 +1998,11 @@ static stateitem_T *push_next_match(void) spp = &(SYN_ITEMS(syn_block)[next_match_idx]); - /* - * Push the item in current_state stack; - */ + // Push the item in current_state stack; push_current_state(next_match_idx); { - /* - * If it's a start-skip-end type that crosses lines, figure out how - * much it continues in this line. Otherwise just fill in the length. - */ + // If it's a start-skip-end type that crosses lines, figure out how + // much it continues in this line. Otherwise just fill in the length. cur_si = &CUR_STATE(current_state.ga_len - 1); cur_si->si_h_startpos = next_match_h_startpos; cur_si->si_m_startcol = current_col; @@ -2192,7 +2023,7 @@ static stateitem_T *push_next_match(void) } else { cur_si->si_m_endpos = next_match_m_endpos; cur_si->si_h_endpos = next_match_h_endpos; - cur_si->si_ends = TRUE; + cur_si->si_ends = true; cur_si->si_flags |= next_match_flags; cur_si->si_eoe_pos = next_match_eoe_pos; cur_si->si_end_idx = next_match_end_idx; @@ -2204,10 +2035,8 @@ static stateitem_T *push_next_match(void) update_si_attr(current_state.ga_len - 1); save_flags = cur_si->si_flags & (HL_CONCEAL | HL_CONCEALENDS); - /* - * If the start pattern has another highlight group, push another item - * on the stack for the start pattern. - */ + // If the start pattern has another highlight group, push another item + // on the stack for the start pattern. if (spp->sp_type == SPTYPE_START && spp->sp_syn_match_id != 0) { push_current_state(next_match_idx); cur_si = &CUR_STATE(current_state.ga_len - 1); @@ -2216,7 +2045,7 @@ static stateitem_T *push_next_match(void) cur_si->si_m_lnum = current_lnum; cur_si->si_m_endpos = next_match_eos_pos; cur_si->si_h_endpos = next_match_eos_pos; - cur_si->si_ends = TRUE; + cur_si->si_ends = true; cur_si->si_end_idx = 0; cur_si->si_flags = HL_MATCH; cur_si->si_seqnr = next_seqnr++; @@ -2235,9 +2064,7 @@ static stateitem_T *push_next_match(void) return cur_si; } -/* - * Check for end of current state (and the states before it). - */ +// Check for end of current state (and the states before it). static void check_state_ends(void) { stateitem_T *cur_si; @@ -2249,12 +2076,10 @@ static void check_state_ends(void) && (cur_si->si_m_endpos.lnum < current_lnum || (cur_si->si_m_endpos.lnum == current_lnum && cur_si->si_m_endpos.col <= current_col))) { - /* - * If there is an end pattern group ID, highlight the end pattern - * now. No need to pop the current item from the stack. - * Only do this if the end pattern continues beyond the current - * position. - */ + // If there is an end pattern group ID, highlight the end pattern + // now. No need to pop the current item from the stack. + // Only do this if the end pattern continues beyond the current + // position. if (cur_si->si_end_idx && (cur_si->si_eoe_pos.lnum > current_lnum || (cur_si->si_eoe_pos.lnum == current_lnum @@ -2331,10 +2156,8 @@ static void check_state_ends(void) } } -/* - * Update an entry in the current_state stack for a match or region. This - * fills in si_attr, si_next_list and si_cont_list. - */ +// Update an entry in the current_state stack for a match or region. This +// fills in si_attr, si_next_list and si_cont_list. static void update_si_attr(int idx) { stateitem_T *sip = &CUR_STATE(idx); @@ -2359,11 +2182,9 @@ static void update_si_attr(int idx) sip->si_cont_list = spp->sp_cont_list; } - /* - * For transparent items, take attr from outer item. - * Also take cont_list, if there is none. - * Don't do this for the matchgroup of a start or end pattern. - */ + // For transparent items, take attr from outer item. + // Also take cont_list, if there is none. + // Don't do this for the matchgroup of a start or end pattern. if ((spp->sp_flags & HL_TRANSP) && !(sip->si_flags & HL_MATCH)) { if (idx == 0) { sip->si_attr = 0; @@ -2382,10 +2203,8 @@ static void update_si_attr(int idx) } } -/* - * Check the current stack for patterns with "keepend" flag. - * Propagate the match-end to contained items, until a "skipend" item is found. - */ +// Check the current stack for patterns with "keepend" flag. +// Propagate the match-end to contained items, until a "skipend" item is found. static void check_keepend(void) { int i; @@ -2393,20 +2212,16 @@ static void check_keepend(void) lpos_T maxpos_h; stateitem_T *sip; - /* - * This check can consume a lot of time; only do it from the level where - * there really is a keepend. - */ + // This check can consume a lot of time; only do it from the level where + // there really is a keepend. if (keepend_level < 0) { return; } - /* - * Find the last index of an "extend" item. "keepend" items before that - * won't do anything. If there is no "extend" item "i" will be - * "keepend_level" and all "keepend" items will work normally. - */ - for (i = current_state.ga_len - 1; i > keepend_level; --i) { + // Find the last index of an "extend" item. "keepend" items before that + // won't do anything. If there is no "extend" item "i" will be + // "keepend_level" and all "keepend" items will work normally. + for (i = current_state.ga_len - 1; i > keepend_level; i--) { if (CUR_STATE(i).si_flags & HL_EXTEND) { break; } @@ -2416,13 +2231,13 @@ static void check_keepend(void) maxpos.col = 0; maxpos_h.lnum = 0; maxpos_h.col = 0; - for (; i < current_state.ga_len; ++i) { + for (; i < current_state.ga_len; i++) { sip = &CUR_STATE(i); if (maxpos.lnum != 0) { limit_pos_zero(&sip->si_m_endpos, &maxpos); limit_pos_zero(&sip->si_h_endpos, &maxpos_h); limit_pos_zero(&sip->si_eoe_pos, &maxpos); - sip->si_ends = TRUE; + sip->si_ends = true; } if (sip->si_ends && (sip->si_flags & HL_KEEPEND)) { if (maxpos.lnum == 0 @@ -2465,10 +2280,8 @@ static void update_si_end(stateitem_T *sip, int startcol, bool force) return; } - /* - * We need to find the end of the region. It may continue in the next - * line. - */ + // We need to find the end of the region. It may continue in the next + // line. int end_idx = 0; lpos_T startpos = { .lnum = current_lnum, @@ -2482,12 +2295,12 @@ static void update_si_end(stateitem_T *sip, int startcol, bool force) // No end pattern matched. if (SYN_ITEMS(syn_block)[sip->si_idx].sp_flags & HL_ONELINE) { // a "oneline" never continues in the next line - sip->si_ends = TRUE; + sip->si_ends = true; sip->si_m_endpos.lnum = current_lnum; - sip->si_m_endpos.col = (colnr_T)STRLEN(syn_getcurline()); + sip->si_m_endpos.col = (colnr_T)strlen(syn_getcurline()); } else { // continues in the next line - sip->si_ends = FALSE; + sip->si_ends = false; sip->si_m_endpos.lnum = 0; } sip->si_h_endpos = sip->si_m_endpos; @@ -2496,15 +2309,13 @@ static void update_si_end(stateitem_T *sip, int startcol, bool force) sip->si_m_endpos = endpos; sip->si_h_endpos = hl_endpos; sip->si_eoe_pos = end_endpos; - sip->si_ends = TRUE; + sip->si_ends = true; sip->si_end_idx = end_idx; } } -/* - * Add a new state to the current state stack. - * It is cleared and the index set to "idx". - */ +// Add a new state to the current state stack. +// It is cleared and the index set to "idx". static void push_current_state(int idx) { stateitem_T *p = GA_APPEND_VIA_PTR(stateitem_T, ¤t_state); @@ -2512,14 +2323,12 @@ static void push_current_state(int idx) p->si_idx = idx; } -/* - * Remove a state from the current_state stack. - */ +// Remove a state from the current_state stack. static void pop_current_state(void) { if (!GA_EMPTY(¤t_state)) { unref_extmatch(CUR_STATE(current_state.ga_len - 1).si_extmatch); - --current_state.ga_len; + current_state.ga_len--; } // after the end of a pattern, try matching a keyword or pattern next_match_idx = -1; @@ -2556,29 +2365,25 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ regmmatch_T regmatch; regmmatch_T best_regmatch; // startpos/endpos of best match lpos_T pos; - char_u *line; + char *line; bool had_match = false; - char_u buf_chartab[32]; // chartab array for syn option iskeyword + char buf_chartab[32]; // chartab array for syn option iskeyword // just in case we are invoked for a keyword if (idx < 0) { return; } - /* - * Check for being called with a START pattern. - * Can happen with a match that continues to the next line, because it - * contained a region. - */ + // Check for being called with a START pattern. + // Can happen with a match that continues to the next line, because it + // contained a region. spp = &(SYN_ITEMS(syn_block)[idx]); if (spp->sp_type != SPTYPE_START) { *hl_endpos = *startpos; return; } - /* - * Find the SKIP or first END pattern after the last START pattern. - */ + // Find the SKIP or first END pattern after the last START pattern. for (;;) { spp = &(SYN_ITEMS(syn_block)[idx]); if (spp->sp_type != SPTYPE_START) { @@ -2587,9 +2392,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ idx++; } - /* - * Lookup the SKIP pattern (if present) - */ + // Lookup the SKIP pattern (if present) if (spp->sp_type == SPTYPE_SKIP) { spp_skip = spp; idx++; @@ -2609,11 +2412,9 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ save_chartab(buf_chartab); for (;;) { - /* - * Find end pattern that matches first after "matchcol". - */ + // Find end pattern that matches first after "matchcol". best_idx = -1; - for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; ++idx) { + for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; idx++) { int lc_col = matchcol; spp = &(SYN_ITEMS(syn_block)[idx]); @@ -2640,18 +2441,14 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ } } - /* - * If all end patterns have been tried, and there is no match, the - * item continues until end-of-line. - */ + // If all end patterns have been tried, and there is no match, the + // item continues until end-of-line. if (best_idx == -1) { break; } - /* - * If the skip pattern matches before the end pattern, - * continue searching after the skip pattern. - */ + // If the skip pattern matches before the end pattern, + // continue searching after the skip pattern. if (spp_skip != NULL) { int lc_col = matchcol - spp_skip->sp_offsets[SPO_LC_OFF]; @@ -2674,7 +2471,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ } line = ml_get_buf(syn_buf, startpos->lnum, false); - int line_len = (int)STRLEN(line); + int line_len = (int)strlen(line); // take care of an empty match or negative offset if (pos.col <= matchcol) { @@ -2697,10 +2494,8 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ } } - /* - * Match from start pattern to end pattern. - * Correct for match and highlight offset of end pattern. - */ + // Match from start pattern to end pattern. + // Correct for match and highlight offset of end pattern. spp = &(SYN_ITEMS(syn_block)[best_idx]); syn_add_end_off(m_endpos, &best_regmatch, spp, SPO_ME_OFF, 1); // can't end before the start @@ -2717,9 +2512,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ // can't end after the match limit_pos(end_endpos, m_endpos); - /* - * If the end group is highlighted differently, adjust the pointers. - */ + // If the end group is highlighted differently, adjust the pointers. if (spp->sp_syn_match_id != spp->sp_syn.id && spp->sp_syn_match_id != 0) { *end_idx = best_idx; if (spp->sp_off_flags & (1 << (SPO_RE_OFF + SPO_COUNT))) { @@ -2764,9 +2557,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_ re_extmatch_in = NULL; } -/* - * Limit "pos" not to be after "limit". - */ +// Limit "pos" not to be after "limit". static void limit_pos(lpos_T *pos, lpos_T *limit) { if (pos->lnum > limit->lnum) { @@ -2776,9 +2567,7 @@ static void limit_pos(lpos_T *pos, lpos_T *limit) } } -/* - * Limit "pos" not to be after "limit", unless pos->lnum is zero. - */ +// Limit "pos" not to be after "limit", unless pos->lnum is zero. static void limit_pos_zero(lpos_T *pos, lpos_T *limit) { if (pos->lnum == 0) { @@ -2800,8 +2589,8 @@ static void syn_add_end_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp { int col; int off; - char_u *base; - char_u *p; + char *base; + char *p; if (spp->sp_off_flags & (1 << idx)) { result->lnum = regmatch->startpos[0].lnum; @@ -2844,8 +2633,8 @@ static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *s { int col; int off; - char_u *base; - char_u *p; + char *base; + char *p; if (spp->sp_off_flags & (1 << (idx + SPO_COUNT))) { result->lnum = regmatch->endpos[0].lnum; @@ -2859,7 +2648,7 @@ static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *s if (result->lnum > syn_buf->b_ml.ml_line_count) { // a "\n" at the end of the pattern may take us below the last line result->lnum = syn_buf->b_ml.ml_line_count; - col = (int)STRLEN(ml_get_buf(syn_buf, result->lnum, false)); + col = (int)strlen(ml_get_buf(syn_buf, result->lnum, false)); } if (off != 0) { base = ml_get_buf(syn_buf, result->lnum, false); @@ -2878,18 +2667,14 @@ static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *s result->col = col; } -/* - * Get current line in syntax buffer. - */ -static char_u *syn_getcurline(void) +/// Get current line in syntax buffer. +static char *syn_getcurline(void) { return ml_get_buf(syn_buf, current_lnum, false); } -/* - * Call vim_regexec() to find a match with "rmp" in "syn_buf". - * Returns TRUE when there is a match. - */ +// Call vim_regexec() to find a match with "rmp" in "syn_buf". +// Returns true when there is a match. static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st) { int timed_out = 0; @@ -2916,9 +2701,9 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T if (profile_cmp(pt, st->slowest) < 0) { st->slowest = pt; } - ++st->count; + st->count++; if (r > 0) { - ++st->match; + st->match++; } } if (timed_out && !syn_win->w_s->b_syn_slow) { @@ -2929,9 +2714,9 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T if (r > 0) { rmp->startpos[0].lnum += lnum; rmp->endpos[0].lnum += lnum; - return TRUE; + return true; } - return FALSE; + return false; } /// Check one position in a line for a matching keyword. @@ -2944,16 +2729,16 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T /// @param next_listp return: next_list of matching keyword /// @param cur_si item at the top of the stack /// @param ccharp conceal substitution char -static int check_keyword_id(char_u *const line, const int startcol, int *const endcolp, +static int check_keyword_id(char *const line, const int startcol, int *const endcolp, long *const flagsp, int16_t **const next_listp, stateitem_T *const cur_si, int *const ccharp) { // Find first character after the keyword. First character was already // checked. - char_u *const kwp = line + startcol; + char *const kwp = line + startcol; int kwlen = 0; do { - kwlen += utfc_ptr2len((char *)kwp + kwlen); + kwlen += utfc_ptr2len(kwp + kwlen); } while (vim_iswordp_buf(kwp + kwlen, syn_buf)); if (kwlen > MAXKEYWLEN) { @@ -2962,8 +2747,8 @@ static int check_keyword_id(char_u *const line, const int startcol, int *const e // Must make a copy of the keyword, so we can add a NUL and make it // lowercase. - char_u keyword[MAXKEYWLEN + 1]; // assume max. keyword len is 80 - STRLCPY(keyword, kwp, kwlen + 1); + char keyword[MAXKEYWLEN + 1]; // assume max. keyword len is 80 + xstrlcpy(keyword, kwp, (size_t)kwlen + 1); keyentry_T *kp = NULL; @@ -2994,17 +2779,17 @@ static int check_keyword_id(char_u *const line, const int startcol, int *const e /// When current_next_list is non-zero accept only that group, otherwise: /// Accept a not-contained keyword at toplevel. /// Accept a keyword at other levels only if it is in the contains list. -static keyentry_T *match_keyword(char_u *keyword, hashtab_T *ht, stateitem_T *cur_si) +static keyentry_T *match_keyword(char *keyword, hashtab_T *ht, stateitem_T *cur_si) { - hashitem_T *hi = hash_find(ht, (char *)keyword); + hashitem_T *hi = hash_find(ht, keyword); if (!HASHITEM_EMPTY(hi)) { for (keyentry_T *kp = HI2KE(hi); kp != NULL; kp = kp->ke_next) { if (current_next_list != 0 ? in_id_list(NULL, current_next_list, &kp->k_syn, 0) : (cur_si == NULL - ? !(kp->flags & HL_CONTAINED) - : in_id_list(cur_si, cur_si->si_cont_list, - &kp->k_syn, kp->flags & HL_CONTAINED))) { + ? !(kp->flags & HL_CONTAINED) + : in_id_list(cur_si, cur_si->si_cont_list, + &kp->k_syn, kp->flags & HL_CONTAINED))) { return kp; } } @@ -3012,15 +2797,13 @@ static keyentry_T *match_keyword(char_u *keyword, hashtab_T *ht, stateitem_T *cu return NULL; } -/* - * Handle ":syntax conceal" command. - */ +// Handle ":syntax conceal" command. static void syn_cmd_conceal(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *next; + char *arg = eap->arg; + char *next; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } @@ -3041,15 +2824,13 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) } } -/* - * Handle ":syntax case" command. - */ +/// Handle ":syntax case" command. static void syn_cmd_case(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *next; + char *arg = eap->arg; + char *next; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } @@ -3073,10 +2854,10 @@ static void syn_cmd_case(exarg_T *eap, int syncing) /// Handle ":syntax foldlevel" command. static void syn_cmd_foldlevel(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *arg_end; + char *arg = eap->arg; + char *arg_end; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } @@ -3103,21 +2884,19 @@ static void syn_cmd_foldlevel(exarg_T *eap, int syncing) return; } - arg = (char_u *)skipwhite((char *)arg_end); + arg = skipwhite(arg_end); if (*arg != NUL) { semsg(_(e_illegal_arg), arg); } } -/* - * Handle ":syntax spell" command. - */ +/// Handle ":syntax spell" command. static void syn_cmd_spell(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *next; + char *arg = eap->arg; + char *next; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } @@ -3143,26 +2922,26 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) } // assume spell checking changed, force a redraw - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } /// Handle ":syntax iskeyword" command. static void syn_cmd_iskeyword(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u save_chartab[32]; - char_u *save_isk; + char *arg = eap->arg; + char save_chartab[32]; + char *save_isk; if (eap->skip) { return; } - arg = (char_u *)skipwhite((char *)arg); + arg = skipwhite(arg); if (*arg == NUL) { msg_puts("\n"); if (curwin->w_s->b_syn_isk != empty_option) { msg_puts("syntax iskeyword "); - msg_outtrans((char *)curwin->w_s->b_syn_isk); + msg_outtrans(curwin->w_s->b_syn_isk); } else { msg_outtrans(_("syntax iskeyword not set")); } @@ -3173,7 +2952,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) } else { memmove(save_chartab, curbuf->b_chartab, (size_t)32); save_isk = curbuf->b_p_isk; - curbuf->b_p_isk = vim_strsave(arg); + curbuf->b_p_isk = xstrdup(arg); buf_init_chartab(curbuf, false); memmove(curwin->w_s->b_syn_chartab, curbuf->b_chartab, (size_t)32); @@ -3183,12 +2962,10 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) curbuf->b_p_isk = save_isk; } } - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } -/* - * Clear all syntax info for one buffer. - */ +// Clear all syntax info for one buffer. void syntax_clear(synblock_T *block) { block->b_syn_error = false; // clear previous error @@ -3236,9 +3013,7 @@ void syntax_clear(synblock_T *block) running_syn_inc_tag = 0; } -/* - * Get rid of ownsyntax for window "wp". - */ +// Get rid of ownsyntax for window "wp". void reset_synblock(win_T *wp) { if (wp->w_s != &wp->w_buffer->b_s) { @@ -3248,9 +3023,7 @@ void reset_synblock(win_T *wp) } } -/* - * Clear syncing info for one buffer. - */ +// Clear syncing info for one buffer. static void syntax_sync_clear(void) { // free the syntax patterns @@ -3273,26 +3046,22 @@ static void syntax_sync_clear(void) syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. } -/* - * Remove one pattern from the buffer's pattern list. - */ +// Remove one pattern from the buffer's pattern list. static void syn_remove_pattern(synblock_T *block, int idx) { synpat_T *spp; spp = &(SYN_ITEMS(block)[idx]); if (spp->sp_flags & HL_FOLD) { - --block->b_syn_folditems; + block->b_syn_folditems--; } syn_clear_pattern(block, idx); memmove(spp, spp + 1, sizeof(synpat_T) * (size_t)(block->b_syn_patterns.ga_len - idx - 1)); block->b_syn_patterns.ga_len--; } -/* - * Clear and free one syntax pattern. When clearing all, must be called from - * last to first! - */ +// Clear and free one syntax pattern. When clearing all, must be called from +// last to first! static void syn_clear_pattern(synblock_T *block, int i) { xfree(SYN_ITEMS(block)[i].sp_pattern); @@ -3305,9 +3074,7 @@ static void syn_clear_pattern(synblock_T *block, int i) } } -/* - * Clear and free one syntax cluster. - */ +// Clear and free one syntax cluster. static void syn_clear_cluster(synblock_T *block, int i) { xfree(SYN_CLSTR(block)[i].scl_name); @@ -3315,34 +3082,28 @@ static void syn_clear_cluster(synblock_T *block, int i) xfree(SYN_CLSTR(block)[i].scl_list); } -/* - * Handle ":syntax clear" command. - */ +/// Handle ":syntax clear" command. static void syn_cmd_clear(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *arg_end; + char *arg = eap->arg; + char *arg_end; int id; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } - /* - * We have to disable this within ":syn include @group filename", - * because otherwise @group would get deleted. - * Only required for Vim 5.x syntax files, 6.0 ones don't contain ":syn - * clear". - */ + // We have to disable this within ":syn include @group filename", + // because otherwise @group would get deleted. + // Only required for Vim 5.x syntax files, 6.0 ones don't contain ":syn + // clear". if (curwin->w_s->b_syn_topgrp != 0) { return; } if (ends_excmd(*arg)) { - /* - * No argument: Clear all syntax items. - */ + // No argument: Clear all syntax items. if (syncing) { syntax_sync_clear(); } else { @@ -3353,9 +3114,7 @@ static void syn_cmd_clear(exarg_T *eap, int syncing) do_unlet(S_LEN("w:current_syntax"), true); } } else { - /* - * Clear the group IDs that are in the argument. - */ + // Clear the group IDs that are in the argument. while (!ends_excmd(*arg)) { arg_end = skiptowhite(arg); if (*arg == '@') { @@ -3363,33 +3122,29 @@ static void syn_cmd_clear(exarg_T *eap, int syncing) if (id == 0) { semsg(_("E391: No such syntax cluster: %s"), arg); break; - } else { - // We can't physically delete a cluster without changing - // the IDs of other clusters, so we do the next best thing - // and make it empty. - int scl_id = id - SYNID_CLUSTER; - - XFREE_CLEAR(SYN_CLSTR(curwin->w_s)[scl_id].scl_list); } + // We can't physically delete a cluster without changing + // the IDs of other clusters, so we do the next best thing + // and make it empty. + int scl_id = id - SYNID_CLUSTER; + + XFREE_CLEAR(SYN_CLSTR(curwin->w_s)[scl_id].scl_list); } else { - id = syn_name2id_len((char *)arg, (size_t)(arg_end - arg)); + id = syn_name2id_len(arg, (size_t)(arg_end - arg)); if (id == 0) { semsg(_(e_nogroup), arg); break; - } else { - syn_clear_one(id, syncing); } + syn_clear_one(id, syncing); } - arg = (char_u *)skipwhite((char *)arg_end); + arg = skipwhite(arg_end); } } - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. } -/* - * Clear one syntax group for the current buffer. - */ +// Clear one syntax group for the current buffer. static void syn_clear_one(const int id, const bool syncing) { synpat_T *spp; @@ -3410,37 +3165,29 @@ static void syn_clear_one(const int id, const bool syncing) } } -/* - * Handle ":syntax on" command. - */ +// Handle ":syntax on" command. static void syn_cmd_on(exarg_T *eap, int syncing) { syn_cmd_onoff(eap, "syntax"); } -/* - * Handle ":syntax reset" command. - * It actually resets highlighting, not syntax. - */ +// Handle ":syntax reset" command. +// It actually resets highlighting, not syntax. static void syn_cmd_reset(exarg_T *eap, int syncing) { - eap->nextcmd = (char *)check_nextcmd((char_u *)eap->arg); + eap->nextcmd = check_nextcmd(eap->arg); if (!eap->skip) { init_highlight(true, true); } } -/* - * Handle ":syntax manual" command. - */ +// Handle ":syntax manual" command. static void syn_cmd_manual(exarg_T *eap, int syncing) { syn_cmd_onoff(eap, "manual"); } -/* - * Handle ":syntax off" command. - */ +// Handle ":syntax off" command. static void syn_cmd_off(exarg_T *eap, int syncing) { syn_cmd_onoff(eap, "nosyntax"); @@ -3449,7 +3196,7 @@ static void syn_cmd_off(exarg_T *eap, int syncing) static void syn_cmd_onoff(exarg_T *eap, char *name) FUNC_ATTR_NONNULL_ALL { - eap->nextcmd = (char *)check_nextcmd((char_u *)eap->arg); + eap->nextcmd = check_nextcmd(eap->arg); if (!eap->skip) { did_syntax_onoff = true; char buf[100]; @@ -3471,13 +3218,13 @@ void syn_maybe_enable(void) /// Handle ":syntax [list]" command: list current syntax words. /// -/// @param syncing when TRUE: list syncing items +/// @param syncing when true: list syncing items static void syn_cmd_list(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *arg_end; + char *arg = eap->arg; + char *arg_end; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } @@ -3520,19 +3267,15 @@ static void syn_cmd_list(exarg_T *eap, int syncing) msg_puts_title(_("\n--- Syntax items ---")); } if (ends_excmd(*arg)) { - /* - * No argument: List all group IDs and all syntax clusters. - */ + // No argument: List all group IDs and all syntax clusters. for (int id = 1; id <= highlight_num_groups() && !got_int; id++) { syn_list_one(id, syncing, false); } - for (int id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; ++id) { + for (int id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; id++) { syn_list_cluster(id); } } else { - /* - * List the group IDs and syntax clusters that are in the argument. - */ + // List the group IDs and syntax clusters that are in the argument. while (!ends_excmd(*arg) && !got_int) { arg_end = skiptowhite(arg); if (*arg == '@') { @@ -3543,17 +3286,17 @@ static void syn_cmd_list(exarg_T *eap, int syncing) syn_list_cluster(id - SYNID_CLUSTER); } } else { - int id = syn_name2id_len((char *)arg, (size_t)(arg_end - arg)); + int id = syn_name2id_len(arg, (size_t)(arg_end - arg)); if (id == 0) { semsg(_(e_nogroup), arg); } else { syn_list_one(id, syncing, true); } } - arg = (char_u *)skipwhite((char *)arg_end); + arg = skipwhite(arg_end); } } - eap->nextcmd = (char *)check_nextcmd(arg); + eap->nextcmd = check_nextcmd(arg); } static void syn_lines_msg(void) @@ -3598,8 +3341,7 @@ static int last_matchgroup; static void syn_list_one(const int id, const bool syncing, const bool link_only) { bool did_header = false; - static struct name_list namelist1[] = - { + static struct name_list namelist1[] = { { HL_DISPLAY, "display" }, { HL_CONTAINED, "contained" }, { HL_ONELINE, "oneline" }, @@ -3612,8 +3354,7 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only) { HL_CONCEALENDS, "concealends" }, { 0, NULL } }; - static struct name_list namelist2[] = - { + static struct name_list namelist2[] = { { HL_SKIPWHITE, "skipwhite" }, { HL_SKIPNL, "skipnl" }, { HL_SKIPEMPTY, "skipempty" }, @@ -3680,8 +3421,8 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only) } msg_putchar(' '); if (spp->sp_sync_idx >= 0) { - msg_outtrans((char *)highlight_group_name(SYN_ITEMS(curwin->w_s) - [spp->sp_sync_idx].sp_syn.id - 1)); + msg_outtrans(highlight_group_name(SYN_ITEMS(curwin->w_s) + [spp->sp_sync_idx].sp_syn.id - 1)); } else { msg_puts("NONE"); } @@ -3694,7 +3435,7 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only) (void)syn_list_header(did_header, 0, id, true); msg_puts_attr("links to", attr); msg_putchar(' '); - msg_outtrans((char *)highlight_group_name(highlight_link_id(id - 1) - 1)); + msg_outtrans(highlight_group_name(highlight_link_id(id - 1) - 1)); } } @@ -3702,7 +3443,7 @@ static void syn_list_flags(struct name_list *nlist, int flags, int attr) { int i; - for (i = 0; nlist[i].flag != 0; ++i) { + for (i = 0; nlist[i].flag != 0; i++) { if (flags & nlist[i].flag) { msg_puts_attr(nlist[i].name, attr); msg_putchar(' '); @@ -3710,16 +3451,14 @@ static void syn_list_flags(struct name_list *nlist, int flags, int attr) } } -/* - * List one syntax cluster, for ":syntax" or "syntax list syntax_name". - */ +// List one syntax cluster, for ":syntax" or "syntax list syntax_name". static void syn_list_cluster(int id) { int endcol = 15; // slight hack: roughly duplicate the guts of syn_list_header() msg_putchar('\n'); - msg_outtrans((char *)SYN_CLSTR(curwin->w_s)[id].scl_name); + msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name); if (msg_col >= endcol) { // output at least one space endcol = msg_col + 1; @@ -3756,9 +3495,9 @@ static void put_id_list(const char *const name, const int16_t *const list, const int scl_id = *p - SYNID_CLUSTER; msg_putchar('@'); - msg_outtrans((char *)SYN_CLSTR(curwin->w_s)[scl_id].scl_name); + msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name); } else { - msg_outtrans((char *)highlight_group_name(*p - 1)); + msg_outtrans(highlight_group_name(*p - 1)); } if (p[1]) { msg_putchar(','); @@ -3780,7 +3519,7 @@ static void put_pattern(const char *const s, const int c, const synpat_T *const if (last_matchgroup == 0) { msg_outtrans("NONE"); } else { - msg_outtrans((char *)highlight_group_name(last_matchgroup - 1)); + msg_outtrans(highlight_group_name(last_matchgroup - 1)); } msg_putchar(' '); } @@ -3790,14 +3529,14 @@ static void put_pattern(const char *const s, const int c, const synpat_T *const msg_putchar(c); // output the pattern, in between a char that is not in the pattern - for (i = 0; vim_strchr((char *)spp->sp_pattern, sepchars[i]) != NULL;) { + for (i = 0; vim_strchr(spp->sp_pattern, (uint8_t)sepchars[i]) != NULL;) { if (sepchars[++i] == NUL) { i = 0; // no good char found, just use the first one break; } } msg_putchar(sepchars[i]); - msg_outtrans((char *)spp->sp_pattern); + msg_outtrans(spp->sp_pattern); msg_putchar(sepchars[i]); // output any pattern options @@ -3865,7 +3604,7 @@ static bool syn_list_keywords(const int id, const hashtab_T *const ht, bool did_ || prev_next_list != kp->next_list) { force_newline = true; } else { - outlen = (int)STRLEN(kp->keyword); + outlen = (int)strlen(kp->keyword); } // output "contained" and "nextgroup" on each line if (syn_list_header(did_header, outlen, id, force_newline)) { @@ -3925,7 +3664,7 @@ static void syn_clear_keyword(int id, hashtab_T *ht) hash_lock(ht); todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { + for (hi = ht->ht_array; todo > 0; hi++) { if (HASHITEM_EMPTY(hi)) { continue; } @@ -3938,7 +3677,7 @@ static void syn_clear_keyword(int id, hashtab_T *ht) if (kp_next == NULL) { hash_remove(ht, hi); } else { - hi->hi_key = KE2HIKEY(kp_next); + hi->hi_key = (char *)KE2HIKEY(kp_next); } } else { kp_prev->ke_next = kp_next; @@ -3956,9 +3695,7 @@ static void syn_clear_keyword(int id, hashtab_T *ht) hash_unlock(ht); } -/* - * Clear a whole keyword table. - */ +// Clear a whole keyword table. static void clear_keywtab(hashtab_T *ht) { hashitem_T *hi; @@ -3967,7 +3704,7 @@ static void clear_keywtab(hashtab_T *ht) keyentry_T *kp_next; todo = (int)ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) { + for (hi = ht->ht_array; todo > 0; hi++) { if (!HASHITEM_EMPTY(hi)) { todo--; for (kp = HI2KE(hi); kp != NULL; kp = kp_next) { @@ -3989,16 +3726,17 @@ static void clear_keywtab(hashtab_T *ht) /// @param flags flags for this keyword /// @param cont_in_list containedin for this keyword /// @param next_list nextgroup for this keyword -static void add_keyword(char_u *const name, const int id, const int flags, +static void add_keyword(char *const name, const int id, const int flags, int16_t *const cont_in_list, int16_t *const next_list, const int conceal_char) { - char_u name_folded[MAXKEYWLEN + 1]; - const char_u *const name_ic = (curwin->w_s->b_syn_ic) - ? str_foldcase(name, (int)STRLEN(name), name_folded, sizeof(name_folded)) + char name_folded[MAXKEYWLEN + 1]; + const char *const name_ic = (curwin->w_s->b_syn_ic) + ? str_foldcase(name, (int)strlen(name), name_folded, + sizeof(name_folded)) : name; - keyentry_T *const kp = xmalloc(sizeof(keyentry_T) + STRLEN(name_ic)); + keyentry_T *const kp = xmalloc(sizeof(keyentry_T) + strlen(name_ic)); STRCPY(kp->keyword, name_ic); kp->k_syn.id = (int16_t)id; kp->k_syn.inc_tag = current_syn_inc_tag; @@ -4006,7 +3744,7 @@ static void add_keyword(char_u *const name, const int id, const int flags, kp->k_char = conceal_char; kp->k_syn.cont_in_list = copy_id_list(cont_in_list); if (cont_in_list != NULL) { - curwin->w_s->b_syn_containedin = TRUE; + curwin->w_s->b_syn_containedin = true; } kp->next_list = copy_id_list(next_list); @@ -4015,7 +3753,7 @@ static void add_keyword(char_u *const name, const int id, const int flags, ? &curwin->w_s->b_keywtab_ic : &curwin->w_s->b_keywtab; hashitem_T *const hi = hash_lookup(ht, (const char *)kp->keyword, - STRLEN(kp->keyword), hash); + strlen(kp->keyword), hash); // even though it looks like only the kp->keyword member is // being used here, vim uses some pointer trickery to get the original @@ -4028,7 +3766,7 @@ static void add_keyword(char_u *const name, const int id, const int flags, } else { // keyword already exists, prepend to list kp->ke_next = HI2KE(hi); - hi->hi_key = KE2HIKEY(kp); + hi->hi_key = (char *)KE2HIKEY(kp); } } @@ -4039,17 +3777,13 @@ static void add_keyword(char_u *const name, const int id, const int flags, /// /// @return a pointer to the first argument. /// Return NULL if the end of the command was found instead of further args. -static char_u *get_group_name(char_u *arg, char_u **name_end) +static char *get_group_name(char *arg, char **name_end) { - char_u *rest; - *name_end = skiptowhite(arg); - rest = (char_u *)skipwhite((char *)(*name_end)); + char *rest = skipwhite(*name_end); - /* - * Check if there are enough arguments. The first argument may be a - * pattern, where '|' is allowed, so only check for NUL. - */ + // Check if there are enough arguments. The first argument may be a + // pattern, where '|' is allowed, so only check for NUL. if (ends_excmd(*arg) || *rest == NUL) { return NULL; } @@ -4063,13 +3797,13 @@ static char_u *get_group_name(char_u *arg, char_u **name_end) /// /// @param arg next argument to be checked /// @param opt various things -/// @param skip TRUE if skipping over command +/// @param skip true if skipping over command /// /// @return a pointer to the next argument (which isn't an option). /// Return NULL for any error; -static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_char, int skip) +static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, int skip) { - char_u *gname_start, *gname; + char *gname_start, *gname; int syn_id; int len = 0; char *p; @@ -4108,11 +3842,9 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha } for (;;) { - /* - * This is used very often when a large number of keywords is defined. - * Need to skip quickly when no option name is found. - * Also avoid tolower(), it's slow. - */ + // This is used very often when a large number of keywords is defined. + // Need to skip quickly when no option name is found. + // Also avoid tolower(), it's slow. if (strchr(first_letters, *arg) == NULL) { break; } @@ -4120,8 +3852,8 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha for (fidx = ARRAY_SIZE(flagtab); --fidx >= 0;) { p = flagtab[fidx].name; int i; - for (i = 0, len = 0; p[i] != NUL; i += 2, ++len) { - if (arg[len] != (char_u)p[i] && arg[len] != (char_u)p[i + 1]) { + for (i = 0, len = 0; p[i] != NUL; i += 2, len++) { + if (arg[len] != p[i] && arg[len] != p[i + 1]) { break; } } @@ -4161,16 +3893,16 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha } } else if (flagtab[fidx].argtype == 11 && arg[5] == '=') { // cchar=? - *conceal_char = utf_ptr2char((char *)arg + 6); - arg += utfc_ptr2len((char *)arg + 6) - 1; + *conceal_char = utf_ptr2char(arg + 6); + arg += utfc_ptr2len(arg + 6) - 1; if (!vim_isprintc_strict(*conceal_char)) { emsg(_("E844: invalid cchar value")); return NULL; } - arg = (char_u *)skipwhite((char *)arg + 7); + arg = skipwhite(arg + 7); } else { opt->flags |= flagtab[fidx].flags; - arg = (char_u *)skipwhite((char *)arg + len); + arg = skipwhite(arg + len); if (flagtab[fidx].flags == HL_SYNC_HERE || flagtab[fidx].flags == HL_SYNC_THERE) { @@ -4183,11 +3915,11 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha if (gname_start == arg) { return NULL; } - gname = vim_strnsave(gname_start, (size_t)(arg - gname_start)); - if (STRCMP(gname, "NONE") == 0) { + gname = xstrnsave(gname_start, (size_t)(arg - gname_start)); + if (strcmp(gname, "NONE") == 0) { *opt->sync_idx = NONE_IDX; } else { - syn_id = syn_name2id((char *)gname); + syn_id = syn_name2id(gname); int i; for (i = curwin->w_s->b_syn_patterns.ga_len; --i >= 0;) { if (SYN_ITEMS(curwin->w_s)[i].sp_syn.id == syn_id @@ -4204,7 +3936,7 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha } xfree(gname); - arg = (char_u *)skipwhite((char *)arg); + arg = skipwhite(arg); } else if (flagtab[fidx].flags == HL_FOLD && foldmethodIsSyntax(curwin)) { // Need to update folds later. @@ -4216,11 +3948,9 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha return arg; } -/* - * Adjustments to syntax item when declared in a ":syn include"'d file. - * Set the contained flag, and if the item is not already contained, add it - * to the specified top-level group, if any. - */ +// Adjustments to syntax item when declared in a ":syn include"'d file. +// Set the contained flag, and if the item is not already contained, add it +// to the specified top-level group, if any. static void syn_incl_toplevel(int id, int *flagsp) { if ((*flagsp & HL_CONTAINED) || curwin->w_s->b_syn_topgrp == 0) { @@ -4239,21 +3969,19 @@ static void syn_incl_toplevel(int id, int *flagsp) } } -/* - * Handle ":syntax include [@{group-name}] filename" command. - */ +// Handle ":syntax include [@{group-name}] filename" command. static void syn_cmd_include(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; + char *arg = eap->arg; int sgl_id = 1; - char_u *group_name_end; - char_u *rest; + char *group_name_end; + char *rest; char *errormsg = NULL; int prev_toplvl_grp; int prev_syn_inc_tag; bool source = false; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } @@ -4270,16 +3998,14 @@ static void syn_cmd_include(exarg_T *eap, int syncing) return; } // separate_nextcmd() and expand_filename() depend on this - eap->arg = (char *)rest; + eap->arg = rest; } - /* - * Everything that's left, up to the next command, should be the - * filename to include. - */ + // Everything that's left, up to the next command, should be the + // filename to include. eap->argt |= (EX_XFILE | EX_NOSPC); separate_nextcmd(eap); - if (*eap->arg == '<' || *eap->arg == '$' || path_is_absolute((char_u *)eap->arg)) { + if (*eap->arg == '<' || *eap->arg == '$' || path_is_absolute(eap->arg)) { // For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the // file. Need to expand the file name first. In other cases // ":runtime!" is used. @@ -4292,10 +4018,8 @@ static void syn_cmd_include(exarg_T *eap, int syncing) } } - /* - * Save and restore the existing top-level grouplist id and ":syn - * include" tag around the actual inclusion. - */ + // Save and restore the existing top-level grouplist id and ":syn + // include" tag around the actual inclusion. if (running_syn_inc_tag >= MAX_SYN_INC_TAG) { emsg(_("E847: Too many syntax includes")); return; @@ -4313,18 +4037,16 @@ static void syn_cmd_include(exarg_T *eap, int syncing) current_syn_inc_tag = prev_syn_inc_tag; } -/* - * Handle ":syntax keyword {group-name} [{option}] keyword .." command. - */ +// Handle ":syntax keyword {group-name} [{option}] keyword .." command. static void syn_cmd_keyword(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *group_name_end; + char *arg = eap->arg; + char *group_name_end; int syn_id; - char_u *rest; - char_u *keyword_copy = NULL; - char_u *p; - char_u *kw; + char *rest; + char *keyword_copy = NULL; + char *p; + char *kw; syn_opt_arg_T syn_opt_arg; int cnt; int conceal_char = NUL; @@ -4335,11 +4057,11 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) if (eap->skip) { syn_id = -1; } else { - syn_id = syn_check_group((char *)arg, (size_t)(group_name_end - arg)); + syn_id = syn_check_group(arg, (size_t)(group_name_end - arg)); } if (syn_id != 0) { // Allocate a buffer, for removing backslashes in the keyword. - keyword_copy = xmalloc(STRLEN(rest) + 1); + keyword_copy = xmalloc(strlen(rest) + 1); } if (keyword_copy != NULL) { syn_opt_arg.flags = 0; @@ -4354,7 +4076,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) // 1: collect the options and copy the keywords to keyword_copy. cnt = 0; p = keyword_copy; - for (; rest != NULL && !ends_excmd(*rest); rest = (char_u *)skipwhite((char *)rest)) { + for (; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest)) { rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest == NULL || ends_excmd(*rest)) { break; @@ -4375,8 +4097,8 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) syn_incl_toplevel(syn_id, &syn_opt_arg.flags); // 2: Add an entry for each keyword. - for (kw = keyword_copy; --cnt >= 0; kw += STRLEN(kw) + 1) { - for (p = (char_u *)vim_strchr((char *)kw, '[');;) { + for (kw = keyword_copy; --cnt >= 0; kw += strlen(kw) + 1) { + for (p = vim_strchr(kw, '[');;) { if (p != NULL) { *p = NUL; } @@ -4399,7 +4121,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) kw = p + 1; break; // skip over the "]" } - const int l = utfc_ptr2len((char *)p + 1); + const int l = utfc_ptr2len(p + 1); memmove(p, p + 1, (size_t)l); p += l; @@ -4415,12 +4137,12 @@ error: } if (rest != NULL) { - eap->nextcmd = (char *)check_nextcmd(rest); + eap->nextcmd = check_nextcmd(rest); } else { semsg(_(e_invarg2), arg); } - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. } @@ -4428,12 +4150,11 @@ error: /// /// Also ":syntax sync match {name} [[grouphere | groupthere] {group-name}] .." /// -/// @param syncing TRUE for ":syntax sync match .. " +/// @param syncing true for ":syntax sync match .. " static void syn_cmd_match(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *group_name_end; - char_u *rest; + char *arg = eap->arg; + char *group_name_end; synpat_T item; // the item found in the line int syn_id; syn_opt_arg_T syn_opt_arg; @@ -4441,7 +4162,7 @@ static void syn_cmd_match(exarg_T *eap, int syncing) int conceal_char = NUL; // Isolate the group name, check for validity - rest = get_group_name(arg, &group_name_end); + char *rest = get_group_name(arg, &group_name_end); // Get options before the pattern syn_opt_arg.flags = 0; @@ -4465,18 +4186,14 @@ static void syn_cmd_match(exarg_T *eap, int syncing) rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest != NULL) { // all arguments are valid - /* - * Check for trailing command and illegal trailing arguments. - */ - eap->nextcmd = (char *)check_nextcmd(rest); + // Check for trailing command and illegal trailing arguments. + eap->nextcmd = check_nextcmd(rest); if (!ends_excmd(*rest) || eap->skip) { rest = NULL; } else { - if ((syn_id = syn_check_group((char *)arg, (size_t)(group_name_end - arg))) != 0) { + if ((syn_id = syn_check_group(arg, (size_t)(group_name_end - arg))) != 0) { syn_incl_toplevel(syn_id, &syn_opt_arg.flags); - /* - * Store the pattern in the syn_items list - */ + // Store the pattern in the syn_items list synpat_T *spp = GA_APPEND_VIA_PTR(synpat_T, &curwin->w_s->b_syn_patterns); *spp = item; @@ -4490,7 +4207,7 @@ static void syn_cmd_match(exarg_T *eap, int syncing) spp->sp_syn.cont_in_list = syn_opt_arg.cont_in_list; spp->sp_cchar = conceal_char; if (syn_opt_arg.cont_in_list != NULL) { - curwin->w_s->b_syn_containedin = TRUE; + curwin->w_s->b_syn_containedin = true; } spp->sp_next_list = syn_opt_arg.next_list; @@ -4499,19 +4216,17 @@ static void syn_cmd_match(exarg_T *eap, int syncing) curwin->w_s->b_syn_sync_flags |= SF_MATCH; } if (syn_opt_arg.flags & HL_FOLD) { - ++curwin->w_s->b_syn_folditems; + curwin->w_s->b_syn_folditems++; } - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. return; // don't free the progs and patterns now } } } - /* - * Something failed, free the allocated memory. - */ + // Something failed, free the allocated memory. vim_regfree(item.sp_prog); xfree(item.sp_pattern); xfree(syn_opt_arg.cont_list); @@ -4526,15 +4241,15 @@ static void syn_cmd_match(exarg_T *eap, int syncing) /// Handle ":syntax region {group-name} [matchgroup={group-name}] /// start {start} .. [skip {skip}] end {end} .. [{options}]". /// -/// @param syncing TRUE for ":syntax sync region .." +/// @param syncing true for ":syntax sync region .." static void syn_cmd_region(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *group_name_end; - char_u *rest; // next arg, NULL on error - char_u *key_end; - char_u *key = NULL; - char_u *p; + char *arg = eap->arg; + char *group_name_end; + char *rest; // next arg, NULL on error + char *key_end; + char *key = NULL; + char *p; int item; #define ITEM_START 0 #define ITEM_SKIP 1 @@ -4589,13 +4304,13 @@ static void syn_cmd_region(exarg_T *eap, int syncing) } xfree(key); key = vim_strnsave_up(rest, (size_t)(key_end - rest)); - if (STRCMP(key, "MATCHGROUP") == 0) { + if (strcmp(key, "MATCHGROUP") == 0) { item = ITEM_MATCHGROUP; - } else if (STRCMP(key, "START") == 0) { + } else if (strcmp(key, "START") == 0) { item = ITEM_START; - } else if (STRCMP(key, "END") == 0) { + } else if (strcmp(key, "END") == 0) { item = ITEM_END; - } else if (STRCMP(key, "SKIP") == 0) { + } else if (strcmp(key, "SKIP") == 0) { if (pat_ptrs[ITEM_SKIP] != NULL) { // One skip pattern allowed. illegal = true; break; @@ -4604,13 +4319,13 @@ static void syn_cmd_region(exarg_T *eap, int syncing) } else { break; } - rest = (char_u *)skipwhite((char *)key_end); + rest = skipwhite(key_end); if (*rest != '=') { rest = NULL; semsg(_("E398: Missing '=': %s"), arg); break; } - rest = (char_u *)skipwhite((char *)rest + 1); + rest = skipwhite(rest + 1); if (*rest == NUL) { not_enough = true; break; @@ -4618,22 +4333,20 @@ static void syn_cmd_region(exarg_T *eap, int syncing) if (item == ITEM_MATCHGROUP) { p = skiptowhite(rest); - if ((p - rest == 4 && STRNCMP(rest, "NONE", 4) == 0) || eap->skip) { + if ((p - rest == 4 && strncmp(rest, "NONE", 4) == 0) || eap->skip) { matchgroup_id = 0; } else { - matchgroup_id = syn_check_group((char *)rest, (size_t)(p - rest)); + matchgroup_id = syn_check_group(rest, (size_t)(p - rest)); if (matchgroup_id == 0) { illegal = true; break; } } - rest = (char_u *)skipwhite((char *)p); + rest = skipwhite(p); } else { - /* - * Allocate room for a syn_pattern, and link it in the list of - * syn_patterns for this item, at the start (because the list is - * used from end to start). - */ + // Allocate room for a syn_pattern, and link it in the list of + // syn_patterns for this item, at the start (because the list is + // used from end to start). ppp = xmalloc(sizeof(struct pat_ptr)); ppp->pp_next = pat_ptrs[item]; pat_ptrs[item] = ppp; @@ -4671,22 +4384,18 @@ static void syn_cmd_region(exarg_T *eap, int syncing) } if (rest != NULL) { - /* - * Check for trailing garbage or command. - * If OK, add the item. - */ - eap->nextcmd = (char *)check_nextcmd(rest); + // Check for trailing garbage or command. + // If OK, add the item. + eap->nextcmd = check_nextcmd(rest); if (!ends_excmd(*rest) || eap->skip) { rest = NULL; } else { ga_grow(&(curwin->w_s->b_syn_patterns), pat_count); - if ((syn_id = syn_check_group((char *)arg, (size_t)(group_name_end - arg))) != 0) { + if ((syn_id = syn_check_group(arg, (size_t)(group_name_end - arg))) != 0) { syn_incl_toplevel(syn_id, &syn_opt_arg.flags); - /* - * Store the start/skip/end in the syn_items list - */ + // Store the start/skip/end in the syn_items list int idx = curwin->w_s->b_syn_patterns.ga_len; - for (item = ITEM_START; item <= ITEM_END; ++item) { + for (item = ITEM_START; item <= ITEM_END; item++) { for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp->pp_next) { SYN_ITEMS(curwin->w_s)[idx] = *(ppp->pp_synp); SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing; @@ -4705,7 +4414,7 @@ static void syn_cmd_region(exarg_T *eap, int syncing) SYN_ITEMS(curwin->w_s)[idx].sp_syn.cont_in_list = syn_opt_arg.cont_in_list; if (syn_opt_arg.cont_in_list != NULL) { - curwin->w_s->b_syn_containedin = TRUE; + curwin->w_s->b_syn_containedin = true; } SYN_ITEMS(curwin->w_s)[idx].sp_next_list = syn_opt_arg.next_list; @@ -4713,22 +4422,20 @@ static void syn_cmd_region(exarg_T *eap, int syncing) curwin->w_s->b_syn_patterns.ga_len++; idx++; if (syn_opt_arg.flags & HL_FOLD) { - ++curwin->w_s->b_syn_folditems; + curwin->w_s->b_syn_folditems++; } } } - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. success = true; // don't free the progs and patterns now } } } - /* - * Free the allocated memory. - */ - for (item = ITEM_START; item <= ITEM_END; ++item) { + // Free the allocated memory. + for (item = ITEM_START; item <= ITEM_END; item++) { for (ppp = pat_ptrs[item]; ppp != NULL; ppp = ppp_next) { if (!success && ppp->pp_synp != NULL) { vim_regfree(ppp->pp_synp->sp_prog); @@ -4771,9 +4478,7 @@ static void syn_combine_list(int16_t **const clstr1, int16_t **const clstr2, con const int16_t *g2; int16_t *clstr = NULL; - /* - * Handle degenerate cases. - */ + // Handle degenerate cases. if (*clstr2 == NULL) { return; } @@ -4809,13 +4514,9 @@ static void syn_combine_list(int16_t **const clstr1, int16_t **const clstr2, con g2 = *clstr2; int count = 0; - /* - * First, loop through the lists until one of them is empty. - */ + // First, loop through the lists until one of them is empty. while (*g1 && *g2) { - /* - * We always want to add from the first list. - */ + // We always want to add from the first list. if (*g1 < *g2) { if (round == 2) { clstr[count] = *g1; @@ -4824,10 +4525,8 @@ static void syn_combine_list(int16_t **const clstr1, int16_t **const clstr2, con g1++; continue; } - /* - * We only want to add from the second list if we're adding the - * lists. - */ + // We only want to add from the second list if we're adding the + // lists. if (list_op == CLUSTER_ADD) { if (round == 2) { clstr[count] = *g2; @@ -4840,11 +4539,9 @@ static void syn_combine_list(int16_t **const clstr1, int16_t **const clstr2, con g2++; } - /* - * Now add the leftovers from whichever list didn't get finished - * first. As before, we only want to add from the second list if - * we're adding the lists. - */ + // Now add the leftovers from whichever list didn't get finished + // first. As before, we only want to add from the second list if + // we're adding the lists. for (; *g1; g1++, count++) { if (round == 2) { clstr[count] = *g1; @@ -4859,10 +4556,8 @@ static void syn_combine_list(int16_t **const clstr1, int16_t **const clstr2, con } if (round == 1) { - /* - * If the group ended up empty, we don't need to allocate any - * space for it. - */ + // If the group ended up empty, we don't need to allocate any + // space for it. if (count == 0) { clstr = NULL; break; @@ -4872,24 +4567,22 @@ static void syn_combine_list(int16_t **const clstr1, int16_t **const clstr2, con } } - /* - * Finally, put the new list in place. - */ + // Finally, put the new list in place. xfree(*clstr1); xfree(*clstr2); *clstr1 = clstr; } -// Lookup a syntax cluster name and return its ID. -// If it is not found, 0 is returned. -static int syn_scl_name2id(char_u *name) +/// Lookup a syntax cluster name and return its ID. +/// If it is not found, 0 is returned. +static int syn_scl_name2id(char *name) { // Avoid using stricmp() too much, it's slow on some systems - char_u *name_u = vim_strsave_up(name); + char *name_u = vim_strsave_up(name); int i; for (i = curwin->w_s->b_syn_clusters.ga_len; --i >= 0;) { if (SYN_CLSTR(curwin->w_s)[i].scl_name_u != NULL - && STRCMP(name_u, SYN_CLSTR(curwin->w_s)[i].scl_name_u) == 0) { + && strcmp(name_u, SYN_CLSTR(curwin->w_s)[i].scl_name_u) == 0) { break; } } @@ -4897,25 +4590,24 @@ static int syn_scl_name2id(char_u *name) return i < 0 ? 0 : i + SYNID_CLUSTER; } -/* - * Like syn_scl_name2id(), but take a pointer + length argument. - */ -static int syn_scl_namen2id(char_u *linep, int len) +/// Like syn_scl_name2id(), but take a pointer + length argument. +static int syn_scl_namen2id(char *linep, int len) { - char_u *name = vim_strnsave(linep, (size_t)len); + char *name = xstrnsave(linep, (size_t)len); int id = syn_scl_name2id(name); xfree(name); return id; } -// Find syntax cluster name in the table and return its ID. -// The argument is a pointer to the name and the length of the name. -// If it doesn't exist yet, a new entry is created. -// Return 0 for failure. -static int syn_check_cluster(char_u *pp, int len) +/// Find syntax cluster name in the table and return its ID. +/// The argument is a pointer to the name and the length of the name. +/// If it doesn't exist yet, a new entry is created. +/// +/// @return 0 for failure. +static int syn_check_cluster(char *pp, int len) { - char_u *name = vim_strnsave(pp, (size_t)len); + char *name = xstrnsave(pp, (size_t)len); int id = syn_scl_name2id(name); if (id == 0) { // doesn't exist yet id = syn_add_cluster(name); @@ -4925,14 +4617,13 @@ static int syn_check_cluster(char_u *pp, int len) return id; } -// Add new syntax cluster and return its ID. -// "name" must be an allocated string, it will be consumed. -// Return 0 for failure. -static int syn_add_cluster(char_u *name) +/// Add new syntax cluster and return its ID. +/// "name" must be an allocated string, it will be consumed. +/// +/// @return 0 for failure. +static int syn_add_cluster(char *name) { - /* - * First call for this growarray: init growing array. - */ + // First call for this growarray: init growing array. if (curwin->w_s->b_syn_clusters.ga_data == NULL) { curwin->w_s->b_syn_clusters.ga_itemsize = sizeof(syn_cluster_T); ga_set_growsize(&curwin->w_s->b_syn_clusters, 10); @@ -4962,20 +4653,18 @@ static int syn_add_cluster(char_u *name) return len + SYNID_CLUSTER; } -/* - * Handle ":syntax cluster {cluster-name} [contains={groupname},..] - * [add={groupname},..] [remove={groupname},..]". - */ +// Handle ":syntax cluster {cluster-name} [contains={groupname},..] +// [add={groupname},..] [remove={groupname},..]". static void syn_cmd_cluster(exarg_T *eap, int syncing) { - char_u *arg = (char_u *)eap->arg; - char_u *group_name_end; - char_u *rest; + char *arg = eap->arg; + char *group_name_end; + char *rest; bool got_clstr = false; int opt_len; int list_op; - eap->nextcmd = (char *)find_nextcmd(arg); + eap->nextcmd = find_nextcmd(arg); if (eap->skip) { return; } @@ -5021,7 +4710,7 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing) } if (got_clstr) { - redraw_curbuf_later(SOME_VALID); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all. } } @@ -5034,21 +4723,18 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing) } } -/* - * On first call for current buffer: Init growing array. - */ +// On first call for current buffer: Init growing array. static void init_syn_patterns(void) { curwin->w_s->b_syn_patterns.ga_itemsize = sizeof(synpat_T); ga_set_growsize(&curwin->w_s->b_syn_patterns, 10); } -/* - * Get one pattern for a ":syntax match" or ":syntax region" command. - * Stores the pattern and program in a synpat_T. - * Returns a pointer to the next argument, or NULL in case of an error. - */ -static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) +/// Get one pattern for a ":syntax match" or ":syntax region" command. +/// Stores the pattern and program in a synpat_T. +/// +/// @return a pointer to the next argument, or NULL in case of an error. +static char *get_syn_pattern(char *arg, synpat_T *ci) { char *end; int *p; @@ -5060,18 +4746,18 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) return NULL; } - end = (char *)skip_regexp(arg + 1, *arg, true, NULL); - if (*end != (char)(*arg)) { // end delimiter not found + end = skip_regexp(arg + 1, *arg, true); + if (*end != *arg) { // end delimiter not found semsg(_("E401: Pattern delimiter not found: %s"), arg); return NULL; } // store the pattern and compiled regexp program - ci->sp_pattern = vim_strnsave(arg + 1, (size_t)(end - (char *)arg) - 1); + ci->sp_pattern = xstrnsave(arg + 1, (size_t)(end - arg) - 1); // Make 'cpoptions' empty, to avoid the 'l' flag cpo_save = p_cpo; - p_cpo = ""; - ci->sp_prog = vim_regcomp((char *)ci->sp_pattern, RE_MAGIC); + p_cpo = empty_option; + ci->sp_prog = vim_regcomp(ci->sp_pattern, RE_MAGIC); p_cpo = cpo_save; if (ci->sp_prog == NULL) { @@ -5080,13 +4766,11 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) ci->sp_ic = curwin->w_s->b_syn_ic; syn_clear_time(&ci->sp_time); - /* - * Check for a match, highlight or region offset. - */ + // Check for a match, highlight or region offset. end++; do { for (idx = SPO_COUNT; --idx >= 0;) { - if (STRNCMP(end, spo_name_tab[idx], 3) == 0) { + if (strncmp(end, spo_name_tab[idx], 3) == 0) { break; } } @@ -5137,59 +4821,57 @@ static char_u *get_syn_pattern(char_u *arg, synpat_T *ci) semsg(_("E402: Garbage after pattern: %s"), arg); return NULL; } - return (char_u *)skipwhite(end); + return skipwhite(end); } -/* - * Handle ":syntax sync .." command. - */ +/// Handle ":syntax sync .." command. static void syn_cmd_sync(exarg_T *eap, int syncing) { - char_u *arg_start = (char_u *)eap->arg; + char *arg_start = eap->arg; char *arg_end; - char_u *key = NULL; - char_u *next_arg; + char *key = NULL; + char *next_arg; int illegal = false; int finished = false; char *cpo_save; if (ends_excmd(*arg_start)) { - syn_cmd_list(eap, TRUE); + syn_cmd_list(eap, true); return; } while (!ends_excmd(*arg_start)) { - arg_end = (char *)skiptowhite(arg_start); - next_arg = (char_u *)skipwhite(arg_end); + arg_end = skiptowhite(arg_start); + next_arg = skipwhite(arg_end); xfree(key); - key = vim_strnsave_up(arg_start, (size_t)(arg_end - (char *)arg_start)); - if (STRCMP(key, "CCOMMENT") == 0) { + key = vim_strnsave_up(arg_start, (size_t)(arg_end - arg_start)); + if (strcmp(key, "CCOMMENT") == 0) { if (!eap->skip) { curwin->w_s->b_syn_sync_flags |= SF_CCOMMENT; } if (!ends_excmd(*next_arg)) { - arg_end = (char *)skiptowhite(next_arg); + arg_end = skiptowhite(next_arg); if (!eap->skip) { curwin->w_s->b_syn_sync_id = - (int16_t)syn_check_group((char *)next_arg, (size_t)(arg_end - (char *)next_arg)); + (int16_t)syn_check_group(next_arg, (size_t)(arg_end - next_arg)); } - next_arg = (char_u *)skipwhite(arg_end); + next_arg = skipwhite(arg_end); } else if (!eap->skip) { curwin->w_s->b_syn_sync_id = (int16_t)syn_name2id("Comment"); } - } else if (STRNCMP(key, "LINES", 5) == 0 - || STRNCMP(key, "MINLINES", 8) == 0 - || STRNCMP(key, "MAXLINES", 8) == 0 - || STRNCMP(key, "LINEBREAKS", 10) == 0) { + } else if (strncmp(key, "LINES", 5) == 0 + || strncmp(key, "MINLINES", 8) == 0 + || strncmp(key, "MAXLINES", 8) == 0 + || strncmp(key, "LINEBREAKS", 10) == 0) { if (key[4] == 'S') { - arg_end = (char *)key + 6; + arg_end = key + 6; } else if (key[0] == 'L') { - arg_end = (char *)key + 11; + arg_end = key + 11; } else { - arg_end = (char *)key + 9; + arg_end = key + 9; } if (arg_end[-1] != '=' || !ascii_isdigit(*arg_end)) { - illegal = TRUE; + illegal = true; break; } linenr_T n = getdigits_int32(&arg_end, false, 0); @@ -5202,23 +4884,23 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) curwin->w_s->b_syn_sync_minlines = n; } } - } else if (STRCMP(key, "FROMSTART") == 0) { + } else if (strcmp(key, "FROMSTART") == 0) { if (!eap->skip) { curwin->w_s->b_syn_sync_minlines = MAXLNUM; curwin->w_s->b_syn_sync_maxlines = 0; } - } else if (STRCMP(key, "LINECONT") == 0) { + } else if (strcmp(key, "LINECONT") == 0) { if (*next_arg == NUL) { // missing pattern illegal = true; break; } if (curwin->w_s->b_syn_linecont_pat != NULL) { emsg(_("E403: syntax sync: line continuations pattern specified twice")); - finished = TRUE; + finished = true; break; } - arg_end = (char *)skip_regexp(next_arg + 1, *next_arg, true, NULL); - if (*arg_end != (char)(*next_arg)) { // end delimiter not found + arg_end = skip_regexp(next_arg + 1, *next_arg, true); + if (*arg_end != *next_arg) { // end delimiter not found illegal = true; break; } @@ -5226,14 +4908,14 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) if (!eap->skip) { // store the pattern and compiled regexp program curwin->w_s->b_syn_linecont_pat = - vim_strnsave(next_arg + 1, (size_t)(arg_end - (char *)next_arg) - 1); + xstrnsave(next_arg + 1, (size_t)(arg_end - next_arg) - 1); curwin->w_s->b_syn_linecont_ic = curwin->w_s->b_syn_ic; // Make 'cpoptions' empty, to avoid the 'l' flag cpo_save = p_cpo; - p_cpo = ""; + p_cpo = empty_option; curwin->w_s->b_syn_linecont_prog = - vim_regcomp((char *)curwin->w_s->b_syn_linecont_pat, RE_MAGIC); + vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC); p_cpo = cpo_save; syn_clear_time(&curwin->w_s->b_syn_linecont_time); @@ -5243,19 +4925,19 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) break; } } - next_arg = (char_u *)skipwhite(arg_end + 1); + next_arg = skipwhite(arg_end + 1); } else { - eap->arg = (char *)next_arg; - if (STRCMP(key, "MATCH") == 0) { - syn_cmd_match(eap, TRUE); - } else if (STRCMP(key, "REGION") == 0) { - syn_cmd_region(eap, TRUE); - } else if (STRCMP(key, "CLEAR") == 0) { - syn_cmd_clear(eap, TRUE); + eap->arg = next_arg; + if (strcmp(key, "MATCH") == 0) { + syn_cmd_match(eap, true); + } else if (strcmp(key, "REGION") == 0) { + syn_cmd_region(eap, true); + } else if (strcmp(key, "CLEAR") == 0) { + syn_cmd_clear(eap, true); } else { - illegal = TRUE; + illegal = true; } - finished = TRUE; + finished = true; break; } arg_start = next_arg; @@ -5264,8 +4946,8 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) if (illegal) { semsg(_("E404: Illegal arguments: %s"), arg_start); } else if (!finished) { - eap->nextcmd = (char *)check_nextcmd(arg_start); - redraw_curbuf_later(SOME_VALID); + eap->nextcmd = check_nextcmd(arg_start); + redraw_curbuf_later(UPD_SOME_VALID); syn_stack_free_all(curwin->w_s); // Need to recompute all syntax. } } @@ -5279,7 +4961,7 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) /// @param list where to store the resulting list, if not NULL, the list is silently skipped! /// /// @return FAIL for some error, OK for success. -static int get_id_list(char_u **const arg, const int keylen, int16_t **const list, const bool skip) +static int get_id_list(char **const arg, const int keylen, int16_t **const list, const bool skip) { char *p = NULL; char *end; @@ -5296,7 +4978,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis // grow when a regexp is used. In that case round 1 is done once again. for (int round = 1; round <= 2; round++) { // skip "contains" - p = skipwhite((char *)(*arg) + keylen); + p = skipwhite(*arg + keylen); if (*p != '=') { semsg(_("E405: Missing equal sign: %s"), *arg); break; @@ -5312,11 +4994,11 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis do { for (end = p; *end && !ascii_iswhite(*end) && *end != ','; end++) {} char *const name = xmalloc((size_t)(end - p) + 3); // leave room for "^$" - STRLCPY(name + 1, p, end - p + 1); - if (STRCMP(name + 1, "ALLBUT") == 0 - || STRCMP(name + 1, "ALL") == 0 - || STRCMP(name + 1, "TOP") == 0 - || STRCMP(name + 1, "CONTAINED") == 0) { + xstrlcpy(name + 1, p, (size_t)(end - p) + 1); + if (strcmp(name + 1, "ALLBUT") == 0 + || strcmp(name + 1, "ALL") == 0 + || strcmp(name + 1, "TOP") == 0 + || strcmp(name + 1, "CONTAINED") == 0) { if (TOUPPER_ASC(**arg) != 'C') { semsg(_("E407: %s not allowed here"), name + 1); failed = true; @@ -5345,12 +5027,10 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis if (skip) { id = -1; } else { - id = syn_check_cluster((char_u *)name + 2, (int)(end - p - 1)); + id = syn_check_cluster(name + 2, (int)(end - p - 1)); } } else { - /* - * Handle full group name. - */ + // Handle full group name. if (strpbrk(name + 1, "\\.*^$~[") == NULL) { id = syn_check_group((name + 1), (size_t)(end - p)); } else { @@ -5364,10 +5044,10 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis break; } - regmatch.rm_ic = TRUE; + regmatch.rm_ic = true; id = 0; for (int i = highlight_num_groups(); --i >= 0;) { - if (vim_regexec(®match, (char *)highlight_group_name(i), (colnr_T)0)) { + if (vim_regexec(®match, highlight_group_name(i), (colnr_T)0)) { if (round == 2) { // Got more items than expected; can happen // when adding items that match: @@ -5421,7 +5101,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis } } - *arg = (char_u *)p; + *arg = p; if (failed || retval == NULL) { xfree(retval); return FAIL; @@ -5435,9 +5115,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis return OK; } -/* - * Make a copy of an ID list. - */ +// Make a copy of an ID list. static int16_t *copy_id_list(const int16_t *const list) { if (list == NULL) { @@ -5472,7 +5150,7 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in static int depth = 0; int r; - // If ssp has a "containedin" list and "cur_si" is in it, return TRUE. + // If ssp has a "containedin" list and "cur_si" is in it, return true. if (cur_si != NULL && ssp->cont_in_list != NULL && !(cur_si->si_flags & HL_MATCH)) { // Ignore transparent items without a contains argument. Double check @@ -5486,54 +5164,48 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in &(SYN_ITEMS(syn_block)[cur_si->si_idx].sp_syn), SYN_ITEMS(syn_block)[cur_si->si_idx].sp_flags & HL_CONTAINED)) { - return TRUE; + return true; } } if (list == NULL) { - return FALSE; + return false; } - /* - * If list is ID_LIST_ALL, we are in a transparent item that isn't - * inside anything. Only allow not-contained groups. - */ + // If list is ID_LIST_ALL, we are in a transparent item that isn't + // inside anything. Only allow not-contained groups. if (list == ID_LIST_ALL) { return !contained; } - /* - * If the first item is "ALLBUT", return TRUE if "id" is NOT in the - * contains list. We also require that "id" is at the same ":syn include" - * level as the list. - */ + // If the first item is "ALLBUT", return true if "id" is NOT in the + // contains list. We also require that "id" is at the same ":syn include" + // level as the list. item = *list; if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER) { if (item < SYNID_TOP) { // ALL or ALLBUT: accept all groups in the same file if (item - SYNID_ALLBUT != ssp->inc_tag) { - return FALSE; + return false; } } else if (item < SYNID_CONTAINED) { // TOP: accept all not-contained groups in the same file if (item - SYNID_TOP != ssp->inc_tag || contained) { - return FALSE; + return false; } } else { // CONTAINED: accept all contained groups in the same file if (item - SYNID_CONTAINED != ssp->inc_tag || !contained) { - return FALSE; + return false; } } item = *++list; - retval = FALSE; + retval = false; } else { - retval = TRUE; + retval = true; } - /* - * Return "retval" if id is in the contains list. - */ + // Return "retval" if id is in the contains list. while (item != 0) { if (item == id) { return retval; @@ -5561,8 +5233,7 @@ struct subcommand { void (*func)(exarg_T *, int); // function to call }; -static struct subcommand subcommands[] = -{ +static struct subcommand subcommands[] = { { "case", syn_cmd_case }, { "clear", syn_cmd_clear }, { "cluster", syn_cmd_cluster }, @@ -5585,21 +5256,19 @@ static struct subcommand subcommands[] = { NULL, NULL } }; -/* - * ":syntax". - * This searches the subcommands[] table for the subcommand name, and calls a - * syntax_subcommand() function to do the rest. - */ +/// ":syntax". +/// This searches the subcommands[] table for the subcommand name, and calls a +/// syntax_subcommand() function to do the rest. void ex_syntax(exarg_T *eap) { - char_u *arg = (char_u *)eap->arg; - char_u *subcmd_end; + char *arg = eap->arg; + char *subcmd_end; syn_cmdlinep = eap->cmdlinep; // isolate subcommand name for (subcmd_end = arg; ASCII_ISALPHA(*subcmd_end); subcmd_end++) {} - char_u *const subcmd_name = vim_strnsave(arg, (size_t)(subcmd_end - arg)); + char *const subcmd_name = xstrnsave(arg, (size_t)(subcmd_end - arg)); if (eap->skip) { // skip error messages for all subcommands emsg_skip++; } @@ -5608,8 +5277,8 @@ void ex_syntax(exarg_T *eap) semsg(_("E410: Invalid :syntax subcommand: %s"), subcmd_name); break; } - if (STRCMP(subcmd_name, (char_u *)subcommands[i].name) == 0) { - eap->arg = skipwhite((char *)subcmd_end); + if (strcmp(subcmd_name, subcommands[i].name) == 0) { + eap->arg = skipwhite(subcmd_end); (subcommands[i].func)(eap, false); break; } @@ -5622,14 +5291,14 @@ void ex_syntax(exarg_T *eap) void ex_ownsyntax(exarg_T *eap) { - char_u *old_value; - char_u *new_value; + char *old_value; + char *new_value; if (curwin->w_s == &curwin->w_buffer->b_s) { curwin->w_s = xcalloc(1, sizeof(synblock_T)); hash_init(&curwin->w_s->b_keywtab); hash_init(&curwin->w_s->b_keywtab_ic); - // TODO: Keep the spell checking as it was. NOLINT(readability/todo) + // TODO(vim): Keep the spell checking as it was. curwin->w_p_spell = false; // No spell checking // make sure option values are "empty_option" instead of NULL clear_string_option(&curwin->w_s->b_p_spc); @@ -5642,7 +5311,7 @@ void ex_ownsyntax(exarg_T *eap) // Save value of b:current_syntax. old_value = get_var_value("b:current_syntax"); if (old_value != NULL) { - old_value = vim_strsave(old_value); + old_value = xstrdup(old_value); } // Apply the "syntax" autocommand event, this finds and loads the syntax file. @@ -5651,14 +5320,14 @@ void ex_ownsyntax(exarg_T *eap) // Move value of b:current_syntax to w:current_syntax. new_value = get_var_value("b:current_syntax"); if (new_value != NULL) { - set_internal_string_var("w:current_syntax", (char *)new_value); + set_internal_string_var("w:current_syntax", new_value); } // Restore value of b:current_syntax. if (old_value == NULL) { do_unlet(S_LEN("b:current_syntax"), true); } else { - set_internal_string_var("b:current_syntax", (char *)old_value); + set_internal_string_var("b:current_syntax", old_value); xfree(old_value); } } @@ -5675,22 +5344,19 @@ static enum { EXP_SUBCMD, // expand ":syn" sub-commands EXP_CASE, // expand ":syn case" arguments EXP_SPELL, // expand ":syn spell" arguments - EXP_SYNC, // expand ":syn sync" arguments + EXP_SYNC, // expand ":syn sync" arguments + EXP_CLUSTER, // expand ":syn list @cluster" arguments } expand_what; -/* - * Reset include_link, include_default, include_none to 0. - * Called when we are done expanding. - */ +// Reset include_link, include_default, include_none to 0. +// Called when we are done expanding. void reset_expand_highlight(void) { include_link = include_default = include_none = 0; } -/* - * Handle command line completion for :match and :echohl command: Add "None" - * as highlight group. - */ +// Handle command line completion for :match and :echohl command: Add "None" +// as highlight group. void set_context_in_echohl_cmd(expand_T *xp, const char *arg) { xp->xp_context = EXPAND_HIGHLIGHT; @@ -5698,9 +5364,7 @@ void set_context_in_echohl_cmd(expand_T *xp, const char *arg) include_none = 1; } -/* - * Handle command line completion for :syntax command. - */ +// Handle command line completion for :syntax command. void set_context_in_syntax_cmd(expand_T *xp, const char *arg) { // Default: expand subcommands. @@ -5710,35 +5374,44 @@ void set_context_in_syntax_cmd(expand_T *xp, const char *arg) include_link = 0; include_default = 0; + if (*arg == NUL) { + return; + } + // (part of) subcommand already typed - if (*arg != NUL) { - const char *p = (const char *)skiptowhite((const char_u *)arg); - if (*p != NUL) { // Past first word. - xp->xp_pattern = skipwhite(p); - if (*skiptowhite((char_u *)xp->xp_pattern) != NUL) { - xp->xp_context = EXPAND_NOTHING; - } else if (STRNICMP(arg, "case", p - arg) == 0) { - expand_what = EXP_CASE; - } else if (STRNICMP(arg, "spell", p - arg) == 0) { - expand_what = EXP_SPELL; - } else if (STRNICMP(arg, "sync", p - arg) == 0) { - expand_what = EXP_SYNC; - } else if (STRNICMP(arg, "keyword", p - arg) == 0 - || STRNICMP(arg, "region", p - arg) == 0 - || STRNICMP(arg, "match", p - arg) == 0 - || STRNICMP(arg, "list", p - arg) == 0) { - xp->xp_context = EXPAND_HIGHLIGHT; - } else { - xp->xp_context = EXPAND_NOTHING; - } + const char *p = (const char *)skiptowhite(arg); + if (*p == NUL) { + return; + } + + // past first world + xp->xp_pattern = skipwhite(p); + if (*skiptowhite(xp->xp_pattern) != NUL) { + xp->xp_context = EXPAND_NOTHING; + } else if (STRNICMP(arg, "case", p - arg) == 0) { + expand_what = EXP_CASE; + } else if (STRNICMP(arg, "spell", p - arg) == 0) { + expand_what = EXP_SPELL; + } else if (STRNICMP(arg, "sync", p - arg) == 0) { + expand_what = EXP_SYNC; + } else if (STRNICMP(arg, "list", p - arg) == 0) { + p = skipwhite(p); + if (*p == '@') { + expand_what = EXP_CLUSTER; + } else { + xp->xp_context = EXPAND_HIGHLIGHT; } + } else if (STRNICMP(arg, "keyword", p - arg) == 0 + || STRNICMP(arg, "region", p - arg) == 0 + || STRNICMP(arg, "match", p - arg) == 0) { + xp->xp_context = EXPAND_HIGHLIGHT; + } else { + xp->xp_context = EXPAND_NOTHING; } } -/* - * Function given to ExpandGeneric() to obtain the list syntax names for - * expansion. - */ +// Function given to ExpandGeneric() to obtain the list syntax names for +// expansion. char *get_syntax_name(expand_T *xp, int idx) { switch (expand_what) { @@ -5760,6 +5433,14 @@ char *get_syntax_name(expand_T *xp, int idx) "maxlines=", "minlines=", "region", NULL }; return sync_args[idx]; } + case EXP_CLUSTER: + if (idx < curwin->w_s->b_syn_clusters.ga_len) { + vim_snprintf(xp->xp_buf, EXPAND_BUF_LEN, "@%s", + SYN_CLSTR(curwin->w_s)[idx].scl_name); + return xp->xp_buf; + } else { + return NULL; + } } return NULL; } @@ -5786,12 +5467,10 @@ int syn_get_id(win_T *wp, linenr_T lnum, colnr_T col, int trans, bool *spellp, i return trans ? current_trans_id : current_id; } -/* - * Get extra information about the syntax item. Must be called right after - * get_syntax_attr(). - * Stores the current item sequence nr in "*seqnrp". - * Returns the current flags. - */ +// Get extra information about the syntax item. Must be called right after +// get_syntax_attr(). +// Stores the current item sequence nr in "*seqnrp". +// Returns the current flags. int get_syntax_info(int *seqnrp) { *seqnrp = current_seqnr; @@ -5815,24 +5494,20 @@ int syn_get_concealed_id(win_T *wp, linenr_T lnum, colnr_T col) return 0; } -/* - * Return conceal substitution character - */ +// Return conceal substitution character int syn_get_sub_char(void) { return current_sub_char; } -/* - * Return the syntax ID at position "i" in the current stack. - * The caller must have called syn_get_id() before to fill the stack. - * Returns -1 when "i" is out of range. - */ +// Return the syntax ID at position "i" in the current stack. +// The caller must have called syn_get_id() before to fill the stack. +// Returns -1 when "i" is out of range. int syn_get_stack_item(int i) { if (i >= current_state.ga_len) { // Need to invalidate the state, because we didn't properly finish it - // for the last character, "keep_state" was TRUE. + // for the last character, "keep_state" was true. invalidate_current_state(); current_col = MAXCOL; return -1; @@ -5890,18 +5565,16 @@ int syn_get_foldlevel(win_T *wp, linenr_T lnum) return level; } -/* - * ":syntime". - */ +// ":syntime". void ex_syntime(exarg_T *eap) { - if (STRCMP(eap->arg, "on") == 0) { + if (strcmp(eap->arg, "on") == 0) { syn_time_on = true; - } else if (STRCMP(eap->arg, "off") == 0) { + } else if (strcmp(eap->arg, "off") == 0) { syn_time_on = false; - } else if (STRCMP(eap->arg, "clear") == 0) { + } else if (strcmp(eap->arg, "clear") == 0) { syntime_clear(); - } else if (STRCMP(eap->arg, "report") == 0) { + } else if (strcmp(eap->arg, "report") == 0) { syntime_report(); } else { semsg(_(e_invarg2), eap->arg); @@ -5916,9 +5589,7 @@ static void syn_clear_time(syn_time_T *st) st->match = 0; } -/* - * Clear the syntax timing for the current buffer. - */ +// Clear the syntax timing for the current buffer. static void syntime_clear(void) { synpat_T *spp; @@ -5927,16 +5598,14 @@ static void syntime_clear(void) msg(_(msg_no_items)); return; } - for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx) { + for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; idx++) { spp = &(SYN_ITEMS(curwin->w_s)[idx]); syn_clear_time(&spp->sp_time); } } -/* - * Function given to ExpandGeneric() to obtain the possible arguments of the - * ":syntime {on,off,clear,report}" command. - */ +// Function given to ExpandGeneric() to obtain the possible arguments of the +// ":syntime {on,off,clear,report}" command. char *get_syntime_arg(expand_T *xp, int idx) { switch (idx) { @@ -5960,9 +5629,7 @@ static int syn_compare_syntime(const void *v1, const void *v2) return profile_cmp(s1->total, s2->total); } -/* - * Clear the syntax timing for the current buffer. - */ +// Clear the syntax timing for the current buffer. static void syntime_report(void) { if (!syntax_present(curwin)) { @@ -5976,7 +5643,7 @@ static void syntime_report(void) proftime_T total_total = profile_zero(); int total_count = 0; time_entry_T *p; - for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; ++idx) { + for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; idx++) { synpat_T *spp = &(SYN_ITEMS(curwin->w_s)[idx]); if (spp->sp_time.count > 0) { p = GA_APPEND_VIA_PTR(time_entry_T, &ga); @@ -6002,7 +5669,7 @@ static void syntime_report(void) msg_puts_title(_(" TOTAL COUNT MATCH SLOWEST AVERAGE NAME PATTERN")); msg_puts("\n"); - for (int idx = 0; idx < ga.ga_len && !got_int; ++idx) { + for (int idx = 0; idx < ga.ga_len && !got_int; idx++) { p = ((time_entry_T *)ga.ga_data) + idx; msg_puts(profile_msg(p->total)); @@ -6020,7 +5687,7 @@ static void syntime_report(void) msg_puts(profile_msg(p->average)); msg_puts(" "); msg_advance(50); - msg_outtrans((char *)highlight_group_name(p->id - 1)); + msg_outtrans(highlight_group_name(p->id - 1)); msg_puts(" "); msg_advance(69); @@ -6030,8 +5697,8 @@ static void syntime_report(void) } else { len = Columns - 70; } - if (len > (int)STRLEN(p->pattern)) { - len = (int)STRLEN(p->pattern); + if (len > (int)strlen(p->pattern)) { + len = (int)strlen(p->pattern); } msg_outtrans_len(p->pattern, len); msg_puts("\n"); diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h index 0d890314c5..0a63392a04 100644 --- a/src/nvim/syntax.h +++ b/src/nvim/syntax.h @@ -6,6 +6,7 @@ #include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/globals.h" +#include "nvim/macros.h" #define HL_CONTAINED 0x01 // not used on toplevel #define HL_TRANSP 0x02 // has no highlighting diff --git a/src/nvim/syntax_defs.h b/src/nvim/syntax_defs.h index c656f21181..3f3101f7e3 100644 --- a/src/nvim/syntax_defs.h +++ b/src/nvim/syntax_defs.h @@ -30,7 +30,7 @@ struct keyentry { int16_t *next_list; // ID list for next match (if non-zero) int flags; int k_char; // conceal substitute character - char_u keyword[1]; // actually longer + char keyword[1]; // actually longer }; // Struct used to store one state of the state stack. diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 65c56bf01b..197184c181 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1,34 +1,41 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * Code to handle tags and the tag stack - */ +// Code to handle tags and the tag stack #include <assert.h> +#include <ctype.h> #include <inttypes.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cmdexpand.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" -#include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" #include "nvim/file_search.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" #include "nvim/help.h" -#include "nvim/if_cscope.h" +#include "nvim/highlight_defs.h" #include "nvim/input.h" #include "nvim/insexpand.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memory.h" @@ -38,49 +45,47 @@ #include "nvim/optionstr.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/os_defs.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" #include "nvim/regexp.h" #include "nvim/runtime.h" #include "nvim/search.h" #include "nvim/strings.h" #include "nvim/tag.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/window.h" -/* - * Structure to hold pointers to various items in a tag line. - */ +// Structure to hold pointers to various items in a tag line. typedef struct tag_pointers { // filled in by parse_tag_line(): - char_u *tagname; // start of tag name (skip "file:") - char_u *tagname_end; // char after tag name - char_u *fname; // first char of file name - char_u *fname_end; // char after file name - char_u *command; // first char of command + char *tagname; // start of tag name (skip "file:") + char *tagname_end; // char after tag name + char *fname; // first char of file name + char *fname_end; // char after file name + char *command; // first char of command // filled in by parse_match(): - char_u *command_end; // first char after command - char_u *tag_fname; // file name of the tags file. This is used + char *command_end; // first char after command + char *tag_fname; // file name of the tags file. This is used // when 'tr' is set. - char_u *tagkind; // "kind:" value - char_u *tagkind_end; // end of tagkind - char_u *user_data; // user_data string - char_u *user_data_end; // end of user_data - linenr_T tagline; // "line:" value + char *tagkind; // "kind:" value + char *tagkind_end; // end of tagkind + char *user_data; // user_data string + char *user_data_end; // end of user_data + linenr_T tagline; // "line:" value } tagptrs_T; -/* - * Structure to hold info about the tag pattern being used. - */ +// Structure to hold info about the tag pattern being used. typedef struct { - char_u *pat; // the pattern - int len; // length of pat[] - char_u *head; // start of pattern head - int headlen; // length of head[] - regmatch_T regmatch; // regexp program, may be NULL + char *pat; // the pattern + int len; // length of pat[] + char *head; // start of pattern head + int headlen; // length of head[] + regmatch_T regmatch; // regexp program, may be NULL } pat_T; // The matching tags are first stored in one of the hash tables. In @@ -88,45 +93,163 @@ typedef struct { // ht_match[] is used to find duplicates, ga_match[] to keep them in sequence. // At the end, the matches from ga_match[] are concatenated, to make a list // sorted on priority. -#define MT_ST_CUR 0 // static match in current file -#define MT_GL_CUR 1 // global match in current file -#define MT_GL_OTH 2 // global match in other file -#define MT_ST_OTH 3 // static match in other file -#define MT_IC_OFF 4 // add for icase match -#define MT_RE_OFF 8 // add for regexp match -#define MT_MASK 7 // mask for printing priority -#define MT_COUNT 16 +enum { + MT_ST_CUR = 0, // static match in current file + MT_GL_CUR = 1, // global match in current file + MT_GL_OTH = 2, // global match in other file + MT_ST_OTH = 3, // static match in other file + MT_IC_OFF = 4, // add for icase match + MT_RE_OFF = 8, // add for regexp match + MT_MASK = 7, // mask for printing priority + MT_COUNT = 16, +}; static char *mt_names[MT_COUNT/2] = { "FSC", "F C", "F ", "FS ", " SC", " C", " ", " S " }; #define NOTAGFILE 99 // return value for jumpto_tag -static char_u *nofile_fname = NULL; // fname for NOTAGFILE error +static char *nofile_fname = NULL; // fname for NOTAGFILE error + +/// Return values used when reading lines from a tags file. +typedef enum { + TAGS_READ_SUCCESS = 1, + TAGS_READ_EOF, + TAGS_READ_IGNORE, +} tags_read_status_T; + +/// States used during a tags search +typedef enum { + TS_START, ///< at start of file + TS_LINEAR, ///< linear searching forward, till EOF + TS_BINARY, ///< binary searching + TS_SKIP_BACK, ///< skipping backwards + TS_STEP_FORWARD, ///< stepping forwards +} tagsearch_state_T; + +/// Binary search file offsets in a tags file +typedef struct { + off_T low_offset; ///< offset for first char of first line that + ///< could match + off_T high_offset; ///< offset of char after last line that could + ///< match + off_T curr_offset; ///< Current file offset in search range + off_T curr_offset_used; ///< curr_offset used when skipping back + off_T match_offset; ///< Where the binary search found a tag + int low_char; ///< first char at low_offset + int high_char; ///< first char at high_offset +} tagsearch_info_T; + +/// Return values used when matching tags against a pattern. +typedef enum { + TAG_MATCH_SUCCESS = 1, + TAG_MATCH_FAIL, + TAG_MATCH_STOP, + TAG_MATCH_NEXT, +} tagmatch_status_T; + +/// Arguments used for matching tags read from a tags file against a pattern. +typedef struct { + int matchoff; ///< tag match offset + bool match_re; ///< true if the tag matches a regexp + bool match_no_ic; ///< true if the tag matches with case + bool has_re; ///< regular expression used + bool sortic; ///< tags file sorted ignoring case (foldcase) + bool sort_error; ///< tags file not sorted +} findtags_match_args_T; + +/// State information used during a tag search +typedef struct { + tagsearch_state_T state; ///< tag search state + bool stop_searching; ///< stop when match found or error + pat_T *orgpat; ///< holds unconverted pattern info + char *lbuf; ///< line buffer + int lbuf_size; ///< length of lbuf + char *tag_fname; ///< name of the tag file + FILE *fp; ///< current tags file pointer + int flags; ///< flags used for tag search + int tag_file_sorted; ///< !_TAG_FILE_SORTED value + bool get_searchpat; ///< used for 'showfulltag' + bool help_only; ///< only search for help tags + bool did_open; ///< did open a tag file + int mincount; ///< MAXCOL: find all matches + ///< other: minimal number of matches + bool linear; ///< do a linear search + vimconv_T vimconv; + char help_lang[3]; ///< lang of current tags file + int help_pri; ///< help language priority + char *help_lang_find; ///< lang to be found + bool is_txt; ///< flag of file extension + int match_count; ///< number of matches found + garray_T ga_match[MT_COUNT]; ///< stores matches in sequence + hashtab_T ht_match[MT_COUNT]; ///< stores matches by key +} findtags_state_T; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tag.c.generated.h" #endif -static char_u *bottommsg = (char_u *)N_("E555: at bottom of tag stack"); -static char_u *topmsg = (char_u *)N_("E556: at top of tag stack"); -static char_u *recurmsg - = (char_u *)N_("E986: cannot modify the tag stack within tagfunc"); -static char_u *tfu_inv_ret_msg - = (char_u *)N_("E987: invalid return value from tagfunc"); +static char *bottommsg = N_("E555: at bottom of tag stack"); +static char *topmsg = N_("E556: at top of tag stack"); +static char *recurmsg = N_("E986: cannot modify the tag stack within tagfunc"); +static char *tfu_inv_ret_msg = N_("E987: invalid return value from tagfunc"); +static char e_window_unexpectedly_close_while_searching_for_tags[] + = N_("E1299: Window unexpectedly closed while searching for tags"); -static char_u *tagmatchname = NULL; // name of last used tag +static char *tagmatchname = NULL; // name of last used tag -/* - * Tag for preview window is remembered separately, to avoid messing up the - * normal tagstack. - */ +// Tag for preview window is remembered separately, to avoid messing up the +// normal tagstack. static taggy_T ptag_entry = { NULL, INIT_FMARK, 0, 0, NULL }; static int tfu_in_use = false; // disallow recursive call of tagfunc +static Callback tfu_cb; // 'tagfunc' callback function // Used instead of NUL to separate tag fields in the growarrays. #define TAG_SEP 0x02 +/// Reads the 'tagfunc' option value and convert that to a callback value. +/// Invoked when the 'tagfunc' option is set. The option value can be a name of +/// a function (string), or function(<name>) or funcref(<name>) or a lambda. +void set_tagfunc_option(char **errmsg) +{ + callback_free(&tfu_cb); + callback_free(&curbuf->b_tfu_cb); + + if (*curbuf->b_p_tfu == NUL) { + return; + } + + if (option_set_callback_func(curbuf->b_p_tfu, &tfu_cb) == FAIL) { + *errmsg = e_invarg; + } + + callback_copy(&curbuf->b_tfu_cb, &tfu_cb); +} + +#if defined(EXITFREE) +void free_tagfunc_option(void) +{ + callback_free(&tfu_cb); +} +#endif + +/// Mark the global 'tagfunc' callback with "copyID" so that it is not garbage +/// collected. +bool set_ref_in_tagfunc(int copyID) +{ + return set_ref_in_callback(&tfu_cb, copyID, NULL, NULL); +} + +/// Copy the global 'tagfunc' callback function to the buffer-local 'tagfunc' +/// callback for 'buf'. +void set_buflocal_tfu_callback(buf_T *buf) +{ + callback_free(&buf->b_tfu_cb); + if (tfu_cb.type != kCallbackNone) { + callback_copy(&buf->b_tfu_cb, &tfu_cb); + } +} + /// Jump to tag; handling of tag commands and tag stack /// /// *tag != NUL: ":tag {tag}", jump to new tag, add to tag stack @@ -140,16 +263,13 @@ static int tfu_in_use = false; // disallow recursive call of tagfunc /// type == DT_LAST: jump to last match of same tag /// type == DT_SELECT: ":tselect [tag]", select tag from a list of all matches /// type == DT_JUMP: ":tjump [tag]", jump to tag or select tag from a list -/// type == DT_CSCOPE: use cscope to find the tag /// type == DT_LTAG: use location list for displaying tag matches /// type == DT_FREE: free cached matches /// -/// for cscope, returns true if we jumped to tag or aborted, false otherwise -/// /// @param tag tag (pattern) to jump to /// @param forceit :ta with ! /// @param verbose print "tag not found" message -bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) +void do_tag(char *tag, int type, int count, int forceit, int verbose) { taggy_T *tagstack = curwin->w_tagstack; int tagstackidx = curwin->w_tagstackidx; @@ -166,13 +286,13 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) int error_cur_match = 0; int save_pos = false; fmark_T saved_fmark; - bool jumped_to_tag = false; int new_num_matches; char **new_matches; int use_tagstack; int skip_msg = false; - char_u *buf_ffname = (char_u *)curbuf->b_ffname; // name for priority computation + char *buf_ffname = curbuf->b_ffname; // name for priority computation int use_tfu = 1; + char *tofree = NULL; // remember the matches for the last used tag static int num_matches = 0; @@ -182,16 +302,15 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) if (tfu_in_use) { emsg(_(recurmsg)); - return false; + return; } #ifdef EXITFREE if (type == DT_FREE) { // remove the list of matches FreeWild(num_matches, matches); - cs_free_tags(); num_matches = 0; - return false; + return; } #endif @@ -215,7 +334,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) new_tag = true; if (g_do_tagpreview != 0) { tagstack_clear_entry(&ptag_entry); - ptag_entry.tagname = vim_strsave(tag); + ptag_entry.tagname = xstrdup(tag); } } else { if (g_do_tagpreview != 0) { @@ -227,25 +346,21 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // new pattern, add to the tag stack if (*tag != NUL && (type == DT_TAG || type == DT_SELECT || type == DT_JUMP - || type == DT_LTAG - || type == DT_CSCOPE - )) { + || type == DT_LTAG)) { if (g_do_tagpreview != 0) { if (ptag_entry.tagname != NULL - && STRCMP(ptag_entry.tagname, tag) == 0) { + && strcmp(ptag_entry.tagname, tag) == 0) { // Jumping to same tag: keep the current match, so that // the CursorHold autocommand example works. cur_match = ptag_entry.cur_match; cur_fnum = ptag_entry.cur_fnum; } else { tagstack_clear_entry(&ptag_entry); - ptag_entry.tagname = vim_strsave(tag); + ptag_entry.tagname = xstrdup(tag); } } else { - /* - * If the last used entry is not at the top, delete all tag - * stack entries above it. - */ + // If the last used entry is not at the top, delete all tag + // stack entries above it. while (tagstackidx < tagstacklen) { tagstack_clear_entry(&tagstack[--tagstacklen]); } @@ -261,7 +376,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) } // put the tag name in the tag stack - tagstack[tagstackidx].tagname = vim_strsave(tag); + tagstack[tagstackidx].tagname = xstrdup(tag); curwin->w_tagstacklen = tagstacklen; @@ -270,8 +385,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) new_tag = true; } else { - if ( - g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : + if (g_do_tagpreview != 0 ? ptag_entry.tagname == NULL : tagstacklen == 0) { // empty stack emsg(_(e_tagstack)); @@ -299,10 +413,8 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // tagstack before it's used. saved_fmark = tagstack[tagstackidx].fmark; if (saved_fmark.fnum != curbuf->b_fnum) { - /* - * Jump to other file. If this fails (e.g. because the - * file was changed) keep original position in tag stack. - */ + // Jump to other file. If this fails (e.g. because the + // file was changed) keep original position in tag stack. if (buflist_getfile(saved_fmark.fnum, saved_fmark.mark.lnum, GETF_SETMARK, forceit) == FAIL) { tagstackidx = oldtagstackidx; // back to old posn @@ -324,7 +436,6 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // remove the old list of matches FreeWild(num_matches, matches); - cs_free_tags(); num_matches = 0; tag_freematch(); goto end_do_tag; @@ -339,11 +450,9 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // ":tag" (no argument): go to newer pattern save_pos = true; // save the cursor position below if ((tagstackidx += count - 1) >= tagstacklen) { - /* - * Beyond the last one, just give an error message and - * go to the last one. Don't store the cursor - * position. - */ + // Beyond the last one, just give an error message and + // go to the last one. Don't store the cursor + // position. tagstackidx = tagstacklen - 1; emsg(_(topmsg)); save_pos = false; @@ -375,7 +484,6 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) cur_match = count - 1; break; case DT_SELECT: case DT_JUMP: - case DT_CSCOPE: case DT_LAST: cur_match = MAXCOL - 1; break; case DT_NEXT: @@ -400,9 +508,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) ptag_entry.cur_fnum = cur_fnum; } } else { - /* - * For ":tag [arg]" or ":tselect" remember position before the jump. - */ + // For ":tag [arg]" or ":tselect" remember position before the jump. saved_fmark = tagstack[tagstackidx].fmark; if (save_pos) { tagstack[tagstackidx].fmark.mark = curwin->w_cursor; @@ -427,32 +533,33 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) buf_T *buf = buflist_findnr(cur_fnum); if (buf != NULL) { - buf_ffname = (char_u *)buf->b_ffname; + buf_ffname = buf->b_ffname; } } - /* - * Repeat searching for tags, when a file has not been found. - */ + // Repeat searching for tags, when a file has not been found. for (;;) { int other_name; - char_u *name; + char *name; // When desired match not found yet, try to find it (and others). if (use_tagstack) { - name = tagstack[tagstackidx].tagname; + // make a copy, the tagstack may change in 'tagfunc' + name = xstrdup(tagstack[tagstackidx].tagname); + xfree(tofree); + tofree = name; } else if (g_do_tagpreview != 0) { name = ptag_entry.tagname; } else { name = tag; } - other_name = (tagmatchname == NULL || STRCMP(tagmatchname, name) != 0); + other_name = (tagmatchname == NULL || strcmp(tagmatchname, name) != 0); if (new_tag || (cur_match >= num_matches && max_num_matches != MAXCOL) || other_name) { if (other_name) { xfree(tagmatchname); - tagmatchname = vim_strsave(name); + tagmatchname = xstrdup(name); } if (type == DT_SELECT || type == DT_JUMP @@ -473,9 +580,6 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) flags = TAG_NOIC; } - if (type == DT_CSCOPE) { - flags = TAG_CSCOPE; - } if (verbose) { flags |= TAG_VERBOSE; } @@ -490,6 +594,15 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // found: all matches found. } + // A tag function may do anything, which may cause various + // information to become invalid. At least check for the tagstack + // to still be the same. + if (tagstack != curwin->w_tagstack) { + emsg(_(e_window_unexpectedly_close_while_searching_for_tags)); + FreeWild(new_num_matches, new_matches); + break; + } + // If there already were some matches for the same name, move them // to the start. Avoids that the order changes when using // ":tnext" and jumping to another file. @@ -501,15 +614,15 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) // Find the position of each old match in the new list. Need // to use parse_match() to find the tag line. for (j = 0; j < num_matches; j++) { - parse_match((char_u *)matches[j], &tagp); + parse_match(matches[j], &tagp); for (i = idx; i < new_num_matches; i++) { - parse_match((char_u *)new_matches[i], &tagp2); - if (STRCMP(tagp.tagname, tagp2.tagname) == 0) { - char_u *p = (char_u *)new_matches[i]; + parse_match(new_matches[i], &tagp2); + if (strcmp(tagp.tagname, tagp2.tagname) == 0) { + char *p = new_matches[i]; for (k = i; k > idx; k--) { new_matches[k] = new_matches[k - 1]; } - new_matches[idx++] = (char *)p; + new_matches[idx++] = p; break; } } @@ -528,10 +641,7 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) } else { bool ask_for_selection = false; - if (type == DT_CSCOPE && num_matches > 1) { - cs_print_tags(); - ask_for_selection = true; - } else if (type == DT_TAG && *tag != NUL) { + if (type == DT_TAG && *tag != NUL) { // If a count is supplied to the ":tag <name>" command, then // jump to count'th matching tag. cur_match = count > 0 ? count - 1 : 0; @@ -555,8 +665,6 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) tagstack[tagstackidx].fmark = saved_fmark; tagstackidx = prevtagstackidx; } - cs_free_tags(); - jumped_to_tag = true; break; } cur_match = i - 1; @@ -584,12 +692,11 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) tagstack[tagstackidx].cur_fnum = cur_fnum; // store user-provided data originating from tagfunc - if (use_tfu && parse_match((char_u *)matches[cur_match], &tagp2) == OK + if (use_tfu && parse_match(matches[cur_match], &tagp2) == OK && tagp2.user_data) { XFREE_CLEAR(tagstack[tagstackidx].user_data); - tagstack[tagstackidx].user_data = vim_strnsave(tagp2.user_data, - (size_t)(tagp2.user_data_end - - tagp2.user_data)); + tagstack[tagstackidx].user_data = + xstrnsave(tagp2.user_data, (size_t)(tagp2.user_data_end - tagp2.user_data)); } tagstackidx++; @@ -598,21 +705,18 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) ptag_entry.cur_fnum = cur_fnum; } - /* - * Only when going to try the next match, report that the previous - * file didn't exist. Otherwise an emsg() is given below. - */ + // Only when going to try the next match, report that the previous + // file didn't exist. Otherwise an emsg() is given below. if (nofile_fname != NULL && error_cur_match != cur_match) { smsg(_("File \"%s\" does not exist"), nofile_fname); } ic = (matches[cur_match][0] & MT_IC_OFF); if (type != DT_TAG && type != DT_SELECT && type != DT_JUMP - && type != DT_CSCOPE && (num_matches > 1 || ic) && !skip_msg) { // Give an indication of the number of matching tags - snprintf((char *)IObuff, sizeof(IObuff), _("tag %d of %d%s"), + snprintf(IObuff, sizeof(IObuff), _("tag %d of %d%s"), cur_match + 1, num_matches, max_num_matches != MAXCOL ? _(" or more") : ""); @@ -624,11 +728,11 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) if (ic) { msg_attr((const char *)IObuff, HL_ATTR(HLF_W)); } else { - msg((char *)IObuff); + msg(IObuff); } msg_scroll = true; // Don't overwrite this message. } else { - give_warning((char *)IObuff, ic); + give_warning(IObuff, ic); } if (ic && !msg_scrolled && msg_silent == 0) { ui_flush(); @@ -637,13 +741,11 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) } // Let the SwapExists event know what tag we are jumping to. - vim_snprintf((char *)IObuff, IOSIZE, ":ta %s\r", name); - set_vim_var_string(VV_SWAPCOMMAND, (char *)IObuff, -1); + vim_snprintf(IObuff, IOSIZE, ":ta %s\r", name); + set_vim_var_string(VV_SWAPCOMMAND, IObuff, -1); - /* - * Jump to the desired match. - */ - i = jumpto_tag((char_u *)matches[cur_match], forceit, type != DT_CSCOPE); + // Jump to the desired match. + i = jumpto_tag(matches[cur_match], forceit, true); set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); @@ -673,7 +775,6 @@ bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) if (use_tagstack && tagstackidx > curwin->w_tagstacklen) { tagstackidx = curwin->w_tagstackidx; } - jumped_to_tag = true; } } break; @@ -686,8 +787,7 @@ end_do_tag: } postponed_split = 0; // don't split next time g_do_tagpreview = 0; // don't do tag preview next time - - return jumped_to_tag; + xfree(tofree); } // List all the matching tags. @@ -696,15 +796,15 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char taggy_T *tagstack = curwin->w_tagstack; int tagstackidx = curwin->w_tagstackidx; int i; - char_u *p; - char_u *command_end; + char *p; + char *command_end; tagptrs_T tagp; int taglen; int attr; // Assume that the first match indicates how long the tags can // be, and align the file names to that. - parse_match((char_u *)matches[0], &tagp); + parse_match(matches[0], &tagp); taglen = (int)(tagp.tagname_end - tagp.tagname + 2); if (taglen < 18) { taglen = 18; @@ -722,7 +822,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char msg_puts_attr(_("file\n"), HL_ATTR(HLF_T)); for (i = 0; i < num_matches && !got_int; i++) { - parse_match((char_u *)matches[i], &tagp); + parse_match(matches[i], &tagp); if (!new_tag && ( (g_do_tagpreview != 0 && i == ptag_entry.cur_match) @@ -732,10 +832,10 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char } else { *IObuff = ' '; } - vim_snprintf((char *)IObuff + 1, IOSIZE - 1, + vim_snprintf(IObuff + 1, IOSIZE - 1, "%2d %s ", i + 1, mt_names[matches[i][0] & MT_MASK]); - msg_puts((char *)IObuff); + msg_puts(IObuff); if (tagp.tagkind != NULL) { msg_outtrans_len(tagp.tagkind, (int)(tagp.tagkind_end - tagp.tagkind)); @@ -772,21 +872,21 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char } // skip "file:" without a value (static tag) - if (STRNCMP(p, "file:", 5) == 0 && ascii_isspace(p[5])) { + if (strncmp(p, "file:", 5) == 0 && ascii_isspace(p[5])) { p += 5; continue; } // skip "kind:<kind>" and "<kind>" if (p == tagp.tagkind || (p + 5 == tagp.tagkind - && STRNCMP(p, "kind:", 5) == 0)) { + && strncmp(p, "kind:", 5) == 0)) { p = tagp.tagkind_end; continue; } // print all other extra fields attr = HL_ATTR(HLF_CM); while (*p && *p != '\r' && *p != '\n') { - if (msg_col + ptr2cells((char *)p) >= Columns) { + if (msg_col + ptr2cells(p) >= Columns) { msg_putchar('\n'); if (got_int) { break; @@ -832,7 +932,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char } while (p != command_end) { - if (msg_col + (*p == TAB ? 1 : ptr2cells((char *)p)) > Columns) { + if (msg_col + (*p == TAB ? 1 : ptr2cells(p)) > Columns) { msg_putchar('\n'); } if (got_int) { @@ -877,14 +977,14 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char /// Add the matching tags to the location list for the current /// window. -static int add_llist_tags(char_u *tag, int num_matches, char **matches) +static int add_llist_tags(char *tag, int num_matches, char **matches) { list_T *list; - char_u tag_name[128 + 1]; - char_u *fname; - char_u *cmd; + char tag_name[128 + 1]; + char *fname; + char *cmd; int i; - char_u *p; + char *p; tagptrs_T tagp; fname = xmalloc(MAXPATHL + 1); @@ -896,14 +996,14 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) long lnum; dict_T *dict; - parse_match((char_u *)matches[i], &tagp); + parse_match(matches[i], &tagp); // Save the tag name len = (int)(tagp.tagname_end - tagp.tagname); if (len > 128) { len = 128; } - STRLCPY(tag_name, tagp.tagname, len + 1); + xstrlcpy(tag_name, tagp.tagname, (size_t)len + 1); tag_name[len] = NUL; // Save the tag file name @@ -911,17 +1011,17 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) if (p == NULL) { continue; } - STRLCPY(fname, p, MAXPATHL); + xstrlcpy(fname, p, MAXPATHL); XFREE_CLEAR(p); // Get the line number or the search pattern used to locate // the tag. lnum = 0; - if (isdigit(*tagp.command)) { + if (isdigit((uint8_t)(*tagp.command))) { // Line number is used to locate the tag - lnum = atol((char *)tagp.command); + lnum = atol(tagp.command); } else { - char_u *cmd_start, *cmd_end; + char *cmd_start, *cmd_end; // Search pattern is used to locate the tag @@ -969,7 +1069,7 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) if (cmd_len > (CMDBUFFSIZE - 5)) { cmd_len = CMDBUFFSIZE - 5; } - snprintf((char *)cmd + len, (size_t)(CMDBUFFSIZE + 1 - len), + snprintf(cmd + len, (size_t)(CMDBUFFSIZE + 1 - len), "%.*s", cmd_len, cmd_start); len += cmd_len; @@ -987,16 +1087,16 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) dict = tv_dict_alloc(); tv_list_append_dict(list, dict); - tv_dict_add_str(dict, S_LEN("text"), (const char *)tag_name); + tv_dict_add_str(dict, S_LEN("text"), tag_name); tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname); tv_dict_add_nr(dict, S_LEN("lnum"), lnum); if (lnum == 0) { - tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cmd); + tv_dict_add_str(dict, S_LEN("pattern"), cmd); } } - vim_snprintf((char *)IObuff, IOSIZE, "ltag %s", tag); - set_errorlist(curwin, list, ' ', (char *)IObuff, NULL); + vim_snprintf(IObuff, IOSIZE, "ltag %s", tag); + set_errorlist(curwin, list, ' ', IObuff, NULL); tv_list_free(list); XFREE_CLEAR(fname); @@ -1005,9 +1105,7 @@ static int add_llist_tags(char_u *tag, int num_matches, char **matches) return OK; } -/* - * Free cached tags. - */ +// Free cached tags. void tag_freematch(void) { XFREE_CLEAR(tagmatchname); @@ -1023,20 +1121,18 @@ static void taglen_advance(int l) } } -/* - * Print the tag stack - */ +// Print the tag stack void do_tags(exarg_T *eap) { int i; - char_u *name; + char *name; taggy_T *tagstack = curwin->w_tagstack; int tagstackidx = curwin->w_tagstackidx; int tagstacklen = curwin->w_tagstacklen; // Highlight title msg_puts_title(_("\n # TO tag FROM line in file/text")); - for (i = 0; i < tagstacklen; ++i) { + for (i = 0; i < tagstacklen; i++) { if (tagstack[i].tagname != NULL) { name = fm_getname(&(tagstack[i].fmark), 30); if (name == NULL) { // file name not available @@ -1044,35 +1140,32 @@ void do_tags(exarg_T *eap) } msg_putchar('\n'); - vim_snprintf((char *)IObuff, IOSIZE, "%c%2d %2d %-15s %5" PRIdLINENR " ", + vim_snprintf(IObuff, IOSIZE, "%c%2d %2d %-15s %5" PRIdLINENR " ", i == tagstackidx ? '>' : ' ', i + 1, tagstack[i].cur_match + 1, tagstack[i].tagname, tagstack[i].fmark.mark.lnum); - msg_outtrans((char *)IObuff); + msg_outtrans(IObuff); msg_outtrans_attr(name, tagstack[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0); xfree(name); } - ui_flush(); // show one line at a time } if (tagstackidx == tagstacklen) { // idx at top of stack msg_puts("\n>"); } } -/* - * Compare two strings, for length "len", ignoring case the ASCII way. - * return 0 for match, < 0 for smaller, > 0 for bigger - * Make sure case is folded to uppercase in comparison (like for 'sort -f') - */ -static int tag_strnicmp(char_u *s1, char_u *s2, size_t len) +// Compare two strings, for length "len", ignoring case the ASCII way. +// return 0 for match, < 0 for smaller, > 0 for bigger +// Make sure case is folded to uppercase in comparison (like for 'sort -f') +static int tag_strnicmp(char *s1, char *s2, size_t len) { int i; while (len > 0) { - i = TOUPPER_ASC(*s1) - TOUPPER_ASC(*s2); + i = TOUPPER_ASC((uint8_t)(*s1)) - TOUPPER_ASC((uint8_t)(*s2)); if (i != 0) { return i; // this character different } @@ -1086,9 +1179,7 @@ static int tag_strnicmp(char_u *s1, char_u *s2, size_t len) return 0; // strings match } -/* - * Extract info from the tag search pattern "pats->pat". - */ +// Extract info from the tag search pattern "pats->pat". static void prepare_pats(pat_T *pats, int has_re) { pats->head = pats->pat; @@ -1105,7 +1196,8 @@ static void prepare_pats(pat_T *pats, int has_re) pats->headlen = 0; } else { for (pats->headlen = 0; pats->head[pats->headlen] != NUL; pats->headlen++) { - if (vim_strchr((p_magic ? ".[~*\\$" : "\\$"), pats->head[pats->headlen]) != NULL) { + if (vim_strchr(magic_isset() ? ".[~*\\$" : "\\$", + (uint8_t)pats->head[pats->headlen]) != NULL) { break; } } @@ -1116,7 +1208,7 @@ static void prepare_pats(pat_T *pats, int has_re) } if (has_re) { - pats->regmatch.regprog = vim_regcomp((char *)pats->pat, p_magic ? RE_MAGIC : 0); + pats->regmatch.regprog = vim_regcomp(pats->pat, magic_isset() ? RE_MAGIC : 0); } else { pats->regmatch.regprog = NULL; } @@ -1133,8 +1225,7 @@ static void prepare_pats(pat_T *pats, int has_re) /// @param match_count here the number of tags found will be placed /// @param flags flags from find_tags (TAG_*) /// @param buf_ffname name of buffer for priority -static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int flags, - char_u *buf_ffname) +static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flags, char *buf_ffname) { pos_T save_pos; list_T *taglist; @@ -1142,7 +1233,7 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl int result = FAIL; typval_T args[4]; typval_T rettv; - char_u flagString[4]; + char flagString[4]; taggy_T *tag = NULL; if (curwin->w_tagstacklen > 0) { @@ -1153,14 +1244,14 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl } } - if (*curbuf->b_p_tfu == NUL) { + if (*curbuf->b_p_tfu == NUL || curbuf->b_tfu_cb.type == kCallbackNone) { return FAIL; } args[0].v_type = VAR_STRING; - args[0].vval.v_string = (char *)pat; + args[0].vval.v_string = pat; args[1].v_type = VAR_STRING; - args[1].vval.v_string = (char *)flagString; + args[1].vval.v_string = flagString; // create 'info' dict argument dict_T *const d = tv_dict_alloc_lock(VAR_FIXED); @@ -1177,14 +1268,14 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl args[3].v_type = VAR_UNKNOWN; - vim_snprintf((char *)flagString, sizeof(flagString), + vim_snprintf(flagString, sizeof(flagString), "%s%s%s", g_tag_at_cursor ? "c": "", flags & TAG_INS_COMP ? "i": "", flags & TAG_REGEXP ? "r": ""); save_pos = curwin->w_cursor; - result = call_vim_function((char *)curbuf->b_p_tfu, 3, args, &rettv); + result = callback_call(&curbuf->b_tfu_cb, 3, args, &rettv); curwin->w_cursor = save_pos; // restore the cursor position d->dv_refcount--; @@ -1203,10 +1294,10 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl taglist = rettv.vval.v_list; TV_LIST_ITER_CONST(taglist, li, { - char_u *res_name; - char_u *res_fname; - char_u *res_cmd; - char_u *res_kind; + char *res_name; + char *res_fname; + char *res_cmd; + char *res_kind; int has_extra = 0; int name_only = flags & TAG_NAMES; @@ -1222,34 +1313,34 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl res_kind = NULL; TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, { - const char_u *dict_key = di->di_key; + const char *dict_key = (char *)di->di_key; typval_T *tv = &di->di_tv; if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) { continue; } - len += STRLEN(tv->vval.v_string) + 1; // Space for "\tVALUE" - if (!STRCMP(dict_key, "name")) { - res_name = (char_u *)tv->vval.v_string; + len += strlen(tv->vval.v_string) + 1; // Space for "\tVALUE" + if (!strcmp(dict_key, "name")) { + res_name = tv->vval.v_string; continue; } - if (!STRCMP(dict_key, "filename")) { - res_fname = (char_u *)tv->vval.v_string; + if (!strcmp(dict_key, "filename")) { + res_fname = tv->vval.v_string; continue; } - if (!STRCMP(dict_key, "cmd")) { - res_cmd = (char_u *)tv->vval.v_string; + if (!strcmp(dict_key, "cmd")) { + res_cmd = tv->vval.v_string; continue; } has_extra = 1; - if (!STRCMP(dict_key, "kind")) { - res_kind = (char_u *)tv->vval.v_string; + if (!strcmp(dict_key, "kind")) { + res_kind = tv->vval.v_string; continue; } // Other elements will be stored as "\tKEY:VALUE" // Allocate space for the key and the colon - len += STRLEN(dict_key) + 1; + len += strlen(dict_key) + 1; }); if (has_extra) { @@ -1261,62 +1352,62 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl break; } - char_u *const mfp = name_only ? vim_strsave(res_name) : xmalloc(len + 2); + char *const mfp = name_only ? xstrdup(res_name) : xmalloc(len + 2); if (!name_only) { - char_u *p = mfp; + char *p = mfp; *p++ = MT_GL_OTH + 1; // mtt *p++ = TAG_SEP; // no tag file name STRCPY(p, res_name); - p += STRLEN(p); + p += strlen(p); *p++ = TAB; STRCPY(p, res_fname); - p += STRLEN(p); + p += strlen(p); *p++ = TAB; STRCPY(p, res_cmd); - p += STRLEN(p); + p += strlen(p); if (has_extra) { STRCPY(p, ";\""); - p += STRLEN(p); + p += strlen(p); if (res_kind) { *p++ = TAB; STRCPY(p, res_kind); - p += STRLEN(p); + p += strlen(p); } TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, { - const char_u *dict_key = di->di_key; + const char *dict_key = (char *)di->di_key; typval_T *tv = &di->di_tv; if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) { continue; } - if (!STRCMP(dict_key, "name")) { + if (!strcmp(dict_key, "name")) { continue; } - if (!STRCMP(dict_key, "filename")) { + if (!strcmp(dict_key, "filename")) { continue; } - if (!STRCMP(dict_key, "cmd")) { + if (!strcmp(dict_key, "cmd")) { continue; } - if (!STRCMP(dict_key, "kind")) { + if (!strcmp(dict_key, "kind")) { continue; } *p++ = TAB; STRCPY(p, dict_key); - p += STRLEN(p); + p += strlen(p); STRCPY(p, ":"); - p += STRLEN(p); + p += strlen(p); STRCPY(p, tv->vval.v_string); - p += STRLEN(p); + p += strlen(p); }); } } @@ -1334,6 +1425,873 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl return result; } +/// Initialize the state used by find_tags() +static void findtags_state_init(findtags_state_T *st, char *pat, int flags, int mincount) +{ + st->tag_fname = xmalloc(MAXPATHL + 1); + st->fp = NULL; + st->orgpat = xmalloc(sizeof(pat_T)); + st->orgpat->pat = pat; + st->orgpat->len = (int)strlen(pat); + st->orgpat->regmatch.regprog = NULL; + st->flags = flags; + st->tag_file_sorted = NUL; + st->help_lang_find = NULL; + st->is_txt = false; + st->did_open = false; + st->help_only = (flags & TAG_HELP); + st->get_searchpat = false; + st->help_lang[0] = NUL; + st->help_pri = 0; + st->mincount = mincount; + st->lbuf_size = LSIZE; + st->lbuf = xmalloc((size_t)st->lbuf_size); + st->match_count = 0; + st->stop_searching = false; + + for (int mtt = 0; mtt < MT_COUNT; mtt++) { + ga_init(&st->ga_match[mtt], sizeof(char *), 100); + hash_init(&st->ht_match[mtt]); + } +} + +/// Free the state used by find_tags() +static void findtags_state_free(findtags_state_T *st) +{ + xfree(st->tag_fname); + xfree(st->lbuf); + vim_regfree(st->orgpat->regmatch.regprog); + xfree(st->orgpat); +} + +/// Initialize the language and priority used for searching tags in a Vim help +/// file. +/// Returns true to process the help file for tags and false to skip the file. +static bool findtags_in_help_init(findtags_state_T *st) +{ + int i; + + // Keep "en" as the language if the file extension is ".txt" + if (st->is_txt) { + STRCPY(st->help_lang, "en"); + } else { + // Prefer help tags according to 'helplang'. Put the two-letter + // language name in help_lang[]. + i = (int)strlen(st->tag_fname); + if (i > 3 && st->tag_fname[i - 3] == '-') { + xstrlcpy(st->help_lang, st->tag_fname + i - 2, 3); + } else { + STRCPY(st->help_lang, "en"); + } + } + // When searching for a specific language skip tags files for other + // languages. + if (st->help_lang_find != NULL + && STRICMP(st->help_lang, st->help_lang_find) != 0) { + return false; + } + + // For CTRL-] in a help file prefer a match with the same language. + if ((st->flags & TAG_KEEP_LANG) + && st->help_lang_find == NULL + && curbuf->b_fname != NULL + && (i = (int)strlen(curbuf->b_fname)) > 4 + && curbuf->b_fname[i - 1] == 'x' + && curbuf->b_fname[i - 4] == '.' + && STRNICMP(curbuf->b_fname + i - 3, st->help_lang, 2) == 0) { + st->help_pri = 0; + } else { + st->help_pri = 1; + char *s; + for (s = p_hlg; *s != NUL; s++) { + if (STRNICMP(s, st->help_lang, 2) == 0) { + break; + } + st->help_pri++; + if ((s = vim_strchr(s, ',')) == NULL) { + break; + } + } + if (s == NULL || *s == NUL) { + // Language not in 'helplang': use last, prefer English, unless + // found already. + st->help_pri++; + if (STRICMP(st->help_lang, "en") != 0) { + st->help_pri++; + } + } + } + + return true; +} + +/// Use the function set in 'tagfunc' (if configured and enabled) to get the +/// tags. +/// Return OK if at least 1 tag has been successfully found, NOTDONE if the +/// 'tagfunc' is not used or the 'tagfunc' returns v:null and FAIL otherwise. +static int findtags_apply_tfu(findtags_state_T *st, char *pat, char *buf_ffname) +{ + const bool use_tfu = ((st->flags & TAG_NO_TAGFUNC) == 0); + + if (!use_tfu || tfu_in_use || *curbuf->b_p_tfu == NUL) { + return NOTDONE; + } + + tfu_in_use = true; + int retval = find_tagfunc_tags(pat, st->ga_match, &st->match_count, + st->flags, buf_ffname); + tfu_in_use = false; + + return retval; +} + +/// Read the next line from a tags file. +/// Returns TAGS_READ_SUCCESS if a tags line is successfully read and should be +/// processed. +/// Returns TAGS_READ_EOF if the end of file is reached. +/// Returns TAGS_READ_IGNORE if the current line should be ignored (used when +/// reached end of a emacs included tags file) +static tags_read_status_T findtags_get_next_line(findtags_state_T *st, tagsearch_info_T *sinfo_p) +{ + int eof; + off_T offset; + + // For binary search: compute the next offset to use. + if (st->state == TS_BINARY) { + offset = sinfo_p->low_offset + ((sinfo_p->high_offset - sinfo_p->low_offset) / 2); + if (offset == sinfo_p->curr_offset) { + return TAGS_READ_EOF; // End the binary search without a match. + } else { + sinfo_p->curr_offset = offset; + } + } else if (st->state == TS_SKIP_BACK) { + // Skipping back (after a match during binary search). + sinfo_p->curr_offset -= st->lbuf_size * 2; + if (sinfo_p->curr_offset < 0) { + sinfo_p->curr_offset = 0; + rewind(st->fp); + st->state = TS_STEP_FORWARD; + } + } + + // When jumping around in the file, first read a line to find the + // start of the next line. + if (st->state == TS_BINARY || st->state == TS_SKIP_BACK) { + // Adjust the search file offset to the correct position + sinfo_p->curr_offset_used = sinfo_p->curr_offset; + vim_ignored = vim_fseek(st->fp, sinfo_p->curr_offset, SEEK_SET); + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + if (!eof && sinfo_p->curr_offset != 0) { + sinfo_p->curr_offset = vim_ftell(st->fp); + if (sinfo_p->curr_offset == sinfo_p->high_offset) { + // oops, gone a bit too far; try from low offset + vim_ignored = vim_fseek(st->fp, sinfo_p->low_offset, SEEK_SET); + sinfo_p->curr_offset = sinfo_p->low_offset; + } + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + } + // skip empty and blank lines + while (!eof && vim_isblankline(st->lbuf)) { + sinfo_p->curr_offset = vim_ftell(st->fp); + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + } + if (eof) { + // Hit end of file. Skip backwards. + st->state = TS_SKIP_BACK; + sinfo_p->match_offset = vim_ftell(st->fp); + sinfo_p->curr_offset = sinfo_p->curr_offset_used; + return TAGS_READ_IGNORE; + } + } else { + // Not jumping around in the file: Read the next line. + + // skip empty and blank lines + do { + eof = vim_fgets(st->lbuf, st->lbuf_size, st->fp); + } while (!eof && vim_isblankline(st->lbuf)); + + if (eof) { + return TAGS_READ_EOF; + } + } + + return TAGS_READ_SUCCESS; +} + +/// Parse a tags file header line in "st->lbuf". +/// Returns true if the current line in st->lbuf is not a tags header line and +/// should be parsed as a regular tag line. Returns false if the line is a +/// header line and the next header line should be read. +static bool findtags_hdr_parse(findtags_state_T *st) +{ + // Header lines in a tags file start with "!_TAG_" + if (strncmp(st->lbuf, "!_TAG_", 6) != 0) { + // Non-header item before the header, e.g. "!" itself. + return true; + } + + // Process the header line. + if (strncmp(st->lbuf, "!_TAG_FILE_SORTED\t", 18) == 0) { + st->tag_file_sorted = (uint8_t)st->lbuf[18]; + } + if (strncmp(st->lbuf, "!_TAG_FILE_ENCODING\t", 20) == 0) { + // Prepare to convert every line from the specified encoding to + // 'encoding'. + char *p; + for (p = st->lbuf + 20; *p > ' ' && *p < 127; p++) {} + *p = NUL; + convert_setup(&st->vimconv, st->lbuf + 20, p_enc); + } + + // Read the next line. Unrecognized flags are ignored. + return false; +} + +/// Handler to initialize the state when starting to process a new tags file. +/// Called in the TS_START state when finding tags from a tags file. +/// Returns true if the line read from the tags file should be parsed and +/// false if the line should be ignored. +static bool findtags_start_state_handler(findtags_state_T *st, bool *sortic, + tagsearch_info_T *sinfo_p) +{ + const bool noic = (st->flags & TAG_NOIC); + + // The header ends when the line sorts below "!_TAG_". When case is + // folded lower case letters sort before "_". + if (strncmp(st->lbuf, "!_TAG_", 6) <= 0 + || (st->lbuf[0] == '!' && ASCII_ISLOWER(st->lbuf[1]))) { + return findtags_hdr_parse(st); + } + + // Headers ends. + + // When there is no tag head, or ignoring case, need to do a + // linear search. + // When no "!_TAG_" is found, default to binary search. If + // the tag file isn't sorted, the second loop will find it. + // When "!_TAG_FILE_SORTED" found: start binary search if + // flag set. + if (st->linear) { + st->state = TS_LINEAR; + } else if (st->tag_file_sorted == NUL) { + st->state = TS_BINARY; + } else if (st->tag_file_sorted == '1') { + st->state = TS_BINARY; + } else if (st->tag_file_sorted == '2') { + st->state = TS_BINARY; + *sortic = true; + st->orgpat->regmatch.rm_ic = (p_ic || !noic); + } else { + st->state = TS_LINEAR; + } + + if (st->state == TS_BINARY && st->orgpat->regmatch.rm_ic && !*sortic) { + // Binary search won't work for ignoring case, use linear + // search. + st->linear = true; + st->state = TS_LINEAR; + } + + // When starting a binary search, get the size of the file and + // compute the first offset. + if (st->state == TS_BINARY) { + if (vim_fseek(st->fp, 0, SEEK_END) != 0) { + // can't seek, don't use binary search + st->state = TS_LINEAR; + } else { + // Get the tag file size. + // Don't use lseek(), it doesn't work + // properly on MacOS Catalina. + const off_T filesize = vim_ftell(st->fp); + vim_ignored = vim_fseek(st->fp, 0, SEEK_SET); + + // Calculate the first read offset in the file. Start + // the search in the middle of the file. + sinfo_p->low_offset = 0; + sinfo_p->low_char = 0; + sinfo_p->high_offset = filesize; + sinfo_p->curr_offset = 0; + sinfo_p->high_char = 0xff; + } + return false; + } + + return true; +} + +/// Parse a tag line read from a tags file. +/// Also compares the tag name in "tagpp->tagname" with a search pattern in +/// "st->orgpat->head" as a quick check if the tag may match. +/// Returns: +/// - TAG_MATCH_SUCCESS if the tag may match +/// - TAG_MATCH_FAIL if the tag doesn't match +/// - TAG_MATCH_NEXT to look for the next matching tag (used in a binary search) +/// - TAG_MATCH_STOP if all the tags are processed without a match. +/// Uses the values in "margs" for doing the comparison. +static tagmatch_status_T findtags_parse_line(findtags_state_T *st, tagptrs_T *tagpp, + findtags_match_args_T *margs, + tagsearch_info_T *sinfo_p) +{ + int status; + int i; + int cmplen; + int tagcmp; + + // Figure out where the different strings are in this line. + // For "normal" tags: Do a quick check if the tag matches. + // This speeds up tag searching a lot! + if (st->orgpat->headlen) { + CLEAR_FIELD(*tagpp); + tagpp->tagname = st->lbuf; + tagpp->tagname_end = vim_strchr(st->lbuf, TAB); + if (tagpp->tagname_end == NULL) { + // Corrupted tag line. + return TAG_MATCH_FAIL; + } + + // Skip this line if the length of the tag is different and + // there is no regexp, or the tag is too short. + cmplen = (int)(tagpp->tagname_end - tagpp->tagname); + if (p_tl != 0 && cmplen > p_tl) { // adjust for 'taglength' + cmplen = (int)p_tl; + } + if ((st->flags & TAG_REGEXP) && st->orgpat->headlen < cmplen) { + cmplen = st->orgpat->headlen; + } else if (st->state == TS_LINEAR && st->orgpat->headlen != cmplen) { + return TAG_MATCH_NEXT; + } + + if (st->state == TS_BINARY) { + // Simplistic check for unsorted tags file. + i = (int)tagpp->tagname[0]; + if (margs->sortic) { + i = TOUPPER_ASC(tagpp->tagname[0]); + } + if (i < sinfo_p->low_char || i > sinfo_p->high_char) { + margs->sort_error = true; + } + + // Compare the current tag with the searched tag. + if (margs->sortic) { + tagcmp = tag_strnicmp(tagpp->tagname, st->orgpat->head, + (size_t)cmplen); + } else { + tagcmp = strncmp(tagpp->tagname, st->orgpat->head, (size_t)cmplen); + } + + // A match with a shorter tag means to search forward. + // A match with a longer tag means to search backward. + if (tagcmp == 0) { + if (cmplen < st->orgpat->headlen) { + tagcmp = -1; + } else if (cmplen > st->orgpat->headlen) { + tagcmp = 1; + } + } + + if (tagcmp == 0) { + // We've located the tag, now skip back and search + // forward until the first matching tag is found. + st->state = TS_SKIP_BACK; + sinfo_p->match_offset = sinfo_p->curr_offset; + return TAG_MATCH_NEXT; + } + if (tagcmp < 0) { + sinfo_p->curr_offset = vim_ftell(st->fp); + if (sinfo_p->curr_offset < sinfo_p->high_offset) { + sinfo_p->low_offset = sinfo_p->curr_offset; + if (margs->sortic) { + sinfo_p->low_char = TOUPPER_ASC(tagpp->tagname[0]); + } else { + sinfo_p->low_char = (uint8_t)tagpp->tagname[0]; + } + return TAG_MATCH_NEXT; + } + } + if (tagcmp > 0 && sinfo_p->curr_offset != sinfo_p->high_offset) { + sinfo_p->high_offset = sinfo_p->curr_offset; + if (margs->sortic) { + sinfo_p->high_char = TOUPPER_ASC(tagpp->tagname[0]); + } else { + sinfo_p->high_char = (uint8_t)tagpp->tagname[0]; + } + return TAG_MATCH_NEXT; + } + + // No match yet and are at the end of the binary search. + return TAG_MATCH_STOP; + } else if (st->state == TS_SKIP_BACK) { + assert(cmplen >= 0); + if (mb_strnicmp(tagpp->tagname, st->orgpat->head, (size_t)cmplen) != 0) { + st->state = TS_STEP_FORWARD; + } else { + // Have to skip back more. Restore the curr_offset + // used, otherwise we get stuck at a long line. + sinfo_p->curr_offset = sinfo_p->curr_offset_used; + } + return TAG_MATCH_NEXT; + } else if (st->state == TS_STEP_FORWARD) { + assert(cmplen >= 0); + if (mb_strnicmp(tagpp->tagname, st->orgpat->head, (size_t)cmplen) != 0) { + if ((off_T)vim_ftell(st->fp) > sinfo_p->match_offset) { + return TAG_MATCH_STOP; // past last match + } else { + return TAG_MATCH_NEXT; // before first match + } + } + } else { + // skip this match if it can't match + assert(cmplen >= 0); + if (mb_strnicmp(tagpp->tagname, st->orgpat->head, (size_t)cmplen) != 0) { + return TAG_MATCH_NEXT; + } + } + + // Can be a matching tag, isolate the file name and command. + tagpp->fname = tagpp->tagname_end + 1; + tagpp->fname_end = vim_strchr(tagpp->fname, TAB); + if (tagpp->fname_end == NULL) { + status = FAIL; + } else { + tagpp->command = tagpp->fname_end + 1; + status = OK; + } + } else { + status = parse_tag_line(st->lbuf, tagpp); + } + + if (status == FAIL) { + return TAG_MATCH_FAIL; + } + + return TAG_MATCH_SUCCESS; +} + +/// Initialize the structure used for tag matching. +static void findtags_matchargs_init(findtags_match_args_T *margs, int flags) +{ + margs->matchoff = 0; // match offset + margs->match_re = false; // match with regexp + margs->match_no_ic = false; // matches with case + margs->has_re = (flags & TAG_REGEXP); // regexp used + margs->sortic = false; // tag file sorted in nocase + margs->sort_error = false; // tags file not sorted +} + +/// Compares the tag name in "tagpp->tagname" with a search pattern in +/// "st->orgpat->pat". +/// Returns true if the tag matches, false if the tag doesn't match. +/// Uses the values in "margs" for doing the comparison. +static bool findtags_match_tag(findtags_state_T *st, tagptrs_T *tagpp, findtags_match_args_T *margs) +{ + bool match = false; + + // First try matching with the pattern literally (also when it is + // a regexp). + int cmplen = (int)(tagpp->tagname_end - tagpp->tagname); + if (p_tl != 0 && cmplen > p_tl) { // adjust for 'taglength' + cmplen = (int)p_tl; + } + // if tag length does not match, don't try comparing + if (st->orgpat->len != cmplen) { + match = false; + } else { + if (st->orgpat->regmatch.rm_ic) { + assert(cmplen >= 0); + match = mb_strnicmp(tagpp->tagname, st->orgpat->pat, (size_t)cmplen) == 0; + if (match) { + margs->match_no_ic = strncmp(tagpp->tagname, st->orgpat->pat, (size_t)cmplen) == 0; + } + } else { + match = strncmp(tagpp->tagname, st->orgpat->pat, (size_t)cmplen) == 0; + } + } + + // Has a regexp: Also find tags matching regexp. + margs->match_re = false; + if (!match && st->orgpat->regmatch.regprog != NULL) { + char cc = *tagpp->tagname_end; + *tagpp->tagname_end = NUL; + match = vim_regexec(&st->orgpat->regmatch, tagpp->tagname, (colnr_T)0); + if (match) { + margs->matchoff = (int)(st->orgpat->regmatch.startp[0] - tagpp->tagname); + if (st->orgpat->regmatch.rm_ic) { + st->orgpat->regmatch.rm_ic = false; + margs->match_no_ic = vim_regexec(&st->orgpat->regmatch, + tagpp->tagname, (colnr_T)0); + st->orgpat->regmatch.rm_ic = true; + } + } + *tagpp->tagname_end = cc; + margs->match_re = true; + } + + return match; +} + +/// Convert the encoding of a line read from a tags file in "st->lbuf". +/// Converting the pattern from 'enc' to the tags file encoding doesn't work, +/// because characters are not recognized. The converted line is saved in +/// st->lbuf. +static void findtags_string_convert(findtags_state_T *st) +{ + char *conv_line = string_convert(&st->vimconv, st->lbuf, NULL); + if (conv_line == NULL) { + return; + } + + // Copy or swap lbuf and conv_line. + int len = (int)strlen(conv_line) + 1; + if (len > st->lbuf_size) { + xfree(st->lbuf); + st->lbuf = conv_line; + st->lbuf_size = len; + } else { + STRCPY(st->lbuf, conv_line); + xfree(conv_line); + } +} + +/// Add a matching tag found in a tags file to st->ht_match and st->ga_match. +static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_match_args_T *margs, + char *buf_ffname, hash_T *hash) +{ + const bool name_only = (st->flags & TAG_NAMES); + int mtt; + size_t len = 0; + bool is_current; // file name matches + bool is_static; // current tag line is static + char *mfp; + char *p; + char *s; + + // Decide in which array to store this match. + is_current = test_for_current(tagpp->fname, tagpp->fname_end, + st->tag_fname, buf_ffname); + is_static = test_for_static(tagpp); + + // Decide in which of the sixteen tables to store this match. + if (is_static) { + if (is_current) { + mtt = MT_ST_CUR; + } else { + mtt = MT_ST_OTH; + } + } else { + if (is_current) { + mtt = MT_GL_CUR; + } else { + mtt = MT_GL_OTH; + } + } + if (st->orgpat->regmatch.rm_ic && !margs->match_no_ic) { + mtt += MT_IC_OFF; + } + if (margs->match_re) { + mtt += MT_RE_OFF; + } + + // Add the found match in ht_match[mtt] and ga_match[mtt]. + // Store the info we need later, which depends on the kind of + // tags we are dealing with. + if (st->help_only) { +#define ML_EXTRA 3 + // Append the help-heuristic number after the tagname, for + // sorting it later. The heuristic is ignored for + // detecting duplicates. + // The format is {tagname}@{lang}NUL{heuristic}NUL + *tagpp->tagname_end = NUL; + len = (size_t)(tagpp->tagname_end - tagpp->tagname); + mfp = xmalloc(sizeof(char) + len + 10 + ML_EXTRA + 1); + + p = mfp; + STRCPY(p, tagpp->tagname); + p[len] = '@'; + STRCPY(p + len + 1, st->help_lang); + snprintf(p + len + 1 + ML_EXTRA, strlen(p) + len + 1 + ML_EXTRA, "%06d", + help_heuristic(tagpp->tagname, + margs->match_re ? margs->matchoff : 0, + !margs->match_no_ic) + st->help_pri); + + *tagpp->tagname_end = TAB; + } else if (name_only) { + if (st->get_searchpat) { + char *temp_end = tagpp->command; + + if (*temp_end == '/') { + while (*temp_end && *temp_end != '\r' + && *temp_end != '\n' + && *temp_end != '$') { + temp_end++; + } + } + + if (tagpp->command + 2 < temp_end) { + len = (size_t)(temp_end - tagpp->command - 2); + mfp = xmalloc(len + 2); + xstrlcpy(mfp, tagpp->command + 2, len + 1); + } else { + mfp = NULL; + } + st->get_searchpat = false; + } else { + len = (size_t)(tagpp->tagname_end - tagpp->tagname); + mfp = xmalloc(sizeof(char) + len + 1); + xstrlcpy(mfp, tagpp->tagname, len + 1); + + // if wanted, re-read line to get long form too + if (State & MODE_INSERT) { + st->get_searchpat = p_sft; + } + } + } else { + size_t tag_fname_len = strlen(st->tag_fname); + // Save the tag in a buffer. + // Use 0x02 to separate fields (Can't use NUL, because the + // hash key is terminated by NUL). + // Emacs tag: <mtt><tag_fname><0x02><ebuf><0x02><lbuf><NUL> + // other tag: <mtt><tag_fname><0x02><0x02><lbuf><NUL> + // without Emacs tags: <mtt><tag_fname><0x02><lbuf><NUL> + // Here <mtt> is the "mtt" value plus 1 to avoid NUL. + len = tag_fname_len + strlen(st->lbuf) + 3; + mfp = xmalloc(sizeof(char) + len + 1); + p = mfp; + p[0] = (char)(mtt + 1); + STRCPY(p + 1, st->tag_fname); +#ifdef BACKSLASH_IN_FILENAME + // Ignore differences in slashes, avoid adding + // both path/file and path\file. + slash_adjust(p + 1); +#endif + p[tag_fname_len + 1] = TAG_SEP; + s = p + 1 + tag_fname_len + 1; + STRCPY(s, st->lbuf); + } + + if (mfp != NULL) { + hashitem_T *hi; + + // Don't add identical matches. + // "mfp" is used as a hash key, there is a NUL byte to end + // the part that matters for comparing, more bytes may + // follow after it. E.g. help tags store the priority + // after the NUL. + *hash = hash_hash(mfp); + hi = hash_lookup(&st->ht_match[mtt], (const char *)mfp, strlen(mfp), *hash); + if (HASHITEM_EMPTY(hi)) { + hash_add_item(&st->ht_match[mtt], hi, mfp, *hash); + GA_APPEND(char *, &st->ga_match[mtt], mfp); + st->match_count++; + } else { + // duplicate tag, drop it + xfree(mfp); + } + } +} + +/// Read and get all the tags from file st->tag_fname. +/// Sets "st->stop_searching" to true to stop searching for additional tags. +static void findtags_get_all_tags(findtags_state_T *st, findtags_match_args_T *margs, + char *buf_ffname) +{ + tagptrs_T tagp; + tagsearch_info_T search_info; + int retval; + hash_T hash = 0; + + // This is only to avoid a compiler warning for using search_info + // uninitialised. + CLEAR_FIELD(search_info); + + // Read and parse the lines in the file one by one + for (;;) { + // check for CTRL-C typed, more often when jumping around + if (st->state == TS_BINARY || st->state == TS_SKIP_BACK) { + line_breakcheck(); + } else { + fast_breakcheck(); + } + if ((st->flags & TAG_INS_COMP)) { // Double brackets for gcc + ins_compl_check_keys(30, false); + } + if (got_int || ins_compl_interrupted()) { + st->stop_searching = true; + break; + } + // When mincount is TAG_MANY, stop when enough matches have been + // found (for completion). + if (st->mincount == TAG_MANY && st->match_count >= TAG_MANY) { + st->stop_searching = true; + break; + } + if (st->get_searchpat) { + goto line_read_in; + } + + retval = (int)findtags_get_next_line(st, &search_info); + if (retval == TAGS_READ_IGNORE) { + continue; + } + if (retval == TAGS_READ_EOF) { + break; + } + +line_read_in: + + if (st->vimconv.vc_type != CONV_NONE) { + findtags_string_convert(st); + } + + // When still at the start of the file, check for Emacs tags file + // format, and for "not sorted" flag. + if (st->state == TS_START) { + if (!findtags_start_state_handler(st, &margs->sortic, &search_info)) { + continue; + } + } + + // When the line is too long the NUL will not be in the + // last-but-one byte (see vim_fgets()). + // Has been reported for Mozilla JS with extremely long names. + // In that case we need to increase lbuf_size. + if (st->lbuf[st->lbuf_size - 2] != NUL) { + st->lbuf_size *= 2; + xfree(st->lbuf); + st->lbuf = xmalloc((size_t)st->lbuf_size); + + if (st->state == TS_STEP_FORWARD || st->state == TS_LINEAR) { + // Seek to the same position to read the same line again + vim_ignored = vim_fseek(st->fp, search_info.curr_offset, SEEK_SET); + } + // this will try the same thing again, make sure the offset is + // different + search_info.curr_offset = 0; + continue; + } + + retval = (int)findtags_parse_line(st, &tagp, margs, &search_info); + if (retval == TAG_MATCH_NEXT) { + continue; + } + if (retval == TAG_MATCH_STOP) { + break; + } + if (retval == TAG_MATCH_FAIL) { + semsg(_("E431: Format error in tags file \"%s\""), st->tag_fname); + semsg(_("Before byte %" PRId64), (int64_t)vim_ftell(st->fp)); + st->stop_searching = true; + return; + } + + // If a match is found, add it to ht_match[] and ga_match[]. + if (findtags_match_tag(st, &tagp, margs)) { + findtags_add_match(st, &tagp, margs, buf_ffname, &hash); + } + } // forever +} + +/// Search for tags matching "st->orgpat.pat" in the "st->tag_fname" tags file. +/// Information needed to search for the tags is in the "st" state structure. +/// The matching tags are returned in "st". If an error is encountered, then +/// "st->stop_searching" is set to true. +static void findtags_in_file(findtags_state_T *st, int flags, char *buf_ffname) +{ + findtags_match_args_T margs; + + st->vimconv.vc_type = CONV_NONE; + st->tag_file_sorted = NUL; + st->fp = NULL; + findtags_matchargs_init(&margs, st->flags); + + // A file that doesn't exist is silently ignored. Only when not a + // single file is found, an error message is given (further on). + if (curbuf->b_help) { + if (!findtags_in_help_init(st)) { + return; + } + } + + st->fp = os_fopen(st->tag_fname, "r"); + if (st->fp == NULL) { + return; + } + + if (p_verbose >= 5) { + verbose_enter(); + smsg(_("Searching tags file %s"), st->tag_fname); + verbose_leave(); + } + st->did_open = true; // remember that we found at least one file + + st->state = TS_START; // we're at the start of the file + + // Read and parse the lines in the file one by one + findtags_get_all_tags(st, &margs, buf_ffname); + + if (st->fp != NULL) { + fclose(st->fp); + st->fp = NULL; + } + if (st->vimconv.vc_type != CONV_NONE) { + convert_setup(&st->vimconv, NULL, NULL); + } + + if (margs.sort_error) { + semsg(_("E432: Tags file not sorted: %s"), st->tag_fname); + } + + // Stop searching if sufficient tags have been found. + if (st->match_count >= st->mincount) { + st->stop_searching = true; + } +} + +/// Copy the tags found by find_tags() to "matchesp". +/// Returns the number of matches copied. +static int findtags_copy_matches(findtags_state_T *st, char ***matchesp) +{ + const bool name_only = (st->flags & TAG_NAMES); + char **matches; + int mtt; + int i; + char *mfp; + char *p; + + if (st->match_count > 0) { + matches = xmalloc((size_t)st->match_count * sizeof(char *)); + } else { + matches = NULL; + } + st->match_count = 0; + for (mtt = 0; mtt < MT_COUNT; mtt++) { + for (i = 0; i < st->ga_match[mtt].ga_len; i++) { + mfp = ((char **)(st->ga_match[mtt].ga_data))[i]; + if (matches == NULL) { + xfree(mfp); + } else { + if (!name_only) { + // Change mtt back to zero-based. + *mfp = (char)(*mfp - 1); + + // change the TAG_SEP back to NUL + for (p = mfp + 1; *p != NUL; p++) { + if (*p == TAG_SEP) { + *p = NUL; + } + } + } + matches[st->match_count++] = mfp; + } + } + + ga_clear(&st->ga_match[mtt]); + hash_clear(&st->ht_match[mtt]); + } + + *matchesp = matches; + return st->match_count; +} + /// find_tags() - search for tags in tags files /// /// Return FAIL if search completely failed (*num_matches will be 0, *matchesp @@ -1357,94 +2315,33 @@ static int find_tagfunc_tags(char_u *pat, garray_T *ga, int *match_count, int fl /// TAG_REGEXP use "pat" as a regexp /// TAG_NOIC don't always ignore case /// TAG_KEEP_LANG keep language -/// TAG_CSCOPE use cscope results for tags /// TAG_NO_TAGFUNC do not call the 'tagfunc' function /// /// @param pat pattern to search for /// @param num_matches return: number of matches found /// @param matchesp return: array of matches found -/// @param mincount MAXCOL: find all matches other: minimal number of matches */ +/// @param mincount MAXCOL: find all matches +/// other: minimal number of matches /// @param buf_ffname name of buffer for priority -int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mincount, - char_u *buf_ffname) +int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int mincount, + char *buf_ffname) { - FILE *fp; - char_u *lbuf; // line buffer - int lbuf_size = LSIZE; // length of lbuf - char_u *tag_fname; // name of tag file + findtags_state_T st; tagname_T tn; // info for get_tagfname() int first_file; // trying first tag file - tagptrs_T tagp; - bool did_open = false; // did open a tag file - bool stop_searching = false; // stop when match found or error int retval = FAIL; // return value - int is_static; // current tag line is static - int is_current; // file name matches - bool eof = false; // found end-of-file - char_u *p; - char_u *s; - int i; - int tag_file_sorted = NUL; // !_TAG_FILE_SORTED value - struct tag_search_info { // Binary search file offsets - off_T low_offset; // offset for first char of first line that - // could match - off_T high_offset; // offset of char after last line that could - // match - off_T curr_offset; // Current file offset in search range - off_T curr_offset_used; // curr_offset used when skipping back - off_T match_offset; // Where the binary search found a tag - int low_char; // first char at low_offset - int high_char; // first char at high_offset - } search_info; - int tagcmp; - off_T offset; int round; - enum { - TS_START, // at start of file - TS_LINEAR, // linear searching forward, till EOF - TS_BINARY, // binary searching - TS_SKIP_BACK, // skipping backwards - TS_STEP_FORWARD, // stepping forwards - } state; // Current search state - int cmplen; - int match; // matches - int match_no_ic = 0; // matches with rm_ic == FALSE - int match_re; // match with regexp - int matchoff = 0; int save_emsg_off; - char *mfp; - garray_T ga_match[MT_COUNT]; // stores matches in sequence - hashtab_T ht_match[MT_COUNT]; // stores matches by key - hash_T hash = 0; - int match_count = 0; // number of matches found - char **matches; - int mtt; int help_save; - int help_pri = 0; - char_u *help_lang_find = NULL; // lang to be found - char_u help_lang[3]; // lang of current tags file - char_u *saved_pat = NULL; // copy of pat[] - bool is_txt = false; - - pat_T orgpat; // holds unconverted pattern info - vimconv_T vimconv; + int i; + char *saved_pat = NULL; // copy of pat[] - int findall = (mincount == MAXCOL || mincount == TAG_MANY); - // find all matching tags - bool sort_error = false; // tags file not sorted - int linear; // do a linear search - bool sortic = false; // tag file sorted in nocase - bool line_error = false; // syntax error + int findall = (mincount == MAXCOL || mincount == TAG_MANY); // find all matching tags int has_re = (flags & TAG_REGEXP); // regexp used - int help_only = (flags & TAG_HELP); - int name_only = (flags & TAG_NAMES); int noic = (flags & TAG_NOIC); - int get_it_again = FALSE; - int use_cscope = (flags & TAG_CSCOPE); int verbose = (flags & TAG_VERBOSE); - int use_tfu = ((flags & TAG_NO_TAGFUNC) == 0); int save_p_ic = p_ic; // Change the value of 'ignorecase' according to 'tagcase' for the @@ -1469,829 +2366,109 @@ int find_tags(char_u *pat, int *num_matches, char ***matchesp, int flags, int mi } help_save = curbuf->b_help; - orgpat.pat = pat; - orgpat.regmatch.regprog = NULL; - vimconv.vc_type = CONV_NONE; - - /* - * Allocate memory for the buffers that are used - */ - lbuf = xmalloc((size_t)lbuf_size); - tag_fname = xmalloc(MAXPATHL + 1); - for (mtt = 0; mtt < MT_COUNT; mtt++) { - ga_init(&ga_match[mtt], sizeof(char *), 100); - hash_init(&ht_match[mtt]); - } - STRCPY(tag_fname, "from cscope"); // for error messages + findtags_state_init(&st, pat, flags, mincount); - /* - * Initialize a few variables - */ - if (help_only) { // want tags from help file + // Initialize a few variables + if (st.help_only) { // want tags from help file curbuf->b_help = true; // will be restored later - } else if (use_cscope) { - // Make sure we don't mix help and cscope, confuses Coverity. - help_only = false; - curbuf->b_help = false; } - orgpat.len = (int)STRLEN(pat); if (curbuf->b_help) { // When "@ab" is specified use only the "ab" language, otherwise // search all languages. - if (orgpat.len > 3 && pat[orgpat.len - 3] == '@' - && ASCII_ISALPHA(pat[orgpat.len - 2]) - && ASCII_ISALPHA(pat[orgpat.len - 1])) { - saved_pat = vim_strnsave(pat, (size_t)orgpat.len - 3); - help_lang_find = &pat[orgpat.len - 2]; - orgpat.pat = saved_pat; - orgpat.len -= 3; + if (st.orgpat->len > 3 && pat[st.orgpat->len - 3] == '@' + && ASCII_ISALPHA(pat[st.orgpat->len - 2]) + && ASCII_ISALPHA(pat[st.orgpat->len - 1])) { + saved_pat = xstrnsave(pat, (size_t)st.orgpat->len - 3); + st.help_lang_find = &pat[st.orgpat->len - 2]; + st.orgpat->pat = saved_pat; + st.orgpat->len -= 3; } } - if (p_tl != 0 && orgpat.len > p_tl) { // adjust for 'taglength' - orgpat.len = (int)p_tl; + if (p_tl != 0 && st.orgpat->len > p_tl) { // adjust for 'taglength' + st.orgpat->len = (int)p_tl; } save_emsg_off = emsg_off; - emsg_off = TRUE; // don't want error for invalid RE here - prepare_pats(&orgpat, has_re); + emsg_off = true; // don't want error for invalid RE here + prepare_pats(st.orgpat, has_re); emsg_off = save_emsg_off; - if (has_re && orgpat.regmatch.regprog == NULL) { + if (has_re && st.orgpat->regmatch.regprog == NULL) { goto findtag_end; } - // This is only to avoid a compiler warning for using search_info - // uninitialised. - CLEAR_FIELD(search_info); + retval = findtags_apply_tfu(&st, pat, buf_ffname); + if (retval != NOTDONE) { + goto findtag_end; + } + + // re-initialize the default return value + retval = FAIL; - if (*curbuf->b_p_tfu != NUL && use_tfu && !tfu_in_use) { - tfu_in_use = true; - retval = find_tagfunc_tags(pat, &ga_match[0], &match_count, - flags, buf_ffname); - tfu_in_use = false; - if (retval != NOTDONE) { - goto findtag_end; - } - } - - /* - * When finding a specified number of matches, first try with matching - * case, so binary search can be used, and try ignore-case matches in a - * second loop. - * When finding all matches, 'tagbsearch' is off, or there is no fixed - * string to look for, ignore case right away to avoid going though the - * tags files twice. - * When the tag file is case-fold sorted, it is either one or the other. - * Only ignore case when TAG_NOIC not used or 'ignorecase' set. - */ // Set a flag if the file extension is .txt if ((flags & TAG_KEEP_LANG) - && help_lang_find == NULL + && st.help_lang_find == NULL && curbuf->b_fname != NULL - && (i = (int)STRLEN(curbuf->b_fname)) > 4 + && (i = (int)strlen(curbuf->b_fname)) > 4 && STRICMP(curbuf->b_fname + i - 4, ".txt") == 0) { - is_txt = true; - } - orgpat.regmatch.rm_ic = ((p_ic || !noic) - && (findall || orgpat.headlen == 0 || !p_tbs)); - for (round = 1; round <= 2; ++round) { - linear = (orgpat.headlen == 0 || !p_tbs || round == 2); + st.is_txt = true; + } + + // When finding a specified number of matches, first try with matching + // case, so binary search can be used, and try ignore-case matches in a + // second loop. + // When finding all matches, 'tagbsearch' is off, or there is no fixed + // string to look for, ignore case right away to avoid going though the + // tags files twice. + // When the tag file is case-fold sorted, it is either one or the other. + // Only ignore case when TAG_NOIC not used or 'ignorecase' set. + st.orgpat->regmatch.rm_ic = ((p_ic || !noic) + && (findall || st.orgpat->headlen == 0 || !p_tbs)); + for (round = 1; round <= 2; round++) { + st.linear = (st.orgpat->headlen == 0 || !p_tbs || round == 2); // Try tag file names from tags option one by one. for (first_file = true; - use_cscope || get_tagfname(&tn, first_file, tag_fname) == OK; + get_tagfname(&tn, first_file, st.tag_fname) == OK; first_file = false) { - // A file that doesn't exist is silently ignored. Only when not a - // single file is found, an error message is given (further on). - if (use_cscope) { - fp = NULL; // avoid GCC warning - } else { - if (curbuf->b_help) { - // Keep en if the file extension is .txt - if (is_txt) { - STRCPY(help_lang, "en"); - } else { - // Prefer help tags according to 'helplang'. Put the - // two-letter language name in help_lang[]. - i = (int)STRLEN(tag_fname); - if (i > 3 && tag_fname[i - 3] == '-') { - STRCPY(help_lang, tag_fname + i - 2); - } else { - STRCPY(help_lang, "en"); - } - } - - // When searching for a specific language skip tags files - // for other languages. - if (help_lang_find != NULL - && STRICMP(help_lang, help_lang_find) != 0) { - continue; - } - - // For CTRL-] in a help file prefer a match with the same - // language. - if ((flags & TAG_KEEP_LANG) - && help_lang_find == NULL - && curbuf->b_fname != NULL - && (i = (int)STRLEN(curbuf->b_fname)) > 4 - && curbuf->b_fname[i - 1] == 'x' - && curbuf->b_fname[i - 4] == '.' - && STRNICMP(curbuf->b_fname + i - 3, help_lang, 2) == 0) { - help_pri = 0; - } else { - help_pri = 1; - for (s = p_hlg; *s != NUL; ++s) { - if (STRNICMP(s, help_lang, 2) == 0) { - break; - } - help_pri++; - if ((s = (char_u *)vim_strchr((char *)s, ',')) == NULL) { - break; - } - } - if (s == NULL || *s == NUL) { - // Language not in 'helplang': use last, prefer English, - // unless found already. - help_pri++; - if (STRICMP(help_lang, "en") != 0) { - help_pri++; - } - } - } - } - - if ((fp = os_fopen((char *)tag_fname, "r")) == NULL) { - continue; - } - - if (p_verbose >= 5) { - verbose_enter(); - smsg(_("Searching tags file %s"), tag_fname); - verbose_leave(); - } - } - did_open = true; // remember that we found at least one file - - state = TS_START; // we're at the start of the file - - /* - * Read and parse the lines in the file one by one - */ - for (;;) { - // check for CTRL-C typed, more often when jumping around - if (state == TS_BINARY || state == TS_SKIP_BACK) { - line_breakcheck(); - } else { - fast_breakcheck(); - } - if ((flags & TAG_INS_COMP)) { // Double brackets for gcc - ins_compl_check_keys(30, false); - } - if (got_int || ins_compl_interrupted()) { - stop_searching = true; - break; - } - // When mincount is TAG_MANY, stop when enough matches have been - // found (for completion). - if (mincount == TAG_MANY && match_count >= TAG_MANY) { - stop_searching = true; - retval = OK; - break; - } - if (get_it_again) { - goto line_read_in; - } - /* - * For binary search: compute the next offset to use. - */ - if (state == TS_BINARY) { - offset = search_info.low_offset + ((search_info.high_offset - - search_info.low_offset) / 2); - if (offset == search_info.curr_offset) { - break; // End the binary search without a match. - } else { - search_info.curr_offset = offset; - } - } else if (state == TS_SKIP_BACK) { - // Skipping back (after a match during binary search). - search_info.curr_offset -= lbuf_size * 2; - if (search_info.curr_offset < 0) { - search_info.curr_offset = 0; - rewind(fp); - state = TS_STEP_FORWARD; - } - } - - /* - * When jumping around in the file, first read a line to find the - * start of the next line. - */ - if (state == TS_BINARY || state == TS_SKIP_BACK) { - // Adjust the search file offset to the correct position - search_info.curr_offset_used = search_info.curr_offset; - vim_fseek(fp, search_info.curr_offset, SEEK_SET); - eof = vim_fgets(lbuf, lbuf_size, fp); - if (!eof && search_info.curr_offset != 0) { - // The explicit cast is to work around a bug in gcc 3.4.2 - // (repeated below). - search_info.curr_offset = vim_ftell(fp); - if (search_info.curr_offset == search_info.high_offset) { - // oops, gone a bit too far; try from low offset - vim_fseek(fp, search_info.low_offset, SEEK_SET); - search_info.curr_offset = search_info.low_offset; - } - eof = vim_fgets(lbuf, lbuf_size, fp); - } - // skip empty and blank lines - while (!eof && vim_isblankline(lbuf)) { - search_info.curr_offset = vim_ftell(fp); - eof = vim_fgets(lbuf, lbuf_size, fp); - } - if (eof) { - // Hit end of file. Skip backwards. - state = TS_SKIP_BACK; - search_info.match_offset = vim_ftell(fp); - search_info.curr_offset = search_info.curr_offset_used; - continue; - } - } - /* - * Not jumping around in the file: Read the next line. - */ - else { - // skip empty and blank lines - do { - eof = use_cscope - ? cs_fgets(lbuf, lbuf_size) - : vim_fgets(lbuf, lbuf_size, fp); - } while (!eof && vim_isblankline(lbuf)); - - if (eof) { - break; // end of file - } - } -line_read_in: - - if (vimconv.vc_type != CONV_NONE) { - char_u *conv_line; - int len; - - // Convert every line. Converting the pattern from 'enc' to - // the tags file encoding doesn't work, because characters are - // not recognized. - conv_line = string_convert(&vimconv, lbuf, NULL); - if (conv_line != NULL) { - // Copy or swap lbuf and conv_line. - len = (int)STRLEN(conv_line) + 1; - if (len > lbuf_size) { - xfree(lbuf); - lbuf = conv_line; - lbuf_size = len; - } else { - STRCPY(lbuf, conv_line); - xfree(conv_line); - } - } - } - - /* - * When still at the start of the file, check for Emacs tags file - * format, and for "not sorted" flag. - */ - if (state == TS_START) { - // The header ends when the line sorts below "!_TAG_". When - // case is folded lower case letters sort before "_". - if (STRNCMP(lbuf, "!_TAG_", 6) <= 0 - || (lbuf[0] == '!' && ASCII_ISLOWER(lbuf[1]))) { - if (STRNCMP(lbuf, "!_TAG_", 6) != 0) { - // Non-header item before the header, e.g. "!" itself. - goto parse_line; - } - - /* - * Read header line. - */ - if (STRNCMP(lbuf, "!_TAG_FILE_SORTED\t", 18) == 0) { - tag_file_sorted = lbuf[18]; - } - if (STRNCMP(lbuf, "!_TAG_FILE_ENCODING\t", 20) == 0) { - // Prepare to convert every line from the specified - // encoding to 'encoding'. - for (p = lbuf + 20; *p > ' ' && *p < 127; p++) {} - *p = NUL; - convert_setup(&vimconv, lbuf + 20, p_enc); - } - - // Read the next line. Unrecognized flags are ignored. - continue; - } - - // Headers ends. - - /* - * When there is no tag head, or ignoring case, need to do a - * linear search. - * When no "!_TAG_" is found, default to binary search. If - * the tag file isn't sorted, the second loop will find it. - * When "!_TAG_FILE_SORTED" found: start binary search if - * flag set. - * For cscope, it's always linear. - */ - if (linear || use_cscope) { - state = TS_LINEAR; - } else if (tag_file_sorted == NUL) { - state = TS_BINARY; - } else if (tag_file_sorted == '1') { - state = TS_BINARY; - } else if (tag_file_sorted == '2') { - state = TS_BINARY; - sortic = true; - orgpat.regmatch.rm_ic = (p_ic || !noic); - } else { - state = TS_LINEAR; - } - - if (state == TS_BINARY && orgpat.regmatch.rm_ic && !sortic) { - // Binary search won't work for ignoring case, use linear - // search. - linear = true; - state = TS_LINEAR; - } - - // When starting a binary search, get the size of the file and - // compute the first offset. - if (state == TS_BINARY) { - if (vim_fseek(fp, 0, SEEK_END) != 0) { - // can't seek, don't use binary search - state = TS_LINEAR; - } else { - // Get the tag file size. - // Don't use lseek(), it doesn't work - // properly on MacOS Catalina. - const off_T filesize = vim_ftell(fp); - vim_fseek(fp, 0, SEEK_SET); - - // Calculate the first read offset in the file. Start - // the search in the middle of the file. - search_info.low_offset = 0; - search_info.low_char = 0; - search_info.high_offset = filesize; - search_info.curr_offset = 0; - search_info.high_char = 0xff; - } - continue; - } - } - -parse_line: - // When the line is too long the NUL will not be in the - // last-but-one byte (see vim_fgets()). - // Has been reported for Mozilla JS with extremely long names. - // In that case we need to increase lbuf_size. - if (lbuf[lbuf_size - 2] != NUL && !use_cscope) { - lbuf_size *= 2; - xfree(lbuf); - lbuf = xmalloc((size_t)lbuf_size); - // this will try the same thing again, make sure the offset is - // different - search_info.curr_offset = 0; - continue; - } - - // Figure out where the different strings are in this line. - // For "normal" tags: Do a quick check if the tag matches. - // This speeds up tag searching a lot! - if (orgpat.headlen) { - CLEAR_FIELD(tagp); - tagp.tagname = lbuf; - tagp.tagname_end = (char_u *)vim_strchr((char *)lbuf, TAB); - if (tagp.tagname_end == NULL) { - // Corrupted tag line. - line_error = true; - break; - } - - /* - * Skip this line if the length of the tag is different and - * there is no regexp, or the tag is too short. - */ - cmplen = (int)(tagp.tagname_end - tagp.tagname); - if (p_tl != 0 && cmplen > p_tl) { // adjust for 'taglength' - cmplen = (int)p_tl; - } - if (has_re && orgpat.headlen < cmplen) { - cmplen = orgpat.headlen; - } else if (state == TS_LINEAR && orgpat.headlen != cmplen) { - continue; - } - - if (state == TS_BINARY) { - /* - * Simplistic check for unsorted tags file. - */ - i = (int)tagp.tagname[0]; - if (sortic) { - i = TOUPPER_ASC(tagp.tagname[0]); - } - if (i < search_info.low_char || i > search_info.high_char) { - sort_error = true; - } - - /* - * Compare the current tag with the searched tag. - */ - if (sortic) { - tagcmp = tag_strnicmp(tagp.tagname, orgpat.head, - (size_t)cmplen); - } else { - tagcmp = STRNCMP(tagp.tagname, orgpat.head, cmplen); - } - - /* - * A match with a shorter tag means to search forward. - * A match with a longer tag means to search backward. - */ - if (tagcmp == 0) { - if (cmplen < orgpat.headlen) { - tagcmp = -1; - } else if (cmplen > orgpat.headlen) { - tagcmp = 1; - } - } - - if (tagcmp == 0) { - // We've located the tag, now skip back and search - // forward until the first matching tag is found. - state = TS_SKIP_BACK; - search_info.match_offset = search_info.curr_offset; - continue; - } - if (tagcmp < 0) { - search_info.curr_offset = vim_ftell(fp); - if (search_info.curr_offset < search_info.high_offset) { - search_info.low_offset = search_info.curr_offset; - if (sortic) { - search_info.low_char = - TOUPPER_ASC(tagp.tagname[0]); - } else { - search_info.low_char = tagp.tagname[0]; - } - continue; - } - } - if (tagcmp > 0 - && search_info.curr_offset != search_info.high_offset) { - search_info.high_offset = search_info.curr_offset; - if (sortic) { - search_info.high_char = - TOUPPER_ASC(tagp.tagname[0]); - } else { - search_info.high_char = tagp.tagname[0]; - } - continue; - } - - // No match yet and are at the end of the binary search. - break; - } else if (state == TS_SKIP_BACK) { - assert(cmplen >= 0); - if (mb_strnicmp(tagp.tagname, orgpat.head, (size_t)cmplen) != 0) { - state = TS_STEP_FORWARD; - } else { - // Have to skip back more. Restore the curr_offset - // used, otherwise we get stuck at a long line. - search_info.curr_offset = search_info.curr_offset_used; - } - continue; - } else if (state == TS_STEP_FORWARD) { - assert(cmplen >= 0); - if (mb_strnicmp(tagp.tagname, orgpat.head, (size_t)cmplen) != 0) { - if ((off_T)vim_ftell(fp) > search_info.match_offset) { - break; // past last match - } else { - continue; // before first match - } - } - } else { - // skip this match if it can't match - assert(cmplen >= 0); - } - if (mb_strnicmp(tagp.tagname, orgpat.head, (size_t)cmplen) != 0) { - continue; - } - - // Can be a matching tag, isolate the file name and command. - tagp.fname = tagp.tagname_end + 1; - tagp.fname_end = (char_u *)vim_strchr((char *)tagp.fname, TAB); - tagp.command = tagp.fname_end + 1; - if (tagp.fname_end == NULL) { - i = FAIL; - } else { - i = OK; - } - } else { - i = parse_tag_line(lbuf, - &tagp); - } - if (i == FAIL) { - line_error = true; - break; - } - - /* - * First try matching with the pattern literally (also when it is - * a regexp). - */ - cmplen = (int)(tagp.tagname_end - tagp.tagname); - if (p_tl != 0 && cmplen > p_tl) { // adjust for 'taglength' - cmplen = (int)p_tl; - } - // if tag length does not match, don't try comparing - if (orgpat.len != cmplen) { - match = FALSE; - } else { - if (orgpat.regmatch.rm_ic) { - assert(cmplen >= 0); - match = mb_strnicmp(tagp.tagname, orgpat.pat, (size_t)cmplen) == 0; - if (match) { - match_no_ic = (STRNCMP(tagp.tagname, orgpat.pat, - cmplen) == 0); - } - } else { - match = (STRNCMP(tagp.tagname, orgpat.pat, cmplen) == 0); - } - } - - /* - * Has a regexp: Also find tags matching regexp. - */ - match_re = FALSE; - if (!match && orgpat.regmatch.regprog != NULL) { - int cc; - - cc = *tagp.tagname_end; - *tagp.tagname_end = NUL; - match = vim_regexec(&orgpat.regmatch, (char *)tagp.tagname, (colnr_T)0); - if (match) { - matchoff = (int)(orgpat.regmatch.startp[0] - tagp.tagname); - if (orgpat.regmatch.rm_ic) { - orgpat.regmatch.rm_ic = false; - match_no_ic = vim_regexec(&orgpat.regmatch, (char *)tagp.tagname, (colnr_T)0); - orgpat.regmatch.rm_ic = true; - } - } - *tagp.tagname_end = (char_u)cc; - match_re = true; - } - - // If a match is found, add it to ht_match[] and ga_match[]. - if (match) { - size_t len = 0; - - if (use_cscope) { - // Don't change the ordering, always use the same table. - mtt = MT_GL_OTH; - } else { - // Decide in which array to store this match. - is_current = test_for_current(tagp.fname, tagp.fname_end, tag_fname, - buf_ffname); - is_static = test_for_static(&tagp); - - // Decide in which of the sixteen tables to store this match. - if (is_static) { - if (is_current) { - mtt = MT_ST_CUR; - } else { - mtt = MT_ST_OTH; - } - } else { - if (is_current) { - mtt = MT_GL_CUR; - } else { - mtt = MT_GL_OTH; - } - } - if (orgpat.regmatch.rm_ic && !match_no_ic) { - mtt += MT_IC_OFF; - } - if (match_re) { - mtt += MT_RE_OFF; - } - } - - // Add the found match in ht_match[mtt] and ga_match[mtt]. - // Store the info we need later, which depends on the kind of - // tags we are dealing with. - if (help_only) { -#define ML_EXTRA 3 - // Append the help-heuristic number after the tagname, for - // sorting it later. The heuristic is ignored for - // detecting duplicates. - // The format is {tagname}@{lang}NUL{heuristic}NUL - *tagp.tagname_end = NUL; - len = (size_t)(tagp.tagname_end - tagp.tagname); - mfp = xmalloc(sizeof(char) + len + 10 + ML_EXTRA + 1); - - p = (char_u *)mfp; - STRCPY(p, tagp.tagname); - p[len] = '@'; - STRCPY(p + len + 1, help_lang); - snprintf((char *)p + len + 1 + ML_EXTRA, STRLEN(p) + len + 1 + ML_EXTRA, "%06d", - help_heuristic((char *)tagp.tagname, - match_re ? matchoff : 0, !match_no_ic) - + help_pri); - - *tagp.tagname_end = TAB; - } else if (name_only) { - if (get_it_again) { - char_u *temp_end = tagp.command; - - if (*temp_end == '/') { - while (*temp_end && *temp_end != '\r' - && *temp_end != '\n' - && *temp_end != '$') { - temp_end++; - } - } - - if (tagp.command + 2 < temp_end) { - len = (size_t)(temp_end - tagp.command - 2); - mfp = xmalloc(len + 2); - STRLCPY(mfp, tagp.command + 2, len + 1); - } else { - mfp = NULL; - } - get_it_again = false; - } else { - len = (size_t)(tagp.tagname_end - tagp.tagname); - mfp = xmalloc(sizeof(char) + len + 1); - STRLCPY(mfp, tagp.tagname, len + 1); - - // if wanted, re-read line to get long form too - if (State & MODE_INSERT) { - get_it_again = p_sft; - } - } - } else { - size_t tag_fname_len = STRLEN(tag_fname); - // Save the tag in a buffer. - // Use 0x02 to separate fields (Can't use NUL, because the - // hash key is terminated by NUL). - // Emacs tag: <mtt><tag_fname><0x02><ebuf><0x02><lbuf><NUL> - // other tag: <mtt><tag_fname><0x02><0x02><lbuf><NUL> - // without Emacs tags: <mtt><tag_fname><0x02><lbuf><NUL> - // Here <mtt> is the "mtt" value plus 1 to avoid NUL. - len = tag_fname_len + STRLEN(lbuf) + 3; - mfp = xmalloc(sizeof(char) + len + 1); - p = (char_u *)mfp; - p[0] = (char_u)(mtt + 1); - STRCPY(p + 1, tag_fname); -#ifdef BACKSLASH_IN_FILENAME - // Ignore differences in slashes, avoid adding - // both path/file and path\file. - slash_adjust(p + 1); -#endif - p[tag_fname_len + 1] = TAG_SEP; - s = p + 1 + tag_fname_len + 1; - STRCPY(s, lbuf); - } - - if (mfp != NULL) { - hashitem_T *hi; - - // Don't add identical matches. - // Add all cscope tags, because they are all listed. - // "mfp" is used as a hash key, there is a NUL byte to end - // the part that matters for comparing, more bytes may - // follow after it. E.g. help tags store the priority - // after the NUL. - if (use_cscope) { - hash++; - } else { - hash = hash_hash((char_u *)mfp); - } - hi = hash_lookup(&ht_match[mtt], (const char *)mfp, - STRLEN(mfp), hash); - if (HASHITEM_EMPTY(hi)) { - hash_add_item(&ht_match[mtt], hi, (char_u *)mfp, hash); - ga_grow(&ga_match[mtt], 1); - ((char **)(ga_match[mtt].ga_data))[ga_match[mtt].ga_len++] = mfp; - match_count++; - } else { - // duplicate tag, drop it - xfree(mfp); - } - } - } - if (use_cscope && eof) { - break; - } - } // forever - - if (line_error) { - semsg(_("E431: Format error in tags file \"%s\""), tag_fname); - if (!use_cscope) { - semsg(_("Before byte %" PRId64), (int64_t)vim_ftell(fp)); - } - stop_searching = true; - line_error = false; - } - - if (!use_cscope) { - fclose(fp); - } - if (vimconv.vc_type != CONV_NONE) { - convert_setup(&vimconv, NULL, NULL); - } - - tag_file_sorted = NUL; - if (sort_error) { - semsg(_("E432: Tags file not sorted: %s"), tag_fname); - sort_error = false; - } - - /* - * Stop searching if sufficient tags have been found. - */ - if (match_count >= mincount) { + findtags_in_file(&st, flags, buf_ffname); + if (st.stop_searching) { retval = OK; - stop_searching = true; - } - - if (stop_searching || use_cscope) { break; } } // end of for-each-file loop - if (!use_cscope) { - tagname_free(&tn); - } + tagname_free(&tn); // stop searching when already did a linear search, or when TAG_NOIC // used, and 'ignorecase' not set or already did case-ignore search - if (stop_searching || linear || (!p_ic && noic) || orgpat.regmatch.rm_ic) { - break; - } - if (use_cscope) { + if (st.stop_searching || st.linear || (!p_ic && noic) + || st.orgpat->regmatch.rm_ic) { break; } - orgpat.regmatch.rm_ic = TRUE; // try another time while ignoring case + + // try another time while ignoring case + st.orgpat->regmatch.rm_ic = true; } - if (!stop_searching) { - if (!did_open && verbose) { // never opened any tags file + if (!st.stop_searching) { + if (!st.did_open && verbose) { // never opened any tags file emsg(_("E433: No tags file")); } retval = OK; // It's OK even when no tag found } findtag_end: - xfree(lbuf); - vim_regfree(orgpat.regmatch.regprog); - xfree(tag_fname); + findtags_state_free(&st); - /* - * Move the matches from the ga_match[] arrays into one list of - * matches. When retval == FAIL, free the matches. - */ + // Move the matches from the ga_match[] arrays into one list of + // matches. When retval == FAIL, free the matches. if (retval == FAIL) { - match_count = 0; + st.match_count = 0; } - if (match_count > 0) { - matches = xmalloc((size_t)match_count * sizeof(char *)); - } else { - matches = NULL; - } - match_count = 0; - for (mtt = 0; mtt < MT_COUNT; mtt++) { - for (i = 0; i < ga_match[mtt].ga_len; i++) { - mfp = ((char **)(ga_match[mtt].ga_data))[i]; - if (matches == NULL) { - xfree(mfp); - } else { - if (!name_only) { - // Change mtt back to zero-based. - *mfp = (char)(*mfp - 1); - - // change the TAG_SEP back to NUL - for (p = (char_u *)mfp + 1; *p != NUL; p++) { - if (*p == TAG_SEP) { - *p = NUL; - } - } - } - matches[match_count++] = mfp; - } - } - - ga_clear(&ga_match[mtt]); - hash_clear(&ht_match[mtt]); - } - - *matchesp = matches; - *num_matches = match_count; + *num_matches = findtags_copy_matches(&st, matchesp); curbuf->b_help = help_save; xfree(saved_pat); @@ -2303,19 +2480,17 @@ findtag_end: static garray_T tag_fnames = GA_EMPTY_INIT_VALUE; -/* - * Callback function for finding all "tags" and "tags-??" files in - * 'runtimepath' doc directories. - */ +// Callback function for finding all "tags" and "tags-??" files in +// 'runtimepath' doc directories. static void found_tagfile_cb(char *fname, void *cookie) { - char_u *const tag_fname = vim_strsave((char_u *)fname); + char *const tag_fname = xstrdup(fname); #ifdef BACKSLASH_IN_FILENAME slash_adjust(tag_fname); #endif simplify_filename(tag_fname); - GA_APPEND(char_u *, &tag_fnames, tag_fname); + GA_APPEND(char *, &tag_fnames, tag_fname); } #if defined(EXITFREE) @@ -2334,25 +2509,23 @@ void free_tag_stuff(void) /// For help files, use "tags" file only. /// /// @param tnp holds status info -/// @param first TRUE when first file name is wanted +/// @param first true when first file name is wanted /// @param buf pointer to buffer of MAXPATHL chars /// /// @return FAIL if no more tag file names, OK otherwise. -int get_tagfname(tagname_T *tnp, int first, char_u *buf) +int get_tagfname(tagname_T *tnp, int first, char *buf) { - char_u *fname = NULL; - char_u *r_ptr; + char *fname = NULL; + char *r_ptr; if (first) { CLEAR_POINTER(tnp); } if (curbuf->b_help) { - /* - * For help files it's done in a completely different way: - * Find "doc/tags" and "doc/tags-??" in all directories in - * 'runtimepath'. - */ + // For help files it's done in a completely different way: + // Find "doc/tags" and "doc/tags-??" in all directories in + // 'runtimepath'. if (first) { ga_clear_strings(&tag_fnames); ga_init(&tag_fnames, (int)sizeof(char *), 10); @@ -2366,7 +2539,7 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) if (tnp->tn_hf_idx > tag_fnames.ga_len || *p_hf == NUL) { return FAIL; } - ++tnp->tn_hf_idx; + tnp->tn_hf_idx++; STRCPY(buf, p_hf); STRCPY(path_tail((char *)buf), "tags"); #ifdef BACKSLASH_IN_FILENAME @@ -2375,12 +2548,12 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) simplify_filename(buf); for (int i = 0; i < tag_fnames.ga_len; i++) { - if (STRCMP(buf, ((char **)(tag_fnames.ga_data))[i]) == 0) { + if (strcmp(buf, ((char **)(tag_fnames.ga_data))[i]) == 0) { return FAIL; // avoid duplicate file names } } } else { - STRLCPY(buf, ((char **)(tag_fnames.ga_data))[tnp->tn_hf_idx++], MAXPATHL); + xstrlcpy(buf, ((char **)(tag_fnames.ga_data))[tnp->tn_hf_idx++], MAXPATHL); } return OK; } @@ -2388,16 +2561,14 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) if (first) { // Init. We make a copy of 'tags', because autocommands may change // the value without notifying us. - tnp->tn_tags = vim_strsave((*curbuf->b_p_tags != NUL) ? curbuf->b_p_tags : p_tags); - tnp->tn_np = (char *)tnp->tn_tags; + tnp->tn_tags = xstrdup((*curbuf->b_p_tags != NUL) ? curbuf->b_p_tags : p_tags); + tnp->tn_np = tnp->tn_tags; } - /* - * Loop until we have found a file name that can be used. - * There are two states: - * tnp->tn_did_filefind_init == FALSE: setup for next part in 'tags'. - * tnp->tn_did_filefind_init == TRUE: find next file in this part. - */ + // Loop until we have found a file name that can be used. + // There are two states: + // tnp->tn_did_filefind_init == false: setup for next part in 'tags'. + // tnp->tn_did_filefind_init == true: find next file in this part. for (;;) { if (tnp->tn_did_filefind_init) { fname = vim_findfile(tnp->tn_search_ctx); @@ -2405,9 +2576,9 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) break; } - tnp->tn_did_filefind_init = FALSE; + tnp->tn_did_filefind_init = false; } else { - char_u *filename = NULL; + char *filename = NULL; // Stop when used all parts of 'tags'. if (*tnp->tn_np == NUL) { @@ -2416,26 +2587,24 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) return FAIL; } - /* - * Copy next file name into buf. - */ + // Copy next file name into buf. buf[0] = NUL; - (void)copy_option_part(&tnp->tn_np, (char *)buf, MAXPATHL - 1, " ,"); + (void)copy_option_part(&tnp->tn_np, buf, MAXPATHL - 1, " ,"); r_ptr = vim_findfile_stopdir(buf); // move the filename one char forward and truncate the // filepath with a NUL - filename = (char_u *)path_tail((char *)buf); + filename = path_tail(buf); STRMOVE(filename + 1, filename); *filename++ = NUL; tnp->tn_search_ctx = vim_findfile_init(buf, filename, r_ptr, 100, - FALSE, // don't free visited list + false, // don't free visited list FINDFILE_FILE, // we search for a file - tnp->tn_search_ctx, true, (char_u *)curbuf->b_ffname); + tnp->tn_search_ctx, true, curbuf->b_ffname); if (tnp->tn_search_ctx != NULL) { - tnp->tn_did_filefind_init = TRUE; + tnp->tn_did_filefind_init = true; } } } @@ -2445,9 +2614,7 @@ int get_tagfname(tagname_T *tnp, int first, char_u *buf) return OK; } -/* - * Free the contents of a tagname_T that was filled by get_tagfname(). - */ +// Free the contents of a tagname_T that was filled by get_tagfname(). void tagname_free(tagname_T *tnp) { xfree(tnp->tn_tags); @@ -2459,18 +2626,18 @@ void tagname_free(tagname_T *tnp) /// Parse one line from the tags file. Find start/end of tag name, start/end of /// file name and start of search pattern. /// -/// If is_etag is TRUE, tagp->fname and tagp->fname_end are not set. +/// If is_etag is true, tagp->fname and tagp->fname_end are not set. /// /// @param lbuf line to be parsed /// /// @return FAIL if there is a format error in this line, OK otherwise. -static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) +static int parse_tag_line(char *lbuf, tagptrs_T *tagp) { - char_u *p; + char *p; // Isolate the tagname, from lbuf up to the first white tagp->tagname = lbuf; - p = (char_u *)vim_strchr((char *)lbuf, TAB); + p = vim_strchr(lbuf, TAB); if (p == NULL) { return FAIL; } @@ -2481,7 +2648,7 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) p++; } tagp->fname = p; - p = (char_u *)vim_strchr((char *)p, TAB); + p = vim_strchr(p, TAB); if (p == NULL) { return FAIL; } @@ -2499,44 +2666,42 @@ static int parse_tag_line(char_u *lbuf, tagptrs_T *tagp) return OK; } -/* - * Check if tagname is a static tag - * - * Static tags produced by the older ctags program have the format: - * 'file:tag file /pattern'. - * This is only recognized when both occurrence of 'file' are the same, to - * avoid recognizing "string::string" or ":exit". - * - * Static tags produced by the new ctags program have the format: - * 'tag file /pattern/;"<Tab>file:' " - * - * Return TRUE if it is a static tag and adjust *tagname to the real tag. - * Return FALSE if it is not a static tag. - */ +// Check if tagname is a static tag +// +// Static tags produced by the older ctags program have the format: +// 'file:tag file /pattern'. +// This is only recognized when both occurrence of 'file' are the same, to +// avoid recognizing "string::string" or ":exit". +// +// Static tags produced by the new ctags program have the format: +// 'tag file /pattern/;"<Tab>file:' " +// +// Return true if it is a static tag and adjust *tagname to the real tag. +// Return false if it is not a static tag. static bool test_for_static(tagptrs_T *tagp) { - char_u *p; + char *p; // Check for new style static tag ":...<Tab>file:[<Tab>...]" p = tagp->command; - while ((p = (char_u *)vim_strchr((char *)p, '\t')) != NULL) { + while ((p = vim_strchr(p, '\t')) != NULL) { p++; - if (STRNCMP(p, "file:", 5) == 0) { - return TRUE; + if (strncmp(p, "file:", 5) == 0) { + return true; } } - return FALSE; + return false; } -// Returns the length of a matching tag line. -static size_t matching_line_len(const char_u *const lbuf) +/// @return the length of a matching tag line. +static size_t matching_line_len(const char *const lbuf) { - const char_u *p = lbuf + 1; + const char *p = lbuf + 1; // does the same thing as parse_match() - p += STRLEN(p) + 1; - return (size_t)(p - lbuf) + STRLEN(p); + p += strlen(p) + 1; + return (size_t)(p - lbuf) + strlen(p); } /// Parse a line from a matching tag. Does not change the line itself. @@ -2550,18 +2715,17 @@ static size_t matching_line_len(const char_u *const lbuf) /// @param tagp output: pointers into the line /// /// @return OK or FAIL. -static int parse_match(char_u *lbuf, tagptrs_T *tagp) +static int parse_match(char *lbuf, tagptrs_T *tagp) { int retval; - char_u *p; - char_u *pc, *pt; + char *p; + char *pc, *pt; tagp->tag_fname = lbuf + 1; - lbuf += STRLEN(tagp->tag_fname) + 2; + lbuf += strlen(tagp->tag_fname) + 2; // Find search pattern and the file name for non-etags. - retval = parse_tag_line(lbuf, - tagp); + retval = parse_tag_line(lbuf, tagp); tagp->tagkind = NULL; tagp->user_data = NULL; @@ -2580,20 +2744,20 @@ static int parse_match(char_u *lbuf, tagptrs_T *tagp) if (*p++ == TAB) { // Accept ASCII alphabetic kind characters and any multi-byte // character. - while (ASCII_ISALPHA(*p) || utfc_ptr2len((char *)p) > 1) { - if (STRNCMP(p, "kind:", 5) == 0) { + while (ASCII_ISALPHA(*p) || utfc_ptr2len(p) > 1) { + if (strncmp(p, "kind:", 5) == 0) { tagp->tagkind = p + 5; - } else if (STRNCMP(p, "user_data:", 10) == 0) { + } else if (strncmp(p, "user_data:", 10) == 0) { tagp->user_data = p + 10; - } else if (STRNCMP(p, "line:", 5) == 0) { - tagp->tagline = atoi((char *)p + 5); + } else if (strncmp(p, "line:", 5) == 0) { + tagp->tagline = atoi(p + 5); } if (tagp->tagkind != NULL && tagp->user_data != NULL) { break; } - pc = (char_u *)vim_strchr((char *)p, ':'); - pt = (char_u *)vim_strchr((char *)p, '\t'); + pc = vim_strchr(p, ':'); + pt = vim_strchr(p, '\t'); if (pc == NULL || (pt != NULL && pc > pt)) { tagp->tagkind = p; } @@ -2621,17 +2785,15 @@ static int parse_match(char_u *lbuf, tagptrs_T *tagp) return retval; } -/* - * Find out the actual file name of a tag. Concatenate the tags file name - * with the matching tag file name. - * Returns an allocated string. - */ -static char_u *tag_full_fname(tagptrs_T *tagp) +// Find out the actual file name of a tag. Concatenate the tags file name +// with the matching tag file name. +// Returns an allocated string. +static char *tag_full_fname(tagptrs_T *tagp) { - int c = *tagp->fname_end; + char c = *tagp->fname_end; *tagp->fname_end = NUL; - char_u *fullname = expand_tag_fname(tagp->fname, tagp->tag_fname, false); - *tagp->fname_end = (char_u)c; + char *fullname = expand_tag_fname(tagp->fname, tagp->tag_fname, false); + *tagp->fname_end = c; return fullname; } @@ -2640,30 +2802,29 @@ static char_u *tag_full_fname(tagptrs_T *tagp) /// /// @param lbuf_arg line from the tags file for this tag /// @param forceit :ta with ! -/// @param keep_help keep help flag (FALSE for cscope) +/// @param keep_help keep help flag /// /// @return OK for success, NOTAGFILE when file not found, FAIL otherwise. -static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) +static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help) { - int save_magic; bool save_p_ws; int save_p_scs, save_p_ic; linenr_T save_lnum; - char_u *str; - char_u *pbuf; // search pattern buffer - char_u *pbuf_end; - char_u *tofree_fname = NULL; - char_u *fname; + char *str; + char *pbuf; // search pattern buffer + char *pbuf_end; + char *tofree_fname = NULL; + char *fname; tagptrs_T tagp; int retval = FAIL; int getfile_result = GETFILE_UNUSED; int search_options; win_T *curwin_save = NULL; - char_u *full_fname = NULL; + char *full_fname = NULL; const bool old_KeyTyped = KeyTyped; // getting the file may reset it const int l_g_do_tagpreview = g_do_tagpreview; const size_t len = matching_line_len(lbuf_arg) + 1; - char_u *lbuf = xmalloc(len); + char *lbuf = xmalloc(len); memmove(lbuf, lbuf_arg, len); pbuf = xmalloc(LSIZE); @@ -2689,9 +2850,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) *pbuf_end = NUL; { - /* - * Remove the "<Tab>fieldname:value" stuff; we don't need it here. - */ + // Remove the "<Tab>fieldname:value" stuff; we don't need it here. str = pbuf; if (find_extra(&str) == OK) { pbuf_end = str; @@ -2699,24 +2858,19 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) } } - /* - * Expand file name, when needed (for environment variables). - * If 'tagrelative' option set, may change file name. - */ + // Expand file name, when needed (for environment variables). + // If 'tagrelative' option set, may change file name. fname = expand_tag_fname(fname, tagp.tag_fname, true); tofree_fname = fname; // free() it later - /* - * Check if the file with the tag exists before abandoning the current - * file. Also accept a file name for which there is a matching BufReadCmd - * autocommand event (e.g., http://sys/file). - */ + // Check if the file with the tag exists before abandoning the current + // file. Also accept a file name for which there is a matching BufReadCmd + // autocommand event (e.g., http://sys/file). if (!os_path_exists(fname) - && !has_autocmd(EVENT_BUFREADCMD, (char *)fname, - NULL)) { + && !has_autocmd(EVENT_BUFREADCMD, fname, NULL)) { retval = NOTAGFILE; xfree(nofile_fname); - nofile_fname = vim_strsave(fname); + nofile_fname = xstrdup(fname); goto erret; } @@ -2726,19 +2880,15 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) postponed_split = 0; // don't split again below curwin_save = curwin; // Save current window - /* - * If we are reusing a window, we may change dir when - * entering it (autocommands) so turn the tag filename - * into a fullpath - */ + // If we are reusing a window, we may change dir when + // entering it (autocommands) so turn the tag filename + // into a fullpath if (!curwin->w_p_pvw) { - full_fname = (char_u *)FullName_save((char *)fname, FALSE); + full_fname = FullName_save(fname, false); fname = full_fname; - /* - * Make the preview window the current window. - * Open a preview window when needed. - */ + // Make the preview window the current window. + // Open a preview window when needed. prepare_tagpreview(true); } } @@ -2746,7 +2896,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) // If it was a CTRL-W CTRL-] command split window now. For ":tab tag" // open a new tab page. if (postponed_split && (swb_flags & (SWB_USEOPEN | SWB_USETAB))) { - buf_T *const existing_buf = buflist_findname_exp((char *)fname); + buf_T *const existing_buf = buflist_findname_exp(fname); if (existing_buf != NULL) { const win_T *wp = NULL; @@ -2791,7 +2941,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) if (getfile_result == GETFILE_UNUSED) { // Careful: getfile() may trigger autocommands and call jumpto_tag() // recursively. - getfile_result = getfile(0, (char *)fname, NULL, true, (linenr_T)0, forceit); + getfile_result = getfile(0, fname, NULL, true, (linenr_T)0, forceit); } keep_help_flag = false; @@ -2799,41 +2949,37 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) curwin->w_set_curswant = true; postponed_split = 0; - save_magic = p_magic; - p_magic = false; // always execute with 'nomagic' + const optmagic_T save_magic_overruled = magic_overruled; + magic_overruled = OPTION_MAGIC_OFF; // always execute with 'nomagic' // Save value of no_hlsearch, jumping to a tag is not a real search const bool save_no_hlsearch = no_hlsearch; - /* - * If 'cpoptions' contains 't', store the search pattern for the "n" - * command. If 'cpoptions' does not contain 't', the search pattern - * is not stored. - */ + // If 'cpoptions' contains 't', store the search pattern for the "n" + // command. If 'cpoptions' does not contain 't', the search pattern + // is not stored. if (vim_strchr(p_cpo, CPO_TAGPAT) != NULL) { search_options = 0; } else { search_options = SEARCH_KEEP; } - /* - * If the command is a search, try here. - * - * Reset 'smartcase' for the search, since the search pattern was not - * typed by the user. - * Only use do_search() when there is a full search command, without - * anything following. - */ + // If the command is a search, try here. + // + // Reset 'smartcase' for the search, since the search pattern was not + // typed by the user. + // Only use do_search() when there is a full search command, without + // anything following. str = pbuf; if (pbuf[0] == '/' || pbuf[0] == '?') { - str = skip_regexp(pbuf + 1, pbuf[0], FALSE, NULL) + 1; + str = skip_regexp(pbuf + 1, pbuf[0], false) + 1; } if (str > pbuf_end - 1) { // search command with nothing following save_p_ws = p_ws; save_p_ic = p_ic; save_p_scs = p_scs; p_ws = true; // need 'wrapscan' for backward searches - p_ic = FALSE; // don't ignore case now - p_scs = FALSE; + p_ic = false; // don't ignore case now + p_scs = false; save_lnum = curwin->w_cursor.lnum; if (tagp.tagline > 0) { // start search before line from "line:" field @@ -2847,11 +2993,9 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) retval = OK; } else { int found = 1; - int cc; + char cc; - /* - * try again, ignore case now - */ + // try again, ignore case now p_ic = true; if (!do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1, search_options, NULL)) { @@ -2860,26 +3004,23 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) (void)test_for_static(&tagp); cc = *tagp.tagname_end; *tagp.tagname_end = NUL; - snprintf((char *)pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname); + snprintf(pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname); if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) { // Guess again: "^char * \<func (" - snprintf((char *)pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", + snprintf(pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(", tagp.tagname); - if (!do_search(NULL, '/', '/', pbuf, (long)1, - search_options, NULL)) { + if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) { found = 0; } } - *tagp.tagname_end = (char_u)cc; + *tagp.tagname_end = cc; } if (found == 0) { emsg(_("E434: Can't find tag pattern")); curwin->w_cursor.lnum = save_lnum; } else { - /* - * Only give a message when really guessed, not when 'ic' - * is set and match found while ignoring case. - */ + // Only give a message when really guessed, not when 'ic' + // is set and match found while ignoring case. if (found == 2 || !save_p_ic) { msg(_("E435: Couldn't find tag, just guessing!")); if (!msg_scrolled && msg_silent == 0) { @@ -2904,7 +3045,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) secure = 1; sandbox++; curwin->w_cursor.lnum = 1; // start command in line 1 - do_cmdline_cmd((char *)pbuf); + do_cmdline_cmd(pbuf); retval = OK; // When the command has done something that is not allowed make sure @@ -2916,7 +3057,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) sandbox--; } - p_magic = save_magic; + magic_overruled = save_magic_overruled; // restore no_hlsearch when keeping the old search pattern if (search_options) { set_no_hlsearch(save_no_hlsearch); @@ -2928,10 +3069,8 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) } if (retval == OK) { - /* - * For a help buffer: Put the cursor line at the top of the window, - * the help subject will be below it. - */ + // For a help buffer: Put the cursor line at the top of the window, + // the help subject will be below it. if (curbuf->b_help) { set_topline(curwin, curwin->w_cursor.lnum); } @@ -2944,7 +3083,7 @@ static int jumpto_tag(const char_u *lbuf_arg, int forceit, int keep_help) && curwin != curwin_save && win_valid(curwin_save)) { // Return cursor to where we were validate_cursor(); - redraw_later(curwin, VALID); + redraw_later(curwin, UPD_VALID); win_enter(curwin_save, true); } @@ -2967,19 +3106,18 @@ erret: return retval; } -// If "expand" is true, expand wildcards in fname. -// If 'tagrelative' option set, change fname (name of file containing tag) -// according to tag_fname (name of tag file containing fname). -// Returns a pointer to allocated memory. -static char_u *expand_tag_fname(char_u *fname, char_u *const tag_fname, const bool expand) +/// If "expand" is true, expand wildcards in fname. +/// If 'tagrelative' option set, change fname (name of file containing tag) +/// according to tag_fname (name of tag file containing fname). +/// +/// @return a pointer to allocated memory. +static char *expand_tag_fname(char *fname, char *const tag_fname, const bool expand) { - char_u *p; - char_u *expanded_fname = NULL; + char *p; + char *expanded_fname = NULL; expand_T xpc; - /* - * Expand file name (for environment variables) when needed. - */ + // Expand file name (for environment variables) when needed. if (expand && path_has_wildcard(fname)) { ExpandInit(&xpc); xpc.xp_context = EXPAND_FILES; @@ -2990,20 +3128,17 @@ static char_u *expand_tag_fname(char_u *fname, char_u *const tag_fname, const bo } } - char_u *retval; + char *retval; if ((p_tr || curbuf->b_help) && !vim_isAbsName(fname) - && (p = (char_u *)path_tail((char *)tag_fname)) != tag_fname) { + && (p = path_tail(tag_fname)) != tag_fname) { retval = xmalloc(MAXPATHL); STRCPY(retval, tag_fname); - STRLCPY(retval + (p - tag_fname), fname, - MAXPATHL - (p - tag_fname)); - /* - * Translate names like "src/a/../b/file.c" into "src/b/file.c". - */ + xstrlcpy(retval + (p - tag_fname), fname, (size_t)(MAXPATHL - (p - tag_fname))); + // Translate names like "src/a/../b/file.c" into "src/b/file.c". simplify_filename(retval); } else { - retval = vim_strsave(fname); + retval = xstrdup(fname); } xfree(expanded_fname); @@ -3011,47 +3146,43 @@ static char_u *expand_tag_fname(char_u *fname, char_u *const tag_fname, const bo return retval; } -/* - * Check if we have a tag for the buffer with name "buf_ffname". - * This is a bit slow, because of the full path compare in path_full_compare(). - * Return TRUE if tag for file "fname" if tag file "tag_fname" is for current - * file. - */ -static int test_for_current(char_u *fname, char_u *fname_end, char_u *tag_fname, char_u *buf_ffname) +/// Check if we have a tag for the buffer with name "buf_ffname". +/// This is a bit slow, because of the full path compare in path_full_compare(). +/// +/// @return true if tag for file "fname" if tag file "tag_fname" is for current +/// file. +static int test_for_current(char *fname, char *fname_end, char *tag_fname, char *buf_ffname) { - int c; - int retval = FALSE; - char_u *fullname; + char c; + int retval = false; if (buf_ffname != NULL) { // if the buffer has a name { c = *fname_end; *fname_end = NUL; } - fullname = expand_tag_fname(fname, tag_fname, true); - retval = (path_full_compare((char *)fullname, (char *)buf_ffname, true, true) & kEqualFiles); + char *fullname = expand_tag_fname(fname, tag_fname, true); + retval = (path_full_compare(fullname, buf_ffname, true, true) & kEqualFiles); xfree(fullname); - *fname_end = (char_u)c; + *fname_end = c; } return retval; } -/* - * Find the end of the tagaddress. - * Return OK if ";\"" is following, FAIL otherwise. - */ -static int find_extra(char_u **pp) +// Find the end of the tagaddress. +// Return OK if ";\"" is following, FAIL otherwise. +static int find_extra(char **pp) { - char_u *str = *pp; - char_u first_char = **pp; + char *str = *pp; + char first_char = **pp; // Repeat for addresses separated with ';' for (;;) { if (ascii_isdigit(*str)) { - str = (char_u *)skipdigits((char *)str + 1); + str = skipdigits(str + 1); } else if (*str == '/' || *str == '?') { - str = skip_regexp(str + 1, *str, false, NULL); + str = skip_regexp(str + 1, *str, false); if (*str != first_char) { str = NULL; } else { @@ -3059,7 +3190,7 @@ static int find_extra(char_u **pp) } } else { // not a line number or search string, look for terminator. - str = (char_u *)strstr((char *)str, "|;\""); + str = strstr(str, "|;\""); if (str != NULL) { str++; break; @@ -3073,7 +3204,7 @@ static int find_extra(char_u **pp) first_char = *str; } - if (str != NULL && STRNCMP(str, ";\"", 2) == 0) { + if (str != NULL && strncmp(str, ";\"", 2) == 0) { *pp = str; return OK; } @@ -3090,11 +3221,11 @@ static void tagstack_clear_entry(taggy_T *item) } /// @param tagnames expand tag names -int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) +int expand_tags(int tagnames, char *pat, int *num_file, char ***file) { int i; int extra_flag; - char_u *name_buf; + char *name_buf; size_t name_buf_size = 100; tagptrs_T t_p; int ret; @@ -3109,11 +3240,11 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) if (pat[0] == '/') { ret = find_tags(pat + 1, num_file, file, TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC, - TAG_MANY, (char_u *)curbuf->b_ffname); + TAG_MANY, curbuf->b_ffname); } else { ret = find_tags(pat, num_file, file, TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC | TAG_NOIC, - TAG_MANY, (char_u *)curbuf->b_ffname); + TAG_MANY, curbuf->b_ffname); } if (ret == OK && !tagnames) { // Reorganize the tags for display and matching as strings of: @@ -3121,10 +3252,10 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) for (i = 0; i < *num_file; i++) { size_t len; - parse_match((char_u *)(*file)[i], &t_p); + parse_match((*file)[i], &t_p); len = (size_t)(t_p.tagname_end - t_p.tagname); if (len > name_buf_size - 3) { - char_u *buf; + char *buf; name_buf_size = len + 3; buf = xrealloc(name_buf, name_buf_size); @@ -3150,8 +3281,7 @@ int expand_tags(int tagnames, char_u *pat, int *num_file, char ***file) /// /// @param start start of the value /// @param end after the value; can be NULL -static int add_tag_field(dict_T *dict, const char *field_name, const char_u *start, - const char_u *end) +static int add_tag_field(dict_T *dict, const char *field_name, const char *start, const char *end) FUNC_ATTR_NONNULL_ARG(1, 2) { int len = 0; @@ -3166,10 +3296,10 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char_u *sta } return FAIL; } - char_u *buf = xmalloc(MAXPATHL); + char *buf = xmalloc(MAXPATHL); if (start != NULL) { if (end == NULL) { - end = start + STRLEN(start); + end = start + strlen(start); while (end > start && (end[-1] == '\r' || end[-1] == '\n')) { end--; } @@ -3178,22 +3308,21 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char_u *sta if (len > MAXPATHL - 1) { len = MAXPATHL - 1; } - STRLCPY(buf, start, len + 1); + xstrlcpy(buf, start, (size_t)len + 1); } buf[len] = NUL; - retval = tv_dict_add_str(dict, field_name, STRLEN(field_name), - (const char *)buf); + retval = tv_dict_add_str(dict, field_name, strlen(field_name), buf); xfree(buf); return retval; } /// Add the tags matching the specified pattern "pat" to the list "list" /// as a dictionary. Use "buf_fname" for priority, unless NULL. -int get_tags(list_T *list, char_u *pat, char_u *buf_fname) +int get_tags(list_T *list, char *pat, char *buf_fname) { int num_matches, i, ret; char **matches; - char_u *full_fname; + char *full_fname; dict_T *dict; tagptrs_T tp; bool is_static; @@ -3202,7 +3331,7 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) TAG_REGEXP | TAG_NOIC, MAXCOL, buf_fname); if (ret == OK && num_matches > 0) { for (i = 0; i < num_matches; i++) { - int parse_result = parse_match((char_u *)matches[i], &tp); + int parse_result = parse_match(matches[i], &tp); // Avoid an unused variable warning in release builds. (void)parse_result; @@ -3211,7 +3340,7 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) is_static = test_for_static(&tp); // Skip pseudo-tag lines. - if (STRNCMP(tp.tagname, "!_TAG_", 6) == 0) { + if (strncmp(tp.tagname, "!_TAG_", 6) == 0) { xfree(matches[i]); continue; } @@ -3232,18 +3361,18 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) xfree(full_fname); if (tp.command_end != NULL) { - for (char_u *p = tp.command_end + 3; + for (char *p = tp.command_end + 3; *p != NUL && *p != '\n' && *p != '\r'; MB_PTR_ADV(p)) { if (p == tp.tagkind - || (p + 5 == tp.tagkind && STRNCMP(p, "kind:", 5) == 0)) { + || (p + 5 == tp.tagkind && strncmp(p, "kind:", 5) == 0)) { // skip "kind:<kind>" and "<kind>" p = tp.tagkind_end - 1; - } else if (STRNCMP(p, "file:", 5) == 0) { + } else if (strncmp(p, "file:", 5) == 0) { // skip "file:" (static tag) p += 4; } else if (!ascii_iswhite(*p)) { - char_u *s, *n; + char *s, *n; int len; // Add extra field as a dict entry. Fields are @@ -3255,17 +3384,17 @@ int get_tags(list_T *list, char_u *pat, char_u *buf_fname) len = (int)(p - n); if (*p == ':' && len > 0) { s = ++p; - while (*p != NUL && *p >= ' ') { + while (*p != NUL && (uint8_t)(*p) >= ' ') { p++; } n[len] = NUL; - if (add_tag_field(dict, (char *)n, s, p) == FAIL) { + if (add_tag_field(dict, n, s, p) == FAIL) { ret = FAIL; } n[len] = ':'; } else { // Skip field without colon. - while (*p != NUL && *p >= ' ') { + while (*p != NUL && (uint8_t)(*p) >= ' ') { p++; } } @@ -3351,9 +3480,9 @@ static void tagstack_shift(win_T *wp) wp->w_tagstacklen--; } -// Push a new item to the tag stack -static void tagstack_push_item(win_T *wp, char_u *tagname, int cur_fnum, int cur_match, pos_T mark, - int fnum, char_u *user_data) +/// Push a new item to the tag stack +static void tagstack_push_item(win_T *wp, char *tagname, int cur_fnum, int cur_match, pos_T mark, + int fnum, char *user_data) { taggy_T *tagstack = wp->w_tagstack; int idx = wp->w_tagstacklen; // top of the stack @@ -3376,13 +3505,13 @@ static void tagstack_push_item(win_T *wp, char_u *tagname, int cur_fnum, int cur tagstack[idx].user_data = user_data; } -// Add a list of items to the tag stack in the specified window +/// Add a list of items to the tag stack in the specified window static void tagstack_push_items(win_T *wp, list_T *l) { listitem_T *li; dictitem_T *di; dict_T *itemdict; - char_u *tagname; + char *tagname; pos_T mark; int fnum; @@ -3401,8 +3530,7 @@ static void tagstack_push_items(win_T *wp, list_T *l) if (list2fpos(&di->di_tv, &mark, &fnum, NULL, false) != OK) { continue; } - if ((tagname = (char_u *)tv_dict_get_string(itemdict, "tagname", true)) - == NULL) { + if ((tagname = tv_dict_get_string(itemdict, "tagname", true)) == NULL) { continue; } @@ -3414,7 +3542,7 @@ static void tagstack_push_items(win_T *wp, list_T *l) (int)tv_dict_get_number(itemdict, "bufnr"), (int)tv_dict_get_number(itemdict, "matchnr") - 1, mark, fnum, - (char_u *)tv_dict_get_string(itemdict, "user_data", true)); + tv_dict_get_string(itemdict, "user_data", true)); } } @@ -3459,10 +3587,12 @@ int set_tagstack(win_T *wp, const dict_T *d, int action) if ((di = tv_dict_find(d, "curidx", -1)) != NULL) { tagstack_set_curidx(wp, (int)tv_get_number(&di->di_tv) - 1); } + if (action == 't') { // truncate the stack taggy_T *const tagstack = wp->w_tagstack; const int tagstackidx = wp->w_tagstackidx; int tagstacklen = wp->w_tagstacklen; + // delete all the tag stack entries above the current entry while (tagstackidx < tagstacklen) { tagstack_clear_entry(&tagstack[--tagstacklen]); diff --git a/src/nvim/tag.h b/src/nvim/tag.h index 0b4039afb6..e08145f727 100644 --- a/src/nvim/tag.h +++ b/src/nvim/tag.h @@ -14,7 +14,6 @@ #define DT_SELECT 7 // jump to selection from list #define DT_HELP 8 // like DT_TAG, but no wildcards #define DT_JUMP 9 // jump to new tag or selection from list -#define DT_CSCOPE 10 // cscope find command (like tjump) #define DT_LTAG 11 // tag using location list #define DT_FREE 99 // free cached matches @@ -23,7 +22,6 @@ #define TAG_NAMES 2 // only return name of tag #define TAG_REGEXP 4 // use tag pattern as regexp #define TAG_NOIC 8 // don't always ignore case -#define TAG_CSCOPE 16 // cscope tag #define TAG_VERBOSE 32 // message verbosity #define TAG_INS_COMP 64 // Currently doing insert completion #define TAG_KEEP_LANG 128 // keep current language @@ -34,7 +32,7 @@ // Structure used for get_tagfname(). typedef struct { - char_u *tn_tags; // value of 'tags' when starting + char *tn_tags; // value of 'tags' when starting char *tn_np; // current position in tn_tags int tn_did_filefind_init; int tn_hf_idx; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 08fcefaa88..a52a34cd66 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -37,45 +37,57 @@ // Some code from pangoterm http://www.leonerd.org.uk/code/pangoterm #include <assert.h> +#include <limits.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <vterm.h> +#include <vterm_keycodes.h> +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/change.h" +#include "nvim/channel.h" #include "nvim/cursor.h" +#include "nvim/drawline.h" #include "nvim/drawscreen.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/time.h" -#include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" #include "nvim/keycodes.h" -#include "nvim/log.h" #include "nvim/macros.h" #include "nvim/main.h" #include "nvim/map.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" +#include "nvim/msgpack_rpc/channel_defs.h" +#include "nvim/normal.h" #include "nvim/option.h" #include "nvim/optionstr.h" -#include "nvim/os/input.h" +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/state.h" #include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" -#include "nvim/window.h" typedef struct terminal_state { VimState state; @@ -111,15 +123,19 @@ struct terminal { // - receive data from libvterm as a result of key presses. char textbuf[0x1fff]; - ScrollbackLine **sb_buffer; // Scrollback buffer storage for libvterm - size_t sb_current; // number of rows pushed to sb_buffer - size_t sb_size; // sb_buffer size + ScrollbackLine **sb_buffer; // Scrollback storage. + size_t sb_current; // Lines stored in sb_buffer. + size_t sb_size; // Capacity of sb_buffer. // "virtual index" that points to the first sb_buffer row that we need to // push to the terminal buffer when refreshing the scrollback. When negative, // it actually points to entries that are no longer in sb_buffer (because the // window height has increased) and must be deleted from the terminal buffer int sb_pending; + char *title; // VTermStringFragment buffer + size_t title_len; // number of rows pushed to sb_buffer + size_t title_size; // sb_buffer size + // buf_T instance that acts as a "drawing surface" for libvterm // we can't store a direct reference to the buffer because the // refresh_timer_cb may be called after the buffer was freed, and there's @@ -150,7 +166,7 @@ static VTermScreenCallbacks vterm_screen_callbacks = { .movecursor = term_movecursor, .settermprop = term_settermprop, .bell = term_bell, - .sb_pushline = term_sb_push, + .sb_pushline = term_sb_push, // Called before a line goes offscreen. .sb_popline = term_sb_pop, }; @@ -230,7 +246,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts) set_option_value("wrap", false, NULL, OPT_LOCAL); set_option_value("list", false, NULL, OPT_LOCAL); if (buf->b_ffname != NULL) { - buf_set_term_title(buf, buf->b_ffname); + buf_set_term_title(buf, buf->b_ffname, strlen(buf->b_ffname)); } RESET_BINDING(curwin); // Reset cursor in current window. @@ -371,7 +387,7 @@ void terminal_check_size(Terminal *term) // Check if there is a window that displays the terminal and find the maximum width and height. // Skip the autocommand window which isn't actually displayed. FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp == aucmd_win) { + if (is_aucmd_win(wp)) { continue; } if (wp->w_buffer && wp->w_buffer->terminal == term) { @@ -418,15 +434,15 @@ bool terminal_enter(void) // placed at end of buffer to "follow" output. #11072 handle_T save_curwin = curwin->handle; bool save_w_p_cul = curwin->w_p_cul; - char_u *save_w_p_culopt = NULL; - char_u save_w_p_culopt_flags = curwin->w_p_culopt_flags; + char *save_w_p_culopt = NULL; + uint8_t save_w_p_culopt_flags = curwin->w_p_culopt_flags; int save_w_p_cuc = curwin->w_p_cuc; long save_w_p_so = curwin->w_p_so; long save_w_p_siso = curwin->w_p_siso; if (curwin->w_p_cul && curwin->w_p_culopt_flags & CULOPT_NBR) { - if (STRCMP(curwin->w_p_culopt, "number")) { + if (strcmp(curwin->w_p_culopt, "number") != 0) { save_w_p_culopt = curwin->w_p_culopt; - curwin->w_p_culopt = (char_u *)xstrdup("number"); + curwin->w_p_culopt = xstrdup("number"); } curwin->w_p_culopt_flags = CULOPT_NBR; } else { @@ -518,7 +534,19 @@ static int terminal_check(VimState *state) terminal_check_cursor(); if (must_redraw) { - update_screen(0); + update_screen(); + + // Make sure an invoked autocmd doesn't delete the buffer (and the + // terminal) under our fingers. + curbuf->b_locked++; + + // save and restore curwin and curbuf, in case the autocmd changes them + aco_save_T aco; + aucmd_prepbuf(&aco, curbuf); + apply_autocmds(EVENT_TEXTCHANGEDT, NULL, NULL, false, curbuf); + aucmd_restbuf(&aco); + + curbuf->b_locked--; } if (need_maketitle) { // Update title in terminal-mode. #7248 @@ -636,6 +664,7 @@ void terminal_destroy(Terminal **termpp) xfree(term->sb_buffer[i]); } xfree(term->sb_buffer); + xfree(term->title); vterm_free(term->vt); xfree(term); *termpp = NULL; // coverity[dead-store] @@ -688,8 +717,8 @@ void terminal_paste(long count, char **y_array, size_t y_size) return; } vterm_keyboard_start_paste(curbuf->terminal->vt); - size_t buff_len = STRLEN(y_array[0]); - char_u *buff = xmalloc(buff_len); + size_t buff_len = strlen(y_array[0]); + char *buff = xmalloc(buff_len); for (int i = 0; i < count; i++) { // -V756 // feed the lines to the terminal for (size_t j = 0; j < y_size; j++) { @@ -697,23 +726,23 @@ void terminal_paste(long count, char **y_array, size_t y_size) // terminate the previous line terminal_send(curbuf->terminal, "\n", 1); } - size_t len = STRLEN(y_array[j]); + size_t len = strlen(y_array[j]); if (len > buff_len) { buff = xrealloc(buff, len); buff_len = len; } - char_u *dst = buff; - char_u *src = (char_u *)y_array[j]; + char *dst = buff; + char *src = y_array[j]; while (*src != '\0') { - len = (size_t)utf_ptr2len((char *)src); - int c = utf_ptr2char((char *)src); + len = (size_t)utf_ptr2len(src); + int c = utf_ptr2char(src); if (!is_filter_char(c)) { memcpy(dst, src, len); dst += len; } src += len; } - terminal_send(curbuf->terminal, (char *)buff, (size_t)(dst - buff)); + terminal_send(curbuf->terminal, buff, (size_t)(dst - buff)); } } xfree(buff); @@ -754,6 +783,22 @@ static int get_rgb(VTermState *state, VTermColor color) return RGB_(color.rgb.red, color.rgb.green, color.rgb.blue); } +static int get_underline_hl_flag(VTermScreenCellAttrs attrs) +{ + switch (attrs.underline) { + case VTERM_UNDERLINE_OFF: + return 0; + case VTERM_UNDERLINE_SINGLE: + return HL_UNDERLINE; + case VTERM_UNDERLINE_DOUBLE: + return HL_UNDERDOUBLE; + case VTERM_UNDERLINE_CURLY: + return HL_UNDERCURL; + default: + return HL_UNDERLINE; + } +} + void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int *term_attrs) { int height, width; @@ -790,7 +835,7 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int *te int hl_attrs = (cell.attrs.bold ? HL_BOLD : 0) | (cell.attrs.italic ? HL_ITALIC : 0) | (cell.attrs.reverse ? HL_INVERSE : 0) - | (cell.attrs.underline ? HL_UNDERLINE : 0) + | get_underline_hl_flag(cell.attrs) | (cell.attrs.strike ? HL_STRIKETHROUGH: 0) | ((fg_indexed && !fg_set) ? HL_FG_INDEXED : 0) | ((bg_indexed && !bg_set) ? HL_BG_INDEXED : 0); @@ -858,13 +903,13 @@ static int term_movecursor(VTermPos new, VTermPos old, int visible, void *data) return 1; } -static void buf_set_term_title(buf_T *buf, char *title) +static void buf_set_term_title(buf_T *buf, const char *title, size_t len) FUNC_ATTR_NONNULL_ALL { Error err = ERROR_INIT; dict_set_var(buf->b_vars, STATIC_CSTR_AS_STRING("term_title"), - STRING_OBJ(cstr_as_string(title)), + STRING_OBJ(((String){ .data = (char *)title, .size = len })), false, false, &err); @@ -887,7 +932,30 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data) case VTERM_PROP_TITLE: { buf_T *buf = handle_get_buffer(term->buf_handle); - buf_set_term_title(buf, val->string); + VTermStringFragment frag = val->string; + + if (frag.initial && frag.final) { + buf_set_term_title(buf, frag.str, frag.len); + break; + } + + if (frag.initial) { + term->title_len = 0; + term->title_size = MAX(frag.len, 1024); + term->title = xmalloc(sizeof(char *) * term->title_size); + } else if (term->title_len + frag.len > term->title_size) { + term->title_size *= 2; + term->title = xrealloc(term->title, sizeof(char *) * term->title_size); + } + + memcpy(term->title + term->title_len, frag.str, frag.len); + term->title_len += frag.len; + + if (frag.final) { + buf_set_term_title(buf, term->title, term->title_len); + xfree(term->title); + term->title = NULL; + } break; } @@ -908,7 +976,10 @@ static int term_bell(void *data) return 1; } -// Scrollback push handler (from pangoterm). +/// Scrollback push handler: called just before a line goes offscreen (and libvterm will forget it), +/// giving us a chance to store it. +/// +/// Code adapted from pangoterm. static int term_sb_push(int cols, const VTermScreenCell *cells, void *data) { Terminal *term = data; @@ -1368,14 +1439,14 @@ static bool send_mouse_event(Terminal *term, int c) int direction = c == K_MOUSEDOWN ? MSCR_DOWN : MSCR_UP; if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { scroll_redraw(direction, curwin->w_botline - curwin->w_topline); - } else { - scroll_redraw(direction, 3L); + } else if (p_mousescroll_vert > 0) { + scroll_redraw(direction, p_mousescroll_vert); } curwin->w_redr_status = true; curwin = save_curwin; curbuf = curwin->w_buffer; - redraw_later(mouse_win, NOT_VALID); + redraw_later(mouse_win, UPD_NOT_VALID); invalidate_terminal(term, -1, -1); // Only need to exit focus if the scrolled window is the terminal window return mouse_win == curwin; diff --git a/src/nvim/testdir/Make_all.mak b/src/nvim/testdir/Make_all.mak new file mode 100644 index 0000000000..b917877422 --- /dev/null +++ b/src/nvim/testdir/Make_all.mak @@ -0,0 +1 @@ +# Tests may depend on the existence of this file. diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 4641408069..9511b311e6 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -11,16 +11,7 @@ ROOT := ../../.. export SHELL := sh export NVIM_PRG := $(NVIM_PRG) -export TMPDIR := $(abspath Xtest-tmpdir) - -SCRIPTS_DEFAULT = \ - test42.out \ - -ifneq ($(OS),Windows_NT) - SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \ - test49.out \ - -endif +export TMPDIR := $(abspath X-test-tmpdir) ifeq ($(OS),Windows_NT) FIXFF = fixff @@ -28,8 +19,6 @@ else FIXFF = endif -SCRIPTS ?= $(SCRIPTS_DEFAULT) - # Tests using runtest.vim. NEW_TESTS_ALOT := test_alot_utf8 test_alot test_alot_latin NEW_TESTS_IN_ALOT := $(shell sed -n '/^source/ s/^source //; s/\.vim$$//p' $(addsuffix .vim,$(NEW_TESTS_ALOT))) @@ -66,11 +55,7 @@ else endif endif -ifdef TESTNUM - SCRIPTS := test$(TESTNUM).out -endif - -nongui: nolog $(FIXFF) $(SCRIPTS) newtests report +nongui: nolog $(FIXFF) newtests report .gdbinit: @echo "[OLDTEST-PREP] Setting up .gdbinit" @@ -88,8 +73,6 @@ report: test1.out: $(NVIM_PRG) -$(SCRIPTS): $(NVIM_PRG) test1.out - NO_PLUGINS = --noplugin --headless # In vim, if the -u command line option is specified, compatible is turned on # and viminfo is not read. Unlike vim, neovim reads viminfo and requires the @@ -147,17 +130,6 @@ test1.out: .gdbinit test1.in @rm -f wrongtermsize @rm -rf X* viminfo -%.out: %.in .gdbinit - @echo "[OLDESTTEST] Running" $* - @rm -rf $*.failed test.ok $(RM_ON_RUN) - @mkdir -p $(TMPDIR) - @cp $*.ok test.ok - @/bin/sh runnvim.sh --oldesttest $(ROOT) $(NVIM_PRG) $* $(RUN_VIM) $*.in - @rm -rf X* test.ok viminfo - -# Explicit dependencies. -test49.out: test49.vim - nolog: @echo "[OLDTEST-PREP] Removing test.log and messages" @rm -f test.log messages @@ -179,4 +151,4 @@ newtestssilent: $(NEW_TESTS_RES) @echo "[OLDTEST] Running" $* @rm -rf $*.failed test.ok $(RM_ON_RUN) @mkdir -p $(TMPDIR) - @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIMTEST) $(NO_INITS) -u NONE -S runtest.vim $*.vim + @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIMTEST) $(NO_INITS) -u NONE --cmd "set shortmess-=F" -S runtest.vim $*.vim diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim index 4107df99d6..680a59006b 100644 --- a/src/nvim/testdir/check.vim +++ b/src/nvim/testdir/check.vim @@ -1,11 +1,16 @@ source shared.vim source term_util.vim +command -nargs=1 MissingFeature throw 'Skipped: ' .. <args> .. ' feature missing' + " Command to check for the presence of a feature. command -nargs=1 CheckFeature call CheckFeature(<f-args>) func CheckFeature(name) + " if !has(a:name, 1) + " throw 'Checking for non-existent feature ' .. a:name + " endif if !has(a:name) - throw 'Skipped: ' .. a:name .. ' feature missing' + MissingFeature a:name endif endfunc @@ -39,6 +44,22 @@ func CheckFunction(name) endif endfunc +" Command to check for the presence of an Ex command +command -nargs=1 CheckCommand call CheckCommand(<f-args>) +func CheckCommand(name) + if !exists(':' .. a:name) + throw 'Skipped: ' .. a:name .. ' command not supported' + endif +endfunc + +" Command to check for the presence of a shell command +command -nargs=1 CheckExecutable call CheckExecutable(<f-args>) +func CheckExecutable(name) + if !executable(a:name) + throw 'Skipped: ' .. a:name .. ' program not executable' + endif +endfunc + " Command to check for the presence of python. Argument should have been " obtained with PythonProg() func CheckPython(name) @@ -71,12 +92,11 @@ func CheckUnix() endif endfunc -" Command to check for not running on a BSD system. -" TODO: using this checks should not be needed -command CheckNotBSD call CheckNotBSD() -func CheckNotBSD() - if has('bsd') - throw 'Skipped: does not work on BSD' +" Command to check for running on Linux +command CheckLinux call CheckLinux() +func CheckLinux() + if !has('linux') + throw 'Skipped: only works on Linux' endif endfunc @@ -105,6 +125,14 @@ func CheckCanRunGui() endif endfunc +" Command to Check for an environment variable +command -nargs=1 CheckEnv call CheckEnv(<f-args>) +func CheckEnv(name) + if empty(eval('$' .. a:name)) + throw 'Skipped: Environment variable ' .. a:name .. ' is not set' + endif +endfunc + " Command to check that we are using the GUI command CheckGui call CheckGui() func CheckGui() @@ -145,6 +173,22 @@ func CheckNotAsan() endif endfunc +" Command to check for not running under valgrind +command CheckNotValgrind call CheckNotValgrind() +func CheckNotValgrind() + if RunningWithValgrind() + throw 'Skipped: does not work well with valgrind' + endif +endfunc + +" Command to check for X11 based GUI +command CheckX11BasedGui call CheckX11BasedGui() +func CheckX11BasedGui() + if !g:x11_based_gui + throw 'Skipped: requires X11 based GUI' + endif +endfunc + " Command to check for satisfying any of the conditions. " e.g. CheckAnyOf Feature:bsd Feature:sun Linux command -nargs=+ CheckAnyOf call CheckAnyOf(<f-args>) diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index fcd3d5724c..3c5699af73 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -105,7 +105,7 @@ set nomore lang mess C " Nvim: append runtime from build dir, which contains the generated doc/tags. -let &runtimepath .= ','.expand($BUILD_DIR).'/runtime/' +let &runtimepath ..= ',' .. expand($BUILD_DIR) .. '/runtime/' let s:t_bold = &t_md let s:t_normal = &t_me @@ -114,6 +114,13 @@ if has('win32') let $PROMPT = '$P$G' endif +if has('mac') + " In MacOS, when starting a shell in a terminal, a bash deprecation warning + " message is displayed. This breaks the terminal test. Disable the warning + " message. + let $BASH_SILENCE_DEPRECATION_WARNING = 1 +endif + " Prepare for calling test_garbagecollect_now(). let v:testing = 1 @@ -164,16 +171,19 @@ func RunTheTest(test) endtry endif + au VimLeavePre * call EarlyExit(g:testfunc) if a:test =~ 'Test_nocatch_' " Function handles errors itself. This avoids skipping commands after the " error. + let g:skipped_reason = '' exe 'call ' . a:test + if g:skipped_reason != '' + call add(s:messages, ' Skipped') + call add(s:skipped, 'SKIPPED ' . a:test . ': ' . g:skipped_reason) + endif else try - let s:test = a:test - au VimLeavePre * call EarlyExit(s:test) exe 'call ' . a:test - au! VimLeavePre catch /^\cskipped/ call add(s:messages, ' Skipped') call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', '')) @@ -181,6 +191,7 @@ func RunTheTest(test) call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) endtry endif + au! VimLeavePre " In case 'insertmode' was set and something went wrong, make sure it is " reset to avoid trouble with anything else. @@ -243,11 +254,11 @@ func AfterTheTest(func_name) if len(v:errors) > 0 if match(s:may_fail_list, '^' .. a:func_name) >= 0 let s:fail_expected += 1 - call add(s:errors_expected, 'Found errors in ' . s:test . ':') + call add(s:errors_expected, 'Found errors in ' . g:testfunc . ':') call extend(s:errors_expected, v:errors) else let s:fail += 1 - call add(s:errors, 'Found errors in ' . s:test . ':') + call add(s:errors, 'Found errors in ' . g:testfunc . ':') call extend(s:errors, v:errors) endif let v:errors = [] @@ -408,22 +419,22 @@ endif let s:may_fail_list = [] if $TEST_MAY_FAIL != '' - " Split the list at commas and add () to make it match s:test. + " Split the list at commas and add () to make it match g:testfunc. let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'}) endif " Execute the tests in alphabetical order. -for s:test in sort(s:tests) +for g:testfunc in sort(s:tests) " Silence, please! set belloff=all let prev_error = '' let total_errors = [] - let run_nr = 1 + let g:run_nr = 1 " A test can set g:test_is_flaky to retry running the test. let g:test_is_flaky = 0 - call RunTheTest(s:test) + call RunTheTest(g:testfunc) " Repeat a flaky test. Give up when: " - $TEST_NO_RETRY is not empty @@ -431,16 +442,16 @@ for s:test in sort(s:tests) " - it fails five times (with a different message) if len(v:errors) > 0 \ && $TEST_NO_RETRY == '' - \ && (index(s:flaky_tests, s:test) >= 0 + \ && (index(s:flaky_tests, g:testfunc) >= 0 \ || g:test_is_flaky) while 1 - call add(s:messages, 'Found errors in ' . s:test . ':') + call add(s:messages, 'Found errors in ' . g:testfunc . ':') call extend(s:messages, v:errors) - call add(total_errors, 'Run ' . run_nr . ':') + call add(total_errors, 'Run ' . g:run_nr . ':') call extend(total_errors, v:errors) - if run_nr == 5 || prev_error == v:errors[0] + if g:run_nr >= 5 || prev_error == v:errors[0] call add(total_errors, 'Flaky test failed too often, giving up') let v:errors = total_errors break @@ -455,9 +466,9 @@ for s:test in sort(s:tests) let prev_error = v:errors[0] let v:errors = [] - let run_nr += 1 + let g:run_nr += 1 - call RunTheTest(s:test) + call RunTheTest(g:testfunc) if len(v:errors) == 0 " Test passed on rerun. @@ -466,7 +477,7 @@ for s:test in sort(s:tests) endwhile endif - call AfterTheTest(s:test) + call AfterTheTest(g:testfunc) endfor call FinishTesting() diff --git a/src/nvim/testdir/samples/re.freeze.txt b/src/nvim/testdir/samples/re.freeze.txt new file mode 100644 index 0000000000..d768c23c5e --- /dev/null +++ b/src/nvim/testdir/samples/re.freeze.txt @@ -0,0 +1,6 @@ +:set re=0 or 2 +Search for the pattern: /\s\+\%#\@<!$/ +vim should not freeze. + +<td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td><td style="border-bottom windowtext 0.5pt solid; border-left windowtext;" class=abc align=right><font face=arial><font color=#ff0000><b>5</b></font></font></td> + diff --git a/src/nvim/testdir/sautest/autoload/foo.vim b/src/nvim/testdir/sautest/autoload/foo.vim index 298e7275d8..21d33a0f4d 100644 --- a/src/nvim/testdir/sautest/autoload/foo.vim +++ b/src/nvim/testdir/sautest/autoload/foo.vim @@ -9,3 +9,7 @@ endfunc func foo#addFoo(head) return a:head .. 'foo' endfunc + +func foo#() + return 'empty' +endfunc diff --git a/src/nvim/testdir/script_util.vim b/src/nvim/testdir/script_util.vim index 9913b1dfaf..28d6a621d6 100644 --- a/src/nvim/testdir/script_util.vim +++ b/src/nvim/testdir/script_util.vim @@ -48,7 +48,7 @@ endfunc " delete it afterwards. However, if an exception is thrown the file may remain, " the caller should call DeleteTheScript() afterwards. let s:script_name = '' -function! ExecAsScript(funcname) +func ExecAsScript(funcname) " Make a script from the function passed as argument. let s:script_name = MakeScript(a:funcname) @@ -56,9 +56,9 @@ function! ExecAsScript(funcname) exec "source" s:script_name call delete(s:script_name) let s:script_name = '' -endfunction +endfunc -function! DeleteTheScript() +func DeleteTheScript() if s:script_name call delete(s:script_name) let s:script_name = '' diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index 6bc3607b69..f895287469 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -5,7 +5,7 @@ if exists('s:did_load') set directory& set directory^=. set display= - set fillchars=vert:\|,fold:- + set fillchars=vert:\|,foldsep:\|,fold:- set formatoptions=tcq set fsync set laststatus=1 @@ -45,6 +45,11 @@ mapclear! aunmenu * tlunmenu * +" roughly equivalent to test_setmouse() in Vim +func Ntest_setmouse(row, col) + call nvim_input_mouse('move', '', '', 0, a:row - 1, a:col - 1) +endfunc + " Prevent Nvim log from writing to stderr. let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index c2809844ac..33f6d9a2d0 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -9,7 +9,7 @@ source view_util.vim " {Nvim} " Filepath captured from output may be truncated, like this: -" /home/va...estdir/Xtest-tmpdir/nvimxbXN4i/10 +" /home/va...estdir/X-test-tmpdir/nvimxbXN4i/10 " Get last 2 segments, then combine with $TMPDIR. func! Fix_truncated_tmpfile(fname) " sanity check @@ -285,6 +285,12 @@ func GetVimCommand(...) return cmd endfunc +" Return one when it looks like the tests are run with valgrind, which means +" that everything is much slower. +func RunningWithValgrind() + return GetVimCommand() =~ '\<valgrind\>' +endfunc + " Get the command to run Vim, with --clean instead of "-u NONE". func GetVimCommandClean() let cmd = GetVimCommand() @@ -317,7 +323,6 @@ func RunVim(before, after, arguments) endfunc func RunVimPiped(before, after, arguments, pipecmd) - let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' let cmd = GetVimCommand() let args = '' if len(a:before) > 0 @@ -332,7 +337,9 @@ func RunVimPiped(before, after, arguments, pipecmd) " Optionally run Vim under valgrind " let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd - exe "silent !" . a:pipecmd . cmd . args . ' ' . a:arguments + let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' + " Nvim does not support -Z flag, remove it. + exe "silent !" . a:pipecmd . cmd . args . ' ' . substitute(a:arguments, '-Z', '', 'g') if len(a:before) > 0 call delete('Xbefore.vim') @@ -364,4 +371,19 @@ func GetMessages() return msg_list endfunc +" Run the list of commands in 'cmds' and look for 'errstr' in exception. +" Note that assert_fails() cannot be used in some places and this function +" can be used. +func AssertException(cmds, errstr) + let save_exception = '' + try + for cmd in a:cmds + exe cmd + endfor + catch + let save_exception = v:exception + endtry + call assert_match(a:errstr, save_exception) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test42.in b/src/nvim/testdir/test42.in Binary files differdeleted file mode 100644 index 456f9ddb07..0000000000 --- a/src/nvim/testdir/test42.in +++ /dev/null diff --git a/src/nvim/testdir/test42.ok b/src/nvim/testdir/test42.ok Binary files differdeleted file mode 100644 index 183430d71c..0000000000 --- a/src/nvim/testdir/test42.ok +++ /dev/null diff --git a/src/nvim/testdir/test49.in b/src/nvim/testdir/test49.in deleted file mode 100644 index 435e62765b..0000000000 --- a/src/nvim/testdir/test49.in +++ /dev/null @@ -1,31 +0,0 @@ -This is a test of the script language. - -If after adding a new test, the test output doesn't appear properly in -test49.failed, try to add one or more "G"s at the line ending in "test.out" - -STARTTEST -:se nomore -:lang mess C -:so test49.vim -:" Go back to this file and append the results from register r. -:buf test49.in -G"rp:/^Results/,$w! test.out -:" -:" make valgrind happy -:redir => funclist -:silent func -:redir END -:for line in split(funclist, "\n") -: let name = matchstr(line, 'function \zs[A-Z]\w*\ze(') -: if name != '' -: exe "delfunc " . name -: endif -:endfor -:for v in keys(g:) -: silent! exe "unlet " . v -:endfor -:unlet v -:qa! -ENDTEST - -Results of test49.vim: diff --git a/src/nvim/testdir/test49.ok b/src/nvim/testdir/test49.ok deleted file mode 100644 index 9f283e808b..0000000000 --- a/src/nvim/testdir/test49.ok +++ /dev/null @@ -1,56 +0,0 @@ -Results of test49.vim: -*** Test 18: OK (67224583) -*** Test 19: OK (69275973) -*** Test 20: OK (1874575085) -*** Test 21: OK (147932225) -*** Test 22: OK (4161) -*** Test 23: OK (49) -*** Test 24: OK (41) -*** Test 27: OK (1996459) -*** Test 28: OK (1996459) -*** Test 29: OK (170428555) -*** Test 30: OK (190905173) -*** Test 31: OK (190905173) -*** Test 34: OK (2146584868) -*** Test 35: OK (2146584868) -*** Test 36: OK (1071644672) -*** Test 37: OK (1071644672) -*** Test 38: OK (357908480) -*** Test 39: OK (357908480) -*** Test 40: OK (357908480) -*** Test 49: OK (179000669) -*** Test 50: OK (363550045) -*** Test 52: OK (1247112011) -*** Test 53: OK (131071) -*** Test 54: OK (2047) -*** Test 55: OK (1023) -*** Test 56: OK (511) -*** Test 57: OK (2147450880) -*** Test 58: OK (624945) -*** Test 59: OK (2038431743) -*** Test 60: OK (311511339) -*** Test 61: OK (374889517) -*** Test 62: OK (286331153) -*** Test 63: OK (236978127) -*** Test 64: OK (1499645335) -*** Test 65: OK (70187) -*** Test 66: OK (5464) -*** Test 67: OK (212514423) -*** Test 68: OK (212514423) -*** Test 76: OK (1610087935) -*** Test 77: OK (1388671) -*** Test 78: OK (134217728) -*** Test 79: OK (70288929) -*** Test 80: OK (17895765) -*** Test 81: OK (387) -*** Test 82: OK (8454401) -*** Test 83: OK (2835) -*** Test 84: OK (934782101) -*** Test 85: OK (198689) ---- Test 86: No Crash for vimgrep on BufUnload -*** Test 86: OK (0) ---- Test 88: All tests were run with throwing exceptions on error. - The $VIMNOERRTHROW control is not configured. ---- Test 88: All tests were run with throwing exceptions on interrupt. - The $VIMNOINTTHROW control is not configured. -*** Test 88: OK (50443995) diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim deleted file mode 100644 index 3ee5e9ff7c..0000000000 --- a/src/nvim/testdir/test49.vim +++ /dev/null @@ -1,6724 +0,0 @@ -" Vim script language tests -" Author: Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com> -" Last Change: 2019 Nov 03 - -"------------------------------------------------------------------------------- -" Test environment {{{1 -"------------------------------------------------------------------------------- - - -" Adding new tests easily. {{{2 -" -" Writing new tests is eased considerably with the following functions and -" abbreviations (see "Commands for recording the execution path", "Automatic -" argument generation"). -" -" To get the abbreviations, execute the command -" -" :let test49_set_env = 1 | source test49.vim -" -" To get them always (from src/nvim/testdir), put a line -" -" au! BufRead test49.vim let test49_set_env = 1 | source test49.vim -" -" into the local .vimrc file in the src/nvim/testdir directory. -" -if exists("test49_set_env") && test49_set_env - - " Automatic argument generation for the test environment commands. - - function! Xsum() - let addend = substitute(getline("."), '^.*"\s*X:\s*\|^.*', '', "") - " Evaluate arithmetic expression. - if addend != "" - exec "let g:Xsum = g:Xsum + " . addend - endif - endfunction - - function! Xcheck() - let g:Xsum=0 - ?XpathINIT?,.call Xsum() - exec "norm A " - return g:Xsum - endfunction - - iab Xcheck Xcheck<Space><C-R>=Xcheck()<CR><C-O>x - - function! Xcomment(num) - let str = "" - let tabwidth = &sts ? &sts : &ts - let tabs = (48+tabwidth - a:num - virtcol(".")) / tabwidth - while tabs > 0 - let str = str . "\t" - let tabs = tabs - 1 - endwhile - let str = str . '" X:' - return str - endfunction - - function! Xloop() - let back = line(".") . "|norm" . virtcol(".") . "|" - norm 0 - let last = search('X\(loop\|path\)INIT\|Xloop\>', "bW") - exec back - let theline = getline(last) - if theline =~ 'X\(loop\|path\)INIT' - let num = 1 - else - let num = 2 * substitute(theline, '.*Xloop\s*\(\d\+\).*', '\1', "") - endif - ?X\(loop\|path\)INIT? - \s/\(XloopINIT!\=\s*\d\+\s\+\)\@<=\(\d\+\)/\=2*submatch(2)/ - exec back - exec "norm a " - return num . Xcomment(strlen(num)) - endfunction - - iab Xloop Xloop<Space><C-R>=Xloop()<CR><C-O>x - - function! Xpath(loopinit) - let back = line(".") . "|norm" . virtcol(".") . "|" - norm 0 - let last = search('XpathINIT\|Xpath\>\|XloopINIT', "bW") - exec back - let theline = getline(last) - if theline =~ 'XpathINIT' - let num = 1 - elseif theline =~ 'Xpath\>' - let num = 2 * substitute(theline, '.*Xpath\s*\(\d\+\).*', '\1', "") - else - let pattern = '.*XloopINIT!\=\s*\(\d\+\)\s*\(\d\+\).*' - let num = substitute(theline, pattern, '\1', "") - let factor = substitute(theline, pattern, '\2', "") - " The "<C-O>x" from the "Xpath" iab and the character triggering its - " expansion are in the input buffer. Save and clear typeahead so - " that it is not read away by the call to "input()" below. Restore - " afterwards. - call inputsave() - let loops = input("Number of iterations in previous loop? ") - call inputrestore() - while (loops > 0) - let num = num * factor - let loops = loops - 1 - endwhile - endif - exec "norm a " - if a:loopinit - return num . " 1" - endif - return num . Xcomment(strlen(num)) - endfunction - - iab Xpath Xpath<Space><C-R>=Xpath(0)<CR><C-O>x - iab XloopINIT XloopINIT<Space><C-R>=Xpath(1)<CR><C-O>x - - " Also useful (see ExtraVim below): - aug ExtraVim - au! - au BufEnter <sfile> syn region ExtraVim - \ start=+^if\s\+ExtraVim(.*)+ end=+^endif+ - \ transparent keepend - au BufEnter <sfile> syn match ExtraComment /^"/ - \ contained containedin=ExtraVim - au BufEnter <sfile> hi link ExtraComment vimComment - aug END - - aug Xpath - au BufEnter <sfile> syn keyword Xpath - \ XpathINIT Xpath XloopINIT Xloop XloopNEXT Xcheck Xout - au BufEnter <sfile> hi link Xpath Special - aug END - - do BufEnter <sfile> - - " Do not execute the tests when sourcing this file for getting the functions - " and abbreviations above, which are intended for easily adding new test - " cases; they are not needed for test execution. Unlet the variable - " controlling this so that an explicit ":source" command for this file will - " execute the tests. - unlet test49_set_env - finish - -endif - - -" Commands for recording the execution path. {{{2 -" -" The Xpath/Xloop commands can be used for computing the eXecution path by -" adding (different) powers of 2 from those script lines, for which the -" execution should be checked. Xloop provides different addends for each -" execution of a loop. Permitted values are 2^0 to 2^30, so that 31 execution -" points (multiply counted inside loops) can be tested. -" -" Note that the arguments of the following commands can be generated -" automatically, see below. -" -" Usage: {{{3 -" -" - Use XpathINIT at the beginning of the test. -" -" - Use Xpath to check if a line is executed. -" Argument: power of 2 (decimal). -" -" - To check multiple execution of loops use Xloop for automatically -" computing Xpath values: -" -" - Use XloopINIT before the loop. -" Two arguments: -" - the first Xpath value (power of 2) to be used (Xnext), -" - factor for computing a new Xnext value when reexecuting a loop -" (by a ":continue" or ":endwhile"); this should be 2^n where -" n is the number of Xloop commands inside the loop. -" If XloopINIT! is used, the first execution of XloopNEXT is -" a no-operation. -" -" - Use Xloop inside the loop: -" One argument: -" The argument and the Xnext value are multiplied to build the -" next Xpath value. No new Xnext value is prepared. The argument -" should be 2^(n-1) for the nth Xloop command inside the loop. -" If the loop has only one Xloop command, the argument can be -" omitted (default: 1). -" -" - Use XloopNEXT before ":continue" and ":endwhile". This computes a new -" Xnext value for the next execution of the loop by multiplying the old -" one with the factor specified in the XloopINIT command. No Argument. -" Alternatively, when XloopINIT! is used, a single XloopNEXT at the -" beginning of the loop can be used. -" -" Nested loops are not supported. -" -" - Use Xcheck at end of each test. It prints the test number, the expected -" execution path value, the test result ("OK" or "FAIL"), and, if the tests -" fails, the actual execution path. -" One argument: -" Expected Xpath/Xloop sum for the correct execution path. -" In order that this value can be computed automatically, do the -" following: For each line in the test with an Xpath and Xloop -" command, add a comment starting with "X:" and specifying an -" expression that evaluates to the value contributed by this line to -" the correct execution path. (For copying an Xpath argument of at -" least two digits into the comment, press <C-P>.) At the end of the -" test, just type "Xcheck" and press <Esc>. -" -" - In order to add additional information to the test output file, use the -" Xout command. Argument(s) like ":echo". -" -" Automatic argument generation: {{{3 -" -" The arguments of the Xpath, XloopINIT, Xloop, and Xcheck commands can be -" generated automatically, so that new tests can easily be written without -" mental arithmetic. The Xcheck argument is computed from the "X:" comments -" of the preceding Xpath and Xloop commands. See the commands and -" abbreviations at the beginning of this file. -" -" Implementation: {{{3 -" XpathINIT, Xpath, XloopINIT, Xloop, XloopNEXT, Xcheck, Xout. -" -" The variants for existing g:ExtraVimResult are needed when executing a script -" in an extra Vim process, see ExtraVim below. - -" EXTRA_VIM_START - do not change or remove this line. - -com! XpathINIT let g:Xpath = 0 - -if exists("g:ExtraVimResult") - com! -count -bar Xpath exec "!echo <count> >>" . g:ExtraVimResult -else - com! -count -bar Xpath let g:Xpath = g:Xpath + <count> -endif - -com! -count -nargs=1 -bang - \ XloopINIT let g:Xnext = <count> | - \ let g:Xfactor = <args> | - \ let g:Xskip = strlen("<bang>") - -if exists("g:ExtraVimResult") - com! -count=1 -bar Xloop exec "!echo " . (g:Xnext * <count>) . " >>" . - \ g:ExtraVimResult -else - com! -count=1 -bar Xloop let g:Xpath = g:Xpath + g:Xnext * <count> -endif - -com! XloopNEXT let g:Xnext = g:Xnext * - \ (g:Xskip ? 1 : g:Xfactor) | - \ let g:Xskip = 0 - -let @r = "" -let Xtest = 1 -com! -count Xcheck let Xresult = "*** Test " . - \ (Xtest<10?" ":Xtest<100?" ":"") . - \ Xtest . ": " . ( - \ (Xpath==<count>) ? "OK (".Xpath.")" : - \ "FAIL (".Xpath." instead of <count>)" - \ ) | - \ let @R = Xresult . "\n" | - \ echo Xresult | - \ let Xtest = Xtest + 1 - -if exists("g:ExtraVimResult") - com! -nargs=+ Xoutq exec "!echo @R:'" . - \ substitute(substitute(<q-args>, - \ "'", '&\\&&', "g"), "\n", "@NL@", "g") - \ . "' >>" . g:ExtraVimResult -else - com! -nargs=+ Xoutq let @R = "--- Test " . - \ (g:Xtest<10?" ":g:Xtest<100?" ":"") . - \ g:Xtest . ": " . substitute(<q-args>, - \ "\n", "&\t ", "g") . "\n" -endif -com! -nargs=+ Xout exec 'Xoutq' <args> - -" Switch off storing of lines for undoing changes. Speeds things up a little. -set undolevels=-1 - -" EXTRA_VIM_STOP - do not change or remove this line. - - -" ExtraVim() - Run a script file in an extra Vim process. {{{2 -" -" This is useful for testing immediate abortion of the script processing due to -" an error in a command dynamically enclosed by a :try/:tryend region or when an -" exception is thrown but not caught or when an interrupt occurs. It can also -" be used for testing :finish. -" -" An interrupt location can be specified by an "INTERRUPT" comment. A number -" telling how often this location is reached (in a loop or in several function -" calls) should be specified as argument. When missing, once per script -" invocation or function call is assumed. INTERRUPT locations are tested by -" setting a breakpoint in that line and using the ">quit" debug command when -" the breakpoint is reached. A function for which an INTERRUPT location is -" specified must be defined before calling it (or executing it as a script by -" using ExecAsScript below). -" -" This function is only called in normal modus ("g:ExtraVimResult" undefined). -" -" Tests to be executed as an extra script should be written as follows: -" -" column 1 column 1 -" | | -" v v -" -" XpathINIT XpathINIT -" if ExtraVim() if ExtraVim() -" ... " ... -" ... " ... -" endif endif -" Xcheck <number> Xcheck <number> -" -" Double quotes in column 1 are removed before the script is executed. -" They should be used if the test has unbalanced conditionals (:if/:endif, -" :while:/endwhile, :try/:endtry) or for a line with a syntax error. The -" extra script may use Xpath, XloopINIT, Xloop, XloopNEXT, and Xout as usual. -" -" A file name may be specified as argument. All messages of the extra Vim -" process are then redirected to the file. An existing file is overwritten. -" -let ExtraVimCount = 0 -let ExtraVimBase = expand("<sfile>") -let ExtraVimTestEnv = "" -" -function ExtraVim(...) - " Count how often this function is called. - let g:ExtraVimCount = g:ExtraVimCount + 1 - - " Disable folds to prevent that the ranges in the ":write" commands below - " are extended up to the end of a closed fold. This also speeds things up - " considerably. - set nofoldenable - - " Open a buffer for this test script and copy the test environment to - " a temporary file. Take account of parts relevant for the extra script - " execution only. - let current_buffnr = bufnr("%") - execute "view +1" g:ExtraVimBase - if g:ExtraVimCount == 1 - let g:ExtraVimTestEnv = tempname() - execute "/E" . "XTRA_VIM_START/+,/E" . "XTRA_VIM_STOP/-w" - \ g:ExtraVimTestEnv "|']+" - execute "/E" . "XTRA_VIM_START/+,/E" . "XTRA_VIM_STOP/-w >>" - \ g:ExtraVimTestEnv "|']+" - execute "/E" . "XTRA_VIM_START/+,/E" . "XTRA_VIM_STOP/-w >>" - \ g:ExtraVimTestEnv "|']+" - execute "/E" . "XTRA_VIM_START/+,/E" . "XTRA_VIM_STOP/-w >>" - \ g:ExtraVimTestEnv "|']+" - endif - - " Start the extra Vim script with a ":source" command for the test - " environment. The source line number where the extra script will be - " appended, needs to be passed as variable "ExtraVimBegin" to the script. - let extra_script = tempname() - exec "!echo 'source " . g:ExtraVimTestEnv . "' >" . extra_script - let extra_begin = 1 - - " Starting behind the test environment, skip over the first g:ExtraVimCount - " occurrences of "if ExtraVim()" and copy the following lines up to the - " matching "endif" to the extra Vim script. - execute "/E" . "ND_OF_TEST_ENVIRONMENT/" - exec 'norm ' . g:ExtraVimCount . '/^\s*if\s\+ExtraVim(.*)/+' . "\n" - execute ".,/^endif/-write >>" . extra_script - - " Open a buffer for the extra Vim script, delete all ^", and write the - " script if was actually modified. - execute "edit +" . (extra_begin + 1) extra_script - ,$s/^"//e - update - - " Count the INTERRUPTs and build the breakpoint and quit commands. - let breakpoints = "" - let debug_quits = "" - let in_func = 0 - exec extra_begin - while search( - \ '"\s*INTERRUPT\h\@!\|^\s*fu\%[nction]\>!\=\s*\%(\u\|s:\)\w*\s*(\|' - \ . '^\s*\\\|^\s*endf\%[unction]\>\|' - \ . '\%(^\s*fu\%[nction]!\=\s*\)\@<!\%(\u\|s:\)\w*\s*(\|' - \ . 'ExecAsScript\s\+\%(\u\|s:\)\w*', - \ "W") > 0 - let theline = getline(".") - if theline =~ '^\s*fu' - " Function definition. - let in_func = 1 - let func_start = line(".") - let func_name = substitute(theline, - \ '^\s*fu\%[nction]!\=\s*\(\%(\u\|s:\)\w*\).*', '\1', "") - elseif theline =~ '^\s*endf' - " End of function definition. - let in_func = 0 - else - let finding = substitute(theline, '.*\(\%' . col(".") . 'c.*\)', - \ '\1', "") - if finding =~ '^"\s*INTERRUPT\h\@!' - " Interrupt comment. Compose as many quit commands as - " specified. - let cnt = substitute(finding, - \ '^"\s*INTERRUPT\s*\(\d*\).*$', '\1', "") - let quits = "" - while cnt > 0 - " Use "\r" rather than "\n" to separate the quit commands. - " "\r" is not interpreted as command separator by the ":!" - " command below but works to separate commands in the - " external vim. - let quits = quits . "q\r" - let cnt = cnt - 1 - endwhile - if in_func - " Add the function breakpoint and note the number of quits - " to be used, if specified, or one for every call else. - let breakpoints = breakpoints . " -c 'breakadd func " . - \ (line(".") - func_start) . " " . - \ func_name . "'" - if quits != "" - let debug_quits = debug_quits . quits - elseif !exists("quits{func_name}") - let quits{func_name} = "q\r" - else - let quits{func_name} = quits{func_name} . "q\r" - endif - else - " Add the file breakpoint and the quits to be used for it. - let breakpoints = breakpoints . " -c 'breakadd file " . - \ line(".") . " " . extra_script . "'" - if quits == "" - let quits = "q\r" - endif - let debug_quits = debug_quits . quits - endif - else - " Add the quits to be used for calling the function or executing - " it as script file. - if finding =~ '^ExecAsScript' - " Sourcing function as script. - let finding = substitute(finding, - \ '^ExecAsScript\s\+\(\%(\u\|s:\)\w*\).*', '\1', "") - else - " Function call. - let finding = substitute(finding, - \ '^\(\%(\u\|s:\)\w*\).*', '\1', "") - endif - if exists("quits{finding}") - let debug_quits = debug_quits . quits{finding} - endif - endif - endif - endwhile - - " Close the buffer for the script and create an (empty) resultfile. - bwipeout - let resultfile = tempname() - exec "!>" . resultfile - - " Run the script in an extra vim. Switch to extra modus by passing the - " resultfile in ExtraVimResult. Redirect messages to the file specified as - " argument if any. Use ":debuggreedy" so that the commands provided on the - " pipe are consumed at the debug prompt. Use "-N" to enable command-line - " continuation ("C" in 'cpo'). Add "nviminfo" to 'viminfo' to avoid - " messing up the user's viminfo file. - let redirect = a:0 ? - \ " -c 'au VimLeave * redir END' -c 'redir\\! >" . a:1 . "'" : "" - exec "!echo '" . debug_quits . "q' | " .. v:progpath .. " -u NONE -N -es" . redirect . - \ " -c 'debuggreedy|set viminfo+=nviminfo'" . - \ " -c 'let ExtraVimBegin = " . extra_begin . "'" . - \ " -c 'let ExtraVimResult = \"" . resultfile . "\"'" . breakpoints . - \ " -S " . extra_script - - " Build the resulting sum for resultfile and add it to g:Xpath. Add Xout - " information provided by the extra Vim process to the test output. - let sum = 0 - exec "edit" resultfile - let line = 1 - while line <= line("$") - let theline = getline(line) - if theline =~ '^@R:' - exec 'Xout "' . substitute(substitute( - \ escape(escape(theline, '"'), '\"'), - \ '^@R:', '', ""), '@NL@', "\n", "g") . '"' - else - let sum = sum + getline(line) - endif - let line = line + 1 - endwhile - bwipeout - let g:Xpath = g:Xpath + sum - - " Delete the extra script and the resultfile. - call delete(extra_script) - call delete(resultfile) - - " Switch back to the buffer that was active when this function was entered. - exec "buffer" current_buffnr - - " Return 0. This protects extra scripts from being run in the main Vim - " process. - return 0 -endfunction - - -" ExtraVimThrowpoint() - Relative throwpoint in ExtraVim script {{{2 -" -" Evaluates v:throwpoint and returns the throwpoint relative to the beginning of -" an ExtraVim script as passed by ExtraVim() in ExtraVimBegin. -" -" EXTRA_VIM_START - do not change or remove this line. -function ExtraVimThrowpoint() - if !exists("g:ExtraVimBegin") - Xout "ExtraVimThrowpoint() used outside ExtraVim() script." - return v:throwpoint - endif - - if v:throwpoint =~ '^function\>' - return v:throwpoint - endif - - return "line " . - \ (substitute(v:throwpoint, '.*, line ', '', "") - g:ExtraVimBegin) . - \ " of ExtraVim() script" -endfunction -" EXTRA_VIM_STOP - do not change or remove this line. - - -" MakeScript() - Make a script file from a function. {{{2 -" -" Create a script that consists of the body of the function a:funcname. -" Replace any ":return" by a ":finish", any argument variable by a global -" variable, and every ":call" by a ":source" for the next following argument -" in the variable argument list. This function is useful if similar tests are -" to be made for a ":return" from a function call or a ":finish" in a script -" file. -" -" In order to execute a function specifying an INTERRUPT location (see ExtraVim) -" as a script file, use ExecAsScript below. -" -" EXTRA_VIM_START - do not change or remove this line. -function MakeScript(funcname, ...) - let script = tempname() - execute "redir! >" . script - execute "function" a:funcname - redir END - execute "edit" script - " Delete the "function" and the "endfunction" lines. Do not include the - " word "function" in the pattern since it might be translated if LANG is - " set. When MakeScript() is being debugged, this deletes also the debugging - " output of its line 3 and 4. - exec '1,/.*' . a:funcname . '(.*)/d' - /^\d*\s*endfunction\>/,$d - %s/^\d*//e - %s/return/finish/e - %s/\<a:\(\h\w*\)/g:\1/ge - normal gg0 - let cnt = 0 - while search('\<call\s*\%(\u\|s:\)\w*\s*(.*)', 'W') > 0 - let cnt = cnt + 1 - s/\<call\s*\%(\u\|s:\)\w*\s*(.*)/\='source ' . a:{cnt}/ - endwhile - g/^\s*$/d - write - bwipeout - return script -endfunction -" EXTRA_VIM_STOP - do not change or remove this line. - - -" ExecAsScript - Source a temporary script made from a function. {{{2 -" -" Make a temporary script file from the function a:funcname, ":source" it, and -" delete it afterwards. -" -" When inside ":if ExtraVim()", add a file breakpoint for each INTERRUPT -" location specified in the function. -" -" EXTRA_VIM_START - do not change or remove this line. -function ExecAsScript(funcname) - " Make a script from the function passed as argument. - let script = MakeScript(a:funcname) - - " When running in an extra Vim process, add a file breakpoint for each - " function breakpoint set when the extra Vim process was invoked by - " ExtraVim(). - if exists("g:ExtraVimResult") - let bplist = tempname() - execute "redir! >" . bplist - breaklist - redir END - execute "edit" bplist - " Get the line number from the function breakpoint. Works also when - " LANG is set. - execute 'v/^\s*\d\+\s\+func\s\+' . a:funcname . '\s.*/d' - %s/^\s*\d\+\s\+func\s\+\%(\u\|s:\)\w*\s\D*\(\d*\).*/\1/e - let cnt = 0 - while cnt < line("$") - let cnt = cnt + 1 - if getline(cnt) != "" - execute "breakadd file" getline(cnt) script - endif - endwhile - bwipeout! - call delete(bplist) - endif - - " Source and delete the script. - exec "source" script - call delete(script) -endfunction - -com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) -" EXTRA_VIM_STOP - do not change or remove this line. - - -" END_OF_TEST_ENVIRONMENT - do not change or remove this line. - - -" Tests 1 to 17 were moved to test_vimscript.vim -let Xtest = 18 - -"------------------------------------------------------------------------------- -" Test 18: Interrupt (Ctrl-C pressed) {{{1 -" -" On an interrupt, the script processing is terminated immediately. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - if 1 - Xpath 1 " X: 1 - while 1 - Xpath 2 " X: 2 - if 1 - Xpath 4 " X: 4 - "INTERRUPT - Xpath 8 " X: 0 - break - finish - endif | Xpath 16 " X: 0 - Xpath 32 " X: 0 - endwhile | Xpath 64 " X: 0 - Xpath 128 " X: 0 - endif | Xpath 256 " X: 0 - Xpath 512 " X: 0 -endif - -if ExtraVim() - try - Xpath 1024 " X: 1024 - "INTERRUPT - Xpath 2048 " X: 0 - endtry | Xpath 4096 " X: 0 - Xpath 8192 " X: 0 -endif - -if ExtraVim() - function! F() - if 1 - Xpath 16384 " X: 16384 - while 1 - Xpath 32768 " X: 32768 - if 1 - Xpath 65536 " X: 65536 - "INTERRUPT - Xpath 131072 " X: 0 - break - return - endif | Xpath 262144 " X: 0 - Xpath Xpath 524288 " X: 0 - endwhile | Xpath 1048576 " X: 0 - Xpath Xpath 2097152 " X: 0 - endif | Xpath Xpath 4194304 " X: 0 - Xpath Xpath 8388608 " X: 0 - endfunction - - call F() | Xpath 16777216 " X: 0 - Xpath 33554432 " X: 0 -endif - -if ExtraVim() - function! G() - try - Xpath 67108864 " X: 67108864 - "INTERRUPT - Xpath 134217728 " X: 0 - endtry | Xpath 268435456 " X: 0 - Xpath 536870912 " X: 0 - endfunction - - call G() | Xpath 1073741824 " X: 0 - " The Xpath command does not accept 2^31 (negative); display explicitly: - exec "!echo 2147483648 >>" . g:ExtraVimResult - " X: 0 -endif - -Xcheck 67224583 - - -"------------------------------------------------------------------------------- -" Test 19: Aborting on errors inside :try/:endtry {{{1 -" -" An error in a command dynamically enclosed in a :try/:endtry region -" aborts script processing immediately. It does not matter whether -" the failing command is outside or inside a function and whether a -" function has an "abort" attribute. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - function! F() abort - Xpath 1 " X: 1 - asdf - Xpath 2 " X: 0 - endfunction - - try - Xpath 4 " X: 4 - call F() - Xpath 8 " X: 0 - endtry | Xpath 16 " X: 0 - Xpath 32 " X: 0 -endif - -if ExtraVim() - function! G() - Xpath 64 " X: 64 - asdf - Xpath 128 " X: 0 - endfunction - - try - Xpath 256 " X: 256 - call G() - Xpath 512 " X: 0 - endtry | Xpath 1024 " X: 0 - Xpath 2048 " X: 0 -endif - -if ExtraVim() - try - Xpath 4096 " X: 4096 - asdf - Xpath 8192 " X: 0 - endtry | Xpath 16384 " X: 0 - Xpath 32768 " X: 0 -endif - -if ExtraVim() - if 1 - try - Xpath 65536 " X: 65536 - asdf - Xpath 131072 " X: 0 - endtry | Xpath 262144 " X: 0 - endif | Xpath 524288 " X: 0 - Xpath 1048576 " X: 0 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 2097152 " X: 2097152 - asdf - Xpath 4194304 " X: 0 - endtry | Xpath 8388608 " X: 0 - endwhile | Xpath 16777216 " X: 0 - Xpath 33554432 " X: 0 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 -" try - Xpath 67108864 " X: 67108864 - endwhile | Xpath 134217728 " X: 0 - Xpath 268435456 " X: 0 -endif - -Xcheck 69275973 -"------------------------------------------------------------------------------- -" Test 20: Aborting on errors after :try/:endtry {{{1 -" -" When an error occurs after the last active :try/:endtry region has -" been left, termination behavior is as if no :try/:endtry has been -" seen. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 1 " X: 1 - endtry - asdf - endwhile | Xpath 2 " X: 0 - Xpath 4 " X: 4 -endif - -if ExtraVim() - while 1 - try - Xpath 8 " X: 8 - break - Xpath 16 " X: 0 - endtry - endwhile - Xpath 32 " X: 32 - asdf - Xpath 64 " X: 64 -endif - -if ExtraVim() - while 1 - try - Xpath 128 " X: 128 - break - Xpath 256 " X: 0 - finally - Xpath 512 " X: 512 - endtry - endwhile - Xpath 1024 " X: 1024 - asdf - Xpath 2048 " X: 2048 -endif - -if ExtraVim() - while 1 - try - Xpath 4096 " X: 4096 - finally - Xpath 8192 " X: 8192 - break - Xpath 16384 " X: 0 - endtry - endwhile - Xpath 32768 " X: 32768 - asdf - Xpath 65536 " X: 65536 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 131072 " X: 131072 - continue - Xpath 262144 " X: 0 - endtry - endwhile - Xpath 524288 " X: 524288 - asdf - Xpath 1048576 " X: 1048576 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 2097152 " X: 2097152 - continue - Xpath 4194304 " X: 0 - finally - Xpath 8388608 " X: 8388608 - endtry - endwhile - Xpath 16777216 " X: 16777216 - asdf - Xpath 33554432 " X: 33554432 -endif - -if ExtraVim() - let p = 1 - while p - let p = 0 - try - Xpath 67108864 " X: 67108864 - finally - Xpath 134217728 " X: 134217728 - continue - Xpath 268435456 " X: 0 - endtry - endwhile - Xpath 536870912 " X: 536870912 - asdf - Xpath 1073741824 " X: 1073741824 -endif - -Xcheck 1874575085 - - -"------------------------------------------------------------------------------- -" Test 21: :finally for :try after :continue/:break/:return/:finish {{{1 -" -" If a :try conditional stays inactive due to a preceding :continue, -" :break, :return, or :finish, its :finally clause should not be -" executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - function F() - let loops = 2 - XloopINIT! 1 256 - while loops > 0 - XloopNEXT - let loops = loops - 1 - try - if loops == 1 - Xloop 1 " X: 1 - continue - Xloop 2 " X: 0 - elseif loops == 0 - Xloop 4 " X: 4*256 - break - Xloop 8 " X: 0 - endif - - try " inactive - Xloop 16 " X: 0 - finally - Xloop 32 " X: 0 - endtry - finally - Xloop 64 " X: 64 + 64*256 - endtry - Xloop 128 " X: 0 - endwhile - - try - Xpath 65536 " X: 65536 - return - Xpath 131072 " X: 0 - try " inactive - Xpath 262144 " X: 0 - finally - Xpath 524288 " X: 0 - endtry - finally - Xpath 1048576 " X: 1048576 - endtry - Xpath 2097152 " X: 0 - endfunction - - try - Xpath 4194304 " X: 4194304 - call F() - Xpath 8388608 " X: 8388608 - finish - Xpath 16777216 " X: 0 - try " inactive - Xpath 33554432 " X: 0 - finally - Xpath 67108864 " X: 0 - endtry - finally - Xpath 134217728 " X: 134217728 - endtry - Xpath 268435456 " X: 0 -endif - -Xcheck 147932225 - - -"------------------------------------------------------------------------------- -" Test 22: :finally for a :try after an error/interrupt/:throw {{{1 -" -" If a :try conditional stays inactive due to a preceding error or -" interrupt or :throw, its :finally clause should not be executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - function! Error() - try - asdf " aborting error, triggering error exception - endtry - endfunction - - Xpath 1 " X: 1 - call Error() - Xpath 2 " X: 0 - - if 1 " not active due to error - try " not active since :if inactive - Xpath 4 " X: 0 - finally - Xpath 8 " X: 0 - endtry - endif - - try " not active due to error - Xpath 16 " X: 0 - finally - Xpath 32 " X: 0 - endtry -endif - -if ExtraVim() - function! Interrupt() - try - "INTERRUPT " triggering interrupt exception - endtry - endfunction - - Xpath 64 " X: 64 - call Interrupt() - Xpath 128 " X: 0 - - if 1 " not active due to interrupt - try " not active since :if inactive - Xpath 256 " X: 0 - finally - Xpath 512 " X: 0 - endtry - endif - - try " not active due to interrupt - Xpath 1024 " X: 0 - finally - Xpath 2048 " X: 0 - endtry -endif - -if ExtraVim() - function! Throw() - throw "xyz" - endfunction - - Xpath 4096 " X: 4096 - call Throw() - Xpath 8192 " X: 0 - - if 1 " not active due to :throw - try " not active since :if inactive - Xpath 16384 " X: 0 - finally - Xpath 32768 " X: 0 - endtry - endif - - try " not active due to :throw - Xpath 65536 " X: 0 - finally - Xpath 131072 " X: 0 - endtry -endif - -Xcheck 4161 - - -"------------------------------------------------------------------------------- -" Test 23: :catch clauses for a :try after a :throw {{{1 -" -" If a :try conditional stays inactive due to a preceding :throw, -" none of its :catch clauses should be executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try - Xpath 1 " X: 1 - throw "xyz" - Xpath 2 " X: 0 - - if 1 " not active due to :throw - try " not active since :if inactive - Xpath 4 " X: 0 - catch /xyz/ - Xpath 8 " X: 0 - endtry - endif - catch /xyz/ - Xpath 16 " X: 16 - endtry - - Xpath 32 " X: 32 - throw "abc" - Xpath 64 " X: 0 - - try " not active due to :throw - Xpath 128 " X: 0 - catch /abc/ - Xpath 256 " X: 0 - endtry -endif - -Xcheck 49 - - -"------------------------------------------------------------------------------- -" Test 24: :endtry for a :try after a :throw {{{1 -" -" If a :try conditional stays inactive due to a preceding :throw, -" its :endtry should not rethrow the exception to the next surrounding -" active :try conditional. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try " try 1 - try " try 2 - Xpath 1 " X: 1 - throw "xyz" " makes try 2 inactive - Xpath 2 " X: 0 - - try " try 3 - Xpath 4 " X: 0 - endtry " no rethrow to try 1 - catch /xyz/ " should catch although try 2 inactive - Xpath 8 " X: 8 - endtry - catch /xyz/ " try 1 active, but exception already caught - Xpath 16 " X: 0 - endtry - Xpath 32 " X: 32 -endif - -Xcheck 41 - -" Tests 25 and 26 were moved to test_trycatch.vim -let Xtest = 27 - - -"------------------------------------------------------------------------------- -" Test 27: Executing :finally clauses after :return {{{1 -" -" For a :return command dynamically enclosed in a :try/:endtry region, -" :finally clauses are executed and the called function is ended. -"------------------------------------------------------------------------------- - -XpathINIT - -function! F() - try - Xpath 1 " X: 1 - try - Xpath 2 " X: 2 - return - Xpath 4 " X: 0 - finally - Xpath 8 " X: 8 - endtry - Xpath 16 " X: 0 - finally - Xpath 32 " X: 32 - endtry - Xpath 64 " X: 0 -endfunction - -function! G() - try - Xpath 128 " X: 128 - return - Xpath 256 " X: 0 - finally - Xpath 512 " X: 512 - call F() - Xpath 1024 " X: 1024 - endtry - Xpath 2048 " X: 0 -endfunction - -function! H() - try - Xpath 4096 " X: 4096 - call G() - Xpath 8192 " X: 8192 - finally - Xpath 16384 " X: 16384 - return - Xpath 32768 " X: 0 - endtry - Xpath 65536 " X: 0 -endfunction - -try - Xpath 131072 " X: 131072 - call H() - Xpath 262144 " X: 262144 -finally - Xpath 524288 " X: 524288 -endtry -Xpath 1048576 " X: 1048576 - -Xcheck 1996459 - -" Leave F, G, and H for execution as scripts in the next test. - - -"------------------------------------------------------------------------------- -" Test 28: Executing :finally clauses after :finish {{{1 -" -" For a :finish command dynamically enclosed in a :try/:endtry region, -" :finally clauses are executed and the sourced file is finished. -" -" This test executes the bodies of the functions F, G, and H from the -" previous test as script files (:return replaced by :finish). -"------------------------------------------------------------------------------- - -XpathINIT - -let scriptF = MakeScript("F") " X: 1 + 2 + 8 + 32 -let scriptG = MakeScript("G", scriptF) " X: 128 + 512 + 1024 -let scriptH = MakeScript("H", scriptG) " X: 4096 + 8192 + 16384 - -try - Xpath 131072 " X: 131072 - exec "source" scriptH - Xpath 262144 " X: 262144 -finally - Xpath 524288 " X: 524288 -endtry -Xpath 1048576 " X: 1048576 - -call delete(scriptF) -call delete(scriptG) -call delete(scriptH) -unlet scriptF scriptG scriptH -delfunction F -delfunction G -delfunction H - -Xcheck 1996459 - - -"------------------------------------------------------------------------------- -" Test 29: Executing :finally clauses on errors {{{1 -" -" After an error in a command dynamically enclosed in a :try/:endtry -" region, :finally clauses are executed and the script processing is -" terminated. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - function! F() - while 1 - try - Xpath 1 " X: 1 - while 1 - try - Xpath 2 " X: 2 - asdf " error - Xpath 4 " X: 0 - finally - Xpath 8 " X: 8 - endtry | Xpath 16 " X: 0 - Xpath 32 " X: 0 - break - endwhile - Xpath 64 " X: 0 - finally - Xpath 128 " X: 128 - endtry | Xpath 256 " X: 0 - Xpath 512 " X: 0 - break - endwhile - Xpath 1024 " X: 0 - endfunction - - while 1 - try - Xpath 2048 " X: 2048 - while 1 - call F() - Xpath 4096 " X: 0 - break - endwhile | Xpath 8192 " X: 0 - Xpath 16384 " X: 0 - finally - Xpath 32768 " X: 32768 - endtry | Xpath 65536 " X: 0 - endwhile | Xpath 131072 " X: 0 - Xpath 262144 " X: 0 -endif - -if ExtraVim() - function! G() abort - if 1 - try - Xpath 524288 " X: 524288 - asdf " error - Xpath 1048576 " X: 0 - finally - Xpath 2097152 " X: 2097152 - endtry | Xpath 4194304 " X: 0 - endif | Xpath 8388608 " X: 0 - Xpath 16777216 " X: 0 - endfunction - - if 1 - try - Xpath 33554432 " X: 33554432 - call G() - Xpath 67108864 " X: 0 - finally - Xpath 134217728 " X: 134217728 - endtry | Xpath 268435456 " X: 0 - endif | Xpath 536870912 " X: 0 - Xpath 1073741824 " X: 0 -endif - -Xcheck 170428555 - - -"------------------------------------------------------------------------------- -" Test 30: Executing :finally clauses on interrupt {{{1 -" -" After an interrupt in a command dynamically enclosed in -" a :try/:endtry region, :finally clauses are executed and the -" script processing is terminated. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - XloopINIT 1 16 - - function! F() - try - Xloop 1 " X: 1 + 1*16 - "INTERRUPT - Xloop 2 " X: 0 - finally - Xloop 4 " X: 4 + 4*16 - endtry - Xloop 8 " X: 0 - endfunction - - try - Xpath 256 " X: 256 - try - Xpath 512 " X: 512 - "INTERRUPT - Xpath 1024 " X: 0 - finally - Xpath 2048 " X: 2048 - try - Xpath 4096 " X: 4096 - try - Xpath 8192 " X: 8192 - finally - Xpath 16384 " X: 16384 - try - Xpath 32768 " X: 32768 - "INTERRUPT - Xpath 65536 " X: 0 - endtry - Xpath 131072 " X: 0 - endtry - Xpath 262144 " X: 0 - endtry - Xpath 524288 " X: 0 - endtry - Xpath 1048576 " X: 0 - finally - Xpath 2097152 " X: 2097152 - try - Xpath 4194304 " X: 4194304 - call F() - Xpath 8388608 " X: 0 - finally - Xpath 16777216 " X: 16777216 - try - Xpath 33554432 " X: 33554432 - XloopNEXT - ExecAsScript F - Xpath 67108864 " X: 0 - finally - Xpath 134217728 " X: 134217728 - endtry - Xpath 268435456 " X: 0 - endtry - Xpath 536870912 " X: 0 - endtry - Xpath 1073741824 " X: 0 -endif - -Xcheck 190905173 - - -"------------------------------------------------------------------------------- -" Test 31: Executing :finally clauses after :throw {{{1 -" -" After a :throw dynamically enclosed in a :try/:endtry region, -" :finally clauses are executed and the script processing is -" terminated. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - XloopINIT 1 16 - - function! F() - try - Xloop 1 " X: 1 + 1*16 - throw "exception" - Xloop 2 " X: 0 - finally - Xloop 4 " X: 4 + 4*16 - endtry - Xloop 8 " X: 0 - endfunction - - try - Xpath 256 " X: 256 - try - Xpath 512 " X: 512 - throw "exception" - Xpath 1024 " X: 0 - finally - Xpath 2048 " X: 2048 - try - Xpath 4096 " X: 4096 - try - Xpath 8192 " X: 8192 - finally - Xpath 16384 " X: 16384 - try - Xpath 32768 " X: 32768 - throw "exception" - Xpath 65536 " X: 0 - endtry - Xpath 131072 " X: 0 - endtry - Xpath 262144 " X: 0 - endtry - Xpath 524288 " X: 0 - endtry - Xpath 1048576 " X: 0 - finally - Xpath 2097152 " X: 2097152 - try - Xpath 4194304 " X: 4194304 - call F() - Xpath 8388608 " X: 0 - finally - Xpath 16777216 " X: 16777216 - try - Xpath 33554432 " X: 33554432 - XloopNEXT - ExecAsScript F - Xpath 67108864 " X: 0 - finally - Xpath 134217728 " X: 134217728 - endtry - Xpath 268435456 " X: 0 - endtry - Xpath 536870912 " X: 0 - endtry - Xpath 1073741824 " X: 0 -endif - -Xcheck 190905173 - -" Tests 32 and 33 were moved to test_trycatch.vim -let Xtest = 34 - - -"------------------------------------------------------------------------------- -" Test 34: :finally reason discarded by :continue {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :continue in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 8 - - function! C(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - continue " discards jump that caused the :finally - Xloop 1 " X: 0 - endtry - Xloop 2 " X: 0 - elseif loop == 2 - Xloop 4 " X: 4*(1+8+64+512+4096+32768+262144) - endif - endwhile - endfunction - - call C("continue") - Xpath 2097152 " X: 2097152 - call C("break") - Xpath 4194304 " X: 4194304 - call C("return") - Xpath 8388608 " X: 8388608 - let g:jump = "finish" - ExecAsScript C - unlet g:jump - Xpath 16777216 " X: 16777216 - try - call C("error") - Xpath 33554432 " X: 33554432 - finally - Xpath 67108864 " X: 67108864 - try - call C("interrupt") - Xpath 134217728 " X: 134217728 - finally - Xpath 268435456 " X: 268435456 - call C("throw") - Xpath 536870912 " X: 536870912 - endtry - endtry - Xpath 1073741824 " X: 1073741824 - - delfunction C - -endif - -Xcheck 2146584868 - - -"------------------------------------------------------------------------------- -" Test 35: :finally reason discarded by :break {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :break in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 8 - - function! B(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - break " discards jump that caused the :finally - Xloop 1 " X: 0 - endtry - elseif loop == 2 - Xloop 2 " X: 0 - endif - endwhile - Xloop 4 " X: 4*(1+8+64+512+4096+32768+262144) - endfunction - - call B("continue") - Xpath 2097152 " X: 2097152 - call B("break") - Xpath 4194304 " X: 4194304 - call B("return") - Xpath 8388608 " X: 8388608 - let g:jump = "finish" - ExecAsScript B - unlet g:jump - Xpath 16777216 " X: 16777216 - try - call B("error") - Xpath 33554432 " X: 33554432 - finally - Xpath 67108864 " X: 67108864 - try - call B("interrupt") - Xpath 134217728 " X: 134217728 - finally - Xpath 268435456 " X: 268435456 - call B("throw") - Xpath 536870912 " X: 536870912 - endtry - endtry - Xpath 1073741824 " X: 1073741824 - - delfunction B - -endif - -Xcheck 2146584868 - - -"------------------------------------------------------------------------------- -" Test 36: :finally reason discarded by :return {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :return in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 8 - - function! R(jump, retval) abort - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - return a:retval " discards jump that caused the :finally - Xloop 1 " X: 0 - endtry - elseif loop == 2 - Xloop 2 " X: 0 - endif - endwhile - Xloop 4 " X: 0 - endfunction - - let sum = -R("continue", -8) - Xpath 2097152 " X: 2097152 - let sum = sum - R("break", -16) - Xpath 4194304 " X: 4194304 - let sum = sum - R("return", -32) - Xpath 8388608 " X: 8388608 - try - let sum = sum - R("error", -64) - Xpath 16777216 " X: 16777216 - finally - Xpath 33554432 " X: 33554432 - try - let sum = sum - R("interrupt", -128) - Xpath 67108864 " X: 67108864 - finally - Xpath 134217728 " X: 134217728 - let sum = sum - R("throw", -256) - Xpath 268435456 " X: 268435456 - endtry - endtry - Xpath 536870912 " X: 536870912 - - let expected = 8 + 16 + 32 + 64 + 128 + 256 - if sum != expected - Xpath 1073741824 " X: 0 - Xout "sum =" . sum . ", expected: " . expected - endif - - unlet sum expected - delfunction R - -endif - -Xcheck 1071644672 - - -"------------------------------------------------------------------------------- -" Test 37: :finally reason discarded by :finish {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :finish in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 8 - - function! F(jump) " not executed as function, transformed to a script - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "finish" - finish - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - finish " discards jump that caused the :finally - Xloop 1 " X: 0 - endtry - elseif loop == 2 - Xloop 2 " X: 0 - endif - endwhile - Xloop 4 " X: 0 - endfunction - - let scriptF = MakeScript("F") - delfunction F - - let g:jump = "continue" - exec "source" scriptF - Xpath 2097152 " X: 2097152 - let g:jump = "break" - exec "source" scriptF - Xpath 4194304 " X: 4194304 - let g:jump = "finish" - exec "source" scriptF - Xpath 8388608 " X: 8388608 - try - let g:jump = "error" - exec "source" scriptF - Xpath 16777216 " X: 16777216 - finally - Xpath 33554432 " X: 33554432 - try - let g:jump = "interrupt" - exec "source" scriptF - Xpath 67108864 " X: 67108864 - finally - Xpath 134217728 " X: 134217728 - try - let g:jump = "throw" - exec "source" scriptF - Xpath 268435456 " X: 268435456 - finally - Xpath 536870912 " X: 536870912 - endtry - endtry - endtry - unlet g:jump - - call delete(scriptF) - unlet scriptF - -endif - -Xcheck 1071644672 - - -"------------------------------------------------------------------------------- -" Test 38: :finally reason discarded by an error {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by an error in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 4 - - function! E(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - asdf " error; discards jump that caused the :finally - endtry - elseif loop == 2 - Xloop 1 " X: 0 - endif - endwhile - Xloop 2 " X: 0 - endfunction - - try - Xpath 16384 " X: 16384 - call E("continue") - Xpath 32768 " X: 0 - finally - try - Xpath 65536 " X: 65536 - call E("break") - Xpath 131072 " X: 0 - finally - try - Xpath 262144 " X: 262144 - call E("return") - Xpath 524288 " X: 0 - finally - try - Xpath 1048576 " X: 1048576 - let g:jump = "finish" - ExecAsScript E - Xpath 2097152 " X: 0 - finally - unlet g:jump - try - Xpath 4194304 " X: 4194304 - call E("error") - Xpath 8388608 " X: 0 - finally - try - Xpath 16777216 " X: 16777216 - call E("interrupt") - Xpath 33554432 " X: 0 - finally - try - Xpath 67108864 " X: 67108864 - call E("throw") - Xpath 134217728 " X: 0 - finally - Xpath 268435456 " X: 268435456 - delfunction E - endtry - endtry - endtry - endtry - endtry - endtry - endtry - Xpath 536870912 " X: 0 - -endif - -Xcheck 357908480 - - -"------------------------------------------------------------------------------- -" Test 39: :finally reason discarded by an interrupt {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by an interrupt in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 4 - - function! I(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - "INTERRUPT - discards jump that caused the :finally - let dummy = 0 - endtry - elseif loop == 2 - Xloop 1 " X: 0 - endif - endwhile - Xloop 2 " X: 0 - endfunction - - try - Xpath 16384 " X: 16384 - call I("continue") - Xpath 32768 " X: 0 - finally - try - Xpath 65536 " X: 65536 - call I("break") - Xpath 131072 " X: 0 - finally - try - Xpath 262144 " X: 262144 - call I("return") - Xpath 524288 " X: 0 - finally - try - Xpath 1048576 " X: 1048576 - let g:jump = "finish" - ExecAsScript I - Xpath 2097152 " X: 0 - finally - unlet g:jump - try - Xpath 4194304 " X: 4194304 - call I("error") - Xpath 8388608 " X: 0 - finally - try - Xpath 16777216 " X: 16777216 - call I("interrupt") - Xpath 33554432 " X: 0 - finally - try - Xpath 67108864 " X: 67108864 - call I("throw") - Xpath 134217728 " X: 0 - finally - Xpath 268435456 " X: 268435456 - delfunction I - endtry - endtry - endtry - endtry - endtry - endtry - endtry - Xpath 536870912 " X: 0 - -endif - -Xcheck 357908480 - - -"------------------------------------------------------------------------------- -" Test 40: :finally reason discarded by :throw {{{1 -" -" When a :finally clause is executed due to a :continue, :break, -" :return, :finish, error, interrupt or :throw, the jump reason is -" discarded by a :throw in the finally clause. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 4 - - function! T(jump) - XloopNEXT - let loop = 0 - while loop < 2 - let loop = loop + 1 - if loop == 1 - try - if a:jump == "continue" - continue - elseif a:jump == "break" - break - elseif a:jump == "return" || a:jump == "finish" - return - elseif a:jump == "error" - asdf - elseif a:jump == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:jump == "throw" - throw "abc" - endif - finally - throw "xyz" " discards jump that caused the :finally - endtry - elseif loop == 2 - Xloop 1 " X: 0 - endif - endwhile - Xloop 2 " X: 0 - endfunction - - try - Xpath 16384 " X: 16384 - call T("continue") - Xpath 32768 " X: 0 - finally - try - Xpath 65536 " X: 65536 - call T("break") - Xpath 131072 " X: 0 - finally - try - Xpath 262144 " X: 262144 - call T("return") - Xpath 524288 " X: 0 - finally - try - Xpath 1048576 " X: 1048576 - let g:jump = "finish" - ExecAsScript T - Xpath 2097152 " X: 0 - finally - unlet g:jump - try - Xpath 4194304 " X: 4194304 - call T("error") - Xpath 8388608 " X: 0 - finally - try - Xpath 16777216 " X: 16777216 - call T("interrupt") - Xpath 33554432 " X: 0 - finally - try - Xpath 67108864 " X: 67108864 - call T("throw") - Xpath 134217728 " X: 0 - finally - Xpath 268435456 " X: 268435456 - delfunction T - endtry - endtry - endtry - endtry - endtry - endtry - endtry - Xpath 536870912 " X: 0 - -endif - -Xcheck 357908480 - -" Tests 41 to 48 were moved to test_trycatch.vim -let Xtest = 49 - - -"------------------------------------------------------------------------------- -" Test 49: Throwing exceptions across functions {{{1 -" -" When an exception is thrown but not caught inside a function, the -" caller is checked for a matching :catch clause. -"------------------------------------------------------------------------------- - -XpathINIT - -function! C() - try - Xpath 1 " X: 1 - throw "arrgh" - Xpath 2 " X: 0 - catch /arrgh/ - Xpath 4 " X: 4 - endtry - Xpath 8 " X: 8 -endfunction - -XloopINIT! 16 16 - -function! T1() - XloopNEXT - try - Xloop 1 " X: 16 + 16*16 - throw "arrgh" - Xloop 2 " X: 0 - finally - Xloop 4 " X: 64 + 64*16 - endtry - Xloop 8 " X: 0 -endfunction - -function! T2() - try - Xpath 4096 " X: 4096 - call T1() - Xpath 8192 " X: 0 - finally - Xpath 16384 " X: 16384 - endtry - Xpath 32768 " X: 0 -endfunction - -try - Xpath 65536 " X: 65536 - call C() " throw and catch - Xpath 131072 " X: 131072 -catch /.*/ - Xpath 262144 " X: 0 - Xout v:exception "in" v:throwpoint -endtry - -try - Xpath 524288 " X: 524288 - call T1() " throw, one level - Xpath 1048576 " X: 0 -catch /arrgh/ - Xpath 2097152 " X: 2097152 -catch /.*/ - Xpath 4194304 " X: 0 - Xout v:exception "in" v:throwpoint -endtry - -try - Xpath 8388608 " X: 8388608 - call T2() " throw, two levels - Xpath 16777216 " X: 0 -catch /arrgh/ - Xpath 33554432 " X: 33554432 -catch /.*/ - Xpath 67108864 " X: 0 - Xout v:exception "in" v:throwpoint -endtry -Xpath 134217728 " X: 134217728 - -Xcheck 179000669 - -" Leave C, T1, and T2 for execution as scripts in the next test. - - -"------------------------------------------------------------------------------- -" Test 50: Throwing exceptions across script files {{{1 -" -" When an exception is thrown but not caught inside a script file, -" the sourcing script or function is checked for a matching :catch -" clause. -" -" This test executes the bodies of the functions C, T1, and T2 from -" the previous test as script files (:return replaced by :finish). -"------------------------------------------------------------------------------- - -XpathINIT - -let scriptC = MakeScript("C") " X: 1 + 4 + 8 -delfunction C - -XloopINIT! 16 16 - -let scriptT1 = MakeScript("T1") " X: 16 + 64 + 16*16 + 64*16 -delfunction T1 - -let scriptT2 = MakeScript("T2", scriptT1) " X: 4096 + 16384 -delfunction T2 - -function! F() - try - Xpath 65536 " X: 65536 - exec "source" g:scriptC - Xpath 131072 " X: 131072 - catch /.*/ - Xpath 262144 " X: 0 - Xout v:exception "in" v:throwpoint - endtry - - try - Xpath 524288 " X: 524288 - exec "source" g:scriptT1 - Xpath 1048576 " X: 0 - catch /arrgh/ - Xpath 2097152 " X: 2097152 - catch /.*/ - Xpath 4194304 " X: 0 - Xout v:exception "in" v:throwpoint - endtry -endfunction - -try - Xpath 8388608 " X: 8388608 - call F() - Xpath 16777216 " X: 16777216 - exec "source" scriptT2 - Xpath 33554432 " X: 0 -catch /arrgh/ - Xpath 67108864 " X: 67108864 -catch /.*/ - Xpath 134217728 " X: 0 - Xout v:exception "in" v:throwpoint -endtry -Xpath 268435456 " X: 268435456 - -call delete(scriptC) -call delete(scriptT1) -call delete(scriptT2) -unlet scriptC scriptT1 scriptT2 -delfunction F - -Xcheck 363550045 - -" Test 51 was moved to test_trycatch.vim -let Xtest = 52 - - -"------------------------------------------------------------------------------- -" Test 52: Uncaught exceptions {{{1 -" -" When an exception is thrown but not caught, an error message is -" displayed when the script is terminated. In case of an interrupt -" or error exception, the normal interrupt or error message(s) are -" displayed. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -function! MESSAGES(...) - try - exec "edit" g:msgfile - catch /^Vim(edit):/ - return 0 - endtry - - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - let match = 1 - norm gg - - let num = a:0 / 2 - let cnt = 1 - while cnt <= num - let enr = a:{2*cnt - 1} - let emsg= a:{2*cnt} - let cnt = cnt + 1 - - if enr == "" - Xout "TODO: Add message number for:" emsg - elseif enr == "INT" - let enr = "" - endif - if enr == "" && !english - continue - endif - let pattern = (enr != "") ? enr . ':.*' : '' - if english - let pattern = pattern . emsg - endif - if !search(pattern, "W") - let match = 0 - Xout "No match for:" pattern - endif - norm $ - endwhile - - bwipeout! - return match -endfunction - -if ExtraVim(msgfile) - Xpath 1 " X: 1 - throw "arrgh" -endif - -Xpath 2 " X: 2 -if !MESSAGES('E605', "Exception not caught") - Xpath 4 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 8 " X: 8 - throw "oops" - catch /arrgh/ - Xpath 16 " X: 0 - endtry - Xpath 32 " X: 0 -endif - -Xpath 64 " X: 64 -if !MESSAGES('E605', "Exception not caught") - Xpath 128 " X: 0 -endif - -if ExtraVim(msgfile) - function! T() - throw "brrr" - endfunction - - try - Xpath 256 " X: 256 - throw "arrgh" - catch /.*/ - Xpath 512 " X: 512 - call T() - endtry - Xpath 1024 " X: 0 -endif - -Xpath 2048 " X: 2048 -if !MESSAGES('E605', "Exception not caught") - Xpath 4096 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 8192 " X: 8192 - throw "arrgh" - finally - Xpath 16384 " X: 16384 - throw "brrr" - endtry - Xpath 32768 " X: 0 -endif - -Xpath 65536 " X: 65536 -if !MESSAGES('E605', "Exception not caught") - Xpath 131072 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 262144 " X: 262144 - "INTERRUPT - endtry - Xpath 524288 " X: 0 -endif - -Xpath 1048576 " X: 1048576 -if !MESSAGES('INT', "Interrupted") - Xpath 2097152 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 4194304 " X: 4194304 - let x = novar " error E121/E15; exception: E121 - catch /E15:/ " should not catch - Xpath 8388608 " X: 0 - endtry - Xpath 16777216 " X: 0 -endif - -Xpath 33554432 " X: 33554432 -if !MESSAGES('E121', "Undefined variable", 'E15', "Invalid expression") - Xpath 67108864 " X: 0 -endif - -if ExtraVim(msgfile) - try - Xpath 134217728 " X: 134217728 -" unlet novar # " error E108/E488; exception: E488 - catch /E108:/ " should not catch - Xpath 268435456 " X: 0 - endtry - Xpath 536870912 " X: 0 -endif - -Xpath 1073741824 " X: 1073741824 -if !MESSAGES('E108', "No such variable", 'E488', "Trailing characters") - " The Xpath command does not accept 2^31 (negative); add explicitly: - let Xpath = Xpath + 2147483648 " X: 0 -endif - -call delete(msgfile) -unlet msgfile - -Xcheck 1247112011 - -" Leave MESSAGES() for the next tests. - - -"------------------------------------------------------------------------------- -" Test 53: Nesting errors: :endif/:else/:elseif {{{1 -" -" For nesting errors of :if conditionals the correct error messages -" should be given. -" -" This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -if ExtraVim(msgfile) -" endif -endif -if MESSAGES('E580', ":endif without :if") - Xpath 1 " X: 1 -endif - -if ExtraVim(msgfile) -" while 1 -" endif -" endwhile -endif -if MESSAGES('E580', ":endif without :if") - Xpath 2 " X: 2 -endif - -if ExtraVim(msgfile) -" try -" finally -" endif -" endtry -endif -if MESSAGES('E580', ":endif without :if") - Xpath 4 " X: 4 -endif - -if ExtraVim(msgfile) -" try -" endif -" endtry -endif -if MESSAGES('E580', ":endif without :if") - Xpath 8 " X: 8 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" endif -" endtry -endif -if MESSAGES('E580', ":endif without :if") - Xpath 16 " X: 16 -endif - -if ExtraVim(msgfile) -" else -endif -if MESSAGES('E581', ":else without :if") - Xpath 32 " X: 32 -endif - -if ExtraVim(msgfile) -" while 1 -" else -" endwhile -endif -if MESSAGES('E581', ":else without :if") - Xpath 64 " X: 64 -endif - -if ExtraVim(msgfile) -" try -" finally -" else -" endtry -endif -if MESSAGES('E581', ":else without :if") - Xpath 128 " X: 128 -endif - -if ExtraVim(msgfile) -" try -" else -" endtry -endif -if MESSAGES('E581', ":else without :if") - Xpath 256 " X: 256 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" else -" endtry -endif -if MESSAGES('E581', ":else without :if") - Xpath 512 " X: 512 -endif - -if ExtraVim(msgfile) -" elseif -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 1024 " X: 1024 -endif - -if ExtraVim(msgfile) -" while 1 -" elseif -" endwhile -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 2048 " X: 2048 -endif - -if ExtraVim(msgfile) -" try -" finally -" elseif -" endtry -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 4096 " X: 4096 -endif - -if ExtraVim(msgfile) -" try -" elseif -" endtry -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 8192 " X: 8192 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" elseif -" endtry -endif -if MESSAGES('E582', ":elseif without :if") - Xpath 16384 " X: 16384 -endif - -if ExtraVim(msgfile) -" if 1 -" else -" else -" endif -endif -if MESSAGES('E583', "multiple :else") - Xpath 32768 " X: 32768 -endif - -if ExtraVim(msgfile) -" if 1 -" else -" elseif 1 -" endif -endif -if MESSAGES('E584', ":elseif after :else") - Xpath 65536 " X: 65536 -endif - -call delete(msgfile) -unlet msgfile - -Xcheck 131071 - -" Leave MESSAGES() for the next test. - - -"------------------------------------------------------------------------------- -" Test 54: Nesting errors: :while/:endwhile {{{1 -" -" For nesting errors of :while conditionals the correct error messages -" should be given. -" -" This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -if ExtraVim(msgfile) -" endwhile -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 1 " X: 1 -endif - -if ExtraVim(msgfile) -" if 1 -" endwhile -" endif -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 2 " X: 2 -endif - -if ExtraVim(msgfile) -" while 1 -" if 1 -" endwhile -endif -if MESSAGES('E171', "Missing :endif") - Xpath 4 " X: 4 -endif - -if ExtraVim(msgfile) -" try -" finally -" endwhile -" endtry -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 8 " X: 8 -endif - -if ExtraVim(msgfile) -" while 1 -" try -" finally -" endwhile -endif -if MESSAGES('E600', "Missing :endtry") - Xpath 16 " X: 16 -endif - -if ExtraVim(msgfile) -" while 1 -" if 1 -" try -" finally -" endwhile -endif -if MESSAGES('E600', "Missing :endtry") - Xpath 32 " X: 32 -endif - -if ExtraVim(msgfile) -" while 1 -" try -" finally -" if 1 -" endwhile -endif -if MESSAGES('E171', "Missing :endif") - Xpath 64 " X: 64 -endif - -if ExtraVim(msgfile) -" try -" endwhile -" endtry -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 128 " X: 128 -endif - -if ExtraVim(msgfile) -" while 1 -" try -" endwhile -" endtry -" endwhile -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 256 " X: 256 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" endwhile -" endtry -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 512 " X: 512 -endif - -if ExtraVim(msgfile) -" while 1 -" try -" throw "a" -" catch /a/ -" endwhile -" endtry -" endwhile -endif -if MESSAGES('E588', ":endwhile without :while") - Xpath 1024 " X: 1024 -endif - - -call delete(msgfile) -unlet msgfile - -Xcheck 2047 - -" Leave MESSAGES() for the next test. - - -"------------------------------------------------------------------------------- -" Test 55: Nesting errors: :continue/:break {{{1 -" -" For nesting errors of :continue and :break commands the correct -" error messages should be given. -" -" This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -if ExtraVim(msgfile) -" continue -endif -if MESSAGES('E586', ":continue without :while") - Xpath 1 " X: 1 -endif - -if ExtraVim(msgfile) -" if 1 -" continue -" endif -endif -if MESSAGES('E586', ":continue without :while") - Xpath 2 " X: 2 -endif - -if ExtraVim(msgfile) -" try -" finally -" continue -" endtry -endif -if MESSAGES('E586', ":continue without :while") - Xpath 4 " X: 4 -endif - -if ExtraVim(msgfile) -" try -" continue -" endtry -endif -if MESSAGES('E586', ":continue without :while") - Xpath 8 " X: 8 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" continue -" endtry -endif -if MESSAGES('E586', ":continue without :while") - Xpath 16 " X: 16 -endif - -if ExtraVim(msgfile) -" break -endif -if MESSAGES('E587', ":break without :while") - Xpath 32 " X: 32 -endif - -if ExtraVim(msgfile) -" if 1 -" break -" endif -endif -if MESSAGES('E587', ":break without :while") - Xpath 64 " X: 64 -endif - -if ExtraVim(msgfile) -" try -" finally -" break -" endtry -endif -if MESSAGES('E587', ":break without :while") - Xpath 128 " X: 128 -endif - -if ExtraVim(msgfile) -" try -" break -" endtry -endif -if MESSAGES('E587', ":break without :while") - Xpath 256 " X: 256 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" break -" endtry -endif -if MESSAGES('E587', ":break without :while") - Xpath 512 " X: 512 -endif - -call delete(msgfile) -unlet msgfile - -Xcheck 1023 - -" Leave MESSAGES() for the next test. - - -"------------------------------------------------------------------------------- -" Test 56: Nesting errors: :endtry {{{1 -" -" For nesting errors of :try conditionals the correct error messages -" should be given. -" -" This test reuses the function MESSAGES() from the previous test. -" This functions checks the messages in g:msgfile. -"------------------------------------------------------------------------------- - -XpathINIT - -let msgfile = tempname() - -if ExtraVim(msgfile) -" endtry -endif -if MESSAGES('E602', ":endtry without :try") - Xpath 1 " X: 1 -endif - -if ExtraVim(msgfile) -" if 1 -" endtry -" endif -endif -if MESSAGES('E602', ":endtry without :try") - Xpath 2 " X: 2 -endif - -if ExtraVim(msgfile) -" while 1 -" endtry -" endwhile -endif -if MESSAGES('E602', ":endtry without :try") - Xpath 4 " X: 4 -endif - -if ExtraVim(msgfile) -" try -" if 1 -" endtry -endif -if MESSAGES('E171', "Missing :endif") - Xpath 8 " X: 8 -endif - -if ExtraVim(msgfile) -" try -" while 1 -" endtry -endif -if MESSAGES('E170', "Missing :endwhile") - Xpath 16 " X: 16 -endif - -if ExtraVim(msgfile) -" try -" finally -" if 1 -" endtry -endif -if MESSAGES('E171', "Missing :endif") - Xpath 32 " X: 32 -endif - -if ExtraVim(msgfile) -" try -" finally -" while 1 -" endtry -endif -if MESSAGES('E170', "Missing :endwhile") - Xpath 64 " X: 64 -endif - -if ExtraVim(msgfile) - try - Xpath 4194304 " X: 4194304 - let x = novar " error E121; exception: E121 - catch /E15:/ " should not catch - Xpath 8388608 " X: 0 - endtry - Xpath 16777216 " X: 0 -endif - -Xpath 33554432 " X: 33554432 -if !MESSAGES('E121', "Undefined variable") - Xpath 67108864 " X: 0 -endif - -if ExtraVim(msgfile) -" try -" throw "a" -" catch /a/ -" while 1 -" endtry -endif -if MESSAGES('E170', "Missing :endwhile") - Xpath 256 " X: 256 -endif - -call delete(msgfile) -unlet msgfile - -delfunction MESSAGES - -Xcheck 511 - - -"------------------------------------------------------------------------------- -" Test 57: v:exception and v:throwpoint for user exceptions {{{1 -" -" v:exception evaluates to the value of the exception that was caught -" most recently and is not finished. (A caught exception is finished -" when the next ":catch", ":finally", or ":endtry" is reached.) -" v:throwpoint evaluates to the script/function name and line number -" where that exception has been thrown. -"------------------------------------------------------------------------------- - -XpathINIT - -function! FuncException() - let g:exception = v:exception -endfunction - -function! FuncThrowpoint() - let g:throwpoint = v:throwpoint -endfunction - -let scriptException = MakeScript("FuncException") -let scriptThrowPoint = MakeScript("FuncThrowpoint") - -command! CmdException let g:exception = v:exception -command! CmdThrowpoint let g:throwpoint = v:throwpoint - -XloopINIT! 1 2 - -function! CHECK(n, exception, throwname, throwline) - XloopNEXT - let error = 0 - if v:exception != a:exception - Xout a:n.": v:exception is" v:exception "instead of" a:exception - let error = 1 - endif - if v:throwpoint !~ a:throwname - let name = escape(a:throwname, '\') - Xout a:n.": v:throwpoint (".v:throwpoint.") does not match" name - let error = 1 - endif - if v:throwpoint !~ a:throwline - let line = escape(a:throwline, '\') - Xout a:n.": v:throwpoint (".v:throwpoint.") does not match" line - let error = 1 - endif - if error - Xloop 1 " X: 0 - endif -endfunction - -function! T(arg, line) - if a:line == 2 - throw a:arg " in line 2 - elseif a:line == 4 - throw a:arg " in line 4 - elseif a:line == 6 - throw a:arg " in line 6 - elseif a:line == 8 - throw a:arg " in line 8 - endif -endfunction - -function! G(arg, line) - call T(a:arg, a:line) -endfunction - -function! F(arg, line) - call G(a:arg, a:line) -endfunction - -let scriptT = MakeScript("T") -let scriptG = MakeScript("G", scriptT) -let scriptF = MakeScript("F", scriptG) - -try - Xpath 32768 " X: 32768 - call F("oops", 2) -catch /.*/ - Xpath 65536 " X: 65536 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(1, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - exec "let exception = v:exception" - exec "let throwpoint = v:throwpoint" - call CHECK(2, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - CmdException - CmdThrowpoint - call CHECK(3, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - call FuncException() - call FuncThrowpoint() - call CHECK(4, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - exec "source" scriptException - exec "source" scriptThrowPoint - call CHECK(5, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - try - Xpath 131072 " X: 131072 - call G("arrgh", 4) - catch /.*/ - Xpath 262144 " X: 262144 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(6, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - try - Xpath 524288 " X: 524288 - let g:arg = "autsch" - let g:line = 6 - exec "source" scriptF - catch /.*/ - Xpath 1048576 " X: 1048576 - let exception = v:exception - let throwpoint = v:throwpoint - " Symbolic links in tempname()s are not resolved, whereas resolving - " is done for v:throwpoint. Resolve the temporary file name for - " scriptT, so that it can be matched against v:throwpoint. - call CHECK(7, "autsch", resolve(scriptT), '\<6\>') - finally - Xpath 2097152 " X: 2097152 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(8, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - try - Xpath 4194304 " X: 4194304 - let g:arg = "brrrr" - let g:line = 8 - exec "source" scriptG - catch /.*/ - Xpath 8388608 " X: 8388608 - let exception = v:exception - let throwpoint = v:throwpoint - " Resolve scriptT for matching it against v:throwpoint. - call CHECK(9, "brrrr", resolve(scriptT), '\<8\>') - finally - Xpath 16777216 " X: 16777216 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(10, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - endtry - Xpath 33554432 " X: 33554432 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(11, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - endtry - Xpath 67108864 " X: 67108864 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(12, "arrgh", '\<G\[1]\.\.T\>', '\<4\>') - finally - Xpath 134217728 " X: 134217728 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(13, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') - endtry - Xpath 268435456 " X: 268435456 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(14, "oops", '\<F\[1]\.\.G\[1]\.\.T\>', '\<2\>') -finally - Xpath 536870912 " X: 536870912 - let exception = v:exception - let throwpoint = v:throwpoint - call CHECK(15, "", '^$', '^$') -endtry - -Xpath 1073741824 " X: 1073741824 - -unlet exception throwpoint -delfunction FuncException -delfunction FuncThrowpoint -call delete(scriptException) -call delete(scriptThrowPoint) -unlet scriptException scriptThrowPoint -delcommand CmdException -delcommand CmdThrowpoint -delfunction T -delfunction G -delfunction F -call delete(scriptT) -call delete(scriptG) -call delete(scriptF) -unlet scriptT scriptG scriptF - -Xcheck 2147450880 - - -"------------------------------------------------------------------------------- -" -" Test 58: v:exception and v:throwpoint for error/interrupt exceptions {{{1 -" -" v:exception and v:throwpoint work also for error and interrupt -" exceptions. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - function! T(line) - if a:line == 2 - delfunction T " error (function in use) in line 2 - elseif a:line == 4 - let dummy = 0 " INTERRUPT1 - interrupt in line 4 - endif - endfunction - - while 1 - try - Xpath 1 " X: 1 - let caught = 0 - call T(2) - catch /.*/ - let caught = 1 - if v:exception !~ 'Vim(delfunction):' - Xpath 2 " X: 0 - endif - if v:throwpoint !~ '\<T\>' - Xpath 4 " X: 0 - endif - if v:throwpoint !~ '\<2\>' - Xpath 8 " X: 0 - endif - finally - Xpath 16 " X: 16 - if caught || $VIMNOERRTHROW - Xpath 32 " X: 32 - endif - if v:exception != "" - Xpath 64 " X: 0 - endif - if v:throwpoint != "" - Xpath 128 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - Xpath 256 " X: 256 - if v:exception != "" - Xpath 512 " X: 0 - endif - if v:throwpoint != "" - Xpath 1024 " X: 0 - endif - - while 1 - try - Xpath 2048 " X: 2048 - let caught = 0 - call T(4) - catch /.*/ - let caught = 1 - if v:exception != 'Vim:Interrupt' - Xpath 4096 " X: 0 - endif - if v:throwpoint !~ '\<T\>' - Xpath 8192 " X: 0 - endif - if v:throwpoint !~ '\<4\>' - Xpath 16384 " X: 0 - endif - finally - Xpath 32768 " X: 32768 - if caught || $VIMNOINTTHROW - Xpath 65536 " X: 65536 - endif - if v:exception != "" - Xpath 131072 " X: 0 - endif - if v:throwpoint != "" - Xpath 262144 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - Xpath 524288 " X: 524288 - if v:exception != "" - Xpath 1048576 " X: 0 - endif - if v:throwpoint != "" - Xpath 2097152 " X: 0 - endif - -endif - -Xcheck 624945 - - -"------------------------------------------------------------------------------- -" -" Test 59: v:exception and v:throwpoint when discarding exceptions {{{1 -" -" When a :catch clause is left by a ":break" etc or an error or -" interrupt exception, v:exception and v:throwpoint are reset. They -" are not affected by an exception that is discarded before being -" caught. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - XloopINIT! 1 2 - - let sfile = expand("<sfile>") - - function! LineNumber() - return substitute(substitute(v:throwpoint, g:sfile, '', ""), - \ '\D*\(\d*\).*', '\1', "") - endfunction - - command! -nargs=1 SetLineNumber - \ try | throw "line" | catch /.*/ | let <args> = LineNumber() | endtry - - " Check v:exception/v:throwpoint against second/fourth parameter if - " specified, check for being empty else. - function! CHECK(n, ...) - XloopNEXT - let exception = a:0 != 0 ? a:1 : "" " second parameter (optional) - let emsg = a:0 != 0 ? a:2 : "" " third parameter (optional) - let line = a:0 != 0 ? a:3 : 0 " fourth parameter (optional) - let error = 0 - if emsg != "" - " exception is the error number, emsg the English error message text - if exception !~ '^E\d\+$' - Xout "TODO: Add message number for:" emsg - elseif v:lang == "C" || v:lang =~ '^[Ee]n' - if exception == "E492" && emsg == "Not an editor command" - let exception = '^Vim:' . exception . ': ' . emsg - else - let exception = '^Vim(\a\+):' . exception . ': ' . emsg - endif - else - if exception == "E492" - let exception = '^Vim:' . exception - else - let exception = '^Vim(\a\+):' . exception - endif - endif - endif - if exception == "" && v:exception != "" - Xout a:n.": v:exception is set:" v:exception - let error = 1 - elseif exception != "" && v:exception !~ exception - Xout a:n.": v:exception (".v:exception.") does not match" exception - let error = 1 - endif - if line == 0 && v:throwpoint != "" - Xout a:n.": v:throwpoint is set:" v:throwpoint - let error = 1 - elseif line != 0 && v:throwpoint !~ '\<' . line . '\>' - Xout a:n.": v:throwpoint (".v:throwpoint.") does not match" line - let error = 1 - endif - if !error - Xloop 1 " X: 2097151 - endif - endfunction - - while 1 - try - throw "x1" - catch /.*/ - break - endtry - endwhile - call CHECK(1) - - while 1 - try - throw "x2" - catch /.*/ - break - finally - call CHECK(2) - endtry - break - endwhile - call CHECK(3) - - while 1 - try - let errcaught = 0 - try - try - throw "x3" - catch /.*/ - SetLineNumber line_before_error - asdf - endtry - catch /.*/ - let errcaught = 1 - call CHECK(4, 'E492', "Not an editor command", - \ line_before_error + 1) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(4) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(5) - - Xpath 2097152 " X: 2097152 - - while 1 - try - let intcaught = 0 - try - try - throw "x4" - catch /.*/ - SetLineNumber two_lines_before_interrupt - "INTERRUPT - let dummy = 0 - endtry - catch /.*/ - let intcaught = 1 - call CHECK(6, "Vim:Interrupt", '', - \ two_lines_before_interrupt + 2) - endtry - finally - if !intcaught && $VIMNOINTTHROW - call CHECK(6) - endif - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - call CHECK(7) - - Xpath 4194304 " X: 4194304 - - while 1 - try - let errcaught = 0 - try - try -" if 1 - SetLineNumber line_before_throw - throw "x5" - " missing endif - catch /.*/ - Xpath 8388608 " X: 0 - endtry - catch /.*/ - let errcaught = 1 - call CHECK(8, 'E171', "Missing :endif", line_before_throw + 3) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(8) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(9) - - Xpath 16777216 " X: 16777216 - - try - while 1 - try - throw "x6" - finally - break - endtry - break - endwhile - catch /.*/ - Xpath 33554432 " X: 0 - endtry - call CHECK(10) - - try - while 1 - try - throw "x7" - finally - break - endtry - break - endwhile - catch /.*/ - Xpath 67108864 " X: 0 - finally - call CHECK(11) - endtry - call CHECK(12) - - while 1 - try - let errcaught = 0 - try - try - throw "x8" - finally - SetLineNumber line_before_error - asdf - endtry - catch /.*/ - let errcaught = 1 - call CHECK(13, 'E492', "Not an editor command", - \ line_before_error + 1) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(13) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(14) - - Xpath 134217728 " X: 134217728 - - while 1 - try - let intcaught = 0 - try - try - throw "x9" - finally - SetLineNumber two_lines_before_interrupt - "INTERRUPT - endtry - catch /.*/ - let intcaught = 1 - call CHECK(15, "Vim:Interrupt", '', - \ two_lines_before_interrupt + 2) - endtry - finally - if !intcaught && $VIMNOINTTHROW - call CHECK(15) - endif - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - call CHECK(16) - - Xpath 268435456 " X: 268435456 - - while 1 - try - let errcaught = 0 - try - try -" if 1 - SetLineNumber line_before_throw - throw "x10" - " missing endif - finally - call CHECK(17) - endtry - catch /.*/ - let errcaught = 1 - call CHECK(18, 'E171', "Missing :endif", line_before_throw + 3) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(18) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(19) - - Xpath 536870912 " X: 536870912 - - while 1 - try - let errcaught = 0 - try - try -" if 1 - SetLineNumber line_before_throw - throw "x11" - " missing endif - endtry - catch /.*/ - let errcaught = 1 - call CHECK(20, 'E171', "Missing :endif", line_before_throw + 3) - endtry - finally - if !errcaught && $VIMNOERRTHROW - call CHECK(20) - endif - break " discard error for $VIMNOERRTHROW - endtry - endwhile - call CHECK(21) - - Xpath 1073741824 " X: 1073741824 - -endif - -Xcheck 2038431743 - - -"------------------------------------------------------------------------------- -" -" Test 60: (Re)throwing v:exception; :echoerr. {{{1 -" -" A user exception can be rethrown after catching by throwing -" v:exception. An error or interrupt exception cannot be rethrown -" because Vim exceptions cannot be faked. A Vim exception using the -" value of v:exception can, however, be triggered by the :echoerr -" command. -"------------------------------------------------------------------------------- - -XpathINIT - -try - try - Xpath 1 " X: 1 - throw "oops" - catch /oops/ - Xpath 2 " X: 2 - throw v:exception " rethrow user exception - catch /.*/ - Xpath 4 " X: 0 - endtry -catch /^oops$/ " catches rethrown user exception - Xpath 8 " X: 8 -catch /.*/ - Xpath 16 " X: 0 -endtry - -function! F() - try - let caught = 0 - try - Xpath 32 " X: 32 - write /n/o/n/w/r/i/t/a/b/l/e/_/f/i/l/e - Xpath 64 " X: 0 - Xout "did_emsg was reset before executing " . - \ "BufWritePost autocommands." - catch /^Vim(write):/ - let caught = 1 - throw v:exception " throw error: cannot fake Vim exception - catch /.*/ - Xpath 128 " X: 0 - finally - Xpath 256 " X: 256 - if !caught && !$VIMNOERRTHROW - Xpath 512 " X: 0 - endif - endtry - catch /^Vim(throw):/ " catches throw error - let caught = caught + 1 - catch /.*/ - Xpath 1024 " X: 0 - finally - Xpath 2048 " X: 2048 - if caught != 2 - if !caught && !$VIMNOERRTHROW - Xpath 4096 " X: 0 - elseif caught - Xpath 8192 " X: 0 - endif - return | " discard error for $VIMNOERRTHROW - endif - endtry -endfunction - -call F() -delfunction F - -function! G() - try - let caught = 0 - try - Xpath 16384 " X: 16384 - asdf - catch /^Vim/ " catch error exception - let caught = 1 - " Trigger Vim error exception with value specified after :echoerr - let value = substitute(v:exception, '^Vim\((.*)\)\=:', '', "") - echoerr value - catch /.*/ - Xpath 32768 " X: 0 - finally - Xpath 65536 " X: 65536 - if !caught - if !$VIMNOERRTHROW - Xpath 131072 " X: 0 - else - let value = "Error" - echoerr value - endif - endif - endtry - catch /^Vim(echoerr):/ - let caught = caught + 1 - if v:exception !~ value - Xpath 262144 " X: 0 - endif - catch /.*/ - Xpath 524288 " X: 0 - finally - Xpath 1048576 " X: 1048576 - if caught != 2 - if !caught && !$VIMNOERRTHROW - Xpath 2097152 " X: 0 - elseif caught - Xpath 4194304 " X: 0 - endif - return | " discard error for $VIMNOERRTHROW - endif - endtry -endfunction - -call G() -delfunction G - -unlet! value caught - -if ExtraVim() - try - let errcaught = 0 - try - Xpath 8388608 " X: 8388608 - let intcaught = 0 - "INTERRUPT - catch /^Vim:/ " catch interrupt exception - let intcaught = 1 - " Trigger Vim error exception with value specified after :echoerr - echoerr substitute(v:exception, '^Vim\((.*)\)\=:', '', "") - catch /.*/ - Xpath 16777216 " X: 0 - finally - Xpath 33554432 " X: 33554432 - if !intcaught - if !$VIMNOINTTHROW - Xpath 67108864 " X: 0 - else - echoerr "Interrupt" - endif - endif - endtry - catch /^Vim(echoerr):/ - let errcaught = 1 - if v:exception !~ "Interrupt" - Xpath 134217728 " X: 0 - endif - finally - Xpath 268435456 " X: 268435456 - if !errcaught && !$VIMNOERRTHROW - Xpath 536870912 " X: 0 - endif - endtry -endif - -Xcheck 311511339 - - -"------------------------------------------------------------------------------- -" Test 61: Catching interrupt exceptions {{{1 -" -" When an interrupt occurs inside a :try/:endtry region, an -" interrupt exception is thrown and can be caught. Its value is -" "Vim:Interrupt". If the interrupt occurs after an error or a :throw -" but before a matching :catch is reached, all following :catches of -" that try block are ignored, but the interrupt exception can be -" caught by the next surrounding try conditional. An interrupt is -" ignored when there is a previous interrupt that has not been caught -" or causes a :finally clause to be executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - while 1 - try - try - Xpath 1 " X: 1 - let caught = 0 - "INTERRUPT - Xpath 2 " X: 0 - catch /^Vim:Interrupt$/ - let caught = 1 - finally - Xpath 4 " X: 4 - if caught || $VIMNOINTTHROW - Xpath 8 " X: 8 - endif - endtry - catch /.*/ - Xpath 16 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - try - Xpath 32 " X: 32 - asdf - Xpath 64 " X: 0 - catch /do_not_catch/ - Xpath 128 " X: 0 - catch /.*/ "INTERRUPT - throw interrupt if !$VIMNOERRTHROW - Xpath 256 " X: 0 - catch /.*/ - Xpath 512 " X: 0 - finally "INTERRUPT - throw interrupt if $VIMNOERRTHROW - Xpath 1024 " X: 1024 - endtry - catch /^Vim:Interrupt$/ - let caught = 1 - finally - Xpath 2048 " X: 2048 - if caught || $VIMNOINTTHROW - Xpath 4096 " X: 4096 - endif - endtry - catch /.*/ - Xpath 8192 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - try - Xpath 16384 " X: 16384 - throw "x" - Xpath 32768 " X: 0 - catch /do_not_catch/ - Xpath 65536 " X: 0 - catch /x/ "INTERRUPT - Xpath 131072 " X: 0 - catch /.*/ - Xpath 262144 " X: 0 - endtry - catch /^Vim:Interrupt$/ - let caught = 1 - finally - Xpath 524288 " X: 524288 - if caught || $VIMNOINTTHROW - Xpath 1048576 " X: 1048576 - endif - endtry - catch /.*/ - Xpath 2097152 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - - while 1 - try - let caught = 0 - try - Xpath 4194304 " X: 4194304 - "INTERRUPT - Xpath 8388608 " X: 0 - catch /do_not_catch/ "INTERRUPT - Xpath 16777216 " X: 0 - catch /^Vim:Interrupt$/ - let caught = 1 - finally - Xpath 33554432 " X: 33554432 - if caught || $VIMNOINTTHROW - Xpath 67108864 " X: 67108864 - endif - endtry - catch /.*/ - Xpath 134217728 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard interrupt for $VIMNOINTTHROW - endtry - endwhile - - Xpath 268435456 " X: 268435456 - -endif - -Xcheck 374889517 - - -"------------------------------------------------------------------------------- -" Test 62: Catching error exceptions {{{1 -" -" An error inside a :try/:endtry region is converted to an exception -" and can be caught. The error exception has a "Vim(cmdname):" prefix -" where cmdname is the name of the failing command, or a "Vim:" prefix -" if no command name is known. The "Vim" prefixes cannot be faked. -"------------------------------------------------------------------------------- - -XpathINIT - -function! MSG(enr, emsg) - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - let match = 1 - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - let match = 0 - if v:errmsg == "" - Xout "Message missing." - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Unexpected message:" v:errmsg - endif - endif - return match -endfunction - -while 1 - try - try - let caught = 0 - unlet novar - catch /^Vim(unlet):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(unlet):', '', "") - finally - Xpath 1 " X: 1 - if !caught && !$VIMNOERRTHROW - Xpath 2 " X: 0 - endif - if !MSG('E108', "No such variable") - Xpath 4 " X: 0 - endif - endtry - catch /.*/ - Xpath 8 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let caught = 0 - throw novar " error in :throw - catch /^Vim(throw):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "") - finally - Xpath 16 " X: 16 - if !caught && !$VIMNOERRTHROW - Xpath 32 " X: 0 - endif - if caught ? !MSG('E121', "Undefined variable") - \ : !MSG('E15', "Invalid expression") - Xpath 64 " X: 0 - endif - endtry - catch /.*/ - Xpath 128 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let caught = 0 - throw "Vim:faked" " error: cannot fake Vim exception - catch /^Vim(throw):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "") - finally - Xpath 256 " X: 256 - if !caught && !$VIMNOERRTHROW - Xpath 512 " X: 0 - endif - if !MSG('E608', "Cannot :throw exceptions with 'Vim' prefix") - Xpath 1024 " X: 0 - endif - endtry - catch /.*/ - Xpath 2048 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -function! F() - while 1 - " Missing :endwhile -endfunction - -while 1 - try - try - let caught = 0 - call F() - catch /^Vim(endfunction):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(endfunction):', '', "") - finally - Xpath 4096 " X: 4096 - if !caught && !$VIMNOERRTHROW - Xpath 8192 " X: 0 - endif - if !MSG('E170', "Missing :endwhile") - Xpath 16384 " X: 0 - endif - endtry - catch /.*/ - Xpath 32768 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let caught = 0 - ExecAsScript F - catch /^Vim:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim:', '', "") - finally - Xpath 65536 " X: 65536 - if !caught && !$VIMNOERRTHROW - Xpath 131072 " X: 0 - endif - if !MSG('E170', "Missing :endwhile") - Xpath 262144 " X: 0 - endif - endtry - catch /.*/ - Xpath 524288 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -function! G() - call G() -endfunction - -while 1 - try - let mfd_save = &mfd - set mfd=3 - try - let caught = 0 - call G() - catch /^Vim(call):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(call):', '', "") - finally - Xpath 1048576 " X: 1048576 - if !caught && !$VIMNOERRTHROW - Xpath 2097152 " X: 0 - endif - if !MSG('E132', "Function call depth is higher than 'maxfuncdepth'") - Xpath 4194304 " X: 0 - endif - endtry - catch /.*/ - Xpath 8388608 " X: 0 - Xout v:exception "in" v:throwpoint - finally - let &mfd = mfd_save - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -function! H() - return H() -endfunction - -while 1 - try - let mfd_save = &mfd - set mfd=3 - try - let caught = 0 - call H() - catch /^Vim(return):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(return):', '', "") - finally - Xpath 16777216 " X: 16777216 - if !caught && !$VIMNOERRTHROW - Xpath 33554432 " X: 0 - endif - if !MSG('E132', "Function call depth is higher than 'maxfuncdepth'") - Xpath 67108864 " X: 0 - endif - endtry - catch /.*/ - Xpath 134217728 " X: 0 - Xout v:exception "in" v:throwpoint - finally - let &mfd = mfd_save - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -unlet! caught mfd_save -delfunction F -delfunction G -delfunction H -Xpath 268435456 " X: 268435456 - -Xcheck 286331153 - -" Leave MSG() for the next test. - - -"------------------------------------------------------------------------------- -" Test 63: Suppressing error exceptions by :silent!. {{{1 -" -" A :silent! command inside a :try/:endtry region suppresses the -" conversion of errors to an exception and the immediate abortion on -" error. When the commands executed by the :silent! themselves open -" a new :try/:endtry region, conversion of errors to exception and -" immediate abortion is switched on again - until the next :silent! -" etc. The :silent! has the effect of setting v:errmsg to the error -" message text (without displaying it) and continuing with the next -" script line. -" -" When a command triggering autocommands is executed by :silent! -" inside a :try/:endtry, the autocommand execution is not suppressed -" on error. -" -" This test reuses the function MSG() from the previous test. -"------------------------------------------------------------------------------- - -XpathINIT - -XloopINIT! 1 4 - -let taken = "" - -function! S(n) abort - XloopNEXT - let g:taken = g:taken . "E" . a:n - let v:errmsg = "" - exec "asdf" . a:n - - " Check that ":silent!" continues: - Xloop 1 - - " Check that ":silent!" sets "v:errmsg": - if MSG('E492', "Not an editor command") - Xloop 2 - endif -endfunction - -function! Foo() - while 1 - try - try - let caught = 0 - " This is not silent: - call S(3) " X: 0 * 16 - catch /^Vim:/ - let caught = 1 - let errmsg3 = substitute(v:exception, '^Vim:', '', "") - silent! call S(4) " X: 3 * 64 - finally - if !caught - let errmsg3 = v:errmsg - " Do call S(4) here if not executed in :catch. - silent! call S(4) - endif - Xpath 1048576 " X: 1048576 - if !caught && !$VIMNOERRTHROW - Xpath 2097152 " X: 0 - endif - let v:errmsg = errmsg3 - if !MSG('E492', "Not an editor command") - Xpath 4194304 " X: 0 - endif - silent! call S(5) " X: 3 * 256 - " Break out of try conditionals that cover ":silent!". This also - " discards the aborting error when $VIMNOERRTHROW is non-zero. - break - endtry - catch /.*/ - Xpath 8388608 " X: 0 - Xout v:exception "in" v:throwpoint - endtry - endwhile - " This is a double ":silent!" (see caller). - silent! call S(6) " X: 3 * 1024 -endfunction - -function! Bar() - try - silent! call S(2) " X: 3 * 4 - " X: 3 * 4096 - silent! execute "call Foo() | call S(7)" - silent! call S(8) " X: 3 * 16384 - endtry " normal end of try cond that covers ":silent!" - " This has a ":silent!" from the caller: - call S(9) " X: 3 * 65536 -endfunction - -silent! call S(1) " X: 3 * 1 -silent! call Bar() -silent! call S(10) " X: 3 * 262144 - -let expected = "E1E2E3E4E5E6E7E8E9E10" -if taken != expected - Xpath 16777216 " X: 0 - Xout "'taken' is" taken "instead of" expected -endif - -augroup TMP - autocmd BufWritePost * Xpath 33554432 " X: 33554432 -augroup END - -Xpath 67108864 " X: 67108864 -write /i/m/p/o/s/s/i/b/l/e -Xpath 134217728 " X: 134217728 - -autocmd! TMP -unlet! caught errmsg3 taken expected -delfunction S -delfunction Foo -delfunction Bar -delfunction MSG - -Xcheck 236978127 - - -"------------------------------------------------------------------------------- -" Test 64: Error exceptions after error, interrupt or :throw {{{1 -" -" When an error occurs after an interrupt or a :throw but before -" a matching :catch is reached, all following :catches of that try -" block are ignored, but the error exception can be caught by the next -" surrounding try conditional. Any previous error exception is -" discarded. An error is ignored when there is a previous error that -" has not been caught. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - while 1 - try - try - Xpath 1 " X: 1 - let caught = 0 - while 1 -" if 1 - " Missing :endif - endwhile " throw error exception - catch /^Vim(/ - let caught = 1 - finally - Xpath 2 " X: 2 - if caught || $VIMNOERRTHROW - Xpath 4 " X: 4 - endif - endtry - catch /.*/ - Xpath 8 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - try - Xpath 16 " X: 16 - let caught = 0 - try -" if 1 - " Missing :endif - catch /.*/ " throw error exception - Xpath 32 " X: 0 - catch /.*/ - Xpath 64 " X: 0 - endtry - catch /^Vim(/ - let caught = 1 - finally - Xpath 128 " X: 128 - if caught || $VIMNOERRTHROW - Xpath 256 " X: 256 - endif - endtry - catch /.*/ - Xpath 512 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - try - Xpath 1024 " X: 1024 - "INTERRUPT - catch /do_not_catch/ - Xpath 2048 " X: 0 -" if 1 - " Missing :endif - catch /.*/ " throw error exception - Xpath 4096 " X: 0 - catch /.*/ - Xpath 8192 " X: 0 - endtry - catch /^Vim(/ - let caught = 1 - finally - Xpath 16384 " X: 16384 - if caught || $VIMNOERRTHROW - Xpath 32768 " X: 32768 - endif - endtry - catch /.*/ - Xpath 65536 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - try - Xpath 131072 " X: 131072 - throw "x" - catch /do_not_catch/ - Xpath 262144 " X: 0 -" if 1 - " Missing :endif - catch /x/ " throw error exception - Xpath 524288 " X: 0 - catch /.*/ - Xpath 1048576 " X: 0 - endtry - catch /^Vim(/ - let caught = 1 - finally - Xpath 2097152 " X: 2097152 - if caught || $VIMNOERRTHROW - Xpath 4194304 " X: 4194304 - endif - endtry - catch /.*/ - Xpath 8388608 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - try - let caught = 0 - Xpath 16777216 " X: 16777216 -" endif " :endif without :if; throw error exception -" if 1 - " Missing :endif - catch /do_not_catch/ " ignore new error - Xpath 33554432 " X: 0 - catch /^Vim(endif):/ - let caught = 1 - catch /^Vim(/ - Xpath 67108864 " X: 0 - finally - Xpath 134217728 " X: 134217728 - if caught || $VIMNOERRTHROW - Xpath 268435456 " X: 268435456 - endif - endtry - catch /.*/ - Xpath 536870912 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - Xpath 1073741824 " X: 1073741824 - -endif - -Xcheck 1499645335 - - -"------------------------------------------------------------------------------- -" Test 65: Errors in the /pattern/ argument of a :catch {{{1 -" -" On an error in the /pattern/ argument of a :catch, the :catch does -" not match. Any following :catches of the same :try/:endtry don't -" match either. Finally clauses are executed. -"------------------------------------------------------------------------------- - -XpathINIT - -function! MSG(enr, emsg) - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - let match = 1 - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - let match = 0 - if v:errmsg == "" - Xout "Message missing." - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Unexpected message:" v:errmsg - endif - endif - return match -endfunction - -try - try - Xpath 1 " X: 1 - throw "oops" - catch /^oops$/ - Xpath 2 " X: 2 - catch /\)/ " not checked; exception has already been caught - Xpath 4 " X: 0 - endtry - Xpath 8 " X: 8 -catch /.*/ - Xpath 16 " X: 0 - Xout v:exception "in" v:throwpoint -endtry - -function! F() - try - let caught = 0 - try - try - Xpath 32 " X: 32 - throw "ab" - catch /abc/ " does not catch - Xpath 64 " X: 0 - catch /\)/ " error; discards exception - Xpath 128 " X: 0 - catch /.*/ " not checked - Xpath 256 " X: 0 - finally - Xpath 512 " X: 512 - endtry - Xpath 1024 " X: 0 - catch /^ab$/ " checked, but original exception is discarded - Xpath 2048 " X: 0 - catch /^Vim(catch):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(catch):', '', "") - finally - Xpath 4096 " X: 4096 - if !caught && !$VIMNOERRTHROW - Xpath 8192 " X: 0 - endif - if !MSG('E475', "Invalid argument") - Xpath 16384 " X: 0 - endif - if !caught - return | " discard error - endif - endtry - catch /.*/ - Xpath 32768 " X: 0 - Xout v:exception "in" v:throwpoint - endtry -endfunction - -call F() -Xpath 65536 " X: 65536 - -delfunction MSG -delfunction F -unlet! caught - -Xcheck 70187 - - -"------------------------------------------------------------------------------- -" Test 66: Stop range :call on error, interrupt, or :throw {{{1 -" -" When a function which is multiply called for a range since it -" doesn't handle the range itself has an error in a command -" dynamically enclosed by :try/:endtry or gets an interrupt or -" executes a :throw, no more calls for the remaining lines in the -" range are made. On an error in a command not dynamically enclosed -" by :try/:endtry, the function is executed again for the remaining -" lines in the range. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - let file = tempname() - exec "edit" file - - insert -line 1 -line 2 -line 3 -. - - XloopINIT! 1 2 - - let taken = "" - let expected = "G1EF1E(1)F1E(2)F1E(3)G2EF2E(1)G3IF3I(1)G4TF4T(1)G5AF5A(1)" - - function! F(reason, n) abort - let g:taken = g:taken . "F" . a:n . - \ substitute(a:reason, '\(\l\).*', '\u\1', "") . - \ "(" . line(".") . ")" - - if a:reason == "error" - asdf - elseif a:reason == "interrupt" - "INTERRUPT - let dummy = 0 - elseif a:reason == "throw" - throw "xyz" - elseif a:reason == "aborting error" - XloopNEXT - if g:taken != g:expected - Xloop 1 " X: 0 - Xout "'taken' is" g:taken "instead of" g:expected - endif - try - bwipeout! - call delete(file) - asdf - endtry - endif - endfunction - - function! G(reason, n) - let g:taken = g:taken . "G" . a:n . - \ substitute(a:reason, '\(\l\).*', '\u\1', "") - 1,3call F(a:reason, a:n) - endfunction - - Xpath 8 " X: 8 - call G("error", 1) - try - Xpath 16 " X: 16 - try - call G("error", 2) - Xpath 32 " X: 0 - finally - Xpath 64 " X: 64 - try - call G("interrupt", 3) - Xpath 128 " X: 0 - finally - Xpath 256 " X: 256 - try - call G("throw", 4) - Xpath 512 " X: 0 - endtry - endtry - endtry - catch /xyz/ - Xpath 1024 " X: 1024 - catch /.*/ - Xpath 2048 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - Xpath 4096 " X: 4096 - call G("aborting error", 5) - Xpath 8192 " X: 0 - Xout "'taken' is" taken "instead of" expected - -endif - -Xcheck 5464 - - -"------------------------------------------------------------------------------- -" Test 67: :throw across :call command {{{1 -" -" On a call command, an exception might be thrown when evaluating the -" function name, during evaluation of the arguments, or when the -" function is being executed. The exception can be caught by the -" caller. -"------------------------------------------------------------------------------- - -XpathINIT - -function! THROW(x, n) - if a:n == 1 - Xpath 1 " X: 1 - elseif a:n == 2 - Xpath 2 " X: 2 - elseif a:n == 3 - Xpath 4 " X: 4 - endif - throw a:x -endfunction - -function! NAME(x, n) - if a:n == 1 - Xpath 8 " X: 0 - elseif a:n == 2 - Xpath 16 " X: 16 - elseif a:n == 3 - Xpath 32 " X: 32 - elseif a:n == 4 - Xpath 64 " X: 64 - endif - return a:x -endfunction - -function! ARG(x, n) - if a:n == 1 - Xpath 128 " X: 0 - elseif a:n == 2 - Xpath 256 " X: 0 - elseif a:n == 3 - Xpath 512 " X: 512 - elseif a:n == 4 - Xpath 1024 " X: 1024 - endif - return a:x -endfunction - -function! F(x, n) - if a:n == 2 - Xpath 2048 " X: 0 - elseif a:n == 4 - Xpath 4096 " X: 4096 - endif -endfunction - -while 1 - try - let error = 0 - let v:errmsg = "" - - while 1 - try - Xpath 8192 " X: 8192 - call {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1) - Xpath 16384 " X: 0 - catch /^name$/ - Xpath 32768 " X: 32768 - catch /.*/ - let error = 1 - Xout "1:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "1:" v:errmsg - endif - if error - Xpath 65536 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 131072 " X: 131072 - call {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2) - Xpath 262144 " X: 0 - catch /^arg$/ - Xpath 524288 " X: 524288 - catch /.*/ - let error = 1 - Xout "2:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "2:" v:errmsg - endif - if error - Xpath 1048576 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 2097152 " X: 2097152 - call {NAME("THROW", 3)}(ARG("call", 3), 3) - Xpath 4194304 " X: 0 - catch /^call$/ - Xpath 8388608 " X: 8388608 - catch /^0$/ " default return value - Xpath 16777216 " X: 0 - Xout "3:" v:throwpoint - catch /.*/ - let error = 1 - Xout "3:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "3:" v:errmsg - endif - if error - Xpath 33554432 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 67108864 " X: 67108864 - call {NAME("F", 4)}(ARG(4711, 4), 4) - Xpath 134217728 " X: 134217728 - catch /.*/ - let error = 1 - Xout "4:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "4:" v:errmsg - endif - if error - Xpath 268435456 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - catch /^0$/ " default return value - Xpath 536870912 " X: 0 - Xout v:throwpoint - catch /.*/ - let error = 1 - Xout v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout v:errmsg - endif - if error - Xpath 1073741824 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -unlet error -delfunction F - -Xcheck 212514423 - -" Leave THROW(), NAME(), and ARG() for the next test. - - -"------------------------------------------------------------------------------- -" Test 68: :throw across function calls in expressions {{{1 -" -" On a function call within an expression, an exception might be -" thrown when evaluating the function name, during evaluation of the -" arguments, or when the function is being executed. The exception -" can be caught by the caller. -" -" This test reuses the functions THROW(), NAME(), and ARG() from the -" previous test. -"------------------------------------------------------------------------------- - -XpathINIT - -function! F(x, n) - if a:n == 2 - Xpath 2048 " X: 0 - elseif a:n == 4 - Xpath 4096 " X: 4096 - endif - return a:x -endfunction - -unlet! var1 var2 var3 var4 - -while 1 - try - let error = 0 - let v:errmsg = "" - - while 1 - try - Xpath 8192 " X: 8192 - let var1 = {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1) - Xpath 16384 " X: 0 - catch /^name$/ - Xpath 32768 " X: 32768 - catch /.*/ - let error = 1 - Xout "1:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "1:" v:errmsg - endif - if error - Xpath 65536 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 131072 " X: 131072 - let var2 = {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2) - Xpath 262144 " X: 0 - catch /^arg$/ - Xpath 524288 " X: 524288 - catch /.*/ - let error = 1 - Xout "2:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "2:" v:errmsg - endif - if error - Xpath 1048576 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 2097152 " X: 2097152 - let var3 = {NAME("THROW", 3)}(ARG("call", 3), 3) - Xpath 4194304 " X: 0 - catch /^call$/ - Xpath 8388608 " X: 8388608 - catch /^0$/ " default return value - Xpath 16777216 " X: 0 - Xout "3:" v:throwpoint - catch /.*/ - let error = 1 - Xout "3:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "3:" v:errmsg - endif - if error - Xpath 33554432 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - while 1 - try - Xpath 67108864 " X: 67108864 - let var4 = {NAME("F", 4)}(ARG(4711, 4), 4) - Xpath 134217728 " X: 134217728 - catch /.*/ - let error = 1 - Xout "4:" v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout "4:" v:errmsg - endif - if error - Xpath 268435456 " X: 0 - endif - let error = 0 - let v:errmsg = "" - break " discard error for $VIMNOERRTHROW - endtry - endwhile - - catch /^0$/ " default return value - Xpath 536870912 " X: 0 - Xout v:throwpoint - catch /.*/ - let error = 1 - Xout v:exception "in" v:throwpoint - finally - if !error && $VIMNOERRTHROW && v:errmsg != "" - let error = 1 - Xout v:errmsg - endif - if error - Xpath 1073741824 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -if exists("var1") || exists("var2") || exists("var3") || - \ !exists("var4") || var4 != 4711 - " The Xpath command does not accept 2^31 (negative); add explicitly: - let Xpath = Xpath + 2147483648 " X: 0 - if exists("var1") - Xout "var1 =" var1 - endif - if exists("var2") - Xout "var2 =" var2 - endif - if exists("var3") - Xout "var3 =" var3 - endif - if !exists("var4") - Xout "var4 unset" - elseif var4 != 4711 - Xout "var4 =" var4 - endif -endif - -unlet! error var1 var2 var3 var4 -delfunction THROW -delfunction NAME -delfunction ARG -delfunction F - -Xcheck 212514423 - -" Tests 69 to 75 were moved to test_trycatch.vim -let Xtest = 76 - - -"------------------------------------------------------------------------------- -" Test 76: Errors, interrupts, :throw during expression evaluation {{{1 -" -" When a function call made during expression evaluation is aborted -" due to an error inside a :try/:endtry region or due to an interrupt -" or a :throw, the expression evaluation is aborted as well. No -" message is displayed for the cancelled expression evaluation. On an -" error not inside :try/:endtry, the expression evaluation continues. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - let taken = "" - - function! ERR(n) - let g:taken = g:taken . "E" . a:n - asdf - endfunction - - function! ERRabort(n) abort - let g:taken = g:taken . "A" . a:n - asdf - endfunction " returns -1; may cause follow-up msg for illegal var/func name - - function! WRAP(n, arg) - let g:taken = g:taken . "W" . a:n - let g:saved_errmsg = v:errmsg - return arg - endfunction - - function! INT(n) - let g:taken = g:taken . "I" . a:n - "INTERRUPT9 - let dummy = 0 - endfunction - - function! THR(n) - let g:taken = g:taken . "T" . a:n - throw "should not be caught" - endfunction - - function! CONT(n) - let g:taken = g:taken . "C" . a:n - endfunction - - function! MSG(n) - let g:taken = g:taken . "M" . a:n - let errmsg = (a:n >= 37 && a:n <= 44) ? g:saved_errmsg : v:errmsg - let msgptn = (a:n >= 10 && a:n <= 27) ? "^$" : "asdf" - if errmsg !~ msgptn - let g:taken = g:taken . "x" - Xout "Expr" a:n.": Unexpected message:" v:errmsg - endif - let v:errmsg = "" - let g:saved_errmsg = "" - endfunction - - let v:errmsg = "" - - try - let t = 1 - XloopINIT 1 2 - while t <= 9 - Xloop 1 " X: 511 - try - if t == 1 - let v{ERR(t) + CONT(t)} = 0 - elseif t == 2 - let v{ERR(t) + CONT(t)} - elseif t == 3 - let var = exists('v{ERR(t) + CONT(t)}') - elseif t == 4 - unlet v{ERR(t) + CONT(t)} - elseif t == 5 - function F{ERR(t) + CONT(t)}() - endfunction - elseif t == 6 - function F{ERR(t) + CONT(t)} - elseif t == 7 - let var = exists('*F{ERR(t) + CONT(t)}') - elseif t == 8 - delfunction F{ERR(t) + CONT(t)} - elseif t == 9 - let var = ERR(t) + CONT(t) - endif - catch /asdf/ - " v:errmsg is not set when the error message is converted to an - " exception. Set it to the original error message. - let v:errmsg = substitute(v:exception, '^Vim:', '', "") - catch /^Vim\((\a\+)\)\=:/ - " An error exception has been thrown after the original error. - let v:errmsg = "" - finally - call MSG(t) - let t = t + 1 - XloopNEXT - continue " discard an aborting error - endtry - endwhile - catch /.*/ - Xpath 512 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - - try - let t = 10 - XloopINIT 1024 2 - while t <= 18 - Xloop 1 " X: 1024 * 511 - try - if t == 10 - let v{INT(t) + CONT(t)} = 0 - elseif t == 11 - let v{INT(t) + CONT(t)} - elseif t == 12 - let var = exists('v{INT(t) + CONT(t)}') - elseif t == 13 - unlet v{INT(t) + CONT(t)} - elseif t == 14 - function F{INT(t) + CONT(t)}() - endfunction - elseif t == 15 - function F{INT(t) + CONT(t)} - elseif t == 16 - let var = exists('*F{INT(t) + CONT(t)}') - elseif t == 17 - delfunction F{INT(t) + CONT(t)} - elseif t == 18 - let var = INT(t) + CONT(t) - endif - catch /^Vim\((\a\+)\)\=:\(Interrupt\)\@!/ - " An error exception has been triggered after the interrupt. - let v:errmsg = substitute(v:exception, - \ '^Vim\((\a\+)\)\=:', '', "") - finally - call MSG(t) - let t = t + 1 - XloopNEXT - continue " discard interrupt - endtry - endwhile - catch /.*/ - Xpath 524288 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - - try - let t = 19 - XloopINIT 1048576 2 - while t <= 27 - Xloop 1 " X: 1048576 * 511 - try - if t == 19 - let v{THR(t) + CONT(t)} = 0 - elseif t == 20 - let v{THR(t) + CONT(t)} - elseif t == 21 - let var = exists('v{THR(t) + CONT(t)}') - elseif t == 22 - unlet v{THR(t) + CONT(t)} - elseif t == 23 - function F{THR(t) + CONT(t)}() - endfunction - elseif t == 24 - function F{THR(t) + CONT(t)} - elseif t == 25 - let var = exists('*F{THR(t) + CONT(t)}') - elseif t == 26 - delfunction F{THR(t) + CONT(t)} - elseif t == 27 - let var = THR(t) + CONT(t) - endif - catch /^Vim\((\a\+)\)\=:/ - " An error exception has been triggered after the :throw. - let v:errmsg = substitute(v:exception, - \ '^Vim\((\a\+)\)\=:', '', "") - finally - call MSG(t) - let t = t + 1 - XloopNEXT - continue " discard exception - endtry - endwhile - catch /.*/ - Xpath 536870912 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - - let v{ERR(28) + CONT(28)} = 0 - call MSG(28) - let v{ERR(29) + CONT(29)} - call MSG(29) - let var = exists('v{ERR(30) + CONT(30)}') - call MSG(30) - unlet v{ERR(31) + CONT(31)} - call MSG(31) - function F{ERR(32) + CONT(32)}() - endfunction - call MSG(32) - function F{ERR(33) + CONT(33)} - call MSG(33) - let var = exists('*F{ERR(34) + CONT(34)}') - call MSG(34) - delfunction F{ERR(35) + CONT(35)} - call MSG(35) - let var = ERR(36) + CONT(36) - call MSG(36) - - let saved_errmsg = "" - - let v{WRAP(37, ERRabort(37)) + CONT(37)} = 0 - call MSG(37) - let v{WRAP(38, ERRabort(38)) + CONT(38)} - call MSG(38) - let var = exists('v{WRAP(39, ERRabort(39)) + CONT(39)}') - call MSG(39) - unlet v{WRAP(40, ERRabort(40)) + CONT(40)} - call MSG(40) - function F{WRAP(41, ERRabort(41)) + CONT(41)}() - endfunction - call MSG(41) - function F{WRAP(42, ERRabort(42)) + CONT(42)} - call MSG(42) - let var = exists('*F{WRAP(43, ERRabort(43)) + CONT(43)}') - call MSG(43) - delfunction F{WRAP(44, ERRabort(44)) + CONT(44)} - call MSG(44) - let var = ERRabort(45) + CONT(45) - call MSG(45) - - Xpath 1073741824 " X: 1073741824 - - let expected = "" - \ . "E1M1E2M2E3M3E4M4E5M5E6M6E7M7E8M8E9M9" - \ . "I10M10I11M11I12M12I13M13I14M14I15M15I16M16I17M17I18M18" - \ . "T19M19T20M20T21M21T22M22T23M23T24M24T25M25T26M26T27M27" - \ . "E28C28M28E29C29M29E30C30M30E31C31M31E32C32M32E33C33M33" - \ . "E34C34M34E35C35M35E36C36M36" - \ . "A37W37C37M37A38W38C38M38A39W39C39M39A40W40C40M40A41W41C41M41" - \ . "A42W42C42M42A43W43C43M43A44W44C44M44A45C45M45" - - if taken != expected - " The Xpath command does not accept 2^31 (negative); display explicitly: - exec "!echo 2147483648 >>" . g:ExtraVimResult - " X: 0 - Xout "'taken' is" taken "instead of" expected - if substitute(taken, - \ '\(.*\)E3C3M3x\(.*\)E30C30M30x\(.*\)A39C39M39x\(.*\)', - \ '\1E3M3\2E30C30M30\3A39C39M39\4', - \ "") == expected - Xout "Is ++emsg_skip for var with expr_start non-NULL" - \ "in f_exists ok?" - endif - endif - - unlet! v var saved_errmsg taken expected - call delete(WA_t5) - call delete(WA_t14) - call delete(WA_t23) - unlet! WA_t5 WA_t14 WA_t23 - delfunction WA_t5 - delfunction WA_t14 - delfunction WA_t23 - -endif - -Xcheck 1610087935 - - -"------------------------------------------------------------------------------- -" Test 77: Errors, interrupts, :throw in name{brace-expression} {{{1 -" -" When a function call made during evaluation of an expression in -" braces as part of a function name after ":function" is aborted due -" to an error inside a :try/:endtry region or due to an interrupt or -" a :throw, the expression evaluation is aborted as well, and the -" function definition is ignored, skipping all commands to the -" ":endfunction". On an error not inside :try/:endtry, the expression -" evaluation continues and the function gets defined, and can be -" called and deleted. -"------------------------------------------------------------------------------- - -XpathINIT - -XloopINIT 1 4 - -function! ERR() abort - Xloop 1 " X: 1 + 4 + 16 + 64 - asdf -endfunction " returns -1 - -function! OK() - Xloop 2 " X: 2 * (1 + 4 + 16) - let v:errmsg = "" - return 0 -endfunction - -let v:errmsg = "" - -Xpath 4096 " X: 4096 -function! F{1 + ERR() + OK()}(arg) - " F0 should be defined. - if exists("a:arg") && a:arg == "calling" - Xpath 8192 " X: 8192 - else - Xpath 16384 " X: 0 - endif -endfunction -if v:errmsg != "" - Xpath 32768 " X: 0 -endif -XloopNEXT - -Xpath 65536 " X: 65536 -call F{1 + ERR() + OK()}("calling") -if v:errmsg != "" - Xpath 131072 " X: 0 -endif -XloopNEXT - -Xpath 262144 " X: 262144 -delfunction F{1 + ERR() + OK()} -if v:errmsg != "" - Xpath 524288 " X: 0 -endif -XloopNEXT - -try - while 1 - let caught = 0 - try - Xpath 1048576 " X: 1048576 - function! G{1 + ERR() + OK()}(arg) - " G0 should not be defined, and the function body should be - " skipped. - if exists("a:arg") && a:arg == "calling" - Xpath 2097152 " X: 0 - else - Xpath 4194304 " X: 0 - endif - " Use an unmatched ":finally" to check whether the body is - " skipped when an error occurs in ERR(). This works whether or - " not the exception is converted to an exception. - finally - Xpath 8388608 " X: 0 - Xout "Body of G{1 + ERR() + OK()}() not skipped" - " Discard the aborting error or exception, and break the - " while loop. - break - " End the try conditional and start a new one to avoid - " ":catch after :finally" errors. - endtry - try - Xpath 16777216 " X: 0 - endfunction - - " When the function was not defined, this won't be reached - whether - " the body was skipped or not. When the function was defined, it - " can be called and deleted here. - Xpath 33554432 " X: 0 - Xout "G0() has been defined" - XloopNEXT - try - call G{1 + ERR() + OK()}("calling") - catch /.*/ - Xpath 67108864 " X: 0 - endtry - Xpath 134217728 " X: 0 - XloopNEXT - try - delfunction G{1 + ERR() + OK()} - catch /.*/ - Xpath 268435456 " X: 0 - endtry - catch /asdf/ - " Jumped to when the function is not defined and the body is - " skipped. - let caught = 1 - catch /.*/ - Xpath 536870912 " X: 0 - finally - if !caught && !$VIMNOERRTHROW - Xpath 1073741824 " X: 0 - endif - break " discard error for $VIMNOERRTHROW - endtry " jumped to when the body is not skipped - endwhile -catch /.*/ - " The Xpath command does not accept 2^31 (negative); add explicitly: - let Xpath = Xpath + 2147483648 " X: 0 - Xout "Body of G{1 + ERR() + OK()}() not skipped, exception caught" - Xout v:exception "in" v:throwpoint -endtry - -Xcheck 1388671 - - -"------------------------------------------------------------------------------- -" Test 78: Messages on parsing errors in expression evaluation {{{1 -" -" When an expression evaluation detects a parsing error, an error -" message is given and converted to an exception, and the expression -" evaluation is aborted. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - let taken = "" - - function! F(n) - let g:taken = g:taken . "F" . a:n - endfunction - - function! MSG(n, enr, emsg) - let g:taken = g:taken . "M" . a:n - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - if v:errmsg == "" - Xout "Expr" a:n.": Message missing." - let g:taken = g:taken . "x" - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Expr" a:n.": Unexpected message:" v:errmsg - Xout "Expected: " . a:enr . ': ' . a:emsg - let g:taken = g:taken . "X" - endif - endif - endfunction - - function! CONT(n) - let g:taken = g:taken . "C" . a:n - endfunction - - let v:errmsg = "" - XloopINIT 1 2 - - try - let t = 1 - while t <= 14 - let g:taken = g:taken . "T" . t - let v:errmsg = "" - try - let caught = 0 - if t == 1 - let v{novar + CONT(t)} = 0 - elseif t == 2 - let v{novar + CONT(t)} - elseif t == 3 - let var = exists('v{novar + CONT(t)}') - elseif t == 4 - unlet v{novar + CONT(t)} - elseif t == 5 - function F{novar + CONT(t)}() - endfunction - elseif t == 6 - function F{novar + CONT(t)} - elseif t == 7 - let var = exists('*F{novar + CONT(t)}') - elseif t == 8 - delfunction F{novar + CONT(t)} - elseif t == 9 - echo novar + CONT(t) - elseif t == 10 - echo v{novar + CONT(t)} - elseif t == 11 - echo F{novar + CONT(t)} - elseif t == 12 - let var = novar + CONT(t) - elseif t == 13 - let var = v{novar + CONT(t)} - elseif t == 14 - let var = F{novar + CONT(t)}() - endif - catch /^Vim\((\a\+)\)\=:/ - " v:errmsg is not set when the error message is converted to an - " exception. Set it to the original error message. - let v:errmsg = substitute(v:exception, - \ '^Vim\((\a\+)\)\=:', '', "") - let caught = 1 - finally - if t <= 8 && t != 3 && t != 7 - call MSG(t, 'E475', 'Invalid argument\>') - else - if !caught " no error exceptions ($VIMNOERRTHROW set) - call MSG(t, 'E15', "Invalid expression") - else - call MSG(t, 'E121', "Undefined variable") - endif - endif - let t = t + 1 - XloopNEXT - continue " discard an aborting error - endtry - endwhile - catch /.*/ - Xloop 1 " X: 0 - Xout t.":" v:exception "in" ExtraVimThrowpoint() - endtry - - function! T(n, expr, enr, emsg) - try - let g:taken = g:taken . "T" . a:n - let v:errmsg = "" - try - let caught = 0 - execute "let var = " . a:expr - catch /^Vim\((\a\+)\)\=:/ - " v:errmsg is not set when the error message is converted to an - " exception. Set it to the original error message. - let v:errmsg = substitute(v:exception, - \ '^Vim\((\a\+)\)\=:', '', "") - let caught = 1 - finally - if !caught " no error exceptions ($VIMNOERRTHROW set) - call MSG(a:n, 'E15', "Invalid expression") - else - call MSG(a:n, a:enr, a:emsg) - endif - XloopNEXT - " Discard an aborting error: - return - endtry - catch /.*/ - Xloop 1 " X: 0 - Xout a:n.":" v:exception "in" ExtraVimThrowpoint() - endtry - endfunction - - call T(15, 'Nofunc() + CONT(15)', 'E117', "Unknown function") - call T(16, 'F(1 2 + CONT(16))', 'E116', "Invalid arguments") - call T(17, 'F(1, 2) + CONT(17)', 'E118', "Too many arguments") - call T(18, 'F() + CONT(18)', 'E119', "Not enough arguments") - call T(19, '{(1} + CONT(19)', 'E110', "Missing ')'") - call T(20, '("abc"[1) + CONT(20)', 'E111', "Missing ']'") - call T(21, '(1 +) + CONT(21)', 'E15', "Invalid expression") - call T(22, '1 2 + CONT(22)', 'E15', "Invalid expression") - call T(23, '(1 ? 2) + CONT(23)', 'E109', "Missing ':' after '?'") - call T(24, '("abc) + CONT(24)', 'E114', "Missing quote") - call T(25, "('abc) + CONT(25)", 'E115', "Missing quote") - call T(26, '& + CONT(26)', 'E112', "Option name missing") - call T(27, '&asdf + CONT(27)', 'E113', "Unknown option") - - Xpath 134217728 " X: 134217728 - - let expected = "" - \ . "T1M1T2M2T3M3T4M4T5M5T6M6T7M7T8M8T9M9T10M10T11M11T12M12T13M13T14M14" - \ . "T15M15T16M16T17M17T18M18T19M19T20M20T21M21T22M22T23M23T24M24T25M25" - \ . "T26M26T27M27" - - if taken != expected - Xpath 268435456 " X: 0 - Xout "'taken' is" taken "instead of" expected - if substitute(taken, '\(.*\)T3M3x\(.*\)', '\1T3M3\2', "") == expected - Xout "Is ++emsg_skip for var with expr_start non-NULL" - \ "in f_exists ok?" - endif - endif - - unlet! var caught taken expected - call delete(WA_t5) - unlet! WA_t5 - delfunction WA_t5 - -endif - -Xcheck 134217728 - - -"------------------------------------------------------------------------------- -" Test 79: Throwing one of several errors for the same command {{{1 -" -" When several errors appear in a row (for instance during expression -" evaluation), the first as the most specific one is used when -" throwing an error exception. If, however, a syntax error is -" detected afterwards, this one is used for the error exception. -" On a syntax error, the next command is not executed, on a normal -" error, however, it is (relevant only in a function without the -" "abort" flag). v:errmsg is not set. -" -" If throwing error exceptions is configured off, v:errmsg is always -" set to the latest error message, that is, to the more general -" message or the syntax error, respectively. -"------------------------------------------------------------------------------- - -XpathINIT - -XloopINIT 1 2 - -function! NEXT(cmd) - exec a:cmd . " | Xloop 1" -endfunction - -call NEXT('echo novar') " X: 1 * 1 (checks nextcmd) -XloopNEXT -call NEXT('let novar #') " X: 0 * 2 (skips nextcmd) -XloopNEXT -call NEXT('unlet novar #') " X: 0 * 4 (skips nextcmd) -XloopNEXT -call NEXT('let {novar}') " X: 0 * 8 (skips nextcmd) -XloopNEXT -call NEXT('unlet{ novar}') " X: 0 * 16 (skips nextcmd) - -function! EXEC(cmd) - exec a:cmd -endfunction - -function! MATCH(expected, msg, enr, emsg) - let msg = a:msg - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let msg = ":" . msg - endif - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if msg !~ '^'.a:enr.':' || (english && msg !~ a:emsg) - let match = 0 - if a:expected " no match although expected - if a:msg == "" - Xout "Message missing." - else - let msg = escape(msg, '"') - Xout "Unexpected message:" msg - Xout "Expected:" a:enr . ": " . a:emsg - endif - endif - else - let match = 1 - if !a:expected " match although not expected - let msg = escape(msg, '"') - Xout "Unexpected message:" msg - Xout "Expected none." - endif - endif - return match -endfunction - -try - - while 1 " dummy loop - try - let v:errmsg = "" - let caught = 0 - let thrmsg = "" - call EXEC('echo novar') " normal error - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let thrmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 32 " X: 32 - if !caught - if !$VIMNOERRTHROW - Xpath 64 " X: 0 - endif - elseif !MATCH(1, thrmsg, 'E121', "Undefined variable") - \ || v:errmsg != "" - Xpath 128 " X: 0 - endif - if !caught && !MATCH(1, v:errmsg, 'E15', "Invalid expression") - Xpath 256 " X: 0 - endif - break " discard error if $VIMNOERRTHROW == 1 - endtry - endwhile - - Xpath 512 " X: 512 - let cmd = "let" - XloopINIT 1024 32 - while cmd != "" - try - let v:errmsg = "" - let caught = 0 - let thrmsg = "" - call EXEC(cmd . ' novar #') " normal plus syntax error - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let thrmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xloop 1 " X: 1024 * (1 + 32) - if !caught - if !$VIMNOERRTHROW - Xloop 2 " X: 0 - endif - else - if cmd == "let" - let match = MATCH(0, thrmsg, 'E121', "Undefined variable") - elseif cmd == "unlet" - let match = MATCH(0, thrmsg, 'E108', "No such variable") - endif - if match " normal error - Xloop 4 " X: 0 - endif - if !MATCH(1, thrmsg, 'E488', "Trailing characters") - \|| v:errmsg != "" - " syntax error - Xloop 8 " X: 0 - endif - endif - if !caught && !MATCH(1, v:errmsg, 'E488', "Trailing characters") - " last error - Xloop 16 " X: 0 - endif - if cmd == "let" - let cmd = "unlet" - else - let cmd = "" - endif - XloopNEXT - continue " discard error if $VIMNOERRTHROW == 1 - endtry - endwhile - - Xpath 1048576 " X: 1048576 - let cmd = "let" - XloopINIT 2097152 32 - while cmd != "" - try - let v:errmsg = "" - let caught = 0 - let thrmsg = "" - call EXEC(cmd . ' {novar}') " normal plus syntax error - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let thrmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xloop 1 " X: 2097152 * (1 + 32) - if !caught - if !$VIMNOERRTHROW - Xloop 2 " X: 0 - endif - else - if MATCH(0, thrmsg, 'E121', "Undefined variable") " normal error - Xloop 4 " X: 0 - endif - if !MATCH(1, thrmsg, 'E475', 'Invalid argument\>') - \ || v:errmsg != "" " syntax error - Xloop 8 " X: 0 - endif - endif - if !caught && !MATCH(1, v:errmsg, 'E475', 'Invalid argument\>') - " last error - Xloop 16 " X: 0 - endif - if cmd == "let" - let cmd = "unlet" - else - let cmd = "" - endif - XloopNEXT - continue " discard error if $VIMNOERRTHROW == 1 - endtry - endwhile - -catch /.*/ - " The Xpath command does not accept 2^31 (negative); add explicitly: - let Xpath = Xpath + 2147483648 " X: 0 - Xout v:exception "in" v:throwpoint -endtry - -unlet! next_command thrmsg match -delfunction NEXT -delfunction EXEC -delfunction MATCH - -Xcheck 70288929 - - -"------------------------------------------------------------------------------- -" Test 80: Syntax error in expression for illegal :elseif {{{1 -" -" If there is a syntax error in the expression after an illegal -" :elseif, an error message is given (or an error exception thrown) -" for the illegal :elseif rather than the expression error. -"------------------------------------------------------------------------------- - -XpathINIT - -function! MSG(enr, emsg) - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - let match = 1 - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - let match = 0 - if v:errmsg == "" - Xout "Message missing." - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Unexpected message:" v:errmsg - endif - endif - return match -endfunction - -let v:errmsg = "" -if 0 -else -elseif 1 ||| 2 -endif -Xpath 1 " X: 1 -if !MSG('E584', ":elseif after :else") - Xpath 2 " X: 0 -endif - -let v:errmsg = "" -if 1 -else -elseif 1 ||| 2 -endif -Xpath 4 " X: 4 -if !MSG('E584', ":elseif after :else") - Xpath 8 " X: 0 -endif - -let v:errmsg = "" -elseif 1 ||| 2 -Xpath 16 " X: 16 -if !MSG('E582', ":elseif without :if") - Xpath 32 " X: 0 -endif - -let v:errmsg = "" -while 1 - elseif 1 ||| 2 -endwhile -Xpath 64 " X: 64 -if !MSG('E582', ":elseif without :if") - Xpath 128 " X: 0 -endif - -while 1 - try - try - let v:errmsg = "" - let caught = 0 - if 0 - else - elseif 1 ||| 2 - endif - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 256 " X: 256 - if !caught && !$VIMNOERRTHROW - Xpath 512 " X: 0 - endif - if !MSG('E584', ":elseif after :else") - Xpath 1024 " X: 0 - endif - endtry - catch /.*/ - Xpath 2048 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let v:errmsg = "" - let caught = 0 - if 1 - else - elseif 1 ||| 2 - endif - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 4096 " X: 4096 - if !caught && !$VIMNOERRTHROW - Xpath 8192 " X: 0 - endif - if !MSG('E584', ":elseif after :else") - Xpath 16384 " X: 0 - endif - endtry - catch /.*/ - Xpath 32768 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let v:errmsg = "" - let caught = 0 - elseif 1 ||| 2 - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 65536 " X: 65536 - if !caught && !$VIMNOERRTHROW - Xpath 131072 " X: 0 - endif - if !MSG('E582', ":elseif without :if") - Xpath 262144 " X: 0 - endif - endtry - catch /.*/ - Xpath 524288 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let v:errmsg = "" - let caught = 0 - while 1 - elseif 1 ||| 2 - endwhile - catch /^Vim\((\a\+)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") - finally - Xpath 1048576 " X: 1048576 - if !caught && !$VIMNOERRTHROW - Xpath 2097152 " X: 0 - endif - if !MSG('E582', ":elseif without :if") - Xpath 4194304 " X: 0 - endif - endtry - catch /.*/ - Xpath 8388608 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -Xpath 16777216 " X: 16777216 - -unlet! caught -delfunction MSG - -Xcheck 17895765 - - -"------------------------------------------------------------------------------- -" Test 81: Discarding exceptions after an error or interrupt {{{1 -" -" When an exception is thrown from inside a :try conditional without -" :catch and :finally clauses and an error or interrupt occurs before -" the :endtry is reached, the exception is discarded. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try - Xpath 1 " X: 1 - try - Xpath 2 " X: 2 - throw "arrgh" - Xpath 4 " X: 0 -" if 1 - Xpath 8 " X: 0 - " error after :throw: missing :endif - endtry - Xpath 16 " X: 0 - catch /arrgh/ - Xpath 32 " X: 0 - endtry - Xpath 64 " X: 0 -endif - -if ExtraVim() - try - Xpath 128 " X: 128 - try - Xpath 256 " X: 256 - throw "arrgh" - Xpath 512 " X: 0 - endtry " INTERRUPT - Xpath 1024 " X: 0 - catch /arrgh/ - Xpath 2048 " X: 0 - endtry - Xpath 4096 " X: 0 -endif - -Xcheck 387 - - -"------------------------------------------------------------------------------- -" Test 82: Ignoring :catch clauses after an error or interrupt {{{1 -" -" When an exception is thrown and an error or interrupt occurs before -" the matching :catch clause is reached, the exception is discarded -" and the :catch clause is ignored (also for the error or interrupt -" exception being thrown then). -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try - try - Xpath 1 " X: 1 - throw "arrgh" - Xpath 2 " X: 0 -" if 1 - Xpath 4 " X: 0 - " error after :throw: missing :endif - catch /.*/ - Xpath 8 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - catch /.*/ - Xpath 16 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - Xpath 32 " X: 0 - catch /arrgh/ - Xpath 64 " X: 0 - endtry - Xpath 128 " X: 0 -endif - -if ExtraVim() - function! E() - try - try - Xpath 256 " X: 256 - throw "arrgh" - Xpath 512 " X: 0 -" if 1 - Xpath 1024 " X: 0 - " error after :throw: missing :endif - catch /.*/ - Xpath 2048 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - catch /.*/ - Xpath 4096 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - Xpath 8192 " X: 0 - catch /arrgh/ - Xpath 16384 " X: 0 - endtry - endfunction - - call E() - Xpath 32768 " X: 0 -endif - -if ExtraVim() - try - try - Xpath 65536 " X: 65536 - throw "arrgh" - Xpath 131072 " X: 0 - catch /.*/ "INTERRUPT - Xpath 262144 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - catch /.*/ - Xpath 524288 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - Xpath 1048576 " X: 0 - catch /arrgh/ - Xpath 2097152 " X: 0 - endtry - Xpath 4194304 " X: 0 -endif - -if ExtraVim() - function I() - try - try - Xpath 8388608 " X: 8388608 - throw "arrgh" - Xpath 16777216 " X: 0 - catch /.*/ "INTERRUPT - Xpath 33554432 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - catch /.*/ - Xpath 67108864 " X: 0 - Xout v:exception "in" ExtraVimThrowpoint() - endtry - Xpath 134217728 " X: 0 - catch /arrgh/ - Xpath 268435456 " X: 0 - endtry - endfunction - - call I() - Xpath 536870912 " X: 0 -endif - -Xcheck 8454401 - - -"------------------------------------------------------------------------------- -" Test 83: Executing :finally clauses after an error or interrupt {{{1 -" -" When an exception is thrown and an error or interrupt occurs before -" the :finally of the innermost :try is reached, the exception is -" discarded and the :finally clause is executed. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - try - Xpath 1 " X: 1 - try - Xpath 2 " X: 2 - throw "arrgh" - Xpath 4 " X: 0 -" if 1 - Xpath 8 " X: 0 - " error after :throw: missing :endif - finally - Xpath 16 " X: 16 - endtry - Xpath 32 " X: 0 - catch /arrgh/ - Xpath 64 " X: 0 - endtry - Xpath 128 " X: 0 -endif - -if ExtraVim() - try - Xpath 256 " X: 256 - try - Xpath 512 " X: 512 - throw "arrgh" - Xpath 1024 " X: 0 - finally "INTERRUPT - Xpath 2048 " X: 2048 - endtry - Xpath 4096 " X: 0 - catch /arrgh/ - Xpath 8192 " X: 0 - endtry - Xpath 16384 " X: 0 -endif - -Xcheck 2835 - - -"------------------------------------------------------------------------------- -" Test 84: Exceptions in autocommand sequences. {{{1 -" -" When an exception occurs in a sequence of autocommands for -" a specific event, the rest of the sequence is not executed. The -" command that triggered the autocommand execution aborts, and the -" exception is propagated to the caller. -" -" For the FuncUndefined event under a function call expression or -" :call command, the function is not executed, even when it has -" been defined by the autocommands before the exception occurred. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - function! INT() - "INTERRUPT - let dummy = 0 - endfunction - - aug TMP - autocmd! - - autocmd User x1 Xpath 1 " X: 1 - autocmd User x1 throw "x1" - autocmd User x1 Xpath 2 " X: 0 - - autocmd User x2 Xpath 4 " X: 4 - autocmd User x2 asdf - autocmd User x2 Xpath 8 " X: 0 - - autocmd User x3 Xpath 16 " X: 16 - autocmd User x3 call INT() - autocmd User x3 Xpath 32 " X: 0 - - autocmd FuncUndefined U1 function! U1() - autocmd FuncUndefined U1 Xpath 64 " X: 0 - autocmd FuncUndefined U1 endfunction - autocmd FuncUndefined U1 Xpath 128 " X: 128 - autocmd FuncUndefined U1 throw "U1" - autocmd FuncUndefined U1 Xpath 256 " X: 0 - - autocmd FuncUndefined U2 function! U2() - autocmd FuncUndefined U2 Xpath 512 " X: 0 - autocmd FuncUndefined U2 endfunction - autocmd FuncUndefined U2 Xpath 1024 " X: 1024 - autocmd FuncUndefined U2 ASDF - autocmd FuncUndefined U2 Xpath 2048 " X: 0 - - autocmd FuncUndefined U3 function! U3() - autocmd FuncUndefined U3 Xpath 4096 " X: 0 - autocmd FuncUndefined U3 endfunction - autocmd FuncUndefined U3 Xpath 8192 " X: 8192 - autocmd FuncUndefined U3 call INT() - autocmd FuncUndefined U3 Xpath 16384 " X: 0 - aug END - - try - try - Xpath 32768 " X: 32768 - doautocmd User x1 - catch /x1/ - Xpath 65536 " X: 65536 - endtry - - while 1 - try - Xpath 131072 " X: 131072 - let caught = 0 - doautocmd User x2 - catch /asdf/ - let caught = 1 - finally - Xpath 262144 " X: 262144 - if !caught && !$VIMNOERRTHROW - Xpath 524288 " X: 0 - " Propagate uncaught error exception, - else - " ... but break loop for caught error exception, - " or discard error and break loop if $VIMNOERRTHROW - break - endif - endtry - endwhile - - while 1 - try - Xpath 1048576 " X: 1048576 - let caught = 0 - doautocmd User x3 - catch /Vim:Interrupt/ - let caught = 1 - finally - Xpath 2097152 " X: 2097152 - if !caught && !$VIMNOINTTHROW - Xpath 4194304 " X: 0 - " Propagate uncaught interrupt exception, - else - " ... but break loop for caught interrupt exception, - " or discard interrupt and break loop if $VIMNOINTTHROW - break - endif - endtry - endwhile - - if exists("*U1") | delfunction U1 | endif - if exists("*U2") | delfunction U2 | endif - if exists("*U3") | delfunction U3 | endif - - try - Xpath 8388608 " X: 8388608 - call U1() - catch /U1/ - Xpath 16777216 " X: 16777216 - endtry - - while 1 - try - Xpath 33554432 " X: 33554432 - let caught = 0 - call U2() - catch /ASDF/ - let caught = 1 - finally - Xpath 67108864 " X: 67108864 - if !caught && !$VIMNOERRTHROW - Xpath 134217728 " X: 0 - " Propagate uncaught error exception, - else - " ... but break loop for caught error exception, - " or discard error and break loop if $VIMNOERRTHROW - break - endif - endtry - endwhile - - while 1 - try - Xpath 268435456 " X: 268435456 - let caught = 0 - call U3() - catch /Vim:Interrupt/ - let caught = 1 - finally - Xpath 536870912 " X: 536870912 - if !caught && !$VIMNOINTTHROW - Xpath 1073741824 " X: 0 - " Propagate uncaught interrupt exception, - else - " ... but break loop for caught interrupt exception, - " or discard interrupt and break loop if $VIMNOINTTHROW - break - endif - endtry - endwhile - catch /.*/ - " The Xpath command does not accept 2^31 (negative); display explicitly: - exec "!echo 2147483648 >>" . g:ExtraVimResult - Xout "Caught" v:exception "in" v:throwpoint - endtry - - unlet caught - delfunction INT - delfunction U1 - delfunction U2 - delfunction U3 - au! TMP - aug! TMP -endif - -Xcheck 934782101 - - -"------------------------------------------------------------------------------- -" Test 85: Error exceptions in autocommands for I/O command events {{{1 -" -" When an I/O command is inside :try/:endtry, autocommands to be -" executed after it should be skipped on an error (exception) in the -" command itself or in autocommands to be executed before the command. -" In the latter case, the I/O command should not be executed either. -" Example 1: BufWritePre, :write, BufWritePost -" Example 2: FileReadPre, :read, FileReadPost. -"------------------------------------------------------------------------------- - -XpathINIT - -function! MSG(enr, emsg) - let english = v:lang == "C" || v:lang =~ '^[Ee]n' - if a:enr == "" - Xout "TODO: Add message number for:" a:emsg - let v:errmsg = ":" . v:errmsg - endif - let match = 1 - if v:errmsg !~ '^'.a:enr.':' || (english && v:errmsg !~ a:emsg) - let match = 0 - if v:errmsg == "" - Xout "Message missing." - else - let v:errmsg = escape(v:errmsg, '"') - Xout "Unexpected message:" v:errmsg - endif - endif - return match -endfunction - -" Remove the autocommands for the events specified as arguments in all used -" autogroups. -function Delete_autocommands(...) - let augfile = tempname() - while 1 - try - exec "redir >" . augfile - aug - redir END - exec "edit" augfile - g/^$/d - norm G$ - let wrap = "w" - while search('\%( \|^\)\@<=.\{-}\%( \)\@=', wrap) > 0 - let wrap = "W" - exec "norm y/ \n" - let argno = 1 - while argno <= a:0 - exec "au!" escape(@", " ") a:{argno} - let argno = argno + 1 - endwhile - endwhile - catch /.*/ - finally - bwipeout! - call delete(augfile) - break " discard errors for $VIMNOERRTHROW - endtry - endwhile -endfunction - -call Delete_autocommands("BufWritePre", "BufWritePost") - -while 1 - try - try - let post = 0 - aug TMP - au! BufWritePost * let post = 1 - aug END - let caught = 0 - write /n/o/n/e/x/i/s/t/e/n/t - catch /^Vim(write):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(write):', '', "") - finally - Xpath 1 " X: 1 - if !caught && !$VIMNOERRTHROW - Xpath 2 " X: 0 - endif - let v:errmsg = substitute(v:errmsg, '^"/n/o/n/e/x/i/s/t/e/n/t" ', - \ '', "") - if !MSG('E212', "Can't open file for writing") - Xpath 4 " X: 0 - endif - if post - Xpath 8 " X: 0 - Xout "BufWritePost commands executed after write error" - endif - au! TMP - aug! TMP - endtry - catch /.*/ - Xpath 16 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - try - let post = 0 - aug TMP - au! BufWritePre * asdf - au! BufWritePost * let post = 1 - aug END - let tmpfile = tempname() - let caught = 0 - exec "write" tmpfile - catch /^Vim\((write)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((write)\)\=:', '', "") - finally - Xpath 32 " X: 32 - if !caught && !$VIMNOERRTHROW - Xpath 64 " X: 0 - endif - let v:errmsg = substitute(v:errmsg, '^"'.tmpfile.'" ', '', "") - if !MSG('E492', "Not an editor command") - Xpath 128 " X: 0 - endif - if filereadable(tmpfile) - Xpath 256 " X: 0 - Xout ":write command not suppressed after BufWritePre error" - endif - if post - Xpath 512 " X: 0 - Xout "BufWritePost commands executed after BufWritePre error" - endif - au! TMP - aug! TMP - endtry - catch /.*/ - Xpath 1024 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -call delete(tmpfile) - -call Delete_autocommands("BufWritePre", "BufWritePost", - \ "BufReadPre", "BufReadPost", "FileReadPre", "FileReadPost") - -while 1 - try - try - let post = 0 - aug TMP - au! FileReadPost * let post = 1 - aug END - let caught = 0 - read /n/o/n/e/x/i/s/t/e/n/t - catch /^Vim(read):/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim(read):', '', "") - finally - Xpath 2048 " X: 2048 - if !caught && !$VIMNOERRTHROW - Xpath 4096 " X: 0 - endif - let v:errmsg = substitute(v:errmsg, ' /n/o/n/e/x/i/s/t/e/n/t$', - \ '', "") - if !MSG('E484', "Can't open file") - Xpath 8192 " X: 0 - endif - if post - Xpath 16384 " X: 0 - Xout "FileReadPost commands executed after write error" - endif - au! TMP - aug! TMP - endtry - catch /.*/ - Xpath 32768 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -while 1 - try - let infile = tempname() - let tmpfile = tempname() - exec "!echo XYZ >" . infile - exec "edit" tmpfile - try - Xpath 65536 " X: 65536 - try - let post = 0 - aug TMP - au! FileReadPre * asdf - au! FileReadPost * let post = 1 - aug END - let caught = 0 - exec "0read" infile - catch /^Vim\((read)\)\=:/ - let caught = 1 - let v:errmsg = substitute(v:exception, '^Vim\((read)\)\=:', '', - \ "") - finally - Xpath 131072 " X: 131072 - if !caught && !$VIMNOERRTHROW - Xpath 262144 " X: 0 - endif - let v:errmsg = substitute(v:errmsg, ' '.infile.'$', '', "") - if !MSG('E492', "Not an editor command") - Xpath 524288 " X: 0 - endif - if getline("1") == "XYZ" - Xpath 1048576 " X: 0 - Xout ":read command not suppressed after FileReadPre error" - endif - if post - Xpath 2097152 " X: 0 - Xout "FileReadPost commands executed after " . - \ "FileReadPre error" - endif - au! TMP - aug! TMP - endtry - finally - bwipeout! - endtry - catch /.*/ - Xpath 4194304 " X: 0 - Xout v:exception "in" v:throwpoint - finally - break " discard error for $VIMNOERRTHROW - endtry -endwhile - -call delete(infile) -call delete(tmpfile) -unlet! caught post infile tmpfile -delfunction MSG -delfunction Delete_autocommands - -Xcheck 198689 - -"------------------------------------------------------------------------------- -" Test 86: setloclist crash {{{1 -" -" Executing a setloclist() on BufUnload shouldn't crash Vim -"------------------------------------------------------------------------------- - -func F - au BufUnload * :call setloclist(0, [{'bufnr':1, 'lnum':1, 'col':1, 'text': 'tango down'}]) - - :lvimgrep /.*/ *.mak -endfunc - -XpathINIT - -ExecAsScript F - -delfunction F -Xout "No Crash for vimgrep on BufUnload" -Xcheck 0 - -" Test 87 was moved to test_vimscript.vim -let Xtest = 88 - - -"------------------------------------------------------------------------------- -" Test 88: $VIMNOERRTHROW and $VIMNOINTTHROW support {{{1 -" -" It is possible to configure Vim for throwing exceptions on error -" or interrupt, controlled by variables $VIMNOERRTHROW and -" $VIMNOINTTHROW. This is just for increasing the number of tests. -" All tests here should run for all four combinations of setting -" these variables to 0 or 1. The variables are intended for the -" development phase only. In the final release, Vim should be -" configured to always use error and interrupt exceptions. -" -" The test result is "OK", -" -" - if the $VIMNOERRTHROW and the $VIMNOINTTHROW control are not -" configured and exceptions are thrown on error and on -" interrupt. -" -" - if the $VIMNOERRTHROW or the $VIMNOINTTHROW control is -" configured and works as intended. -" -" What actually happens, is shown in the test output. -" -" Otherwise, the test result is "FAIL", and the test output describes -" the problem. -" -" IMPORTANT: This must be the last test because it sets $VIMNOERRTHROW and -" $VIMNOINTTHROW. -"------------------------------------------------------------------------------- - -XpathINIT - -if ExtraVim() - - function! ThrowOnError() - XloopNEXT - let caught = 0 - try - Xloop 1 " X: 1 + 8 + 64 - asdf - catch /.*/ - let caught = 1 " error exception caught - finally - Xloop 2 " X: 2 + 16 + 128 - return caught " discard aborting error - endtry - Xloop 4 " X: 0 - endfunction - - let quits_skipped = 0 - - function! ThrowOnInterrupt() - XloopNEXT - let caught = 0 - try - Xloop 1 " X: (1 + 8 + 64) * 512 - "INTERRUPT3 - let dummy = 0 - let g:quits_skipped = g:quits_skipped + 1 - catch /.*/ - let caught = 1 " interrupt exception caught - finally - Xloop 2 " X: (2 + 16 + 128) * 512 - return caught " discard interrupt - endtry - Xloop 4 " X: 0 - endfunction - - function! CheckThrow(Type) - execute 'return ThrowOn' . a:Type . '()' - endfunction - - function! CheckConfiguration(type) " type is "error" or "interrupt" - - let type = a:type - let Type = substitute(type, '.*', '\u&', "") - let VAR = '$VIMNO' . substitute(type, '\(...\).*', '\U\1', "") . 'THROW' - - if type == "error" - XloopINIT! 1 8 - elseif type == "interrupt" - XloopINIT! 512 8 - endif - - exec 'let requested_for_tests = exists(VAR) && ' . VAR . ' == 0' - exec 'let suppressed_for_tests = ' . VAR . ' != 0' - let used_in_tests = CheckThrow(Type) - - exec 'let ' . VAR . ' = 0' - let request_works = CheckThrow(Type) - - exec 'let ' . VAR . ' = 1' - let suppress_works = !CheckThrow(Type) - - if type == "error" - XloopINIT! 262144 8 - elseif type == "interrupt" - XloopINIT! 2097152 8 - - if g:quits_skipped != 0 - Xloop 1 " X: 0*2097152 - Xout "Test environment error. Interrupt breakpoints skipped: " - \ . g:quits_skipped . ".\n" - \ . "Cannot check whether interrupt exceptions are thrown." - return - endif - endif - - let failure = - \ !suppressed_for_tests && !used_in_tests - \ || !request_works - - let contradiction = - \ used_in_tests - \ ? suppressed_for_tests && !request_works - \ : !suppressed_for_tests - - if failure - " Failure in configuration. - Xloop 2 " X: 0 * 2* (262144 + 2097152) - elseif contradiction - " Failure in test logic. Should not happen. - Xloop 4 " X: 0 * 4 * (262144 + 2097152) - endif - - let var_control_configured = - \ request_works != used_in_tests - \ || suppress_works == used_in_tests - - let var_control_not_configured = - \ requested_for_tests || suppressed_for_tests - \ ? request_works && !suppress_works - \ : request_works == used_in_tests - \ && suppress_works != used_in_tests - - let with = used_in_tests ? "with" : "without" - - let set = suppressed_for_tests ? "non-zero" : - \ requested_for_tests ? "0" : "unset" - - let although = contradiction && !var_control_not_configured - \ ? ",\nalthough " - \ : ".\n" - - let output = "All tests were run " . with . " throwing exceptions on " - \ . type . although - - if !var_control_not_configured - let output = output . VAR . " was " . set . "." - - if !request_works && !requested_for_tests - let output = output . - \ "\n" . Type . " exceptions are not thrown when " . VAR . - \ " is\nset to 0." - endif - - if !suppress_works && (!used_in_tests || - \ !request_works && - \ !requested_for_tests && !suppressed_for_tests) - let output = output . - \ "\n" . Type . " exceptions are thrown when " . VAR . - \ " is set to 1." - endif - - if !failure && var_control_configured - let output = output . - \ "\nRun tests also with " . substitute(VAR, '^\$', '', "") - \ . "=" . used_in_tests . "." - \ . "\nThis is for testing in the development phase only." - \ . " Remove the \n" - \ . VAR . " control in the final release." - endif - else - let output = output . - \ "The " . VAR . " control is not configured." - endif - - Xout output - endfunction - - call CheckConfiguration("error") - Xpath 16777216 " X: 16777216 - call CheckConfiguration("interrupt") - Xpath 33554432 " X: 33554432 -endif - -Xcheck 50443995 - -" IMPORTANT: No test should be added after this test because it changes -" $VIMNOERRTHROW and $VIMNOINTTHROW. - - -"------------------------------------------------------------------------------- -" Modelines {{{1 -" vim: ts=8 sw=4 tw=80 fdm=marker -"------------------------------------------------------------------------------- diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index a83ef50abc..a3d240f27e 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -15,7 +15,6 @@ source test_fnamemodify.vim source test_ga.vim source test_glob2regpat.vim source test_global.vim -source test_lispwords.vim source test_move.vim source test_put.vim source test_reltime.vim diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index 521c3fcd57..fb8b17cd16 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -1,5 +1,6 @@ " Test argument list commands +source check.vim source shared.vim source term_util.vim @@ -417,15 +418,19 @@ func Test_argdedupe() call Reset_arglist() argdedupe call assert_equal([], argv()) + args a a a aa b b a b aa argdedupe call assert_equal(['a', 'aa', 'b'], argv()) + args a b c argdedupe call assert_equal(['a', 'b', 'c'], argv()) + args a argdedupe call assert_equal(['a'], argv()) + args a A b B argdedupe if has('fname_case') @@ -433,11 +438,17 @@ func Test_argdedupe() else call assert_equal(['a', 'b'], argv()) endif + args a b a c a b last argdedupe next call assert_equal('c', expand('%:t')) + + args a ./a + argdedupe + call assert_equal(['a'], argv()) + %argd endfunc @@ -509,18 +520,14 @@ func Test_arglist_autocmd() new " redefine arglist; go to Xxx1 next! Xxx1 Xxx2 Xxx3 - " open window for all args; Reading Xxx2 will change the arglist and the - " third window will get Xxx1: - " win 1: Xxx1 - " win 2: Xxx2 - " win 3: Xxx1 - all + " open window for all args; Reading Xxx2 will try to change the arglist and + " that will fail + call assert_fails("all", "E1156:") call assert_equal('test file Xxx1', getline(1)) wincmd w - wincmd w - call assert_equal('test file Xxx1', getline(1)) - rewind call assert_equal('test file Xxx2', getline(1)) + wincmd w + call assert_equal('test file Xxx3', getline(1)) autocmd! BufReadPost Xxx2 enew! | only @@ -554,11 +561,9 @@ func Test_argdo() bwipe Xa.c Xb.c Xc.c endfunc -" Test for quiting Vim with unedited files in the argument list +" Test for quitting Vim with unedited files in the argument list func Test_quit_with_arglist() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run vim in terminal' - endif + CheckRunVimInTerminal let buf = RunVimInTerminal('', {'rows': 6}) call term_sendkeys(buf, ":set nomore\n") call term_sendkeys(buf, ":args a b c\n") @@ -591,4 +596,153 @@ func Test_quit_with_arglist() call delete('.c.swp') endfunc +" Test for ":all" not working when in the cmdline window +func Test_all_not_allowed_from_cmdwin() + au BufEnter * all + next x + " Use try/catch here, somehow assert_fails() doesn't work on MS-Windows + " console. + let caught = 'no' + try + exe ":norm! 7q?apat\<CR>" + catch /E11:/ + let caught = 'yes' + endtry + call assert_equal('yes', caught) + au! BufEnter +endfunc + +func Test_clear_arglist_in_all() + n 0 00 000 0000 00000 000000 + au WinNew 0 n 0 + call assert_fails("all", "E1156") + au! * +endfunc + +" Test for the :all command +func Test_all_command() + %argdelete + + " :all command should not close windows with files in the argument list, + " but can rearrange the windows. + args Xargnew1 Xargnew2 + %bw! + edit Xargold1 + split Xargnew1 + let Xargnew1_winid = win_getid() + split Xargold2 + split Xargnew2 + let Xargnew2_winid = win_getid() + split Xargold3 + all + call assert_equal(2, winnr('$')) + call assert_equal([Xargnew1_winid, Xargnew2_winid], + \ [win_getid(1), win_getid(2)]) + call assert_equal([bufnr('Xargnew1'), bufnr('Xargnew2')], + \ [winbufnr(1), winbufnr(2)]) + + " :all command should close windows for files which are not in the + " argument list in the current tab page. + %bw! + edit Xargold1 + split Xargold2 + tabedit Xargold3 + split Xargold4 + tabedit Xargold5 + tabfirst + all + call assert_equal(3, tabpagenr('$')) + call assert_equal([bufnr('Xargnew1'), bufnr('Xargnew2')], tabpagebuflist(1)) + call assert_equal([bufnr('Xargold4'), bufnr('Xargold3')], tabpagebuflist(2)) + call assert_equal([bufnr('Xargold5')], tabpagebuflist(3)) + + " :tab all command should close windows for files which are not in the + " argument list across all the tab pages. + %bw! + edit Xargold1 + split Xargold2 + tabedit Xargold3 + split Xargold4 + tabedit Xargold5 + tabfirst + args Xargnew1 Xargnew2 + tab all + call assert_equal(2, tabpagenr('$')) + call assert_equal([bufnr('Xargnew1')], tabpagebuflist(1)) + call assert_equal([bufnr('Xargnew2')], tabpagebuflist(2)) + + " If a count is specified, then :all should open only that many windows. + %bw! + args Xargnew1 Xargnew2 Xargnew3 Xargnew4 Xargnew5 + all 3 + call assert_equal(3, winnr('$')) + call assert_equal([bufnr('Xargnew1'), bufnr('Xargnew2'), bufnr('Xargnew3')], + \ [winbufnr(1), winbufnr(2), winbufnr(3)]) + + " The :all command should not open more than 'tabpagemax' tab pages. + " If there are more files, then they should be opened in the last tab page. + %bw! + set tabpagemax=3 + tab all + call assert_equal(3, tabpagenr('$')) + call assert_equal([bufnr('Xargnew1')], tabpagebuflist(1)) + call assert_equal([bufnr('Xargnew2')], tabpagebuflist(2)) + call assert_equal([bufnr('Xargnew3'), bufnr('Xargnew4'), bufnr('Xargnew5')], + \ tabpagebuflist(3)) + set tabpagemax& + + " Without the 'hidden' option, modified buffers should not be closed. + args Xargnew1 Xargnew2 + %bw! + edit Xargtemp1 + call setline(1, 'temp buffer 1') + split Xargtemp2 + call setline(1, 'temp buffer 2') + all + call assert_equal(4, winnr('$')) + call assert_equal([bufnr('Xargtemp2'), bufnr('Xargtemp1'), bufnr('Xargnew1'), + \ bufnr('Xargnew2')], + \ [winbufnr(1), winbufnr(2), winbufnr(3), winbufnr(4)]) + + " With the 'hidden' option set, both modified and unmodified buffers in + " closed windows should be hidden. + set hidden + all + call assert_equal(2, winnr('$')) + call assert_equal([bufnr('Xargnew1'), bufnr('Xargnew2')], + \ [winbufnr(1), winbufnr(2)]) + call assert_equal([1, 1, 0, 0], [getbufinfo('Xargtemp1')[0].hidden, + \ getbufinfo('Xargtemp2')[0].hidden, + \ getbufinfo('Xargnew1')[0].hidden, + \ getbufinfo('Xargnew2')[0].hidden]) + set nohidden + + " When 'winheight' is set to a large value, :all should open only one + " window. + args Xargnew1 Xargnew2 Xargnew3 Xargnew4 Xargnew5 + %bw! + set winheight=9999 + call assert_fails('all', 'E36:') + call assert_equal([1, bufnr('Xargnew1')], [winnr('$'), winbufnr(1)]) + set winheight& + + " When 'winwidth' is set to a large value, :vert all should open only one + " window. + %bw! + set winwidth=9999 + call assert_fails('vert all', 'E36:') + call assert_equal([1, bufnr('Xargnew1')], [winnr('$'), winbufnr(1)]) + set winwidth& + + " empty argument list tests + %bw! + %argdelete + call assert_equal('', execute('args')) + all + call assert_equal(1, winnr('$')) + + %argdelete + %bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim index fdd8b0bef6..431908e95c 100644 --- a/src/nvim/testdir/test_assert.vim +++ b/src/nvim/testdir/test_assert.vim @@ -48,6 +48,11 @@ func Test_assert_equal() call assert_equal('XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX') call assert_match("Expected 'X\\\\\\[x occurs 21 times]X' but got 'X\\\\\\[y occurs 25 times]X'", v:errors[0]) call remove(v:errors, 0) + + " special characters are escaped + call assert_equal("\b\e\f\n\t\r\\\x01\x7f", 'x') + call assert_match('Expected ''\\b\\e\\f\\n\\t\\r\\\\\\x01\\x7f'' but got ''x''', v:errors[0]) + call remove(v:errors, 0) endfunc func Test_assert_equal_dict() @@ -146,6 +151,14 @@ func Test_assert_exception() try nocommand catch + call assert_equal(1, assert_exception('E12345:')) + endtry + call assert_match("Expected 'E12345:' but got 'Vim:E492: ", v:errors[0]) + call remove(v:errors, 0) + + try + nocommand + catch try " illegal argument, get NULL for error call assert_equal(1, assert_exception([])) @@ -153,6 +166,10 @@ func Test_assert_exception() call assert_equal(0, assert_exception('E730:')) endtry endtry + + call assert_equal(1, assert_exception('E492:')) + call assert_match('v:exception is not set', v:errors[0]) + call remove(v:errors, 0) endfunc func Test_wrong_error_type() @@ -202,6 +219,14 @@ func Test_assert_fail_fails() call assert_match("stupid: Expected 'E9876' but got 'E492:", v:errors[0]) call remove(v:errors, 0) + call assert_equal(1, assert_fails('xxx', ['E9876'])) + call assert_match("Expected \\['E9876'\\] but got 'E492:", v:errors[0]) + call remove(v:errors, 0) + + call assert_equal(1, assert_fails('xxx', ['E492:', 'E9876'])) + call assert_match("Expected \\['E492:', 'E9876'\\] but got 'E492:", v:errors[0]) + call remove(v:errors, 0) + call assert_equal(1, assert_fails('echo', '', 'echo command')) call assert_match("command did not fail: echo command", v:errors[0]) call remove(v:errors, 0) @@ -209,6 +234,41 @@ func Test_assert_fail_fails() call assert_equal(1, 'echo'->assert_fails('', 'echo command')) call assert_match("command did not fail: echo command", v:errors[0]) call remove(v:errors, 0) + + try + call assert_equal(1, assert_fails('xxx', [])) + catch + let exp = v:exception + endtry + call assert_match("E856: assert_fails() second argument", exp) + + try + call assert_equal(1, assert_fails('xxx', ['1', '2', '3'])) + catch + let exp = v:exception + endtry + call assert_match("E856: assert_fails() second argument", exp) + + try + call assert_equal(1, assert_fails('xxx', #{one: 1})) + catch + let exp = v:exception + endtry + call assert_match("E856: assert_fails() second argument", exp) + + try + call assert_equal(1, assert_fails('xxx', 'E492', '', 'burp')) + catch + let exp = v:exception + endtry + call assert_match("E1115: assert_fails() fourth argument must be a number", exp) + + try + call assert_equal(1, assert_fails('xxx', 'E492', '', 54, 123)) + catch + let exp = v:exception + endtry + call assert_match("E1116: assert_fails() fifth argument must be a string", exp) endfunc func Test_assert_fails_in_try_block() @@ -278,19 +338,18 @@ func Test_assert_with_msg() endfunc func Test_mouse_position() - throw 'Skipped: Nvim does not have test_setmouse()' let save_mouse = &mouse set mouse=a new call setline(1, ['line one', 'line two']) call assert_equal([0, 1, 1, 0], getpos('.')) - call test_setmouse(1, 5) + call Ntest_setmouse(1, 5) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 1, 5, 0], getpos('.')) - call test_setmouse(2, 20) + call Ntest_setmouse(2, 20) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 2, 8, 0], getpos('.')) - call test_setmouse(5, 1) + call Ntest_setmouse(5, 1) call feedkeys("\<LeftMouse>", "xt") call assert_equal([0, 2, 1, 0], getpos('.')) bwipe! diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim index 4229095f9f..a8810047a0 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -122,9 +122,7 @@ endfunc func Test_multibyte() " using an invalid character should not cause a crash set wic - " Except on Windows, E472 is also thrown last, but v8.1.1183 isn't ported yet - " call assert_fails('tc *', has('win32') ? 'E480:' : 'E344:') - call assert_fails('tc *', has('win32') ? 'E480:' : 'E472:') + call assert_fails('tc *', has('win32') ? 'E480:' : 'E344:') set nowic endfunc diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 716511210d..83af0f6be0 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -4,6 +4,7 @@ source shared.vim source check.vim source term_util.vim source screendump.vim +source load.vim func s:cleanup_buffers() abort for bnr in range(1, bufnr('$')) @@ -20,8 +21,35 @@ func Test_vim_did_enter() " becomes one. endfunc +" Test for the CursorHold autocmd +func Test_CursorHold_autocmd() + CheckRunVimInTerminal + call writefile(['one', 'two', 'three'], 'Xfile') + let before =<< trim END + set updatetime=10 + au CursorHold * call writefile([line('.')], 'Xoutput', 'a') + END + call writefile(before, 'Xinit') + let buf = RunVimInTerminal('-S Xinit Xfile', {}) + call term_sendkeys(buf, "G") + call term_wait(buf, 20) + call term_sendkeys(buf, "gg") + call term_wait(buf) + call WaitForAssert({-> assert_equal(['1'], readfile('Xoutput')[-1:-1])}) + call term_sendkeys(buf, "j") + call term_wait(buf) + call WaitForAssert({-> assert_equal(['1', '2'], readfile('Xoutput')[-2:-1])}) + call term_sendkeys(buf, "j") + call term_wait(buf) + call WaitForAssert({-> assert_equal(['1', '2', '3'], readfile('Xoutput')[-3:-1])}) + call StopVimInTerminal(buf) + + call delete('Xinit') + call delete('Xoutput') + call delete('Xfile') +endfunc + if has('timers') - source load.vim func ExitInsertMode(id) call feedkeys("\<Esc>") @@ -148,6 +176,12 @@ func Test_autocmd_bufunload_with_tabnext() quit endfunc +func Test_argdelete_in_next() + au BufNew,BufEnter,BufLeave,BufWinEnter * argdel + call assert_fails('next a b', 'E1156:') + au! BufNew,BufEnter,BufLeave,BufWinEnter * +endfunc + func Test_autocmd_bufwinleave_with_tabfirst() tabedit augroup sample @@ -169,9 +203,7 @@ func Test_autocmd_bufunload_avoiding_SEGV_01() exe 'autocmd BufUnload <buffer> ' . (lastbuf + 1) . 'bwipeout!' augroup END - " Todo: check for E937 generated first - " call assert_fails('edit bb.txt', 'E937:') - call assert_fails('edit bb.txt', 'E517:') + call assert_fails('edit bb.txt', 'E937:') autocmd! test_autocmd_bufunload augroup! test_autocmd_bufunload @@ -263,6 +295,61 @@ func Test_win_tab_autocmd() unlet g:record endfunc +func Test_WinResized() + CheckRunVimInTerminal + + let lines =<< trim END + set scrolloff=0 + call setline(1, ['111', '222']) + vnew + call setline(1, ['aaa', 'bbb']) + new + call setline(1, ['foo', 'bar']) + + let g:resized = 0 + au WinResized * let g:resized += 1 + + func WriteResizedEvent() + call writefile([json_encode(v:event)], 'XresizeEvent') + endfunc + au WinResized * call WriteResizedEvent() + END + call writefile(lines, 'Xtest_winresized', 'D') + let buf = RunVimInTerminal('-S Xtest_winresized', {'rows': 10}) + + " redraw now to avoid a redraw after the :echo command + call term_sendkeys(buf, ":redraw!\<CR>") + call TermWait(buf) + + call term_sendkeys(buf, ":echo g:resized\<CR>") + call WaitForAssert({-> assert_match('^0$', term_getline(buf, 10))}, 1000) + + " increase window height, two windows will be reported + call term_sendkeys(buf, "\<C-W>+") + call TermWait(buf) + call term_sendkeys(buf, ":echo g:resized\<CR>") + call WaitForAssert({-> assert_match('^1$', term_getline(buf, 10))}, 1000) + + let event = readfile('XresizeEvent')[0]->json_decode() + call assert_equal({ + \ 'windows': [1002, 1001], + \ }, event) + + " increase window width, three windows will be reported + call term_sendkeys(buf, "\<C-W>>") + call TermWait(buf) + call term_sendkeys(buf, ":echo g:resized\<CR>") + call WaitForAssert({-> assert_match('^2$', term_getline(buf, 10))}, 1000) + + let event = readfile('XresizeEvent')[0]->json_decode() + call assert_equal({ + \ 'windows': [1002, 1001, 1000], + \ }, event) + + call delete('XresizeEvent') + call StopVimInTerminal(buf) +endfunc + func Test_WinScrolled() CheckRunVimInTerminal @@ -273,13 +360,17 @@ func Test_WinScrolled() endfor let win_id = win_getid() let g:matched = v:false + func WriteScrollEvent() + call writefile([json_encode(v:event)], 'XscrollEvent') + endfunc execute 'au WinScrolled' win_id 'let g:matched = v:true' let g:scrolled = 0 au WinScrolled * let g:scrolled += 1 au WinScrolled * let g:amatch = str2nr(expand('<amatch>')) au WinScrolled * let g:afile = str2nr(expand('<afile>')) + au WinScrolled * call WriteScrollEvent() END - call writefile(lines, 'Xtest_winscrolled') + call writefile(lines, 'Xtest_winscrolled', 'D') let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6}) call term_sendkeys(buf, ":echo g:scrolled\<CR>") @@ -289,15 +380,33 @@ func Test_WinScrolled() call term_sendkeys(buf, "zlzh:echo g:scrolled\<CR>") call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000) + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 1, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': -1, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + " Scroll up/down in Normal mode. call term_sendkeys(buf, "\<c-e>\<c-y>:echo g:scrolled\<CR>") call WaitForAssert({-> assert_match('^4 ', term_getline(buf, 6))}, 1000) + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': 0, 'topline': -1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + " Scroll up/down in Insert mode. call term_sendkeys(buf, "Mi\<c-x>\<c-e>\<Esc>i\<c-x>\<c-y>\<Esc>") call term_sendkeys(buf, ":echo g:scrolled\<CR>") call WaitForAssert({-> assert_match('^6 ', term_getline(buf, 6))}, 1000) + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': 0, 'topline': -1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + " Scroll the window horizontally to focus the last letter of the third line " containing only six characters. Moving to the previous and shorter lines " should trigger another autocommand as Vim has to make them visible. @@ -305,6 +414,12 @@ func Test_WinScrolled() call term_sendkeys(buf, ":echo g:scrolled\<CR>") call WaitForAssert({-> assert_match('^8 ', term_getline(buf, 6))}, 1000) + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 5, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': -5, 'topline': 0, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + " Ensure the command was triggered for the specified window ID. call term_sendkeys(buf, ":echo g:matched\<CR>") call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000) @@ -313,8 +428,38 @@ func Test_WinScrolled() call term_sendkeys(buf, ":echo g:amatch == win_id && g:afile == win_id\<CR>") call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000) + call delete('XscrollEvent') + call StopVimInTerminal(buf) +endfunc + +func Test_WinScrolled_mouse() + CheckRunVimInTerminal + + let lines =<< trim END + set nowrap scrolloff=0 + set mouse=a term=xterm ttymouse=sgr mousetime=200 clipboard= + call setline(1, ['foo']->repeat(32)) + split + let g:scrolled = 0 + au WinScrolled * let g:scrolled += 1 + END + call writefile(lines, 'Xtest_winscrolled_mouse', 'D') + let buf = RunVimInTerminal('-S Xtest_winscrolled_mouse', {'rows': 10}) + + " With the upper split focused, send a scroll-down event to the unfocused one. + call test_setmouse(7, 1) + call term_sendkeys(buf, "\<ScrollWheelDown>") + call TermWait(buf) + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^1', term_getline(buf, 10))}, 1000) + + " Again, but this time while we're in insert mode. + call term_sendkeys(buf, "i\<ScrollWheelDown>\<Esc>") + call TermWait(buf) + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^2', term_getline(buf, 10))}, 1000) + call StopVimInTerminal(buf) - call delete('Xtest_winscrolled') endfunc func Test_WinScrolled_close_curwin() @@ -327,7 +472,7 @@ func Test_WinScrolled_close_curwin() au WinScrolled * close au VimLeave * call writefile(['123456'], 'Xtestout') END - call writefile(lines, 'Xtest_winscrolled_close_curwin') + call writefile(lines, 'Xtest_winscrolled_close_curwin', 'D') let buf = RunVimInTerminal('-S Xtest_winscrolled_close_curwin', {'rows': 6}) " This was using freed memory @@ -335,12 +480,158 @@ func Test_WinScrolled_close_curwin() call TermWait(buf) call StopVimInTerminal(buf) + " check the startup script finished to the end call assert_equal(['123456'], readfile('Xtestout')) - - call delete('Xtest_winscrolled_close_curwin') call delete('Xtestout') endfunc +func Test_WinScrolled_once_only() + CheckRunVimInTerminal + + let lines =<< trim END + set cmdheight=2 + call setline(1, ['aaa', 'bbb']) + let trigger_count = 0 + func ShowInfo(id) + echo g:trigger_count g:winid winlayout() + endfunc + + vsplit + split + " use a timer to show the info after a redraw + au WinScrolled * let trigger_count += 1 | let winid = expand('<amatch>') | call timer_start(100, 'ShowInfo') + wincmd j + wincmd l + END + call writefile(lines, 'Xtest_winscrolled_once', 'D') + let buf = RunVimInTerminal('-S Xtest_winscrolled_once', #{rows: 10, cols: 60, statusoff: 2}) + + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_winscrolled_once_only_1', {}) + + call StopVimInTerminal(buf) +endfunc + +" Check that WinScrolled is not triggered immediately when defined and there +" are split windows. +func Test_WinScrolled_not_when_defined() + CheckRunVimInTerminal + + let lines =<< trim END + call setline(1, ['aaa', 'bbb']) + echo 'nothing happened' + func ShowTriggered(id) + echo 'triggered' + endfunc + END + call writefile(lines, 'Xtest_winscrolled_not', 'D') + let buf = RunVimInTerminal('-S Xtest_winscrolled_not', #{rows: 10, cols: 60, statusoff: 2}) + call term_sendkeys(buf, ":split\<CR>") + call TermWait(buf) + " use a timer to show the message after redrawing + call term_sendkeys(buf, ":au WinScrolled * call timer_start(100, 'ShowTriggered')\<CR>") + call VerifyScreenDump(buf, 'Test_winscrolled_not_when_defined_1', {}) + + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_winscrolled_not_when_defined_2', {}) + + call StopVimInTerminal(buf) +endfunc + +func Test_WinScrolled_long_wrapped() + CheckRunVimInTerminal + + let lines =<< trim END + set scrolloff=0 + let height = winheight(0) + let width = winwidth(0) + let g:scrolled = 0 + au WinScrolled * let g:scrolled += 1 + call setline(1, repeat('foo', height * width)) + call cursor(1, height * width) + END + call writefile(lines, 'Xtest_winscrolled_long_wrapped', 'D') + let buf = RunVimInTerminal('-S Xtest_winscrolled_long_wrapped', {'rows': 6}) + + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^0 ', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, 'gj') + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^1 ', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, '0') + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, '$') + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^3 ', term_getline(buf, 6))}, 1000) + + call StopVimInTerminal(buf) +endfunc + +func Test_WinScrolled_diff() + CheckRunVimInTerminal + + let lines =<< trim END + set diffopt+=foldcolumn:0 + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + vnew + call setline(1, ['d', 'e', 'f', 'g', 'h', 'i']) + windo diffthis + func WriteScrollEvent() + call writefile([json_encode(v:event)], 'XscrollEvent') + endfunc + au WinScrolled * call WriteScrollEvent() + END + call writefile(lines, 'Xtest_winscrolled_diff', 'D') + let buf = RunVimInTerminal('-S Xtest_winscrolled_diff', {'rows': 8}) + + call term_sendkeys(buf, "\<C-E>") + call WaitForAssert({-> assert_match('^d', term_getline(buf, 3))}, 1000) + + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 0, 'topline': 1, 'topfill': 1, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1001': {'leftcol': 0, 'topline': 0, 'topfill': -1, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + + call term_sendkeys(buf, "2\<C-E>") + call WaitForAssert({-> assert_match('^f', term_getline(buf, 3))}, 1000) + + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 0, 'topline': 2, 'topfill': 2, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': 0, 'topline': 2, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1001': {'leftcol': 0, 'topline': 0, 'topfill': -2, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + + call term_sendkeys(buf, "\<C-E>") + call WaitForAssert({-> assert_match('^g', term_getline(buf, 3))}, 1000) + + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 0, 'topline': 2, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1001': {'leftcol': 0, 'topline': 1, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + + call term_sendkeys(buf, "2\<C-Y>") + call WaitForAssert({-> assert_match('^e', term_getline(buf, 3))}, 1000) + + let event = readfile('XscrollEvent')[0]->json_decode() + call assert_equal({ + \ 'all': {'leftcol': 0, 'topline': 3, 'topfill': 1, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1000': {'leftcol': 0, 'topline': -2, 'topfill': 0, 'width': 0, 'height': 0, 'skipcol': 0}, + \ '1001': {'leftcol': 0, 'topline': -1, 'topfill': 1, 'width': 0, 'height': 0, 'skipcol': 0} + \ }, event) + + call StopVimInTerminal(buf) + call delete('XscrollEvent') +endfunc + func Test_WinClosed() " Test that the pattern is matched against the closed window's ID, and both " <amatch> and <afile> are set to it. @@ -414,6 +705,26 @@ func Test_WinClosed_throws_with_tabs() augroup! test-WinClosed endfunc +" This used to trigger WinClosed twice for the same window, and the window's +" buffer was NULL in the second autocommand. +func Test_WinClosed_switch_tab() + edit Xa + split Xb + split Xc + tab split + new + augroup test-WinClosed + autocmd WinClosed * tabprev | bwipe! + augroup END + close + " Check that the tabline has been fully removed + call assert_equal([1, 1], win_screenpos(0)) + + autocmd! test-WinClosed + augroup! test-WinClosed + %bwipe! +endfunc + func s:AddAnAutocmd() augroup vimBarTest au BufReadCmd * echo 'hello' @@ -424,17 +735,20 @@ endfunc func Test_early_bar() " test that a bar is recognized before the {event} call s:AddAnAutocmd() - augroup vimBarTest | au! | augroup END + augroup vimBarTest | au! | let done = 77 | augroup END call assert_equal(1, len(split(execute('au vimBarTest'), "\n"))) + call assert_equal(77, done) call s:AddAnAutocmd() - augroup vimBarTest| au!| augroup END + augroup vimBarTest| au!| let done = 88 | augroup END call assert_equal(1, len(split(execute('au vimBarTest'), "\n"))) + call assert_equal(88, done) " test that a bar is recognized after the {event} call s:AddAnAutocmd() - augroup vimBarTest| au!BufReadCmd| augroup END + augroup vimBarTest| au!BufReadCmd| let done = 99 | augroup END call assert_equal(1, len(split(execute('au vimBarTest'), "\n"))) + call assert_equal(99, done) " test that a bar is recognized after the {group} call s:AddAnAutocmd() @@ -474,6 +788,8 @@ func Test_augroup_warning() redir END call assert_notmatch("W19:", res) au! VimEnter + + call assert_fails('augroup!', 'E471:') endfunc func Test_BufReadCmdHelp() @@ -493,6 +809,28 @@ func Test_BufReadCmdHelpJump() au! BufReadCmd endfunc +" BufReadCmd is triggered for a "nofile" buffer. Check all values. +func Test_BufReadCmdNofile() + for val in ['nofile', + \ 'nowrite', + \ 'acwrite', + \ 'quickfix', + \ 'help', + "\ 'terminal', + \ 'prompt', + "\ 'popup', + \ ] + new somefile + exe 'set buftype=' .. val + au BufReadCmd somefile call setline(1, 'triggered') + edit + call assert_equal('triggered', getline(1)) + + au! BufReadCmd + bwipe! + endfor +endfunc + func Test_augroup_deleted() " This caused a crash before E936 was introduced augroup x @@ -587,9 +925,28 @@ func Test_BufEnter() " On MS-Windows we can't edit the directory, make sure we wipe the right " buffer. bwipe! Xdir - call delete('Xdir', 'd') au! BufEnter + + " Editing a "nofile" buffer doesn't read the file but does trigger BufEnter + " for historic reasons. Also test other 'buftype' values. + for val in ['nofile', + \ 'nowrite', + \ 'acwrite', + \ 'quickfix', + \ 'help', + "\ 'terminal', + \ 'prompt', + "\ 'popup', + \ ] + new somefile + exe 'set buftype=' .. val + au BufEnter somefile call setline(1, 'some text') + edit + call assert_equal('some text', getline(1)) + bwipe! + au! BufEnter + endfor endfunc " Closing a window might cause an endless loop @@ -1766,6 +2123,21 @@ func Test_BufReadCmd() au! BufWriteCmd endfunc +func Test_BufWriteCmd() + autocmd BufWriteCmd Xbufwritecmd let g:written = 1 + new + file Xbufwritecmd + set buftype=acwrite + call mkdir('Xbufwritecmd') + write + " BufWriteCmd should be triggered even if a directory has the same name + call assert_equal(1, g:written) + call delete('Xbufwritecmd', 'd') + unlet g:written + au! BufWriteCmd + bwipe! +endfunc + func SetChangeMarks(start, end) exe a:start .. 'mark [' exe a:end .. 'mark ]' @@ -1860,9 +2232,7 @@ func Test_change_mark_in_autocmds() endfunc func Test_Filter_noshelltemp() - if !executable('cat') - return - endif + CheckExecutable cat enew! call setline(1, ['a', 'b', 'c', 'd']) @@ -1939,6 +2309,7 @@ endfunc func Test_autocommand_all_events() call assert_fails('au * * bwipe', 'E1155:') call assert_fails('au * x bwipe', 'E1155:') + call assert_fails('au! * x bwipe', 'E1155:') endfunc func Test_autocmd_user() @@ -2026,9 +2397,8 @@ function Test_dirchanged_auto() endfunc " Test TextChangedI and TextChangedP -" See test/functional/viml/completion_spec.lua' func Test_ChangedP() - CheckFunction test_override + throw 'Skipped: use test/functional/editor/completion_spec.lua' new call setline(1, ['foo', 'bar', 'foobar']) call test_override("char_avail", 1) @@ -2549,7 +2919,7 @@ func Test_autocmd_CmdWinEnter() call term_sendkeys(buf, "q:") call term_wait(buf) call term_sendkeys(buf, ":echo b:dummy_var\<cr>") - call WaitForAssert({-> assert_match('^This is a dummy', term_getline(buf, 6))}, 1000) + call WaitForAssert({-> assert_match('^This is a dummy', term_getline(buf, 6))}, 2000) call term_sendkeys(buf, ":echo &buftype\<cr>") call WaitForAssert({-> assert_notmatch('^nofile', term_getline(buf, 6))}, 1000) call term_sendkeys(buf, ":echo winnr\<cr>") @@ -2636,6 +3006,17 @@ func Test_FileType_spell() setglobal spellfile= endfunc +" this was wiping out the current buffer and using freed memory +func Test_SpellFileMissing_bwipe() + next 0 + au SpellFileMissing 0 bwipe + call assert_fails('set spell spelllang=0', 'E937:') + + au! SpellFileMissing + set nospell spelllang=en + bwipe +endfunc + " Test closing a window or editing another buffer from a FileChangedRO handler " in a readonly buffer func Test_FileChangedRO_winclose() @@ -2724,6 +3105,31 @@ func Test_autocmd_FileReadCmd() delfunc ReadFileCmd endfunc +" Test for passing invalid arguments to autocmd +func Test_autocmd_invalid_args() + " Additional character after * for event + call assert_fails('autocmd *a Xfile set ff=unix', 'E215:') + augroup Test + augroup END + " Invalid autocmd event + call assert_fails('autocmd Bufabc Xfile set ft=vim', 'E216:') + " Invalid autocmd event in a autocmd group + call assert_fails('autocmd Test Bufabc Xfile set ft=vim', 'E216:') + augroup! Test + " Execute all autocmds + call assert_fails('doautocmd * BufEnter', 'E217:') + call assert_fails('augroup! x1a2b3', 'E367:') + call assert_fails('autocmd BufNew <buffer=999> pwd', 'E680:') + call assert_fails('autocmd BufNew \) set ff=unix', 'E55:') +endfunc + +" Test for deep nesting of autocmds +func Test_autocmd_deep_nesting() + autocmd BufEnter Xfile doautocmd BufEnter Xfile + call assert_fails('doautocmd BufEnter Xfile', 'E218:') + autocmd! BufEnter Xfile +endfunc + " Tests for SigUSR1 autocmd event, which is only available on posix systems. func Test_autocmd_sigusr1() CheckUnix @@ -2781,7 +3187,7 @@ func Test_BufDelete_changebuf() augroup END let save_cpo = &cpo set cpo+=f - call assert_fails('r Xfile', 'E484:') + call assert_fails('r Xfile', ['E812:', 'E484:']) call assert_equal('somefile', @%) let &cpo = save_cpo augroup TestAuCmd @@ -2871,6 +3277,15 @@ func Test_Visual_doautoall_redraw() %bwipe! endfunc +" This was using freed memory. +func Test_BufNew_arglocal() + arglocal + au BufNew * arglocal + call assert_fails('drop xx', 'E1156:') + + au! BufNew +endfunc + func Test_autocmd_closes_window() au BufNew,BufWinLeave * e %e file yyy @@ -3039,19 +3454,29 @@ func Test_mode_changes() call assert_equal(5, g:nori_to_any) endif - if has('cmdwin') - let g:n_to_c = 0 - au ModeChanged n:c let g:n_to_c += 1 - let g:c_to_n = 0 - au ModeChanged c:n let g:c_to_n += 1 - let g:mode_seq += ['c', 'n', 'c', 'n'] - call feedkeys("q:\<C-C>\<Esc>", 'tnix') - call assert_equal(len(g:mode_seq) - 1, g:index) - call assert_equal(2, g:n_to_c) - call assert_equal(2, g:c_to_n) - unlet g:n_to_c - unlet g:c_to_n - endif + let g:n_to_c = 0 + au ModeChanged n:c let g:n_to_c += 1 + let g:c_to_n = 0 + au ModeChanged c:n let g:c_to_n += 1 + let g:mode_seq += ['c', 'n', 'c', 'n'] + call feedkeys("q:\<C-C>\<Esc>", 'tnix') + call assert_equal(len(g:mode_seq) - 1, g:index) + call assert_equal(2, g:n_to_c) + call assert_equal(2, g:c_to_n) + unlet g:n_to_c + unlet g:c_to_n + + let g:n_to_v = 0 + au ModeChanged n:v let g:n_to_v += 1 + let g:v_to_n = 0 + au ModeChanged v:n let g:v_to_n += 1 + let g:mode_seq += ['v', 'n'] + call feedkeys("v\<C-C>", 'tnix') + call assert_equal(len(g:mode_seq) - 1, g:index) + call assert_equal(1, g:n_to_v) + call assert_equal(1, g:v_to_n) + unlet g:n_to_v + unlet g:v_to_n au! ModeChanged delfunc TestMode @@ -3100,4 +3525,48 @@ func Test_noname_autocmd() augroup! test_noname_autocmd_group endfunc +func Test_autocmd_split_dummy() + " Autocommand trying to split a window containing a dummy buffer. + auto BufReadPre * exe "sbuf " .. expand("<abuf>") + " Avoid the "W11" prompt + au FileChangedShell * let v:fcs_choice = 'reload' + func Xautocmd_changelist() + cal writefile(['Xtestfile2:4:4'], 'Xerr') + edit Xerr + lex 'Xtestfile2:4:4' + endfunc + call Xautocmd_changelist() + " Should get E86, but it doesn't always happen (timing?) + silent! call Xautocmd_changelist() + + au! BufReadPre + au! FileChangedShell + delfunc Xautocmd_changelist + bwipe! Xerr + call delete('Xerr') +endfunc + +" This was crashing because there was only one window to execute autocommands +" in. +func Test_autocmd_nested_setbufvar() + CheckFeature python3 + + set hidden + edit Xaaa + edit Xbbb + call setline(1, 'bar') + enew + au BufWriteCmd Xbbb ++nested call setbufvar('Xaaa', '&ft', 'foo') | bw! Xaaa + au FileType foo call py3eval('vim.current.buffer.options["cindent"]') + wall + + au! BufWriteCmd + au! FileType foo + set nohidden + call delete('Xaaa') + call delete('Xbbb') + %bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_autoload.vim b/src/nvim/testdir/test_autoload.vim index b8c4fa251f..e89fe3943b 100644 --- a/src/nvim/testdir/test_autoload.vim +++ b/src/nvim/testdir/test_autoload.vim @@ -10,6 +10,9 @@ func Test_autoload_dict_func() call assert_equal(1, g:called_foo_bar_echo) eval 'bar'->g:foo#addFoo()->assert_equal('barfoo') + + " empty name works in legacy script + call assert_equal('empty', foo#()) endfunc func Test_source_autoload() @@ -17,3 +20,6 @@ func Test_source_autoload() source sautest/autoload/sourced.vim call assert_equal(1, g:loaded_sourced_vim) endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim index ce2bfe72bc..d304773da4 100644 --- a/src/nvim/testdir/test_backup.vim +++ b/src/nvim/testdir/test_backup.vim @@ -1,5 +1,7 @@ " Tests for the backup function +source check.vim + func Test_backup() set backup backupdir=. backupskip= new @@ -17,6 +19,22 @@ func Test_backup() call delete('Xbackup.txt~') endfunc +func Test_backup_backupskip() + set backup backupdir=. backupskip=*.txt + new + call setline(1, ['line1', 'line2']) + :f Xbackup.txt + :w! Xbackup.txt + " backup file is only created after + " writing a second time (before overwriting) + :w! Xbackup.txt + call assert_false(filereadable('Xbackup.txt~')) + bw! + set backup&vim backupdir&vim backupskip&vim + call delete('Xbackup.txt') + call delete('Xbackup.txt~') +endfunc + func Test_backup2() set backup backupdir=.// backupskip= new @@ -28,7 +46,7 @@ func Test_backup2() :w! Xbackup.txt sp *Xbackup.txt~ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) - let f=expand('%') + let f = expand('%') call assert_match('%testdir%Xbackup.txt\~', f) bw! bw! @@ -48,7 +66,7 @@ func Test_backup2_backupcopy() :w! Xbackup.txt sp *Xbackup.txt~ call assert_equal(['line1', 'line2', 'line3'], getline(1,'$')) - let f=expand('%') + let f = expand('%') call assert_match('%testdir%Xbackup.txt\~', f) bw! bw! @@ -56,3 +74,16 @@ func Test_backup2_backupcopy() call delete(f) set backup&vim backupdir&vim backupcopy&vim backupskip&vim endfunc + +" Test for using a non-existing directory as a backup directory +func Test_non_existing_backupdir() + throw 'Skipped: Nvim auto-creates backup directory' + set backupdir=./non_existing_dir backupskip= + call writefile(['line1'], 'Xfile') + new Xfile + call assert_fails('write', 'E510:') + set backupdir&vim backupskip&vim + call delete('Xfile') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_blob.vim b/src/nvim/testdir/test_blob.vim index af42b3857d..046acb81e1 100644 --- a/src/nvim/testdir/test_blob.vim +++ b/src/nvim/testdir/test_blob.vim @@ -88,6 +88,7 @@ func Test_blob_get_range() call assert_equal(0z0011223344, b[:]) call assert_equal(0z0011223344, b[:-1]) call assert_equal(0z, b[5:6]) + call assert_equal(0z0011, b[-10:1]) endfunc func Test_blob_get() @@ -208,6 +209,7 @@ func Test_blob_add() call assert_equal(0z001122, b) call add(b, '51') call assert_equal(0z00112233, b) + call assert_equal(1, add(v:_null_blob, 0x22)) call assert_fails('call add(b, [9])', 'E745:') call assert_fails('call add("", 0x01)', 'E897:') @@ -250,6 +252,7 @@ func Test_blob_func_remove() call assert_fails("call remove(b, 3, 2)", 'E979:') call assert_fails("call remove(1, 0)", 'E896:') call assert_fails("call remove(b, b)", 'E974:') + call assert_fails("call remove(b, 1, [])", 'E745:') call assert_fails("call remove(v:_null_blob, 1, 2)", 'E979:') " Translated from v8.2.3284 @@ -272,6 +275,7 @@ endfunc " filter() item in blob func Test_blob_filter() + call assert_equal(v:_null_blob, filter(v:_null_blob, '0')) call assert_equal(0z, filter(0zDEADBEEF, '0')) call assert_equal(0zADBEEF, filter(0zDEADBEEF, 'v:val != 0xDE')) call assert_equal(0zDEADEF, filter(0zDEADBEEF, 'v:val != 0xBE')) @@ -294,9 +298,12 @@ func Test_blob_index() call assert_equal(2, index(0zDEADBEEF, 0xBE)) call assert_equal(-1, index(0zDEADBEEF, 0)) call assert_equal(2, index(0z11111111, 0x11, 2)) - call assert_equal(3, index(0z11110111, 0x11, 2)) + call assert_equal(3, 0z11110111->index(0x11, 2)) call assert_equal(2, index(0z11111111, 0x11, -2)) call assert_equal(3, index(0z11110111, 0x11, -2)) + call assert_equal(0, index(0z11110111, 0x11, -10)) + call assert_fails("echo index(0z11110111, 0x11, [])", 'E745:') + call assert_equal(-1, index(v:_null_blob, 1)) call assert_fails('call index("asdf", 0)', 'E897:') endfunc @@ -313,6 +320,9 @@ func Test_blob_insert() call assert_fails('call insert(b, -1)', 'E475:') call assert_fails('call insert(b, 257)', 'E475:') call assert_fails('call insert(b, 0, [9])', 'E745:') + call assert_fails('call insert(b, 0, -20)', 'E475:') + call assert_fails('call insert(b, 0, 20)', 'E475:') + call assert_fails('call insert(b, [])', 'E745:') call assert_equal(0, insert(v:_null_blob, 0x33)) " Translated from v8.2.3284 diff --git a/src/nvim/testdir/test_breakindent.vim b/src/nvim/testdir/test_breakindent.vim index a37751e748..2e377aa434 100644 --- a/src/nvim/testdir/test_breakindent.vim +++ b/src/nvim/testdir/test_breakindent.vim @@ -4,11 +4,11 @@ " while the test is run, the breakindent caching gets in its way. " It helps to change the tabstop setting and force a redraw (e.g. see " Test_breakindent08()) -if !exists('+breakindent') - throw 'Skipped: breakindent option not supported' -endif +source check.vim +CheckOption breakindent source view_util.vim +source screendump.vim let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" @@ -422,6 +422,7 @@ func Test_breakindent11() let width = strlen(text[1:]) + indent(2) + strlen(&sbr) * 3 " text wraps 3 times call assert_equal(width, strdisplaywidth(text)) call s:close_windows('set sbr=') + call assert_equal(4, strdisplaywidth("\t", 4)) endfunc func Test_breakindent11_vartabs() @@ -855,7 +856,7 @@ func Test_breakindent20_list() " check formatlistpat indent with different list level " showbreak and sbr - setl briopt=min:5,sbr,list:-1,shift:2 + setl briopt=min:5,sbr,list:-1 setl showbreak=> redraw! let expect = [ @@ -868,6 +869,44 @@ func Test_breakindent20_list() \ ] let lines = s:screen_lines2(1, 6, 20) call s:compare_lines(expect, lines) + + " check formatlistpat indent with different list level + " showbreak sbr and shift + setl briopt=min:5,sbr,list:-1,shift:2 + setl showbreak=> + redraw! + let expect = [ + \ "* Congress shall ", + \ "> make no law ", + \ "*** Congress shall ", + \ "> make no law ", + \ "**** Congress shall ", + \ "> make no law ", + \ ] + let lines = s:screen_lines2(1, 6, 20) + call s:compare_lines(expect, lines) + + " check breakindent works if breakindentopt=list:-1 + " for a non list content + %delete _ + call setline(1, [' Congress shall make no law', + \ ' Congress shall make no law', + \ ' Congress shall make no law']) + norm! 1gg + setl briopt=min:5,list:-1 + setl showbreak= + redraw! + let expect = [ + \ " Congress shall ", + \ " make no law ", + \ " Congress shall ", + \ " make no law ", + \ " Congress shall ", + \ " make no law ", + \ ] + let lines = s:screen_lines2(1, 6, 20) + call s:compare_lines(expect, lines) + call s:close_windows('set breakindent& briopt& linebreak& list& listchars& showbreak&') endfunc @@ -876,17 +915,164 @@ endfunc func Test_window_resize_with_linebreak() new 53vnew - set linebreak - set showbreak=>> - set breakindent - set breakindentopt=shift:4 + setl linebreak + setl showbreak=>> + setl breakindent + setl breakindentopt=shift:4 call setline(1, "\naaaaaaaaa\n\na\naaaaa\n¯aaaaaaaaaa\naaaaaaaaaaaa\naaa\n\"a:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - aaaaaaaa\"\naaaaaaaa\n\"a") redraw! call assert_equal([" >>aa^@\"a: "], ScreenLines(2, 14)) vertical resize 52 redraw! call assert_equal([" >>aaa^@\"a:"], ScreenLines(2, 14)) + set linebreak& showbreak& breakindent& breakindentopt& %bw! endfunc +func Test_cursor_position_with_showbreak() + CheckScreendump + + let lines =<< trim END + vim9script + &signcolumn = 'yes' + &showbreak = '+ ' + var leftcol: number = win_getid()->getwininfo()->get(0, {})->get('textoff') + repeat('x', &columns - leftcol - 1)->setline(1) + 'second line'->setline(2) + END + call writefile(lines, 'XscriptShowbreak') + let buf = RunVimInTerminal('-S XscriptShowbreak', #{rows: 6}) + + call term_sendkeys(buf, "AX") + call VerifyScreenDump(buf, 'Test_cursor_position_with_showbreak', {}) + + call StopVimInTerminal(buf) + call delete('XscriptShowbreak') +endfunc + +func Test_no_spurious_match() + let s:input = printf('- y %s y %s', repeat('x', 50), repeat('x', 50)) + call s:test_windows('setl breakindent breakindentopt=list:-1 formatlistpat=^- hls') + let @/ = '\%>3v[y]' + redraw! + call searchcount().total->assert_equal(1) + " cleanup + set hls&vim + let s:input = "\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" + bwipeout! +endfunc + +func Test_no_extra_indent() + call s:test_windows('setl breakindent breakindentopt=list:-1,min:10') + %d + let &l:formatlistpat='^\s*\d\+\.\s\+' + let text = 'word ' + let len = text->strcharlen() + let line1 = text->repeat((winwidth(0) / len) * 2) + let line2 = repeat(' ', 2) .. '1. ' .. line1 + call setline(1, [line2]) + redraw! + " 1) matches formatlist pattern, so indent + let expect = [ + \ " 1. word word word ", + \ " word word word ", + \ " word word ", + \ "~ ", + \ ] + let lines = s:screen_lines2(1, 4, 20) + call s:compare_lines(expect, lines) + " 2) change formatlist pattern + " -> indent adjusted + let &l:formatlistpat='^\s*\d\+\.' + let expect = [ + \ " 1. word word word ", + \ " word word word ", + \ " word word ", + \ "~ ", + \ ] + let lines = s:screen_lines2(1, 4, 20) + " 3) no local formatlist pattern, + " so use global one -> indent + let g_flp = &g:flp + let &g:formatlistpat='^\s*\d\+\.\s\+' + let &l:formatlistpat='' + let expect = [ + \ " 1. word word word ", + \ " word word word ", + \ " word word ", + \ "~ ", + \ ] + let lines = s:screen_lines2(1, 4, 20) + call s:compare_lines(expect, lines) + let &g:flp = g_flp + let &l:formatlistpat='^\s*\d\+\.' + " 4) add something in front, no additional indent + norm! gg0 + exe ":norm! 5iword \<esc>" + redraw! + let expect = [ + \ "word word word word ", + \ "word 1. word word ", + \ "word word word word ", + \ "word word ", + \ "~ ", + \ ] + let lines = s:screen_lines2(1, 5, 20) + call s:compare_lines(expect, lines) + bwipeout! +endfunc + +func Test_breakindent_column() + " restore original + let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" + call s:test_windows('setl breakindent breakindentopt=column:10') + redraw! + " 1) default: does not indent, too wide :( + let expect = [ + \ " ", + \ " abcdefghijklmnop", + \ "qrstuvwxyzABCDEFGHIJ", + \ "KLMNOP " + \ ] + let lines = s:screen_lines2(1, 4, 20) + call s:compare_lines(expect, lines) + " 2) lower min value, so that breakindent works + setl breakindentopt+=min:5 + redraw! + let expect = [ + \ " ", + \ " abcdefghijklmnop", + \ " qrstuvwxyz", + \ " ABCDEFGHIJ", + \ " KLMNOP " + \ ] + let lines = s:screen_lines2(1, 5, 20) + " 3) set shift option -> no influence + setl breakindentopt+=shift:5 + redraw! + let expect = [ + \ " ", + \ " abcdefghijklmnop", + \ " qrstuvwxyz", + \ " ABCDEFGHIJ", + \ " KLMNOP " + \ ] + let lines = s:screen_lines2(1, 5, 20) + call s:compare_lines(expect, lines) + " 4) add showbreak value + setl showbreak=++ + redraw! + let expect = [ + \ " ", + \ " abcdefghijklmnop", + \ " ++qrstuvwx", + \ " ++yzABCDEF", + \ " ++GHIJKLMN", + \ " ++OP " + \ ] + let lines = s:screen_lines2(1, 6, 20) + call s:compare_lines(expect, lines) + bwipeout! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_buffer.vim b/src/nvim/testdir/test_buffer.vim index 67be3e6747..9220cc17b9 100644 --- a/src/nvim/testdir/test_buffer.vim +++ b/src/nvim/testdir/test_buffer.vim @@ -67,16 +67,7 @@ func Test_bunload_with_offset() call assert_fails('1,4bunload', 'E16:') call assert_fails(',100bunload', 'E16:') - " Use a try-catch for this test. When assert_fails() is used for this - " test, the command fails with E515: instead of E90: - let caught_E90 = 0 - try - $bunload - catch /E90:/ - let caught_E90 = 1 - endtry - call assert_equal(1, caught_E90) - call assert_fails('$bunload', 'E515:') + call assert_fails('$bunload', 'E90:') endfunc " Test for :buffer, :bnext, :bprevious, :brewind, :blast and :bmodified @@ -147,6 +138,7 @@ func Test_bdelete_cmd() %bwipe! call assert_fails('bdelete 5', 'E516:') call assert_fails('1,1bdelete 1 2', 'E488:') + call assert_fails('bdelete \)', 'E55:') " Deleting a unlisted and unloaded buffer edit Xfile1 @@ -154,6 +146,24 @@ func Test_bdelete_cmd() set nobuflisted enew call assert_fails('bdelete ' .. bnr, 'E516:') + + " Deleting more than one buffer + new Xbuf1 + new Xbuf2 + exe 'bdel ' .. bufnr('Xbuf2') .. ' ' .. bufnr('Xbuf1') + call assert_equal(1, winnr('$')) + call assert_equal(0, getbufinfo('Xbuf1')[0].loaded) + call assert_equal(0, getbufinfo('Xbuf2')[0].loaded) + + " Deleting more than one buffer and an invalid buffer + new Xbuf1 + new Xbuf2 + let cmd = "exe 'bdel ' .. bufnr('Xbuf2') .. ' xxx ' .. bufnr('Xbuf1')" + call assert_fails(cmd, 'E94:') + call assert_equal(2, winnr('$')) + call assert_equal(1, getbufinfo('Xbuf1')[0].loaded) + call assert_equal(0, getbufinfo('Xbuf2')[0].loaded) + %bwipe! endfunc @@ -168,6 +178,194 @@ func Test_buffer_error() %bwipe endfunc +" Test for the status messages displayed when unloading, deleting or wiping +" out buffers +func Test_buffer_statusmsg() + CheckEnglish + set report=1 + new Xbuf1 + new Xbuf2 + let bnr = bufnr() + exe "normal 2\<C-G>" + call assert_match('buf ' .. bnr .. ':', v:statusmsg) + bunload Xbuf1 Xbuf2 + call assert_equal('2 buffers unloaded', v:statusmsg) + bdel Xbuf1 Xbuf2 + call assert_equal('2 buffers deleted', v:statusmsg) + bwipe Xbuf1 Xbuf2 + call assert_equal('2 buffers wiped out', v:statusmsg) + set report& +endfunc + +" Test for quitting the 'swapfile exists' dialog with the split buffer +" command. +func Test_buffer_sbuf_cleanup() + call writefile([], 'Xfile') + " first open the file in a buffer + new Xfile + let bnr = bufnr() + close + " create the swap file + call writefile([], '.Xfile.swp') + " Remove the catch-all that runtest.vim adds + au! SwapExists + augroup BufTest + au! + autocmd SwapExists Xfile let v:swapchoice='q' + augroup END + exe 'sbuf ' . bnr + call assert_equal(1, winnr('$')) + call assert_equal(0, getbufinfo('Xfile')[0].loaded) + + " test for :sball + sball + call assert_equal(1, winnr('$')) + call assert_equal(0, getbufinfo('Xfile')[0].loaded) + + %bw! + set shortmess+=F + let v:statusmsg = '' + edit Xfile + call assert_equal('', v:statusmsg) + call assert_equal(1, winnr('$')) + call assert_equal(0, getbufinfo('Xfile')[0].loaded) + set shortmess& + + call delete('Xfile') + call delete('.Xfile.swp') + augroup BufTest + au! + augroup END + augroup! BufTest +endfunc + +" Test for deleting a modified buffer with :confirm +func Test_bdel_with_confirm() + " requires a UI to be active + throw 'Skipped: use test/functional/legacy/buffer_spec.lua' + CheckUnix + CheckNotGui + CheckFeature dialog_con + new + call setline(1, 'test') + call assert_fails('bdel', 'E89:') + call feedkeys('c', 'L') + confirm bdel + call assert_equal(2, winnr('$')) + call assert_equal(1, &modified) + call feedkeys('n', 'L') + confirm bdel + call assert_equal(1, winnr('$')) +endfunc + +" Test for editing another buffer from a modified buffer with :confirm +func Test_goto_buf_with_confirm() + " requires a UI to be active + throw 'Skipped: use test/functional/legacy/buffer_spec.lua' + CheckUnix + CheckNotGui + CheckFeature dialog_con + new Xfile + enew + call setline(1, 'test') + call assert_fails('b Xfile', 'E37:') + call feedkeys('c', 'L') + call assert_fails('confirm b Xfile', 'E37:') + call assert_equal(1, &modified) + call assert_equal('', @%) + call feedkeys('y', 'L') + call assert_fails('confirm b Xfile', ['', 'E37:']) + call assert_equal(1, &modified) + call assert_equal('', @%) + call feedkeys('n', 'L') + confirm b Xfile + call assert_equal('Xfile', @%) + close! +endfunc + +" Test for splitting buffer with 'switchbuf' +func Test_buffer_switchbuf() + new Xfile + wincmd w + set switchbuf=useopen + sbuf Xfile + call assert_equal(1, winnr()) + call assert_equal(2, winnr('$')) + set switchbuf=usetab + tabnew + sbuf Xfile + call assert_equal(1, tabpagenr()) + call assert_equal(2, tabpagenr('$')) + set switchbuf& + %bw +endfunc + +" Test for BufAdd autocommand wiping out the buffer +func Test_bufadd_autocmd_bwipe() + %bw! + augroup BufAdd_Wipe + au! + autocmd BufAdd Xfile %bw! + augroup END + edit Xfile + call assert_equal('', @%) + call assert_equal(0, bufexists('Xfile')) + augroup BufAdd_Wipe + au! + augroup END + augroup! BufAdd_Wipe +endfunc + +" Test for trying to load a buffer with text locked +" <C-\>e in the command line is used to lock the text +func Test_load_buf_with_text_locked() + new Xfile1 + edit Xfile2 + let cmd = ":\<C-\>eexecute(\"normal \<C-O>\")\<CR>\<C-C>" + call assert_fails("call feedkeys(cmd, 'xt')", 'E565:') + %bw! +endfunc + +" Test for using CTRL-^ to edit the alternative file keeping the cursor +" position with 'nostartofline'. Also test using the 'buf' command. +func Test_buffer_edit_altfile() + call writefile(repeat(['one two'], 50), 'Xfile1') + call writefile(repeat(['five six'], 50), 'Xfile2') + set nosol + edit Xfile1 + call cursor(25, 5) + edit Xfile2 + call cursor(30, 4) + exe "normal \<C-^>" + call assert_equal([0, 25, 5, 0], getpos('.')) + exe "normal \<C-^>" + call assert_equal([0, 30, 4, 0], getpos('.')) + buf Xfile1 + call assert_equal([0, 25, 5, 0], getpos('.')) + buf Xfile2 + call assert_equal([0, 30, 4, 0], getpos('.')) + set sol& + call delete('Xfile1') + call delete('Xfile2') +endfunc + +" Test for running the :sball command with a maximum window count and a +" modified buffer +func Test_sball_with_count() + %bw! + edit Xfile1 + call setline(1, ['abc']) + new Xfile2 + new Xfile3 + new Xfile4 + 2sball + call assert_equal(bufnr('Xfile4'), winbufnr(1)) + call assert_equal(bufnr('Xfile1'), winbufnr(2)) + call assert_equal(0, getbufinfo('Xfile2')[0].loaded) + call assert_equal(0, getbufinfo('Xfile3')[0].loaded) + %bw! +endfunc + func Test_badd_options() new SomeNewBuffer setlocal numberwidth=3 @@ -195,12 +393,12 @@ func Test_buffer_scheme() set noshellslash %bwipe! let bufnames = [ - \ #{id: 'b0', name: 'test://xyz/foo/b0' , match: 1}, - \ #{id: 'b1', name: 'test+abc://xyz/foo/b1', match: 0}, - \ #{id: 'b2', name: 'test_abc://xyz/foo/b2', match: 0}, - \ #{id: 'b3', name: 'test-abc://xyz/foo/b3', match: 1}, - \ #{id: 'b4', name: '-test://xyz/foo/b4' , match: 0}, - \ #{id: 'b5', name: 'test-://xyz/foo/b5' , match: 0}, + \ #{id: 'ssb0', name: 'test://xyz/foo/ssb0' , match: 1}, + \ #{id: 'ssb1', name: 'test+abc://xyz/foo/ssb1', match: 0}, + \ #{id: 'ssb2', name: 'test_abc://xyz/foo/ssb2', match: 0}, + \ #{id: 'ssb3', name: 'test-abc://xyz/foo/ssb3', match: 1}, + \ #{id: 'ssb4', name: '-test://xyz/foo/ssb4' , match: 0}, + \ #{id: 'ssb5', name: 'test-://xyz/foo/ssb5' , match: 0}, \] for buf in bufnames new `=buf.name` @@ -225,6 +423,32 @@ func Test_buf_pattern_invalid() vsplit 00000000000000000000000000 silent! buf [0--]\&\zs*\zs*e bwipe! + + " similar case with different code path + split 0 + edit ÿ + silent! buf [0--]\&\zs*\zs*0 + bwipe! +endfunc + +" Test for the 'maxmem' and 'maxmemtot' options +func Test_buffer_maxmem() + " use 1KB per buffer and 2KB for all the buffers + " set maxmem=1 maxmemtot=2 + new + let v:errmsg = '' + " try opening some files + edit test_arglist.vim + call assert_equal('test_arglist.vim', bufname()) + edit test_eval_stuff.vim + call assert_equal('test_eval_stuff.vim', bufname()) + b test_arglist.vim + call assert_equal('test_arglist.vim', bufname()) + b test_eval_stuff.vim + call assert_equal('test_eval_stuff.vim', bufname()) + close + call assert_equal('', v:errmsg) + " set maxmem& maxmemtot& endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim index 579d3a5eb5..0468025f55 100644 --- a/src/nvim/testdir/test_bufline.vim +++ b/src/nvim/testdir/test_bufline.vim @@ -2,14 +2,18 @@ source shared.vim source screendump.vim +source check.vim func Test_setbufline_getbufline() + " similar to Test_set_get_bufline() new let b = bufnr('%') hide call assert_equal(0, setbufline(b, 1, ['foo', 'bar'])) call assert_equal(['foo'], getbufline(b, 1)) + call assert_equal('foo', getbufoneline(b, 1)) call assert_equal(['bar'], getbufline(b, '$')) + call assert_equal('bar', getbufoneline(b, '$')) call assert_equal(['foo', 'bar'], getbufline(b, 1, 2)) exe "bd!" b call assert_equal([], getbufline(b, 1, 2)) @@ -18,13 +22,36 @@ func Test_setbufline_getbufline() call setline(1, ['a', 'b', 'c']) let b = bufnr('%') wincmd w + + call assert_equal(1, setbufline(b, 5, 'x')) call assert_equal(1, setbufline(b, 5, ['x'])) - call assert_equal(1, setbufline(bufnr('$') + 1, 1, ['x'])) + call assert_equal(1, setbufline(b, 5, [])) + call assert_equal(1, setbufline(b, 5, v:_null_list)) + + call assert_equal(1, 'x'->setbufline(bufnr('$') + 1, 1)) + call assert_equal(1, ['x']->setbufline(bufnr('$') + 1, 1)) + call assert_equal(1, []->setbufline(bufnr('$') + 1, 1)) + call assert_equal(1, v:_null_list->setbufline(bufnr('$') + 1, 1)) + + call assert_equal(['a', 'b', 'c'], getbufline(b, 1, '$')) + call assert_equal(0, setbufline(b, 4, ['d', 'e'])) call assert_equal(['c'], b->getbufline(3)) + call assert_equal('c', b->getbufoneline(3)) call assert_equal(['d'], getbufline(b, 4)) + call assert_equal('d', getbufoneline(b, 4)) call assert_equal(['e'], getbufline(b, 5)) + call assert_equal('e', getbufoneline(b, 5)) call assert_equal([], getbufline(b, 6)) + call assert_equal([], getbufline(b, 2, 1)) + + if has('job') + call setbufline(b, 2, [function('eval'), #{key: 123}, test_null_job()]) + call assert_equal(["function('eval')", + \ "{'key': 123}", + \ "no process"], + \ getbufline(b, 2, 4)) + endif exe "bwipe! " . b endfunc @@ -71,20 +98,53 @@ func Test_appendbufline() new let b = bufnr('%') hide + + new + call setline(1, ['line1', 'line2', 'line3']) + normal! 2gggg + call assert_equal(2, line("''")) + call assert_equal(0, appendbufline(b, 0, ['foo', 'bar'])) call assert_equal(['foo'], getbufline(b, 1)) call assert_equal(['bar'], getbufline(b, 2)) call assert_equal(['foo', 'bar'], getbufline(b, 1, 2)) + call assert_equal(0, appendbufline(b, 0, 'baz')) + call assert_equal(['baz', 'foo', 'bar'], getbufline(b, 1, 3)) + + " appendbufline() in a hidden buffer shouldn't move marks in current window. + call assert_equal(2, line("''")) + bwipe! + exe "bd!" b - call assert_equal([], getbufline(b, 1, 2)) + call assert_equal([], getbufline(b, 1, 3)) split Xtest call setline(1, ['a', 'b', 'c']) let b = bufnr('%') wincmd w + + call assert_equal(1, appendbufline(b, -1, 'x')) call assert_equal(1, appendbufline(b, -1, ['x'])) + call assert_equal(1, appendbufline(b, -1, [])) + call assert_equal(1, appendbufline(b, -1, v:_null_list)) + + call assert_equal(1, appendbufline(b, 4, 'x')) call assert_equal(1, appendbufline(b, 4, ['x'])) + call assert_equal(1, appendbufline(b, 4, [])) + call assert_equal(1, appendbufline(b, 4, v:_null_list)) + + call assert_equal(1, appendbufline(1234, 1, 'x')) call assert_equal(1, appendbufline(1234, 1, ['x'])) + call assert_equal(1, appendbufline(1234, 1, [])) + call assert_equal(1, appendbufline(1234, 1, v:_null_list)) + + call assert_equal(0, appendbufline(b, 1, [])) + call assert_equal(0, appendbufline(b, 1, v:_null_list)) + call assert_equal(1, appendbufline(b, 3, [])) + call assert_equal(1, appendbufline(b, 3, v:_null_list)) + + call assert_equal(['a', 'b', 'c'], getbufline(b, 1, '$')) + call assert_equal(0, appendbufline(b, 3, ['d', 'e'])) call assert_equal(['c'], getbufline(b, 3)) call assert_equal(['d'], getbufline(b, 4)) @@ -98,10 +158,21 @@ func Test_deletebufline() let b = bufnr('%') call setline(1, ['aaa', 'bbb', 'ccc']) hide + + new + call setline(1, ['line1', 'line2', 'line3']) + normal! 2gggg + call assert_equal(2, line("''")) + call assert_equal(0, deletebufline(b, 2)) call assert_equal(['aaa', 'ccc'], getbufline(b, 1, 2)) call assert_equal(0, deletebufline(b, 2, 8)) call assert_equal(['aaa'], getbufline(b, 1, 2)) + + " deletebufline() in a hidden buffer shouldn't move marks in current window. + call assert_equal(2, line("''")) + bwipe! + exe "bd!" b call assert_equal(1, b->deletebufline(1)) @@ -130,9 +201,8 @@ func Test_deletebufline() endfunc func Test_appendbufline_redraw() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump + let lines =<< trim END new foo let winnr = 'foo'->bufwinnr() @@ -187,4 +257,40 @@ func Test_deletebufline_select_mode() bwipe! endfunc +func Test_setbufline_startup_nofile() + let before =<< trim [CODE] + set shortmess+=F + file Xresult + set buftype=nofile + call setbufline('', 1, 'success') + [CODE] + let after =<< trim [CODE] + set buftype= + write + quit + [CODE] + + if !RunVim(before, after, '--clean') + return + endif + call assert_equal(['success'], readfile('Xresult')) + call delete('Xresult') +endfunc + +" Test that setbufline(), appendbufline() and deletebufline() should fail and +" return 1 when "textlock" is active. +func Test_change_bufline_with_textlock() + new + inoremap <buffer> <expr> <F2> setbufline('', 1, '') + call assert_fails("normal a\<F2>", 'E565:') + call assert_equal('1', getline(1)) + inoremap <buffer> <expr> <F2> appendbufline('', 1, '') + call assert_fails("normal a\<F2>", 'E565:') + call assert_equal('11', getline(1)) + inoremap <buffer> <expr> <F2> deletebufline('', 1) + call assert_fails("normal a\<F2>", 'E565:') + call assert_equal('111', getline(1)) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index a1e53df774..2b37f2c7c0 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -5,7 +5,7 @@ source check.vim func Test_cd_large_path() " This used to crash with a heap write overflow. - call assert_fails('cd ' . repeat('x', 5000), 'E472:') + call assert_fails('cd ' . repeat('x', 5000), 'E344:') endfunc func Test_cd_up_and_down() @@ -45,9 +45,7 @@ func Test_cd_minus() call assert_equal(path, getcwd()) " Test for :cd - after a failed :cd - " v8.2.1183 is not ported yet - " call assert_fails('cd /nonexistent', 'E344:') - call assert_fails('cd /nonexistent', 'E472:') + call assert_fails('cd /nonexistent', 'E344:') call assert_equal(path, getcwd()) cd - call assert_equal(path_dotdot, getcwd()) @@ -68,30 +66,6 @@ func Test_cd_minus() call delete('Xresult') endfunc -func Test_cd_with_cpo_chdir() - e Xfoo - call setline(1, 'foo') - let path = getcwd() - " set cpo+=. - - " :cd should fail when buffer is modified and 'cpo' contains dot. - " call assert_fails('cd ..', 'E747:') - call assert_equal(path, getcwd()) - - " :cd with exclamation mark should succeed. - cd! .. - call assert_notequal(path, getcwd()) - - " :cd should succeed when buffer has been written. - w! - exe 'cd ' .. fnameescape(path) - call assert_equal(path, getcwd()) - - call delete('Xfoo') - set cpo& - bw! -endfunc - " Test for chdir() func Test_chdir_func() let topdir = getcwd() @@ -113,7 +87,7 @@ func Test_chdir_func() call assert_equal('z', fnamemodify(3->getcwd(2), ':t')) tabnext | wincmd t call assert_match('^\[tabpage\] .*/y$', trim(execute('verbose pwd'))) - call chdir('..') + eval '..'->chdir() call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t')) call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t')) call assert_equal('z', fnamemodify(getcwd(3, 2), ':t')) @@ -127,7 +101,7 @@ func Test_chdir_func() call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t')) " Error case - call assert_fails("call chdir('dir-abcd')", 'E472:') + call assert_fails("call chdir('dir-abcd')", 'E344:') silent! let d = chdir("dir_abcd") call assert_equal("", d) " Should not crash @@ -259,6 +233,8 @@ endfunc func Test_getcwd_actual_dir() CheckFunction test_autochdir + CheckOption autochdir + let startdir = getcwd() call mkdir('Xactual') call test_autochdir() diff --git a/src/nvim/testdir/test_charsearch.vim b/src/nvim/testdir/test_charsearch.vim index d386d74f8d..54e0a62ce5 100644 --- a/src/nvim/testdir/test_charsearch.vim +++ b/src/nvim/testdir/test_charsearch.vim @@ -43,36 +43,6 @@ func Test_charsearch() enew! endfunc -" Test for t,f,F,T movement commands and 'cpo-;' setting -func Test_search_cmds() - enew! - call append(0, ["aaa two three four", " zzz", "yyy ", - \ "bbb yee yoo four", "ccc two three four", - \ "ddd yee yoo four"]) - set cpo-=; - 1 - normal! 0tt;D - 2 - normal! 0fz;D - 3 - normal! $Fy;D - 4 - normal! $Ty;D - set cpo+=; - 5 - normal! 0tt;;D - 6 - normal! $Ty;;D - - call assert_equal('aaa two', getline(1)) - call assert_equal(' z', getline(2)) - call assert_equal('y', getline(3)) - call assert_equal('bbb y', getline(4)) - call assert_equal('ccc', getline(5)) - call assert_equal('ddd yee y', getline(6)) - enew! -endfunc - " Test for character search in virtual edit mode with <Tab> func Test_csearch_virtualedit() new @@ -81,7 +51,7 @@ func Test_csearch_virtualedit() normal! tb call assert_equal([0, 1, 2, 6], getpos('.')) set virtualedit& - close! + bw! endfunc " Test for character search failure in latin1 encoding @@ -95,7 +65,34 @@ func Test_charsearch_latin1() call assert_beeps('normal $Fz') call assert_beeps('normal $Tx') let &encoding = save_enc - close! + bw! +endfunc + +" Test for using character search to find a multibyte character with composing +" characters. +func Test_charsearch_composing_char() + new + call setline(1, "one two thq\u0328\u0301r\u0328\u0301ree") + call feedkeys("fr\u0328\u0301", 'xt') + call assert_equal([0, 1, 16, 0, 12], getcurpos()) + + " use character search with a multi-byte character followed by a + " non-composing character + call setline(1, "abc deȉf ghi") + call feedkeys("ggcf\u0209\u0210", 'xt') + call assert_equal("\u0210f ghi", getline(1)) + bw! +endfunc + +" Test for character search with 'hkmap' +func Test_charsearch_hkmap() + new + set hkmap + call setline(1, "ùðáâ÷ëòéïçìêöî") + call feedkeys("fë", 'xt') + call assert_equal([0, 1, 11, 0, 6], getcurpos()) + set hkmap& + bw! endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_checkpath.vim b/src/nvim/testdir/test_checkpath.vim index eff30cf205..f6a3bbce08 100644 --- a/src/nvim/testdir/test_checkpath.vim +++ b/src/nvim/testdir/test_checkpath.vim @@ -102,3 +102,20 @@ func Test_checkpath3() set include& set includeexpr& endfunc + +" Test for invalid regex in 'include' and 'define' options +func Test_checkpath_errors() + let save_include = &include + set include=\\%( + call assert_fails('checkpath', 'E53:') + let &include = save_include + + let save_define = &define + set define=\\%( + call assert_fails('dsearch abc', 'E53:') + let &define = save_define + + call assert_fails('psearch \%(', 'E53:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_clientserver.vim b/src/nvim/testdir/test_clientserver.vim index edf36b413b..d8b29f6c42 100644 --- a/src/nvim/testdir/test_clientserver.vim +++ b/src/nvim/testdir/test_clientserver.vim @@ -2,15 +2,18 @@ source check.vim CheckFeature job + +if !has('clientserver') + call assert_fails('call remote_startserver("local")', 'E942:') +endif + CheckFeature clientserver source shared.vim func Check_X11_Connection() if has('x11') - if empty($DISPLAY) - throw 'Skipped: $DISPLAY is not set' - endif + CheckEnv DISPLAY try call remote_send('xxx', '') catch @@ -59,15 +62,16 @@ func Test_client_server() " the GUI and check that the remote command still works. " Need to wait for the GUI to start up, otherwise the send hangs in trying " to send to the terminal window. - if has('gui_athena') || has('gui_motif') - " For those GUIs, ignore the 'failed to create input context' error. + if has('gui_motif') + " For this GUI ignore the 'failed to create input context' error. call remote_send(name, ":call test_ignore_error('E285') | gui -f\<CR>") else call remote_send(name, ":gui -f\<CR>") endif " Wait for the server to be up and answering requests. - sleep 100m - call WaitForAssert({-> assert_true(name->remote_expr("v:version", "", 1) != "")}) + " When using valgrind this can be very, very slow. + sleep 1 + call WaitForAssert({-> assert_match('\d', name->remote_expr("v:version", "", 1))}, 10000) call remote_send(name, ":let testvar = 'maybe'\<CR>") call WaitForAssert({-> assert_equal('maybe', remote_expr(name, "testvar", "", 2))}) @@ -83,6 +87,7 @@ func Test_client_server() call writefile(['one', 'two'], 'Xclientfile') call system(cmd) call WaitForAssert({-> assert_equal('two', remote_expr(name, "getline(2)", "", 2))}) + call delete('Xclientfile') " Expression evaluated locally. if v:servername == '' @@ -96,7 +101,7 @@ func Test_client_server() call remote_send(v:servername, ":let g:testvar2 = 75\<CR>") call feedkeys('', 'x') call assert_equal(75, g:testvar2) - call assert_fails('let v = remote_expr(v:servername, "/2")', 'E449:') + call assert_fails('let v = remote_expr(v:servername, "/2")', ['E15:.*/2']) call remote_send(name, ":call server2client(expand('<client>'), 'got it')\<CR>", 'g:myserverid') call assert_equal('got it', g:myserverid->remote_read(2)) @@ -136,19 +141,19 @@ func Test_client_server() " Edit multiple files using --remote call system(cmd .. ' --remote Xfile1 Xfile2 Xfile3') - call assert_equal("Xfile1\nXfile2\nXfile3\n", remote_expr(name, 'argv()')) + call assert_match(".*Xfile1\n.*Xfile2\n.*Xfile3\n", remote_expr(name, 'argv()')) eval name->remote_send(":%bw!\<CR>") " Edit files in separate tab pages call system(cmd .. ' --remote-tab Xfile1 Xfile2 Xfile3') - call assert_equal('3', remote_expr(name, 'tabpagenr("$")')) - call assert_equal('Xfile2', remote_expr(name, 'bufname(tabpagebuflist(2)[0])')) + call WaitForAssert({-> assert_equal('3', remote_expr(name, 'tabpagenr("$")'))}) + call assert_match('.*\<Xfile2', remote_expr(name, 'bufname(tabpagebuflist(2)[0])')) eval name->remote_send(":%bw!\<CR>") " Edit a file using --remote-wait eval name->remote_send(":source $VIMRUNTIME/plugin/rrhelper.vim\<CR>") call system(cmd .. ' --remote-wait +enew Xfile1') - call assert_equal("Xfile1", remote_expr(name, 'bufname("#")')) + call assert_match('.*\<Xfile1', remote_expr(name, 'bufname("#")')) eval name->remote_send(":%bw!\<CR>") " Edit files using --remote-tab-wait @@ -176,8 +181,12 @@ func Test_client_server() endif endtry + call assert_fails('call remote_startserver([])', 'E730:') call assert_fails("let x = remote_peek([])", 'E730:') - call assert_fails("let x = remote_read('vim10')", 'E277:') + call assert_fails("let x = remote_read('vim10')", + \ has('unix') ? ['E573:.*vim10'] : 'E277:') + call assert_fails("call server2client('abc', 'xyz')", + \ has('unix') ? ['E573:.*abc'] : 'E258:') endfunc " Uncomment this line to get a debugging log diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 35886d42c5..cf1d56ae38 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -3,10 +3,24 @@ source check.vim source screendump.vim source view_util.vim +source shared.vim + +func SetUp() + func SaveLastScreenLine() + let g:Sline = Screenline(&lines - 1) + return '' + endfunc + cnoremap <expr> <F4> SaveLastScreenLine() +endfunc + +func TearDown() + delfunc SaveLastScreenLine + cunmap <F4> +endfunc func Test_complete_tab() call writefile(['testfile'], 'Xtestfile') - call feedkeys(":e Xtestf\t\r", "tx") + call feedkeys(":e Xtest\t\r", "tx") call assert_equal('testfile', getline(1)) " Pressing <Tab> after '%' completes the current file, also on MS-Windows @@ -24,6 +38,47 @@ func Test_complete_list() " used. call feedkeys(":chistory \<C-D>\<C-B>\"\<CR>", 'xt') call assert_equal("\"chistory \<C-D>", @:) + + " Test for displaying the tail of the completion matches + set wildmode=longest,full + call mkdir('Xtest') + call writefile([], 'Xtest/a.c') + call writefile([], 'Xtest/a.h') + let g:Sline = '' + call feedkeys(":e Xtest/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt') + call assert_equal('a.c a.h', g:Sline) + call assert_equal('"e Xtest/', @:) + if has('win32') + " Test for 'completeslash' + set completeslash=backslash + call feedkeys(":e Xtest\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtest\', @:) + call feedkeys(":e Xtest/\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtest\a.', @:) + set completeslash=slash + call feedkeys(":e Xtest\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtest/', @:) + call feedkeys(":e Xtest\\\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtest/a.', @:) + set completeslash& + endif + + " Test for displaying the tail with wildcards + let g:Sline = '' + call feedkeys(":e Xtes?/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt') + call assert_equal('Xtest/a.c Xtest/a.h', g:Sline) + call assert_equal('"e Xtes?/', @:) + let g:Sline = '' + call feedkeys(":e Xtes*/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt') + call assert_equal('Xtest/a.c Xtest/a.h', g:Sline) + call assert_equal('"e Xtes*/', @:) + let g:Sline = '' + call feedkeys(":e Xtes[/\<C-D>\<F4>\<C-B>\"\<CR>", 'xt') + call assert_equal(':e Xtes[/', g:Sline) + call assert_equal('"e Xtes[/', @:) + + call delete('Xtest', 'rf') + set wildmode& endfunc func Test_complete_wildmenu() @@ -88,14 +143,25 @@ func Test_complete_wildmenu() call assert_equal('"e Xtestfile3 Xtestfile4', @:) cd - + " test for wildmenumode() + cnoremap <expr> <F2> wildmenumode() + call feedkeys(":cd Xdir\<Tab>\<F2>\<C-B>\"\<CR>", 'tx') + call assert_equal('"cd Xdir1/0', @:) + call feedkeys(":e Xdir1/\<Tab>\<F2>\<C-B>\"\<CR>", 'tx') + call assert_equal('"e Xdir1/Xdir2/1', @:) + cunmap <F2> + + " Test for canceling the wild menu by pressing <PageDown> or <PageUp>. + " After this pressing <Left> or <Right> should not change the selection. + call feedkeys(":sign \<Tab>\<PageDown>\<Left>\<Right>\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign define', @:) + call histadd('cmd', 'TestWildMenu') + call feedkeys(":sign \<Tab>\<PageUp>\<Left>\<Right>\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"TestWildMenu', @:) + " cleanup %bwipe - call delete('Xdir1/Xdir2/Xtestfile4') - call delete('Xdir1/Xdir2/Xtestfile3') - call delete('Xdir1/Xtestfile2') - call delete('Xdir1/Xtestfile1') - call delete('Xdir1/Xdir2', 'd') - call delete('Xdir1', 'd') + call delete('Xdir1', 'rf') set nowildmenu endfunc @@ -126,13 +192,74 @@ func Test_wildmenu_screendump() call delete('XTest_wildmenu') endfunc +func Test_redraw_in_autocmd() + CheckScreendump + + let lines =<< trim END + set cmdheight=2 + autocmd CmdlineChanged * redraw + END + call writefile(lines, 'XTest_redraw', 'D') + + let buf = RunVimInTerminal('-S XTest_redraw', {'rows': 8}) + call term_sendkeys(buf, ":for i in range(3)\<CR>") + call VerifyScreenDump(buf, 'Test_redraw_in_autocmd_1', {}) + + call term_sendkeys(buf, "let i =") + call VerifyScreenDump(buf, 'Test_redraw_in_autocmd_2', {}) + + " clean up + call term_sendkeys(buf, "\<CR>") + call StopVimInTerminal(buf) +endfunc + +func Test_redrawstatus_in_autocmd() + CheckScreendump + + let lines =<< trim END + set laststatus=2 + set statusline=%=:%{getcmdline()} + autocmd CmdlineChanged * redrawstatus + END + call writefile(lines, 'XTest_redrawstatus', 'D') + + let buf = RunVimInTerminal('-S XTest_redrawstatus', {'rows': 8}) + " :redrawstatus is postponed if messages have scrolled + call term_sendkeys(buf, ":echo \"one\\ntwo\\nthree\\nfour\"\<CR>") + call term_sendkeys(buf, ":foobar") + call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_1', {}) + " it is not postponed if messages have not scrolled + call term_sendkeys(buf, "\<Esc>:for in in range(3)") + call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_2', {}) + " with cmdheight=1 messages have scrolled when typing :endfor + call term_sendkeys(buf, "\<CR>:endfor") + call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_3', {}) + call term_sendkeys(buf, "\<CR>:set cmdheight=2\<CR>") + " with cmdheight=2 messages haven't scrolled when typing :for or :endfor + call term_sendkeys(buf, ":for in in range(3)") + call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_4', {}) + call term_sendkeys(buf, "\<CR>:endfor") + call VerifyScreenDump(buf, 'Test_redrawstatus_in_autocmd_5', {}) + + " clean up + call term_sendkeys(buf, "\<CR>") + call StopVimInTerminal(buf) +endfunc + func Test_changing_cmdheight() CheckScreendump let lines =<< trim END set cmdheight=1 laststatus=2 + func EchoTwo() + set laststatus=2 + set cmdheight=5 + echo 'foo' + echo 'bar' + set cmdheight=1 + endfunc END - call writefile(lines, 'XTest_cmdheight') + call writefile(lines, 'XTest_cmdheight', 'D') let buf = RunVimInTerminal('-S XTest_cmdheight', {'rows': 8}) call term_sendkeys(buf, ":resize -3\<CR>") @@ -150,20 +277,30 @@ func Test_changing_cmdheight() call term_sendkeys(buf, ":set cmdheight-=2\<CR>") call VerifyScreenDump(buf, 'Test_changing_cmdheight_4', {}) - " reducing window size and then setting cmdheight + " reducing window size and then setting cmdheight call term_sendkeys(buf, ":resize -1\<CR>") call term_sendkeys(buf, ":set cmdheight=1\<CR>") call VerifyScreenDump(buf, 'Test_changing_cmdheight_5', {}) + " setting 'cmdheight' works after outputting two messages + call term_sendkeys(buf, ":call EchoTwo()\<CR>") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_6', {}) + + " clean up + call StopVimInTerminal(buf) +endfunc + +func Test_cmdheight_tabline() + CheckScreendump + + let buf = RunVimInTerminal('-c "set ls=2" -c "set stal=2" -c "set cmdheight=1"', {'rows': 6}) + call VerifyScreenDump(buf, 'Test_cmdheight_tabline_1', {}) + " clean up call StopVimInTerminal(buf) - call delete('XTest_cmdheight') endfunc func Test_map_completion() - if !has('cmdline_compl') - return - endif call feedkeys(":map <unique> <si\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"map <unique> <silent>', getreg(':')) call feedkeys(":map <script> <un\<Tab>\<Home>\"\<CR>", 'xt') @@ -234,29 +371,30 @@ func Test_map_completion() unmap <Left> " set cpo-=k + call assert_fails('call feedkeys(":map \\\\%(\<Tab>\<Home>\"\<CR>", "xt")', 'E53:') + unmap <Middle>x set cpo&vim endfunc func Test_match_completion() - if !has('cmdline_compl') - return - endif hi Aardig ctermfg=green - call feedkeys(":match \<Tab>\<Home>\"\<CR>", 'xt') - call assert_equal('"match Aardig', getreg(':')) + " call feedkeys(":match \<Tab>\<Home>\"\<CR>", 'xt') + call feedkeys(":match A\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"match Aardig', @:) call feedkeys(":match \<S-Tab>\<Home>\"\<CR>", 'xt') - call assert_equal('"match none', getreg(':')) + call assert_equal('"match none', @:) + call feedkeys(":match | chist\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"match | chistory', @:) endfunc func Test_highlight_completion() - if !has('cmdline_compl') - return - endif hi Aardig ctermfg=green - call feedkeys(":hi \<Tab>\<Home>\"\<CR>", 'xt') + " call feedkeys(":hi \<Tab>\<Home>\"\<CR>", 'xt') + call feedkeys(":hi A\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"hi Aardig', getreg(':')) - call feedkeys(":hi default \<Tab>\<Home>\"\<CR>", 'xt') + " call feedkeys(":hi default \<Tab>\<Home>\"\<CR>", 'xt') + call feedkeys(":hi default A\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"hi default Aardig', getreg(':')) call feedkeys(":hi clear Aa\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"hi clear Aardig', getreg(':')) @@ -276,39 +414,7 @@ func Test_highlight_completion() call assert_equal([], getcompletion('A', 'highlight')) endfunc -func Test_expr_completion() - if !has('cmdline_compl') - return - endif - for cmd in [ - \ 'let a = ', - \ 'const a = ', - \ 'if', - \ 'elseif', - \ 'while', - \ 'for', - \ 'echo', - \ 'echon', - \ 'execute', - \ 'echomsg', - \ 'echoerr', - \ 'call', - \ 'return', - \ 'cexpr', - \ 'caddexpr', - \ 'cgetexpr', - \ 'lexpr', - \ 'laddexpr', - \ 'lgetexpr'] - call feedkeys(":" . cmd . " getl\<Tab>\<Home>\"\<CR>", 'xt') - call assert_equal('"' . cmd . ' getline(', getreg(':')) - endfor -endfunc - func Test_getcompletion() - if !has('cmdline_compl') - return - endif let groupcount = len(getcompletion('', 'event')) call assert_true(groupcount > 0) let matchcount = len('File'->getcompletion('event')) @@ -323,6 +429,7 @@ func Test_getcompletion() call assert_true(matchcount > 0) let matchcount = len(getcompletion('File.', 'menu')) call assert_true(matchcount > 0) + source $VIMRUNTIME/delmenu.vim endif let l = getcompletion('v:n', 'var') @@ -333,6 +440,8 @@ func Test_getcompletion() args a.c b.c let l = getcompletion('', 'arglist') call assert_equal(['a.c', 'b.c'], l) + let l = getcompletion('a.', 'buffer') + call assert_equal(['a.c'], l) %argdelete let l = getcompletion('', 'augroup') @@ -388,6 +497,11 @@ func Test_getcompletion() let l = getcompletion('run', 'file', 1) call assert_true(index(l, 'runtest.vim') < 0) set wildignore& + " Directory name with space character + call mkdir('Xdir with space') + call assert_equal(['Xdir with space/'], getcompletion('Xdir\ w', 'shellcmd')) + call assert_equal(['./Xdir with space/'], getcompletion('./Xdir', 'shellcmd')) + call delete('Xdir with space', 'd') let l = getcompletion('ha', 'filetype') call assert_true(index(l, 'hamster') >= 0) @@ -462,11 +576,17 @@ func Test_getcompletion() call assert_equal(cmds, l) let l = getcompletion('list ', 'sign') call assert_equal(['Testing'], l) + let l = getcompletion('de*', 'sign') + call assert_equal(['define'], l) + let l = getcompletion('p?', 'sign') + call assert_equal(['place'], l) + let l = getcompletion('j.', 'sign') + call assert_equal(['jump'], l) endif " Command line completion tests let l = getcompletion('cd ', 'cmdline') - call assert_true(index(l, 'sautest/') >= 0) + call assert_true(index(l, 'samples/') >= 0) let l = getcompletion('cd NoMatch', 'cmdline') call assert_equal([], l) let l = getcompletion('let v:n', 'cmdline') @@ -514,10 +634,39 @@ func Test_getcompletion() call delete('Xtags') set tags& + edit a~b + enew + call assert_equal(['a~b'], getcompletion('a~', 'buffer')) + bw a~b + + if has('unix') + edit Xtest\ + enew + call assert_equal(['Xtest\'], getcompletion('Xtest\', 'buffer')) + bw Xtest\ + endif + + call assert_fails("call getcompletion('\\\\@!\\\\@=', 'buffer')", 'E871:') call assert_fails('call getcompletion("", "burp")', 'E475:') call assert_fails('call getcompletion("abc", [])', 'E475:') endfunc +" Test for getcompletion() with "fuzzy" in 'wildoptions' +func Test_getcompletion_wildoptions() + let save_wildoptions = &wildoptions + set wildoptions& + let l = getcompletion('space', 'option') + call assert_equal([], l) + let l = getcompletion('ier', 'command') + call assert_equal([], l) + set wildoptions=fuzzy + let l = getcompletion('space', 'option') + call assert_true(index(l, 'backspace') >= 0) + let l = getcompletion('ier', 'command') + call assert_true(index(l, 'compiler') >= 0) + let &wildoptions = save_wildoptions +endfunc + func Test_fullcommand() let tests = { \ '': '', @@ -590,7 +739,7 @@ func Test_expand_star_star() call mkdir('a/b', 'p') call writefile(['asdfasdf'], 'a/b/fileXname') call feedkeys(":find **/fileXname\<Tab>\<CR>", 'xt') - call assert_equal('find a/b/fileXname', getreg(':')) + call assert_equal('find a/b/fileXname', @:) bwipe! call delete('a', 'rf') endfunc @@ -738,7 +887,31 @@ func Test_cmdline_complete_user_cmd() call assert_equal('"Foo blue', @:) call feedkeys(":Foo b\<Tab>\<Home>\"\<cr>", 'tx') call assert_equal('"Foo blue', @:) + call feedkeys(":Foo a b\<Tab>\<Home>\"\<cr>", 'tx') + call assert_equal('"Foo a blue', @:) + call feedkeys(":Foo b\\\<Tab>\<Home>\"\<cr>", 'tx') + call assert_equal('"Foo b\', @:) + call feedkeys(":Foo b\\x\<Tab>\<Home>\"\<cr>", 'tx') + call assert_equal('"Foo b\x', @:) delcommand Foo + + redraw + call assert_equal('~', Screenline(&lines - 1)) + command! FooOne : + command! FooTwo : + + set nowildmenu + call feedkeys(":Foo\<Tab>\<Home>\"\<cr>", 'tx') + call assert_equal('"FooOne', @:) + call assert_equal('~', Screenline(&lines - 1)) + + call feedkeys(":Foo\<S-Tab>\<Home>\"\<cr>", 'tx') + call assert_equal('"FooTwo', @:) + call assert_equal('~', Screenline(&lines - 1)) + + delcommand FooOne + delcommand FooTwo + set wildmenu& endfunc func Test_complete_user_cmd() @@ -809,7 +982,7 @@ func Test_cmdline_complete_bang() endif endfunc -funct Test_cmdline_complete_languages() +func Test_cmdline_complete_languages() let lang = substitute(execute('language time'), '.*"\(.*\)"$', '\1', '') call assert_equal(lang, v:lc_time) @@ -846,10 +1019,8 @@ endfunc func Test_cmdline_complete_env_variable() let $X_VIM_TEST_COMPLETE_ENV = 'foo' - call feedkeys(":edit $X_VIM_TEST_COMPLETE_E\<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('"edit $X_VIM_TEST_COMPLETE_ENV', @:) - unlet $X_VIM_TEST_COMPLETE_ENV endfunc @@ -926,6 +1097,10 @@ func Test_cmdline_complete_various() call feedkeys(":all abc\<C-A>\<C-B>\"\<CR>", 'xt') call assert_equal("\"all abc\<C-A>", @:) + " completion for :wincmd with :horizontal modifier + call feedkeys(":horizontal wincm\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"horizontal wincmd", @:) + " completion for a command with a command modifier call feedkeys(":topleft new\<C-A>\<C-B>\"\<CR>", 'xt') call assert_equal("\"topleft new", @:) @@ -934,14 +1109,6 @@ func Test_cmdline_complete_various() call feedkeys(":match Search /pat/\<C-A>\<C-B>\"\<CR>", 'xt') call assert_equal("\"match Search /pat/\<C-A>", @:) - " completion for the :s command - call feedkeys(":s/from/to/g\<C-A>\<C-B>\"\<CR>", 'xt') - call assert_equal("\"s/from/to/g\<C-A>", @:) - - " completion for the :dlist command - call feedkeys(":dlist 10 /pat/ a\<C-A>\<C-B>\"\<CR>", 'xt') - call assert_equal("\"dlist 10 /pat/ a\<C-A>", @:) - " completion for the :doautocmd command call feedkeys(":doautocmd User MyCmd a.c\<C-A>\<C-B>\"\<CR>", 'xt') call assert_equal("\"doautocmd User MyCmd a.c\<C-A>", @:) @@ -1001,7 +1168,7 @@ func Test_cmdline_complete_various() map <F3> :ls<CR> com -nargs=* -complete=mapping MyCmd call feedkeys(":MyCmd <F\<C-A>\<C-B>\"\<CR>", 'xt') - call assert_equal('"MyCmd <F3>', @:) + call assert_equal('"MyCmd <F3> <F4>', @:) mapclear delcom MyCmd @@ -1025,9 +1192,77 @@ func Test_cmdline_complete_various() call feedkeys(":e `a1b2c\t\<C-B>\"\<CR>", 'xt') call assert_equal('"e `a1b2c', @:) - " completion for the expression register - call feedkeys(":\"\<C-R>=float2\t\"\<C-B>\"\<CR>", 'xt') - call assert_equal('"float2nr("', @=) + " completion for :language command with an invalid argument + call feedkeys(":language dummy \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"language dummy \t", @:) + + " completion for commands after a :global command + call feedkeys(":g/a\\xb/clearj\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"g/a\xb/clearjumps', @:) + + " completion with ambiguous user defined commands + com TCmd1 echo 'TCmd1' + com TCmd2 echo 'TCmd2' + call feedkeys(":TCmd \t\<C-B>\"\<CR>", 'xt') + call assert_equal('"TCmd ', @:) + delcom TCmd1 + delcom TCmd2 + + " completion after a range followed by a pipe (|) character + call feedkeys(":1,10 | chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"1,10 | chistory', @:) + + " completion after a :global command + call feedkeys(":g/a/chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"g/a/chistory', @:) + call feedkeys(":g/a\\/chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"g/a\\/chist\t", @:) + + " use <Esc> as the 'wildchar' for completion + set wildchar=<Esc> + call feedkeys(":g/a\\xb/clearj\<Esc>\<C-B>\"\<CR>", 'xt') + call assert_equal('"g/a\xb/clearjumps', @:) + " pressing <esc> twice should cancel the command + call feedkeys(":chist\<Esc>\<Esc>", 'xt') + call assert_equal('"g/a\xb/clearjumps', @:) + set wildchar& + + if has('unix') + " should be able to complete a file name that starts with a '~'. + call writefile([], '~Xtest') + call feedkeys(":e \\~X\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e \~Xtest', @:) + call delete('~Xtest') + + " should be able to complete a file name that has a '*' + call writefile([], 'Xx*Yy') + call feedkeys(":e Xx\*\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xx\*Yy', @:) + call delete('Xx*Yy') + + " use a literal star + call feedkeys(":e \\*\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e \*', @:) + endif + + call feedkeys(":py3f\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"py3file', @:) +endfunc + +" Test for 'wildignorecase' +func Test_cmdline_wildignorecase() + CheckUnix + call writefile([], 'XTEST') + set wildignorecase + call feedkeys(":e xt\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e XTEST', @:) + call assert_equal(['XTEST'], getcompletion('xt', 'file')) + let g:Sline = '' + call feedkeys(":e xt\<C-d>\<F4>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e xt', @:) + call assert_equal('XTEST', g:Sline) + set wildignorecase& + call delete('XTEST') endfunc func Test_cmdline_write_alternatefile() @@ -1051,6 +1286,18 @@ func Test_cmdline_write_alternatefile() bw! endfunc +func Test_cmdline_expand_cur_alt_file() + enew + file http://some.com/file.txt + call feedkeys(":e %\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e http://some.com/file.txt', @:) + edit another + call feedkeys(":e #\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e http://some.com/file.txt', @:) + bwipe + bwipe http://some.com/file.txt +endfunc + " using a leading backslash here set cpo+=C @@ -1072,7 +1319,7 @@ func Test_cmdline_search_range() call assert_equal('B', getline(2)) let @/ = 'apple' - call assert_fails('\/print', 'E486:') + call assert_fails('\/print', ['E486:.*apple']) bwipe! endfunc @@ -1082,7 +1329,7 @@ func Test_tick_mark_in_range() " If only the tick is passed as a range and no command is specified, there " should not be an error call feedkeys(":'\<CR>", 'xt') - call assert_equal("'", getreg(':')) + call assert_equal("'", @:) call assert_fails("',print", 'E78:') endfunc @@ -1188,6 +1435,11 @@ func Test_verbosefile() let log = readfile('Xlog') call assert_match("foo\nbar", join(log, "\n")) call delete('Xlog') + call mkdir('Xdir') + if !has('win32') " FIXME: no error on Windows, libuv bug? + call assert_fails('set verbosefile=Xdir', ['E484:.*Xdir', 'E474:']) + endif + call delete('Xdir', 'd') endfunc func Test_verbose_option() @@ -1260,6 +1512,16 @@ func Test_cmdline_overstrike() let &encoding = encoding_save endfunc +func Test_cmdwin_bug() + let winid = win_getid() + sp + try + call feedkeys("q::call win_gotoid(" .. winid .. ")\<CR>:q\<CR>", 'x!') + catch /^Vim\%((\a\+)\)\=:E11/ + endtry + bw! +endfunc + func Test_cmdwin_restore() CheckScreendump @@ -1414,6 +1676,7 @@ func Test_cmdline_expand_special() call assert_fails('e <afile>', 'E495:') call assert_fails('e <abuf>', 'E496:') call assert_fails('e <amatch>', 'E497:') + call writefile([], 'Xfile.cpp') call writefile([], 'Xfile.java') new Xfile.cpp @@ -1428,7 +1691,7 @@ func Test_cmdwin_jump_to_win() call assert_fails('call feedkeys("q:\<C-W>\<C-W>\<CR>", "xt")', 'E11:') new set modified - call assert_fails('call feedkeys("q/:qall\<CR>", "xt")', 'E162:') + call assert_fails('call feedkeys("q/:qall\<CR>", "xt")', ['E37:', 'E162:']) close! call feedkeys("q/:close\<CR>", "xt") call assert_equal(1, winnr('$')) @@ -1442,16 +1705,36 @@ endfunc func Test_cmdwin_tabpage() tabedit - " v8.2.1919 isn't ported yet, so E492 is thrown after E11 here. - " v8.2.1183 also isn't ported yet, so we also can't assert E11 directly. - " For now, assert E11 and E492 separately. When v8.2.1183 is ported, the - " assert for E492 will fail and this workaround should be removed. - " call assert_fails("silent norm q/g :I\<Esc>", 'E11:') - call assert_fails("silent norm q/g ", 'E11:') - call assert_fails("silent norm q/g :I\<Esc>", 'E492:') + call assert_fails("silent norm q/g :I\<Esc>", 'E11:') tabclose! endfunc +func Test_cmdwin_interrupted() + CheckScreendump + + " aborting the :smile output caused the cmdline window to use the current + " buffer. + let lines =<< trim [SCRIPT] + au WinNew * smile + [SCRIPT] + call writefile(lines, 'XTest_cmdwin') + + let buf = RunVimInTerminal('-S XTest_cmdwin', {'rows': 18}) + " open cmdwin + call term_sendkeys(buf, "q:") + call WaitForAssert({-> assert_match('-- More --', term_getline(buf, 18))}) + " quit more prompt for :smile command + call term_sendkeys(buf, "q") + call WaitForAssert({-> assert_match('^$', term_getline(buf, 18))}) + " execute a simple command + call term_sendkeys(buf, "aecho 'done'\<CR>") + call VerifyScreenDump(buf, 'Test_cmdwin_interrupted', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_cmdwin') +endfunc + " Test for backtick expression in the command line func Test_cmd_backtick() CheckNotMSWindows " FIXME: see #19297 @@ -1508,6 +1791,53 @@ func Test_cmd_bang_E135() %bwipe! endfunc +func Test_cmd_bang_args() + new + :.! + call assert_equal(0, v:shell_error) + + " Note that below there is one space char after the '!'. This caused a + " shell error in the past, see https://github.com/vim/vim/issues/11495. + :.! + call assert_equal(0, v:shell_error) + bwipe! + + CheckUnix + :.!pwd + call assert_equal(0, v:shell_error) + :.! pwd + call assert_equal(0, v:shell_error) + + " Note there is one space after 'pwd'. + :.! pwd + call assert_equal(0, v:shell_error) + + " Note there are two spaces after 'pwd'. + :.! pwd + call assert_equal(0, v:shell_error) + :.!ls ~ + call assert_equal(0, v:shell_error) + + " Note there is one space char after '~'. + :.!ls ~ + call assert_equal(0, v:shell_error) + + " Note there are two spaces after '~'. + :.!ls ~ + call assert_equal(0, v:shell_error) + + :.!echo "foo" + call assert_equal(getline('.'), "foo") + :.!echo "foo " + call assert_equal(getline('.'), "foo ") + :.!echo " foo " + call assert_equal(getline('.'), " foo ") + :.!echo " foo " + call assert_equal(getline('.'), " foo ") + + %bwipe! +endfunc + " Test for using ~ for home directory in cmdline completion matches func Test_cmdline_expand_home() call mkdir('Xdir') @@ -1543,22 +1873,16 @@ func Test_cmdline_ctrl_g() endfunc " Test for 'wildmode' -func Test_wildmode() +func Wildmode_tests() func T(a, c, p) return "oneA\noneB\noneC" endfunc command -nargs=1 -complete=custom,T MyCmd - func SaveScreenLine() - let g:Sline = Screenline(&lines - 1) - return '' - endfunc - cnoremap <expr> <F2> SaveScreenLine() - set nowildmenu set wildmode=full,list let g:Sline = '' - call feedkeys(":MyCmd \t\t\<F2>\<C-B>\"\<CR>", 'xt') + call feedkeys(":MyCmd \t\t\<F4>\<C-B>\"\<CR>", 'xt') call assert_equal('oneA oneB oneC', g:Sline) call assert_equal('"MyCmd oneA', @:) @@ -1574,7 +1898,7 @@ func Test_wildmode() set wildmode=list:longest let g:Sline = '' - call feedkeys(":MyCmd \t\<F2>\<C-B>\"\<CR>", 'xt') + call feedkeys(":MyCmd \t\<F4>\<C-B>\"\<CR>", 'xt') call assert_equal('oneA oneB oneC', g:Sline) call assert_equal('"MyCmd one', @:) @@ -1585,27 +1909,67 @@ func Test_wildmode() " Test for wildmode=longest with 'fileignorecase' set set wildmode=longest set fileignorecase - argadd AA AAA AAAA - call feedkeys(":buffer \t\<C-B>\"\<CR>", 'xt') - call assert_equal('"buffer AA', @:) + argadd AAA AAAA AAAAA + call feedkeys(":buffer a\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"buffer AAA', @:) set fileignorecase& " Test for listing files with wildmode=list set wildmode=list let g:Sline = '' - call feedkeys(":b A\t\t\<F2>\<C-B>\"\<CR>", 'xt') - call assert_equal('AA AAA AAAA', g:Sline) + call feedkeys(":b A\t\t\<F4>\<C-B>\"\<CR>", 'xt') + call assert_equal('AAA AAAA AAAAA', g:Sline) call assert_equal('"b A', @:) + " when using longest completion match, matches shorter than the argument + " should be ignored (happens with :help) + set wildmode=longest,full + set wildmenu + call feedkeys(":help a*\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"help a', @:) + " non existing file + call feedkeys(":e a1b2y3z4\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"e a1b2y3z4', @:) + set wildmenu& + + " Test for longest file name completion with 'fileignorecase' + " On MS-Windows, file names are case insensitive. + if has('unix') + call writefile([], 'XTESTfoo') + call writefile([], 'Xtestbar') + set nofileignorecase + call feedkeys(":e XT\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e XTESTfoo', @:) + call feedkeys(":e Xt\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtestbar', @:) + set fileignorecase + call feedkeys(":e XT\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtest', @:) + call feedkeys(":e Xt\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xtest', @:) + set fileignorecase& + call delete('XTESTfoo') + call delete('Xtestbar') + endif + %argdelete delcommand MyCmd delfunc T - delfunc SaveScreenLine - cunmap <F2> set wildmode& %bwipe! endfunc +func Test_wildmode() + " Test with utf-8 encoding + call Wildmode_tests() + + " Test with latin1 encoding + let save_encoding = &encoding + " set encoding=latin1 + " call Wildmode_tests() + let &encoding = save_encoding +endfunc + " Test for interrupting the command-line completion func Test_interrupt_compl() func F(lead, cmdl, p) @@ -1627,6 +1991,14 @@ func Test_interrupt_compl() endtry call assert_equal(1, interrupted) + let interrupted = 0 + try + call feedkeys(":Tcmd tw\<C-d>\<C-B>\"\<CR>", 'xt') + catch /^Vim:Interrupt$/ + let interrupted = 1 + endtry + call assert_equal(1, interrupted) + delcommand Tcmd delfunc F set wildmode& @@ -1683,6 +2055,11 @@ func Test_cmdline_expr() call assert_equal("\"e \<C-\>\<C-Y>", @:) endfunc +" This was making the insert position negative +func Test_cmdline_expr_register() + exe "sil! norm! ?\<C-\>e0\<C-R>0\<Esc>?\<C-\>e0\<CR>" +endfunc + " Test for 'imcmdline' and 'imsearch' " This test doesn't actually test the input method functionality. func Test_cmdline_inputmethod() @@ -1809,8 +2186,45 @@ func Test_read_shellcmd() endif endfunc +" Test for going up and down the directory tree using 'wildmenu' +func Test_wildmenu_dirstack() + CheckUnix + %bw! + call mkdir('Xdir1/dir2/dir3/dir4', 'p') + call writefile([], 'Xdir1/file1_1.txt') + call writefile([], 'Xdir1/file1_2.txt') + call writefile([], 'Xdir1/dir2/file2_1.txt') + call writefile([], 'Xdir1/dir2/file2_2.txt') + call writefile([], 'Xdir1/dir2/dir3/file3_1.txt') + call writefile([], 'Xdir1/dir2/dir3/file3_2.txt') + call writefile([], 'Xdir1/dir2/dir3/dir4/file4_1.txt') + call writefile([], 'Xdir1/dir2/dir3/dir4/file4_2.txt') + set wildmenu + + cd Xdir1/dir2/dir3/dir4 + call feedkeys(":e \<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e file4_1.txt', @:) + call feedkeys(":e \<Tab>\<Up>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ../dir4/', @:) + call feedkeys(":e \<Tab>\<Up>\<Up>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ../../dir3/', @:) + call feedkeys(":e \<Tab>\<Up>\<Up>\<Up>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ../../../dir2/', @:) + call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ../../dir3/dir4/', @:) + call feedkeys(":e \<Tab>\<Up>\<Up>\<Down>\<Down>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e ../../dir3/dir4/file4_1.txt', @:) + cd - + call feedkeys(":e Xdir1/\<Tab>\<Down>\<Down>\<Down>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xdir1/dir2/dir3/dir4/file4_1.txt', @:) + + call delete('Xdir1', 'rf') + set wildmenu& +endfunc + " Test for recalling newer or older cmdline from history with <Up>, <Down>, -" <S-Up>, <S-Down>, <PageUp>, <PageDown>, <C-p>, or <C-n>. +" <S-Up>, <S-Down>, <PageUp>, <PageDown>, <kPageUp>, <kPageDown>, <C-p>, or +" <C-n>. func Test_recalling_cmdline() CheckFeature cmdline_hist @@ -1818,17 +2232,18 @@ func Test_recalling_cmdline() cnoremap <Plug>(save-cmdline) <Cmd>let g:cmdlines += [getcmdline()]<CR> let histories = [ - \ {'name': 'cmd', 'enter': ':', 'exit': "\<Esc>"}, - \ {'name': 'search', 'enter': '/', 'exit': "\<Esc>"}, - \ {'name': 'expr', 'enter': ":\<C-r>=", 'exit': "\<Esc>\<Esc>"}, - \ {'name': 'input', 'enter': ":call input('')\<CR>", 'exit': "\<CR>"}, + \ #{name: 'cmd', enter: ':', exit: "\<Esc>"}, + \ #{name: 'search', enter: '/', exit: "\<Esc>"}, + \ #{name: 'expr', enter: ":\<C-r>=", exit: "\<Esc>\<Esc>"}, + \ #{name: 'input', enter: ":call input('')\<CR>", exit: "\<CR>"}, "\ TODO: {'name': 'debug', ...} \] let keypairs = [ - \ {'older': "\<Up>", 'newer': "\<Down>", 'prefixmatch': v:true}, - \ {'older': "\<S-Up>", 'newer': "\<S-Down>", 'prefixmatch': v:false}, - \ {'older': "\<PageUp>", 'newer': "\<PageDown>", 'prefixmatch': v:false}, - \ {'older': "\<C-p>", 'newer': "\<C-n>", 'prefixmatch': v:false}, + \ #{older: "\<Up>", newer: "\<Down>", prefixmatch: v:true}, + \ #{older: "\<S-Up>", newer: "\<S-Down>", prefixmatch: v:false}, + \ #{older: "\<PageUp>", newer: "\<PageDown>", prefixmatch: v:false}, + \ #{older: "\<kPageUp>", newer: "\<kPageDown>", prefixmatch: v:false}, + \ #{older: "\<C-p>", newer: "\<C-n>", prefixmatch: v:false}, \] let prefix = 'vi' for h in histories @@ -1857,6 +2272,59 @@ func Test_recalling_cmdline() cunmap <Plug>(save-cmdline) endfunc +func Test_cmd_map_cmdlineChanged() + let g:log = [] + cnoremap <F1> l<Cmd><CR>s + augroup test + autocmd! + autocmd CmdlineChanged : let g:log += [getcmdline()] + augroup END + + call feedkeys(":\<F1>\<CR>", 'xt') + call assert_equal(['l', 'ls'], g:log) + + let @b = 'b' + cnoremap <F1> a<C-R>b + let g:log = [] + call feedkeys(":\<F1>\<CR>", 'xt') + call assert_equal(['a', 'ab'], g:log) + + unlet g:log + cunmap <F1> + augroup test + autocmd! + augroup END +endfunc + +" Test for the 'suffixes' option +func Test_suffixes_opt() + call writefile([], 'Xfile') + call writefile([], 'Xfile.c') + call writefile([], 'Xfile.o') + set suffixes= + call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile Xfile.c Xfile.o', @:) + call feedkeys(":e Xfi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile.c', @:) + set suffixes=.c + call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile Xfile.o Xfile.c', @:) + call feedkeys(":e Xfi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile.o', @:) + set suffixes=,, + call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile.c Xfile.o Xfile', @:) + call feedkeys(":e Xfi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"e Xfile.o', @:) + set suffixes& + " Test for getcompletion() with different patterns + call assert_equal(['Xfile', 'Xfile.c', 'Xfile.o'], getcompletion('Xfile', 'file')) + call assert_equal(['Xfile'], getcompletion('Xfile$', 'file')) + call delete('Xfile') + call delete('Xfile.c') + call delete('Xfile.o') +endfunc + " Test for using a popup menu for the command line completion matches " (wildoptions=pum) func Test_wildmenu_pum() @@ -1868,39 +2336,63 @@ func Test_wildmenu_pum() set shm+=I set noruler set noshowcmd + + func CmdCompl(a, b, c) + return repeat(['aaaa'], 120) + endfunc + command -nargs=* -complete=customlist,CmdCompl Tcmd + + func MyStatusLine() abort + return 'status' + endfunc + func SetupStatusline() + set statusline=%!MyStatusLine() + set laststatus=2 + endfunc + + func MyTabLine() + return 'my tab line' + endfunc + func SetupTabline() + set statusline= + set tabline=%!MyTabLine() + set showtabline=2 + endfunc + + func DoFeedKeys() + let &wildcharm = char2nr("\t") + call feedkeys(":edit $VIMRUNTIME/\<Tab>\<Left>\<C-U>ab\<Tab>") + endfunc [CODE] call writefile(commands, 'Xtest') let buf = RunVimInTerminal('-S Xtest', #{rows: 10}) call term_sendkeys(buf, ":sign \<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_01', {}) + " going down the popup menu using <Down> call term_sendkeys(buf, "\<Down>\<Down>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_02', {}) + " going down the popup menu using <C-N> call term_sendkeys(buf, "\<C-N>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_03', {}) + " going up the popup menu using <C-P> call term_sendkeys(buf, "\<C-P>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_04', {}) + " going up the popup menu using <Up> call term_sendkeys(buf, "\<Up>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_05', {}) " pressing <C-E> should end completion and go back to the original match call term_sendkeys(buf, "\<C-E>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_06', {}) " pressing <C-Y> should select the current match and end completion call term_sendkeys(buf, "\<Tab>\<C-P>\<C-P>\<C-Y>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_07', {}) " With 'wildmode' set to 'longest,full', completing a match should display @@ -1908,31 +2400,25 @@ func Test_wildmenu_pum() call term_sendkeys(buf, ":\<C-U>set wildmode=longest,full\<CR>") call TermWait(buf) call term_sendkeys(buf, ":sign u\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_08', {}) " pressing <Tab> should display the wildmenu call term_sendkeys(buf, "\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_09', {}) " pressing <Tab> second time should select the next entry in the menu call term_sendkeys(buf, "\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_10', {}) call term_sendkeys(buf, ":\<C-U>set wildmode=full\<CR>") - " " showing popup menu in different columns in the cmdline + " showing popup menu in different columns in the cmdline call term_sendkeys(buf, ":sign define \<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_11', {}) call term_sendkeys(buf, " \<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_12', {}) call term_sendkeys(buf, " \<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_13', {}) " Directory name completion @@ -1942,95 +2428,77 @@ func Test_wildmenu_pum() call writefile([], 'Xdir/XdirA/XdirB/XfileC') call term_sendkeys(buf, "\<C-U>e Xdi\<Tab>\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_14', {}) " Pressing <Right> on a directory name should go into that directory call term_sendkeys(buf, "\<Right>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_15', {}) " Pressing <Left> on a directory name should go to the parent directory call term_sendkeys(buf, "\<Left>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_16', {}) " Pressing <C-A> when the popup menu is displayed should list all the - " matches and remove the popup menu + " matches but the popup menu should still remain call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-A>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_17', {}) " Pressing <C-D> when the popup menu is displayed should remove the popup " menu call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-D>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_18', {}) " Pressing <S-Tab> should open the popup menu with the last entry selected call term_sendkeys(buf, "\<C-U>\<CR>:sign \<S-Tab>\<C-P>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_19', {}) " Pressing <Esc> should close the popup menu and cancel the cmd line call term_sendkeys(buf, "\<C-U>\<CR>:sign \<Tab>\<Esc>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_20', {}) " Typing a character when the popup is open, should close the popup call term_sendkeys(buf, ":sign \<Tab>x") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_21', {}) " When the popup is open, entering the cmdline window should close the popup call term_sendkeys(buf, "\<C-U>sign \<Tab>\<C-F>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_22', {}) call term_sendkeys(buf, ":q\<CR>") " After the last popup menu item, <C-N> should show the original string call term_sendkeys(buf, ":sign u\<Tab>\<C-N>\<C-N>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_23', {}) " Use the popup menu for the command name call term_sendkeys(buf, "\<C-U>bu\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_24', {}) " Pressing the left arrow should remove the popup menu call term_sendkeys(buf, "\<Left>\<Left>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_25', {}) " Pressing <BS> should remove the popup menu and erase the last character call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<BS>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_26', {}) " Pressing <C-W> should remove the popup menu and erase the previous word call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<C-W>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_27', {}) " Pressing <C-U> should remove the popup menu and erase the entire line call term_sendkeys(buf, "\<C-E>\<C-U>sign \<Tab>\<C-U>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_28', {}) " Using <C-E> to cancel the popup menu and then pressing <Up> should recall " the cmdline from history call term_sendkeys(buf, "sign xyz\<Esc>:sign \<Tab>\<C-E>\<Up>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_29', {}) " Check "list" still works call term_sendkeys(buf, "\<C-U>set wildmode=longest,list\<CR>") call term_sendkeys(buf, ":cn\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_30', {}) call term_sendkeys(buf, "s") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_31', {}) " Tests a directory name contained full-width characters. @@ -2041,15 +2509,933 @@ func Test_wildmenu_pum() call term_sendkeys(buf, "\<C-U>set wildmode&\<CR>") call term_sendkeys(buf, ":\<C-U>e Xdir/あいう/\<Tab>") - call TermWait(buf) call VerifyScreenDump(buf, 'Test_wildmenu_pum_32', {}) + " Pressing <C-A> when the popup menu is displayed should list all the + " matches and pressing a key after that should remove the popup menu + call term_sendkeys(buf, "\<C-U>set wildmode=full\<CR>") + call term_sendkeys(buf, ":sign \<Tab>\<C-A>x") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_33', {}) + + " Pressing <C-A> when the popup menu is displayed should list all the + " matches and pressing <Left> after that should move the cursor + call term_sendkeys(buf, "\<C-U>abc\<Esc>") + call term_sendkeys(buf, ":sign \<Tab>\<C-A>\<Left>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_34', {}) + + " When <C-A> displays a lot of matches (screen scrolls), all the matches + " should be displayed correctly on the screen. + call term_sendkeys(buf, "\<End>\<C-U>Tcmd \<Tab>\<C-A>\<Left>\<Left>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_35', {}) + + " After using <C-A> to expand all the filename matches, pressing <Up> + " should not open the popup menu again. + call term_sendkeys(buf, "\<C-E>\<C-U>:cd Xdir/XdirA\<CR>") + call term_sendkeys(buf, ":e \<Tab>\<C-A>\<Up>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_36', {}) + call term_sendkeys(buf, "\<C-E>\<C-U>:cd -\<CR>") + + " After using <C-A> to expand all the matches, pressing <S-Tab> used to + " crash Vim + call term_sendkeys(buf, ":sign \<Tab>\<C-A>\<S-Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_37', {}) + + " After removing the pum the command line is redrawn + call term_sendkeys(buf, ":edit foo\<CR>") + call term_sendkeys(buf, ":edit bar\<CR>") + call term_sendkeys(buf, ":ls\<CR>") + call term_sendkeys(buf, ":com\<Tab> ") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_38', {}) + call term_sendkeys(buf, "\<C-U>\<CR>") + + " Esc still works to abort the command when 'statusline' is set + call term_sendkeys(buf, ":call SetupStatusline()\<CR>") + call term_sendkeys(buf, ":si\<Tab>") + call term_sendkeys(buf, "\<Esc>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_39', {}) + + " Esc still works to abort the command when 'tabline' is set + call term_sendkeys(buf, ":call SetupTabline()\<CR>") + call term_sendkeys(buf, ":si\<Tab>") + call term_sendkeys(buf, "\<Esc>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_40', {}) + + " popup is cleared also when 'lazyredraw' is set + call term_sendkeys(buf, ":set showtabline=1 laststatus=1 lazyredraw\<CR>") + call term_sendkeys(buf, ":call DoFeedKeys()\<CR>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_41', {}) + call term_sendkeys(buf, "\<Esc>") + + " Pressing <PageDown> should scroll the menu downward + call term_sendkeys(buf, ":sign \<Tab>\<PageDown>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_42', {}) + call term_sendkeys(buf, "\<PageDown>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_43', {}) + call term_sendkeys(buf, "\<PageDown>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_44', {}) + call term_sendkeys(buf, "\<PageDown>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_45', {}) + call term_sendkeys(buf, "\<C-U>sign \<Tab>\<Down>\<Down>\<PageDown>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_46', {}) + + " Pressing <PageUp> should scroll the menu upward + call term_sendkeys(buf, "\<C-U>sign \<Tab>\<PageUp>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_47', {}) + call term_sendkeys(buf, "\<PageUp>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_48', {}) + call term_sendkeys(buf, "\<PageUp>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_49', {}) + call term_sendkeys(buf, "\<PageUp>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_50', {}) + call term_sendkeys(buf, "\<C-U>\<CR>") call StopVimInTerminal(buf) call delete('Xtest') call delete('Xdir', 'rf') endfunc +" Test for wildmenumode() with the cmdline popup menu +func Test_wildmenumode_with_pum() + set wildmenu + set wildoptions=pum + cnoremap <expr> <F2> wildmenumode() + call feedkeys(":sign \<Tab>\<F2>\<F2>\<C-B>\"\<CR>", 'xt') + call assert_equal('"sign define10', @:) + call feedkeys(":sign \<Tab>\<C-A>\<F2>\<C-B>\"\<CR>", 'xt') + call assert_equal('"sign define jump list place undefine unplace0', @:) + call feedkeys(":sign \<Tab>\<C-E>\<F2>\<C-B>\"\<CR>", 'xt') + call assert_equal('"sign 0', @:) + call feedkeys(":sign \<Tab>\<C-Y>\<F2>\<C-B>\"\<CR>", 'xt') + call assert_equal('"sign define0', @:) + set nowildmenu wildoptions& + cunmap <F2> +endfunc + +" Test for opening the cmdline completion popup menu from the terminal window. +" The popup menu should be positioned correctly over the status line of the +" bottom-most window. +func Test_wildmenu_pum_from_terminal() + CheckRunVimInTerminal + let python = PythonProg() + call CheckPython(python) + + %bw! + let cmds = ['set wildmenu wildoptions=pum'] + let pcmd = python .. ' -c "import sys; sys.stdout.write(sys.stdin.read())"' + call add(cmds, "call term_start('" .. pcmd .. "')") + call writefile(cmds, 'Xtest') + let buf = RunVimInTerminal('-S Xtest', #{rows: 10}) + call term_sendkeys(buf, "\r\r\r") + call term_wait(buf) + call term_sendkeys(buf, "\<C-W>:sign \<Tab>") + call term_wait(buf) + call VerifyScreenDump(buf, 'Test_wildmenu_pum_term_01', {}) + call term_wait(buf) + call StopVimInTerminal(buf) + call delete('Xtest') +endfunc + +func Test_wildmenu_pum_clear_entries() + CheckRunVimInTerminal + + " This was using freed memory. Run in a terminal to get the pum to update. + let lines =<< trim END + set wildoptions=pum + set wildchar=<C-E> + END + call writefile(lines, 'XwildmenuTest', 'D') + let buf = RunVimInTerminal('-S XwildmenuTest', #{rows: 10}) + + call term_sendkeys(buf, ":\<C-E>\<C-E>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_clear_entries_1', {}) + + set wildoptions& wildchar& +endfunc + +" Test for completion after a :substitute command followed by a pipe (|) +" character +func Test_cmdline_complete_substitute() + call feedkeys(":s | \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s | \t", @:) + call feedkeys(":s/ | \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/ | \t", @:) + call feedkeys(":s/one | \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/one | \t", @:) + call feedkeys(":s/one/ | \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/one/ | \t", @:) + call feedkeys(":s/one/two | \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/one/two | \t", @:) + call feedkeys(":s/one/two/ | chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"s/one/two/ | chistory', @:) + call feedkeys(":s/one/two/g \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/one/two/g \t", @:) + call feedkeys(":s/one/two/g | chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/one/two/g | chistory", @:) + call feedkeys(":s/one/t\\/ | \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"s/one/t\\/ | \t", @:) + call feedkeys(":s/one/t\"o/ | chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"s/one/t"o/ | chistory', @:) + call feedkeys(":s/one/t|o/ | chist\t\<C-B>\"\<CR>", 'xt') + call assert_equal('"s/one/t|o/ | chistory', @:) + call feedkeys(":&\t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"&\t", @:) +endfunc + +" Test for the :dlist command completion +func Test_cmdline_complete_dlist() + call feedkeys(":dlist 10 /pat/ a\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"dlist 10 /pat/ a\<C-A>", @:) + call feedkeys(":dlist 10 /pat/ \t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"dlist 10 /pat/ \t", @:) + call feedkeys(":dlist 10 /pa\\t/\t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"dlist 10 /pa\\t/\t", @:) + call feedkeys(":dlist 10 /pat\\\t\<C-B>\"\<CR>", 'xt') + call assert_equal("\"dlist 10 /pat\\\t", @:) + call feedkeys(":dlist 10 /pat/ | chist\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"dlist 10 /pat/ | chistory", @:) +endfunc + +" argument list (only for :argdel) fuzzy completion +func Test_fuzzy_completion_arglist() + argadd change.py count.py charge.py + set wildoptions& + call feedkeys(":argdel cge\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"argdel cge', @:) + set wildoptions=fuzzy + call feedkeys(":argdel cge\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"argdel change.py charge.py', @:) + %argdelete + set wildoptions& +endfunc + +" autocmd group name fuzzy completion +func Test_fuzzy_completion_autocmd() + set wildoptions& + augroup MyFuzzyGroup + augroup END + call feedkeys(":augroup mfg\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"augroup mfg', @:) + call feedkeys(":augroup My*p\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"augroup MyFuzzyGroup', @:) + set wildoptions=fuzzy + call feedkeys(":augroup mfg\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"augroup MyFuzzyGroup', @:) + call feedkeys(":augroup My*p\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"augroup My*p', @:) + augroup! MyFuzzyGroup + set wildoptions& +endfunc + +" buffer name fuzzy completion +func Test_fuzzy_completion_bufname() + set wildoptions& + " Use a long name to reduce the risk of matching a random directory name + edit SomeRandomFileWithLetters.txt + enew + call feedkeys(":b SRFWL\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"b SRFWL', @:) + call feedkeys(":b S*FileWithLetters.txt\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"b SomeRandomFileWithLetters.txt', @:) + set wildoptions=fuzzy + call feedkeys(":b SRFWL\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"b SomeRandomFileWithLetters.txt', @:) + call feedkeys(":b S*FileWithLetters.txt\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"b S*FileWithLetters.txt', @:) + %bw! + set wildoptions& +endfunc + +" buffer name (full path) fuzzy completion +func Test_fuzzy_completion_bufname_fullpath() + CheckUnix + set wildoptions& + call mkdir('Xcmd/Xstate/Xfile.js', 'p') + edit Xcmd/Xstate/Xfile.js + cd Xcmd/Xstate + enew + call feedkeys(":b CmdStateFile\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"b CmdStateFile', @:) + set wildoptions=fuzzy + call feedkeys(":b CmdStateFile\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match('Xcmd/Xstate/Xfile.js$', @:) + cd - + call delete('Xcmd', 'rf') + set wildoptions& +endfunc + +" :behave suboptions fuzzy completion +func Test_fuzzy_completion_behave() + set wildoptions& + call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"behave xm', @:) + call feedkeys(":behave xt*m\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"behave xterm', @:) + set wildoptions=fuzzy + call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"behave xterm', @:) + call feedkeys(":behave xt*m\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"behave xt*m', @:) + let g:Sline = '' + call feedkeys(":behave win\<C-D>\<F4>\<C-B>\"\<CR>", 'tx') + call assert_equal('mswin', g:Sline) + call assert_equal('"behave win', @:) + set wildoptions& +endfunc + +" " colorscheme name fuzzy completion - NOT supported +" func Test_fuzzy_completion_colorscheme() +" endfunc + +" built-in command name fuzzy completion +func Test_fuzzy_completion_cmdname() + set wildoptions& + call feedkeys(":sbwin\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sbwin', @:) + call feedkeys(":sbr*d\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sbrewind', @:) + set wildoptions=fuzzy + call feedkeys(":sbwin\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sbrewind', @:) + call feedkeys(":sbr*d\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sbr*d', @:) + set wildoptions& +endfunc + +" " compiler name fuzzy completion - NOT supported +" func Test_fuzzy_completion_compiler() +" endfunc + +" :cscope suboptions fuzzy completion +func Test_fuzzy_completion_cscope() + CheckFeature cscope + set wildoptions& + call feedkeys(":cscope ret\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"cscope ret', @:) + call feedkeys(":cscope re*t\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"cscope reset', @:) + set wildoptions=fuzzy + call feedkeys(":cscope ret\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"cscope reset', @:) + call feedkeys(":cscope re*t\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"cscope re*t', @:) + set wildoptions& +endfunc + +" :diffget/:diffput buffer name fuzzy completion +func Test_fuzzy_completion_diff() + new SomeBuffer + diffthis + new OtherBuffer + diffthis + set wildoptions& + call feedkeys(":diffget sbuf\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget sbuf', @:) + call feedkeys(":diffput sbuf\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput sbuf', @:) + set wildoptions=fuzzy + call feedkeys(":diffget sbuf\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffget SomeBuffer', @:) + call feedkeys(":diffput sbuf\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"diffput SomeBuffer', @:) + %bw! + set wildoptions& +endfunc + +" " directory name fuzzy completion - NOT supported +" func Test_fuzzy_completion_dirname() +" endfunc + +" environment variable name fuzzy completion +func Test_fuzzy_completion_env() + set wildoptions& + call feedkeys(":echo $VUT\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"echo $VUT', @:) + set wildoptions=fuzzy + call feedkeys(":echo $VUT\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"echo $VIMRUNTIME', @:) + set wildoptions& +endfunc + +" autocmd event fuzzy completion +func Test_fuzzy_completion_autocmd_event() + set wildoptions& + call feedkeys(":autocmd BWout\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"autocmd BWout', @:) + set wildoptions=fuzzy + call feedkeys(":autocmd BWout\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"autocmd BufWipeout', @:) + set wildoptions& +endfunc + +" vim expression fuzzy completion +func Test_fuzzy_completion_expr() + let g:PerPlaceCount = 10 + set wildoptions& + call feedkeys(":let c = ppc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"let c = ppc', @:) + set wildoptions=fuzzy + call feedkeys(":let c = ppc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"let c = PerPlaceCount', @:) + set wildoptions& +endfunc + +" " file name fuzzy completion - NOT supported +" func Test_fuzzy_completion_filename() +" endfunc + +" " files in path fuzzy completion - NOT supported +" func Test_fuzzy_completion_filesinpath() +" endfunc + +" " filetype name fuzzy completion - NOT supported +" func Test_fuzzy_completion_filetype() +" endfunc + +" user defined function name completion +func Test_fuzzy_completion_userdefined_func() + set wildoptions& + call feedkeys(":call Test_f_u_f\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"call Test_f_u_f', @:) + set wildoptions=fuzzy + call feedkeys(":call Test_f_u_f\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"call Test_fuzzy_completion_userdefined_func()', @:) + set wildoptions& +endfunc + +" <SNR> functions should be sorted to the end +func Test_fuzzy_completion_userdefined_snr_func() + func s:Sendmail() + endfunc + func SendSomemail() + endfunc + func S1e2n3dmail() + endfunc + set wildoptions=fuzzy + call feedkeys(":call sendmail\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"call SendSomemail() S1e2n3dmail() ' + \ .. expand("<SID>") .. 'Sendmail()', @:) + set wildoptions& + delfunc s:Sendmail + delfunc SendSomemail + delfunc S1e2n3dmail +endfunc + +" user defined command name completion +func Test_fuzzy_completion_userdefined_cmd() + set wildoptions& + call feedkeys(":MsFeat\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"MsFeat', @:) + set wildoptions=fuzzy + call feedkeys(":MsFeat\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"MissingFeature', @:) + set wildoptions& +endfunc + +" " :help tag fuzzy completion - NOT supported +" func Test_fuzzy_completion_helptag() +" endfunc + +" highlight group name fuzzy completion +func Test_fuzzy_completion_hlgroup() + set wildoptions& + call feedkeys(":highlight SKey\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"highlight SKey', @:) + call feedkeys(":highlight Sp*Key\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"highlight SpecialKey', @:) + set wildoptions=fuzzy + call feedkeys(":highlight SKey\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"highlight SpecialKey', @:) + call feedkeys(":highlight Sp*Key\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"highlight Sp*Key', @:) + set wildoptions& +endfunc + +" :history suboptions fuzzy completion +func Test_fuzzy_completion_history() + set wildoptions& + call feedkeys(":history dg\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"history dg', @:) + call feedkeys(":history se*h\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"history search', @:) + set wildoptions=fuzzy + call feedkeys(":history dg\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"history debug', @:) + call feedkeys(":history se*h\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"history se*h', @:) + set wildoptions& +endfunc + +" :language locale name fuzzy completion +func Test_fuzzy_completion_lang() + CheckUnix + set wildoptions& + call feedkeys(":lang psx\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"lang psx', @:) + set wildoptions=fuzzy + call feedkeys(":lang psx\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"lang POSIX', @:) + set wildoptions& +endfunc + +" :mapclear buffer argument fuzzy completion +func Test_fuzzy_completion_mapclear() + set wildoptions& + call feedkeys(":mapclear buf\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"mapclear buf', @:) + set wildoptions=fuzzy + call feedkeys(":mapclear buf\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"mapclear <buffer>', @:) + set wildoptions& +endfunc + +" map name fuzzy completion +func Test_fuzzy_completion_mapname() + " test regex completion works + set wildoptions=fuzzy + call feedkeys(":cnoremap <ex\<Tab> <esc> \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"cnoremap <expr> <esc> \<Tab>", @:) + nmap <plug>MyLongMap :p<CR> + call feedkeys(":nmap MLM\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"nmap <Plug>MyLongMap", @:) + call feedkeys(":nmap MLM \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"nmap MLM \t", @:) + call feedkeys(":nmap <F2> one two \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"nmap <F2> one two \t", @:) + " duplicate entries should be removed + vmap <plug>MyLongMap :<C-U>#<CR> + call feedkeys(":nmap MLM\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"nmap <Plug>MyLongMap", @:) + nunmap <plug>MyLongMap + vunmap <plug>MyLongMap + call feedkeys(":nmap ABC\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"nmap ABC\t", @:) + " results should be sorted by best match + nmap <Plug>format : + nmap <Plug>goformat : + nmap <Plug>TestFOrmat : + nmap <Plug>fendoff : + nmap <Plug>state : + nmap <Plug>FendingOff : + call feedkeys(":nmap <Plug>fo\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"nmap <Plug>format <Plug>TestFOrmat <Plug>FendingOff <Plug>goformat <Plug>fendoff", @:) + nunmap <Plug>format + nunmap <Plug>goformat + nunmap <Plug>TestFOrmat + nunmap <Plug>fendoff + nunmap <Plug>state + nunmap <Plug>FendingOff + set wildoptions& +endfunc + +" abbreviation fuzzy completion +func Test_fuzzy_completion_abbr() + set wildoptions=fuzzy + call feedkeys(":iabbr wait\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"iabbr <nowait>", @:) + iabbr WaitForCompletion WFC + call feedkeys(":iabbr fcl\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"iabbr WaitForCompletion", @:) + call feedkeys(":iabbr a1z\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"iabbr a1z\t", @:) + + iunabbrev WaitForCompletion + set wildoptions& +endfunc + +" menu name fuzzy completion +func Test_fuzzy_completion_menu() + CheckFeature menu + + source $VIMRUNTIME/menu.vim + set wildoptions& + call feedkeys(":menu pup\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"menu pup', @:) + set wildoptions=fuzzy + call feedkeys(":menu pup\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"menu PopUp.', @:) + + set wildoptions& + source $VIMRUNTIME/delmenu.vim +endfunc + +" :messages suboptions fuzzy completion +func Test_fuzzy_completion_messages() + set wildoptions& + call feedkeys(":messages clr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"messages clr', @:) + set wildoptions=fuzzy + call feedkeys(":messages clr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"messages clear', @:) + set wildoptions& +endfunc + +" :set option name fuzzy completion +func Test_fuzzy_completion_option() + set wildoptions& + call feedkeys(":set brkopt\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set brkopt', @:) + set wildoptions=fuzzy + call feedkeys(":set brkopt\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set breakindentopt', @:) + set wildoptions& + call feedkeys(":set fixeol\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set fixendofline', @:) + set wildoptions=fuzzy + call feedkeys(":set fixeol\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set fixendofline', @:) + set wildoptions& +endfunc + +" :set <term_option> +func Test_fuzzy_completion_term_option() + throw 'Skipped: Nvim does not support term options' + set wildoptions& + call feedkeys(":set t_E\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set t_EC', @:) + call feedkeys(":set <t_E\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set <t_EC>', @:) + set wildoptions=fuzzy + call feedkeys(":set t_E\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set t_EC', @:) + call feedkeys(":set <t_E\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set <t_EC>', @:) + set wildoptions& +endfunc + +" " :packadd directory name fuzzy completion - NOT supported +" func Test_fuzzy_completion_packadd() +" endfunc + +" " shell command name fuzzy completion - NOT supported +" func Test_fuzzy_completion_shellcmd() +" endfunc + +" :sign suboptions fuzzy completion +func Test_fuzzy_completion_sign() + set wildoptions& + call feedkeys(":sign ufe\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign ufe', @:) + set wildoptions=fuzzy + call feedkeys(":sign ufe\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign undefine', @:) + set wildoptions& +endfunc + +" :syntax suboptions fuzzy completion +func Test_fuzzy_completion_syntax_cmd() + set wildoptions& + call feedkeys(":syntax kwd\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syntax kwd', @:) + set wildoptions=fuzzy + call feedkeys(":syntax kwd\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syntax keyword', @:) + set wildoptions& +endfunc + +" syntax group name fuzzy completion +func Test_fuzzy_completion_syntax_group() + set wildoptions& + call feedkeys(":syntax list mpar\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syntax list mpar', @:) + set wildoptions=fuzzy + call feedkeys(":syntax list mpar\<Tab>\<C-B>\"\<CR>", 'tx') + " Fuzzy match prefers NvimParenthesis over MatchParen + " call assert_equal('"syntax list MatchParen', @:) + call assert_equal('"syntax list NvimParenthesis', @:) + set wildoptions& +endfunc + +" :syntime suboptions fuzzy completion +func Test_fuzzy_completion_syntime() + CheckFeature profile + set wildoptions& + call feedkeys(":syntime clr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syntime clr', @:) + set wildoptions=fuzzy + call feedkeys(":syntime clr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"syntime clear', @:) + set wildoptions& +endfunc + +" " tag name fuzzy completion - NOT supported +" func Test_fuzzy_completion_tagname() +" endfunc + +" " tag name and file fuzzy completion - NOT supported +" func Test_fuzzy_completion_tagfile() +" endfunc + +" " user names fuzzy completion - how to test this functionality? +" func Test_fuzzy_completion_username() +" endfunc + +" user defined variable name fuzzy completion +func Test_fuzzy_completion_userdefined_var() + let g:SomeVariable=10 + set wildoptions& + call feedkeys(":let SVar\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"let SVar', @:) + set wildoptions=fuzzy + call feedkeys(":let SVar\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"let SomeVariable', @:) + set wildoptions& +endfunc + +" Test for sorting the results by the best match +func Test_fuzzy_completion_cmd_sort_results() + %bw! + command T123format : + command T123goformat : + command T123TestFOrmat : + command T123fendoff : + command T123state : + command T123FendingOff : + set wildoptions=fuzzy + call feedkeys(":T123fo\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"T123format T123TestFOrmat T123FendingOff T123goformat T123fendoff', @:) + delcommand T123format + delcommand T123goformat + delcommand T123TestFOrmat + delcommand T123fendoff + delcommand T123state + delcommand T123FendingOff + %bw + set wildoptions& +endfunc + +" Test for fuzzy completion of a command with lower case letters and a number +func Test_fuzzy_completion_cmd_alnum() + command Foo2Bar : + set wildoptions=fuzzy + call feedkeys(":foo2\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"Foo2Bar', @:) + call feedkeys(":foo\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"Foo2Bar', @:) + call feedkeys(":bar\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"Foo2Bar', @:) + delcommand Foo2Bar + set wildoptions& +endfunc + +" Test for command completion for a command starting with 'k' +func Test_fuzzy_completion_cmd_k() + command KillKillKill : + set wildoptions& + call feedkeys(":killkill\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"killkill\<Tab>", @:) + set wildoptions=fuzzy + call feedkeys(":killkill\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"KillKillKill', @:) + delcom KillKillKill + set wildoptions& +endfunc + +" Test for fuzzy completion for user defined custom completion function +func Test_fuzzy_completion_custom_func() + func Tcompl(a, c, p) + return "format\ngoformat\nTestFOrmat\nfendoff\nstate" + endfunc + command -nargs=* -complete=custom,Tcompl Fuzzy : + set wildoptions& + call feedkeys(":Fuzzy fo\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy format", @:) + call feedkeys(":Fuzzy xy\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy xy", @:) + call feedkeys(":Fuzzy ttt\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy ttt", @:) + set wildoptions=fuzzy + call feedkeys(":Fuzzy \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy format goformat TestFOrmat fendoff state", @:) + call feedkeys(":Fuzzy fo\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy format TestFOrmat goformat fendoff", @:) + call feedkeys(":Fuzzy xy\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy xy", @:) + call feedkeys(":Fuzzy ttt\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"Fuzzy TestFOrmat", @:) + delcom Fuzzy + set wildoptions& +endfunc + +" Test for :breakadd argument completion +func Test_cmdline_complete_breakadd() + call feedkeys(":breakadd \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr file func here", @:) + call feedkeys(":breakadd \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr", @:) + call feedkeys(":breakadd \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr", @:) + call feedkeys(":breakadd he\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd here", @:) + call feedkeys(":breakadd he\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd here", @:) + call feedkeys(":breakadd abc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd abc", @:) + call assert_equal(['expr', 'file', 'func', 'here'], getcompletion('', 'breakpoint')) + let l = getcompletion('not', 'breakpoint') + call assert_equal([], l) + + " Test for :breakadd file [lnum] <file> + call writefile([], 'Xscript') + call feedkeys(":breakadd file Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file Xscript", @:) + call feedkeys(":breakadd file Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file Xscript", @:) + call feedkeys(":breakadd file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file 20 Xscript", @:) + call feedkeys(":breakadd file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file 20 Xscript", @:) + call feedkeys(":breakadd file 20x Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file 20x Xsc\t", @:) + call feedkeys(":breakadd file 20\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file 20\t", @:) + call feedkeys(":breakadd file 20x\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file 20x\t", @:) + call feedkeys(":breakadd file Xscript \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file Xscript ", @:) + call feedkeys(":breakadd file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd file X1B2C3", @:) + call delete('Xscript') + + " Test for :breakadd func [lnum] <function> + func Xbreak_func() + endfunc + call feedkeys(":breakadd func Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func Xbreak_func", @:) + call feedkeys(":breakadd func Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func Xbreak_func", @:) + call feedkeys(":breakadd func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func 20 Xbreak_func", @:) + call feedkeys(":breakadd func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func 20 Xbreak_func", @:) + call feedkeys(":breakadd func 20x Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func 20x Xbr\t", @:) + call feedkeys(":breakadd func 20\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func 20\t", @:) + call feedkeys(":breakadd func 20x\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func 20x\t", @:) + call feedkeys(":breakadd func Xbreak_func \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func Xbreak_func ", @:) + call feedkeys(":breakadd func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd func X1B2C3", @:) + delfunc Xbreak_func + + " Test for :breakadd expr <expression> + let g:Xtest_var = 10 + call feedkeys(":breakadd expr Xtest\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr Xtest_var", @:) + call feedkeys(":breakadd expr Xtest\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr Xtest_var", @:) + call feedkeys(":breakadd expr Xtest_var \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr Xtest_var ", @:) + call feedkeys(":breakadd expr X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd expr X1B2C3", @:) + unlet g:Xtest_var + + " Test for :breakadd here + call feedkeys(":breakadd here Xtest\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd here Xtest", @:) + call feedkeys(":breakadd here Xtest\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd here Xtest", @:) + call feedkeys(":breakadd here \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakadd here ", @:) +endfunc + +" Test for :breakdel argument completion +func Test_cmdline_complete_breakdel() + call feedkeys(":breakdel \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file func here", @:) + call feedkeys(":breakdel \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file", @:) + call feedkeys(":breakdel \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file", @:) + call feedkeys(":breakdel he\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel here", @:) + call feedkeys(":breakdel he\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel here", @:) + call feedkeys(":breakdel abc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel abc", @:) + + " Test for :breakdel file [lnum] <file> + call writefile([], 'Xscript') + call feedkeys(":breakdel file Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file Xscript", @:) + call feedkeys(":breakdel file Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file Xscript", @:) + call feedkeys(":breakdel file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file 20 Xscript", @:) + call feedkeys(":breakdel file 20 Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file 20 Xscript", @:) + call feedkeys(":breakdel file 20x Xsc\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file 20x Xsc\t", @:) + call feedkeys(":breakdel file 20\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file 20\t", @:) + call feedkeys(":breakdel file 20x\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file 20x\t", @:) + call feedkeys(":breakdel file Xscript \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file Xscript ", @:) + call feedkeys(":breakdel file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel file X1B2C3", @:) + call delete('Xscript') + + " Test for :breakdel func [lnum] <function> + func Xbreak_func() + endfunc + call feedkeys(":breakdel func Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func Xbreak_func", @:) + call feedkeys(":breakdel func Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func Xbreak_func", @:) + call feedkeys(":breakdel func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func 20 Xbreak_func", @:) + call feedkeys(":breakdel func 20 Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func 20 Xbreak_func", @:) + call feedkeys(":breakdel func 20x Xbr\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func 20x Xbr\t", @:) + call feedkeys(":breakdel func 20\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func 20\t", @:) + call feedkeys(":breakdel func 20x\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func 20x\t", @:) + call feedkeys(":breakdel func Xbreak_func \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func Xbreak_func ", @:) + call feedkeys(":breakdel func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel func X1B2C3", @:) + delfunc Xbreak_func + + " Test for :breakdel here + call feedkeys(":breakdel here Xtest\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel here Xtest", @:) + call feedkeys(":breakdel here Xtest\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel here Xtest", @:) + call feedkeys(":breakdel here \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"breakdel here ", @:) +endfunc + +" Test for :scriptnames argument completion +func Test_cmdline_complete_scriptnames() + set wildmenu + call writefile(['let a = 1'], 'Xa1b2c3.vim') + source Xa1b2c3.vim + call feedkeys(":script \<Tab>\<Left>\<Left>\<C-B>\"\<CR>", 'tx') + call assert_match("\"script .*Xa1b2c3.vim$", @:) + call feedkeys(":script \<Tab>\<Left>\<Left>\<C-B>\"\<CR>", 'tx') + call assert_match("\"script .*Xa1b2c3.vim$", @:) + call feedkeys(":script b2c3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"script b2c3", @:) + call feedkeys(":script 2\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match("\"script 2\<Tab>$", @:) + call feedkeys(":script \<Tab>\<Left>\<Left> \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match("\"script .*Xa1b2c3.vim $", @:) + call feedkeys(":script \<Tab>\<Left>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"script ", @:) + call assert_match('Xa1b2c3.vim$', getcompletion('.*Xa1b2.*', 'scriptnames')[0]) + call assert_equal([], getcompletion('Xa1b2', 'scriptnames')) + new + call feedkeys(":script \<Tab>\<Left>\<Left>\<CR>", 'tx') + call assert_equal('Xa1b2c3.vim', fnamemodify(@%, ':t')) + bw! + call delete('Xa1b2c3.vim') + set wildmenu& +endfunc + " this was going over the end of IObuff func Test_report_error_with_composing() let caught = 'no' @@ -2074,6 +3460,16 @@ func Test_cmdline_complete_substitute_short() endfor endfunc +" Test for :! shell command argument completion +func Test_cmdline_complete_bang_cmd_argument() + set wildoptions=fuzzy + call feedkeys(":!vim test_cmdline.\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"!vim test_cmdline.vim', @:) + set wildoptions& + call feedkeys(":!vim test_cmdline.\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"!vim test_cmdline.vim', @:) +endfunc + func Check_completion() call assert_equal('let a', getcmdline()) call assert_equal(6, getcmdpos()) @@ -2129,4 +3525,57 @@ func Test_wildmenu_pum_disable_while_shown() set wildoptions& wildmenu& endfunc +func Test_setcmdline() + func SetText(text, pos) + autocmd CmdlineChanged * let g:cmdtype = expand('<afile>') + call assert_equal(0, setcmdline(a:text)) + call assert_equal(a:text, getcmdline()) + call assert_equal(len(a:text) + 1, getcmdpos()) + call assert_equal(getcmdtype(), g:cmdtype) + unlet g:cmdtype + autocmd! CmdlineChanged + + call assert_equal(0, setcmdline(a:text, a:pos)) + call assert_equal(a:text, getcmdline()) + call assert_equal(a:pos, getcmdpos()) + + call assert_fails('call setcmdline("' .. a:text .. '", -1)', 'E487:') + call assert_fails('call setcmdline({}, 0)', 'E1174:') + call assert_fails('call setcmdline("' .. a:text .. '", {})', 'E1210:') + + return '' + endfunc + + call feedkeys(":\<C-R>=SetText('set rtp?', 2)\<CR>\<CR>", 'xt') + call assert_equal('set rtp?', @:) + + call feedkeys(":let g:str = input('? ')\<CR>", 't') + call feedkeys("\<C-R>=SetText('foo', 4)\<CR>\<CR>", 'xt') + call assert_equal('foo', g:str) + unlet g:str + + delfunc SetText + + " setcmdline() returns 1 when not editing the command line. + call assert_equal(1, 'foo'->setcmdline()) + + " Called in custom function + func CustomComplete(A, L, P) + call assert_equal(0, setcmdline("DoCmd ")) + return "January\nFebruary\nMars\n" + endfunc + + com! -nargs=* -complete=custom,CustomComplete DoCmd : + call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"DoCmd January February Mars', @:) + delcom DoCmd + delfunc CustomComplete + + " Called in <expr> + cnoremap <expr>a setcmdline('let foo=') + call feedkeys(":a\<CR>", 'tx') + call assert_equal('let foo=0', @:) + cunmap a +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_comments.vim b/src/nvim/testdir/test_comments.vim new file mode 100644 index 0000000000..c34b85c42d --- /dev/null +++ b/src/nvim/testdir/test_comments.vim @@ -0,0 +1,277 @@ +" Tests for the various flags in the 'comments' option + +" Test for the 'n' flag in 'comments' +func Test_comment_nested() + new + setlocal comments=n:> fo+=ro + exe "normal i> B\nD\<C-C>ggOA\<C-C>joC\<C-C>Go\<BS>>>> F\nH" + exe "normal 5GOE\<C-C>6GoG" + let expected =<< trim END + > A + > B + > C + > D + >>>> E + >>>> F + >>>> G + >>>> H + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for the 'b' flag in 'comments' +func Test_comment_blank() + new + setlocal comments=b:* fo+=ro + exe "normal i* E\nF\n\<BS>G\nH\<C-C>ggOC\<C-C>O\<BS>B\<C-C>OA\<C-C>2joD" + let expected =<< trim END + A + *B + * C + * D + * E + * F + *G + H + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for the 'f' flag in 'comments' (only the first line has a comment +" string) +func Test_comment_firstline() + new + setlocal comments=f:- fo+=ro + exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" + call assert_equal(['A', '- B', ' C', ' D'], getline(1, '$')) + %d + setlocal comments=:- + exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" + call assert_equal(['- A', '- B', '- C', '- D'], getline(1, '$')) + close! +endfunc + +" Test for the 's', 'm' and 'e' flags in 'comments' +" Test for automatically adding comment leaders in insert mode +func Test_comment_threepiece() + new + setlocal expandtab + call setline(1, ["\t/*"]) + setlocal formatoptions=croql + call cursor(1, 3) + call feedkeys("A\<cr>\<cr>/", 'tnix') + call assert_equal(["\t/*", " *", " */"], getline(1, '$')) + + " If a comment ends in a single line, then don't add it in the next line + %d + call setline(1, '/* line1 */') + call feedkeys("A\<CR>next line", 'xt') + call assert_equal(['/* line1 */', 'next line'], getline(1, '$')) + + %d + " Copy the trailing indentation from the leader comment to a new line + setlocal autoindent noexpandtab + call feedkeys("a\t/*\tone\ntwo\n/", 'xt') + call assert_equal(["\t/*\tone", "\t *\ttwo", "\t */"], getline(1, '$')) + close! +endfunc + +" Test for the 'r' flag in 'comments' (right align comment) +func Test_comment_rightalign() + new + setlocal comments=sr:/***,m:**,ex-2:******/ fo+=ro + exe "normal i=\<C-C>o\t /***\nD\n/" + exe "normal 2GOA\<C-C>joB\<C-C>jOC\<C-C>joE\<C-C>GOF\<C-C>joG" + let expected =<< trim END + = + A + /*** + ** B + ** C + ** D + ** E + ** F + ******/ + G + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for the 'O' flag in 'comments' +func Test_comment_O() + new + setlocal comments=Ob:* fo+=ro + exe "normal i* B\nD\<C-C>kOA\<C-C>joC" + let expected =<< trim END + A + * B + * C + * D + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for using a multibyte character as a comment leader +func Test_comment_multibyte_leader() + new + let t =<< trim END + { + X + Xa + XaY + XY + XYZ + X Y + X YZ + XX + XXa + XXY + } + END + call setline(1, t) + call cursor(2, 1) + + set tw=2 fo=cqm comments=n:X + exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq" + let t =<< trim END + X + Xa + XaY + XY + XYZ + X Y + X YZ + XX + XXa + XXY + END + exe "normal o\n" . join(t, "\n") + + let expected =<< trim END + { + X + Xa + Xa + XY + XY + XY + XZ + X Y + X Y + X Z + XX + XXa + XXY + + X + Xa + Xa + XY + XY + XY + XZ + X Y + X Y + X Z + XX + XXa + XXY + } + END + call assert_equal(expected, getline(1, '$')) + + set tw& fo& comments& + close! +endfunc + +" Test for a space character in 'comments' setting +func Test_comment_space() + new + setlocal comments=b:\ > fo+=ro + exe "normal i> B\nD\<C-C>ggOA\<C-C>joC" + exe "normal Go > F\nH\<C-C>kOE\<C-C>joG" + let expected =<< trim END + A + > B + C + D + > E + > F + > G + > H + END + call assert_equal(expected, getline(1, '$')) + close! +endfunc + +" Test for formatting lines with and without comments +func Test_comment_format_lines() + new + call setline(1, ['one', '/* two */', 'three']) + normal gggqG + call assert_equal(['one', '/* two */', 'three'], getline(1, '$')) + close! +endfunc + +" Test for using 'a' in 'formatoptions' with comments +func Test_comment_autoformat() + new + setlocal formatoptions+=a + call feedkeys("a- one\n- two\n", 'xt') + call assert_equal(['- one', '- two', ''], getline(1, '$')) + + %d + call feedkeys("a\none\n", 'xt') + call assert_equal(['', 'one', ''], getline(1, '$')) + + setlocal formatoptions+=aw + %d + call feedkeys("aone \ntwo\n", 'xt') + call assert_equal(['one two', ''], getline(1, '$')) + + %d + call feedkeys("aone\ntwo\n", 'xt') + call assert_equal(['one', 'two', ''], getline(1, '$')) + + close! +endfunc + +" Test for joining lines with comments ('j' flag in 'formatoptions') +func Test_comment_join_lines_fo_j() + new + setlocal fo+=j comments=:// + call setline(1, ['i++; // comment1', ' // comment2']) + normal J + call assert_equal('i++; // comment1 comment2', getline(1)) + setlocal fo-=j + call setline(1, ['i++; // comment1', ' // comment2']) + normal J + call assert_equal('i++; // comment1 // comment2', getline(1)) + " Test with nested comments + setlocal fo+=j comments=n:>,n:) + call setline(1, ['i++; > ) > ) comment1', ' > ) comment2']) + normal J + call assert_equal('i++; > ) > ) comment1 comment2', getline(1)) + close! +endfunc + +" Test for formatting lines where only the first line has a comment. +func Test_comment_format_firstline_comment() + new + setlocal formatoptions=tcq + call setline(1, ['- one two', 'three']) + normal gggqG + call assert_equal(['- one two three'], getline(1, '$')) + + %d + call setline(1, ['- one', '- two']) + normal gggqG + call assert_equal(['- one', '- two'], getline(1, '$')) + close! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index 3dc8710d63..ec7d143030 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -1,9 +1,11 @@ " Test the :compiler command +source check.vim +source shared.vim + func Test_compiler() - if !executable('perl') - return - endif + CheckExecutable perl + CheckFeature quickfix " $LANG changes the output of Perl. if $LANG != '' diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim index 0d064617a5..4a6c8779dd 100644 --- a/src/nvim/testdir/test_const.vim +++ b/src/nvim/testdir/test_const.vim @@ -194,6 +194,14 @@ func Test_lockvar() if 0 | lockvar x | endif let x = 'again' + + let val = [1, 2, 3] + lockvar 0 val + let val[0] = 9 + call assert_equal([9, 2, 3], val) + call add(val, 4) + call assert_equal([9, 2, 3, 4], val) + call assert_fails('let val = [4, 5, 6]', 'E1122:') endfunc @@ -231,6 +239,14 @@ func Test_const_with_special_variables() call assert_fails('const &filetype = "vim"', 'E996:') call assert_fails('const &l:filetype = "vim"', 'E996:') call assert_fails('const &g:encoding = "utf-8"', 'E996:') + + call assert_fails('const [a, $CONST_FOO] = [369, "abc"]', 'E996:') + call assert_equal(369, a) + call assert_equal(v:null, getenv("CONST_FOO")) + + call assert_fails('const [b; $CONST_FOO] = [246, 2, "abc"]', 'E996:') + call assert_equal(246, b) + call assert_equal(v:null, getenv("CONST_FOO")) endfunc func Test_const_with_eval_name() @@ -274,3 +290,5 @@ func Test_lock_depth_is_2() const d2 = #{a: 0, b: lvar, c: 4} let d2.b[1] = 'd' endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cpoptions.vim b/src/nvim/testdir/test_cpoptions.vim new file mode 100644 index 0000000000..b9307ab30b --- /dev/null +++ b/src/nvim/testdir/test_cpoptions.vim @@ -0,0 +1,927 @@ +" Test for the various 'cpoptions' (cpo) flags + +source check.vim +source shared.vim +source view_util.vim + +" Test for the 'a' flag in 'cpo'. Reading a file should set the alternate +" file name. +func Test_cpo_a() + let save_cpo = &cpo + call writefile(['one'], 'XfileCpoA') + " Wipe out all the buffers, so that the alternate file is empty + edit Xfoo | %bw + set cpo-=a + new + read XfileCpoA + call assert_equal('', @#) + %d + set cpo+=a + read XfileCpoA + call assert_equal('XfileCpoA', @#) + close! + call delete('XfileCpoA') + let &cpo = save_cpo +endfunc + +" Test for the 'A' flag in 'cpo'. Writing a file should set the alternate +" file name. +func Test_cpo_A() + let save_cpo = &cpo + " Wipe out all the buffers, so that the alternate file is empty + edit Xfoo | %bw + set cpo-=A + new Xfile1 + write Xfile2 + call assert_equal('', @#) + %bw + call delete('Xfile2') + new Xfile1 + set cpo+=A + write Xfile2 + call assert_equal('Xfile2', @#) + close! + call delete('Xfile2') + let &cpo = save_cpo +endfunc + +" Test for the 'b' flag in 'cpo'. "\|" at the end of a map command is +" recognized as the end of the map. +func Test_cpo_b() + let save_cpo = &cpo + set cpo+=b + nnoremap <F5> :pwd\<CR>\|let i = 1 + call assert_equal(':pwd\<CR>\', maparg('<F5>')) + nunmap <F5> + exe "nnoremap <F5> :pwd\<C-V>|let i = 1" + call assert_equal(':pwd|let i = 1', maparg('<F5>')) + nunmap <F5> + set cpo-=b + nnoremap <F5> :pwd\<CR>\|let i = 1 + call assert_equal(':pwd\<CR>|let i = 1', maparg('<F5>')) + let &cpo = save_cpo + nunmap <F5> +endfunc + +" Test for the 'B' flag in 'cpo'. A backslash in mappings, abbreviations, user +" commands and menu commands has no special meaning. +func Test_cpo_B() + let save_cpo = &cpo + new + set cpo-=B + iabbr <buffer> abc ab\<BS>d + exe "normal iabc " + call assert_equal('ab<BS>d ', getline(1)) + %d + set cpo+=B + iabbr <buffer> abc ab\<BS>d + exe "normal iabc " + call assert_equal('abd ', getline(1)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'c' flag in 'cpo'. +func Test_cpo_c() + let save_cpo = &cpo + set cpo+=c + new + call setline(1, ' abababababab') + exe "normal gg/abab\<CR>" + call assert_equal(3, searchcount().total) + set cpo-=c + exe "normal gg/abab\<CR>" + call assert_equal(5, searchcount().total) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'C' flag in 'cpo' (line continuation) +func Test_cpo_C() + let save_cpo = &cpo + call writefile(['let l = [', '\ 1,', '\ 2]'], 'XfileCpoC') + set cpo-=C + source XfileCpoC + call assert_equal([1, 2], g:l) + set cpo+=C + call assert_fails('source XfileCpoC', ['E697:', 'E10:']) + call delete('XfileCpoC') + let &cpo = save_cpo +endfunc + +" Test for the 'd' flag in 'cpo' (tags relative to the current file) +func Test_cpo_d() + let save_cpo = &cpo + call mkdir('XdirCpoD') + call writefile(["one\tXfile1\t/^one$/"], 'tags') + call writefile(["two\tXfile2\t/^two$/"], 'XdirCpoD/tags') + set tags=./tags + set cpo-=d + edit XdirCpoD/Xfile + call assert_equal('two', taglist('.*')[0].name) + set cpo+=d + call assert_equal('one', taglist('.*')[0].name) + %bw! + call delete('tags') + call delete('XdirCpoD', 'rf') + set tags& + let &cpo = save_cpo +endfunc + +" Test for the 'D' flag in 'cpo' (digraph after a r, f or t) +func Test_cpo_D() + CheckFeature digraphs + let save_cpo = &cpo + new + set cpo-=D + call setline(1, 'abcdefgh|') + exe "norm! 1gg0f\<c-k>!!" + call assert_equal(9, col('.')) + set cpo+=D + exe "norm! 1gg0f\<c-k>!!" + call assert_equal(1, col('.')) + set cpo-=D + close! + let &cpo = save_cpo +endfunc + +" Test for the 'e' flag in 'cpo' +func Test_cpo_e() + let save_cpo = &cpo + let @a='let i = 45' + set cpo+=e + call feedkeys(":@a\<CR>", 'xt') + call assert_equal(45, i) + set cpo-=e + call feedkeys(":@a\<CR>6\<CR>", 'xt') + call assert_equal(456, i) + let &cpo = save_cpo +endfunc + +" Test for the 'E' flag in 'cpo' with yank, change, delete, etc. operators +func Test_cpo_E() + new + call setline(1, '') + set cpo+=E + " yank an empty line + call assert_beeps('normal "ayl') + " change an empty line + call assert_beeps('normal lcTa') + call assert_beeps('normal 0c0') + " delete an empty line + call assert_beeps('normal D') + call assert_beeps('normal dl') + call assert_equal('', getline(1)) + " change case of an empty line + call assert_beeps('normal gul') + call assert_beeps('normal gUl') + " replace a character + call assert_beeps('normal vrx') + " increment and decrement + call assert_beeps('exe "normal v\<C-A>"') + call assert_beeps('exe "normal v\<C-X>"') + set cpo-=E + close! +endfunc + +" Test for the 'f' flag in 'cpo' (read in an empty buffer sets the file name) +func Test_cpo_f() + let save_cpo = &cpo + new + set cpo-=f + read test_cpoptions.vim + call assert_equal('', @%) + %d + set cpo+=f + read test_cpoptions.vim + call assert_equal('test_cpoptions.vim', @%) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'F' flag in 'cpo' (write in an empty buffer sets the file name) +func Test_cpo_F() + let save_cpo = &cpo + new + set cpo-=F + write XfileCpoF + call assert_equal('', @%) + call delete('XfileCpoF') + set cpo+=F + write XfileCpoF + call assert_equal('XfileCpoF', @%) + close! + call delete('XfileCpoF') + let &cpo = save_cpo +endfunc + +" Test for the 'g' flag in 'cpo' (jump to line 1 when re-editing a file) +func Test_cpo_g() + throw 'Skipped: Nvim does not support cpoptions flag "g"' + let save_cpo = &cpo + new test_cpoptions.vim + set cpo-=g + normal 20G + edit + call assert_equal(20, line('.')) + set cpo+=g + edit + call assert_equal(1, line('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for inserting text in a line with only spaces ('H' flag in 'cpoptions') +func Test_cpo_H() + throw 'Skipped: Nvim does not support cpoptions flag "H"' + let save_cpo = &cpo + new + set cpo-=H + call setline(1, ' ') + normal! Ia + call assert_equal(' a', getline(1)) + set cpo+=H + call setline(1, ' ') + normal! Ia + call assert_equal(' a ', getline(1)) + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 'i' flag in 'cpo' +" Interrupting the reading of a file will leave it modified. + +" Test for the 'I' flag in 'cpo' (deleting autoindent when using arrow keys) +func Test_cpo_I() + let save_cpo = &cpo + new + setlocal autoindent + set cpo+=I + exe "normal i one\<CR>\<Up>" + call assert_equal(' ', getline(2)) + set cpo-=I + %d + exe "normal i one\<CR>\<Up>" + call assert_equal('', getline(2)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'j' flag in 'cpo' is in the test_join.vim file. + +" Test for the 'J' flag in 'cpo' (two spaces after a sentence) +func Test_cpo_J() + let save_cpo = &cpo + new + set cpo-=J + call setline(1, 'one. two! three? four."'' five.)]') + normal 0 + for colnr in [6, 12, 19, 28, 34] + normal ) + call assert_equal(colnr, col('.')) + endfor + for colnr in [28, 19, 12, 6, 1] + normal ( + call assert_equal(colnr, col('.')) + endfor + set cpo+=J + normal 0 + for colnr in [12, 28, 34] + normal ) + call assert_equal(colnr, col('.')) + endfor + for colnr in [28, 12, 1] + normal ( + call assert_equal(colnr, col('.')) + endfor + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 'k' flag in 'cpo'. +" Disable the recognition of raw key codes in mappings, abbreviations, and the +" "to" part of menu commands. + +" TODO: Add a test for the 'K' flag in 'cpo'. +" Don't wait for a key code to complete when it is halfway a mapping. + +" Test for the 'l' flag in 'cpo' (backslash in a [] range) +func Test_cpo_l() + let save_cpo = &cpo + new + call setline(1, ['', "a\tc" .. '\t']) + set cpo-=l + exe 'normal gg/[\t]' .. "\<CR>" + call assert_equal([2, 8], [col('.'), virtcol('.')]) + set cpo+=l + exe 'normal gg/[\t]' .. "\<CR>" + call assert_equal([4, 10], [col('.'), virtcol('.')]) + close! + let &cpo = save_cpo +endfunc + +" Test for inserting tab in virtual replace mode ('L' flag in 'cpoptions') +func Test_cpo_L() + let save_cpo = &cpo + new + set cpo-=L + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>ijklmnopqr", getline(1)) + set cpo+=L + set list + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>cdefghijklmnopqr", getline(1)) + set nolist + call setline(1, 'abcdefghijklmnopqr') + exe "normal 0gR\<Tab>" + call assert_equal("\<Tab>ijklmnopqr", getline(1)) + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 'm' flag in 'cpo'. +" When included, a showmatch will always wait half a second. When not +" included, a showmatch will wait half a second or until a character is typed. + +" Test for the 'M' flag in 'cpo' (% with escape parenthesis) +func Test_cpo_M() + let save_cpo = &cpo + new + call setline(1, ['( \( )', '\( ( \)']) + + set cpo-=M + call cursor(1, 1) + normal % + call assert_equal(6, col('.')) + call cursor(1, 4) + call assert_beeps('normal %') + call cursor(2, 2) + normal % + call assert_equal(7, col('.')) + call cursor(2, 4) + call assert_beeps('normal %') + + set cpo+=M + call cursor(1, 4) + normal % + call assert_equal(6, col('.')) + call cursor(1, 1) + call assert_beeps('normal %') + call cursor(2, 4) + normal % + call assert_equal(7, col('.')) + call cursor(2, 1) + call assert_beeps('normal %') + + close! + let &cpo = save_cpo +endfunc + +" Test for the 'n' flag in 'cpo' (using number column for wrapped lines) +func Test_cpo_n() + let save_cpo = &cpo + new + call setline(1, repeat('a', &columns)) + setlocal number + set cpo-=n + redraw! + call assert_equal(' aaaa', Screenline(2)) + set cpo+=n + redraw! + call assert_equal('aaaa', Screenline(2)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'o' flag in 'cpo' (line offset to search command) +func Test_cpo_o() + let save_cpo = &cpo + new + call setline(1, ['', 'one', 'two', 'three', 'one', 'two', 'three']) + set cpo-=o + exe "normal /one/+2\<CR>" + normal n + call assert_equal(7, line('.')) + set cpo+=o + exe "normal /one/+2\<CR>" + normal n + call assert_equal(5, line('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'O' flag in 'cpo' (overwriting an existing file) +func Test_cpo_O() + let save_cpo = &cpo + new XfileCpoO + call setline(1, 'one') + call writefile(['two'], 'XfileCpoO') + set cpo-=O + call assert_fails('write', 'E13:') + set cpo+=O + write + call assert_equal(['one'], readfile('XfileCpoO')) + close! + call delete('XfileCpoO') + let &cpo = save_cpo +endfunc + +" Test for the 'p' flag in 'cpo' is in the test_lispwords.vim file. + +" Test for the 'P' flag in 'cpo' (appending to a file sets the current file +" name) +func Test_cpo_P() + let save_cpo = &cpo + call writefile([], 'XfileCpoP') + new + call setline(1, 'one') + set cpo+=F + set cpo-=P + write >> XfileCpoP + call assert_equal('', @%) + set cpo+=P + write >> XfileCpoP + call assert_equal('XfileCpoP', @%) + close! + call delete('XfileCpoP') + let &cpo = save_cpo +endfunc + +" Test for the 'q' flag in 'cpo' (joining multiple lines) +func Test_cpo_q() + let save_cpo = &cpo + new + call setline(1, ['one', 'two', 'three', 'four', 'five']) + set cpo-=q + normal gg4J + call assert_equal(14, col('.')) + %d + call setline(1, ['one', 'two', 'three', 'four', 'five']) + set cpo+=q + normal gg4J + call assert_equal(4, col('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'r' flag in 'cpo' (redo command with a search motion) +func Test_cpo_r() + let save_cpo = &cpo + new + call setline(1, repeat(['one two three four'], 2)) + set cpo-=r + exe "normal ggc/two\<CR>abc " + let @/ = 'three' + normal 2G. + call assert_equal('abc two three four', getline(2)) + %d + call setline(1, repeat(['one two three four'], 2)) + set cpo+=r + exe "normal ggc/two\<CR>abc " + let @/ = 'three' + normal 2G. + call assert_equal('abc three four', getline(2)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'R' flag in 'cpo' (clear marks after a filter command) +func Test_cpo_R() + CheckUnix + let save_cpo = &cpo + new + call setline(1, ['three', 'one', 'two']) + set cpo-=R + 3mark r + %!sort + call assert_equal(3, line("'r")) + %d + call setline(1, ['three', 'one', 'two']) + set cpo+=R + 3mark r + %!sort + call assert_equal(0, line("'r")) + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 's' flag in 'cpo'. +" Set buffer options when entering the buffer for the first time. If not +" present the options are set when the buffer is created. + +" Test for the 'S' flag in 'cpo' (copying buffer options) +func Test_cpo_S() + let save_cpo = &cpo + new Xfile1 + set noautoindent + new Xfile2 + set cpo-=S + set autoindent + wincmd p + call assert_equal(0, &autoindent) + wincmd p + call assert_equal(1, &autoindent) + set cpo+=S + wincmd p + call assert_equal(1, &autoindent) + set noautoindent + wincmd p + call assert_equal(0, &autoindent) + wincmd t + close! + close! + let &cpo = save_cpo +endfunc + +" Test for the 't' flag in 'cpo' is in the test_tagjump.vim file. + +" Test for the 'u' flag in 'cpo' (Vi-compatible undo) +func Test_cpo_u() + let save_cpo = &cpo + new + set cpo-=u + exe "normal iabc\<C-G>udef\<C-G>ughi" + normal uu + call assert_equal('abc', getline(1)) + %d + set cpo+=u + exe "normal iabc\<C-G>udef\<C-G>ughi" + normal uu + call assert_equal('abcdefghi', getline(1)) + close! + let &cpo = save_cpo +endfunc + +" TODO: Add a test for the 'v' flag in 'cpo'. +" Backspaced characters remain visible on the screen in Insert mode. + +" Test for the 'w' flag in 'cpo' ('cw' on a blank character changes only one +" character) +func Test_cpo_w() + throw 'Skipped: Nvim does not support cpoptions flag "w"' + let save_cpo = &cpo + new + set cpo+=w + call setline(1, 'here are some words') + norm! 1gg0elcwZZZ + call assert_equal('hereZZZ are some words', getline('.')) + norm! 1gg2elcWYYY + call assert_equal('hereZZZ areYYY some words', getline('.')) + set cpo-=w + call setline(1, 'here are some words') + norm! 1gg0elcwZZZ + call assert_equal('hereZZZare some words', getline('.')) + norm! 1gg2elcWYYY + call assert_equal('hereZZZare someYYYwords', getline('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'W' flag in 'cpo' is in the test_writefile.vim file + +" Test for the 'x' flag in 'cpo' (Esc on command-line executes command) +func Test_cpo_x() + let save_cpo = &cpo + set cpo-=x + let i = 1 + call feedkeys(":let i=10\<Esc>", 'xt') + call assert_equal(1, i) + set cpo+=x + call feedkeys(":let i=10\<Esc>", 'xt') + call assert_equal(10, i) + let &cpo = save_cpo +endfunc + +" Test for the 'X' flag in 'cpo' ('R' with a count) +func Test_cpo_X() + let save_cpo = &cpo + new + call setline(1, 'aaaaaa') + set cpo-=X + normal gg4Rx + call assert_equal('xxxxaa', getline(1)) + normal ggRy + normal 4. + call assert_equal('yyyyaa', getline(1)) + call setline(1, 'aaaaaa') + set cpo+=X + normal gg4Rx + call assert_equal('xxxxaaaaa', getline(1)) + normal ggRy + normal 4. + call assert_equal('yyyyxxxaaaaa', getline(1)) + close! + let &cpo = save_cpo +endfunc + +" Test for the 'y' flag in 'cpo' (repeating a yank command) +func Test_cpo_y() + let save_cpo = &cpo + new + call setline(1, ['one', 'two']) + set cpo-=y + normal ggyy + normal 2G. + call assert_equal("one\n", @") + %d + call setline(1, ['one', 'two']) + set cpo+=y + normal ggyy + normal 2G. + call assert_equal("two\n", @") + close! + let &cpo = save_cpo +endfunc + +" Test for the 'Z' flag in 'cpo' (write! resets 'readonly') +func Test_cpo_Z() + let save_cpo = &cpo + call writefile([], 'XfileCpoZ') + new XfileCpoZ + setlocal readonly + set cpo-=Z + write! + call assert_equal(0, &readonly) + set cpo+=Z + setlocal readonly + write! + call assert_equal(1, &readonly) + close! + call delete('XfileCpoZ') + let &cpo = save_cpo +endfunc + +" Test for the '!' flag in 'cpo' is in the test_normal.vim file + +" Test for displaying dollar when changing text ('$' flag in 'cpoptions') +func Test_cpo_dollar() + throw 'Skipped: use test/functional/legacy/cpoptions_spec.lua' + new + let g:Line = '' + func SaveFirstLine() + let g:Line = Screenline(1) + return '' + endfunc + inoremap <expr> <buffer> <F2> SaveFirstLine() + call test_override('redraw_flag', 1) + set cpo+=$ + call setline(1, 'one two three') + redraw! + exe "normal c2w\<F2>vim" + call assert_equal('one tw$ three', g:Line) + call assert_equal('vim three', getline(1)) + set cpo-=$ + call test_override('ALL', 0) + delfunc SaveFirstLine + %bw! +endfunc + +" Test for the '%' flag in 'cpo' (parenthesis matching inside strings) +func Test_cpo_percent() + let save_cpo = &cpo + new + call setline(1, ' if (strcmp("ab)cd(", s))') + set cpo-=% + normal 8|% + call assert_equal(28, col('.')) + normal 15|% + call assert_equal(27, col('.')) + normal 27|% + call assert_equal(15, col('.')) + call assert_beeps("normal 19|%") + call assert_beeps("normal 22|%") + set cpo+=% + normal 8|% + call assert_equal(28, col('.')) + normal 15|% + call assert_equal(19, col('.')) + normal 27|% + call assert_equal(22, col('.')) + normal 19|% + call assert_equal(15, col('.')) + normal 22|% + call assert_equal(27, col('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for cursor movement with '-' in 'cpoptions' +func Test_cpo_minus() + throw 'Skipped: Nvim does not support cpoptions flag "-"' + new + call setline(1, ['foo', 'bar', 'baz']) + let save_cpo = &cpo + set cpo+=- + call assert_beeps('normal 10j') + call assert_equal(1, line('.')) + normal G + call assert_beeps('normal 10k') + call assert_equal(3, line('.')) + call assert_fails(10, 'E16:') + close! + let &cpo = save_cpo +endfunc + +" Test for the '+' flag in 'cpo' ('write file' command resets the 'modified' +" flag) +func Test_cpo_plus() + let save_cpo = &cpo + call writefile([], 'XfileCpoPlus') + new XfileCpoPlus + call setline(1, 'foo') + write X1 + call assert_equal(1, &modified) + set cpo+=+ + write X2 + call assert_equal(0, &modified) + close! + call delete('XfileCpoPlus') + call delete('X1') + call delete('X2') + let &cpo = save_cpo +endfunc + +" Test for the '*' flag in 'cpo' (':*' is same as ':@') +func Test_cpo_star() + throw 'Skipped: Nvim does not support cpoptions flag "*"' + let save_cpo = &cpo + let x = 0 + new + set cpo-=* + let @a = 'let x += 1' + call assert_fails('*a', 'E20:') + set cpo+=* + *a + call assert_equal(1, x) + close! + let &cpo = save_cpo +endfunc + +" Test for the '<' flag in 'cpo' is in the test_mapping.vim file + +" Test for the '>' flag in 'cpo' (use a new line when appending to a register) +func Test_cpo_gt() + let save_cpo = &cpo + new + call setline(1, 'one two') + set cpo-=> + let @r = '' + normal gg"Rye + normal "Rye + call assert_equal("oneone", @r) + set cpo+=> + let @r = '' + normal gg"Rye + normal "Rye + call assert_equal("\none\none", @r) + close! + let &cpo = save_cpo +endfunc + +" Test for the ';' flag in 'cpo' +" Test for t,f,F,T movement commands and 'cpo-;' setting +func Test_cpo_semicolon() + let save_cpo = &cpo + new + call append(0, ["aaa two three four", " zzz", "yyy ", + \ "bbb yee yoo four", "ccc two three four", + \ "ddd yee yoo four"]) + set cpo-=; + 1 + normal! 0tt;D + 2 + normal! 0fz;D + 3 + normal! $Fy;D + 4 + normal! $Ty;D + set cpo+=; + 5 + normal! 0tt;;D + 6 + normal! $Ty;;D + + call assert_equal('aaa two', getline(1)) + call assert_equal(' z', getline(2)) + call assert_equal('y', getline(3)) + call assert_equal('bbb y', getline(4)) + call assert_equal('ccc', getline(5)) + call assert_equal('ddd yee y', getline(6)) + close! + let &cpo = save_cpo +endfunc + +" Test for the '#' flag in 'cpo' (count before 'D', 'o' and 'O' operators) +func Test_cpo_hash() + throw 'Skipped: Nvim does not support cpoptions flag "#"' + let save_cpo = &cpo + new + set cpo-=# + call setline(1, ['one', 'two', 'three']) + normal gg2D + call assert_equal(['three'], getline(1, '$')) + normal gg2ofour + call assert_equal(['three', 'four', 'four'], getline(1, '$')) + normal gg2Otwo + call assert_equal(['two', 'two', 'three', 'four', 'four'], getline(1, '$')) + %d + set cpo+=# + call setline(1, ['one', 'two', 'three']) + normal gg2D + call assert_equal(['', 'two', 'three'], getline(1, '$')) + normal gg2oone + call assert_equal(['', 'one', 'two', 'three'], getline(1, '$')) + normal gg2Ozero + call assert_equal(['zero', '', 'one', 'two', 'three'], getline(1, '$')) + close! + let &cpo = save_cpo +endfunc + +" Test for the '&' flag in 'cpo'. The swap file is kept when a buffer is still +" loaded and ':preserve' is used. +func Test_cpo_ampersand() + throw 'Skipped: Nvim does not support cpoptions flag "&"' + call writefile(['one'], 'XfileCpoAmp') + let after =<< trim [CODE] + set cpo+=& + preserve + qall + [CODE] + if RunVim([], after, 'XfileCpoAmp') + call assert_equal(1, filereadable('.XfileCpoAmp.swp')) + call delete('.XfileCpoAmp.swp') + endif + call delete('XfileCpoAmp') +endfunc + +" Test for the '\' flag in 'cpo' (backslash in a [] range in a search pattern) +func Test_cpo_backslash() + throw 'Skipped: Nvim does not support cpoptions flag "\"' + let save_cpo = &cpo + new + call setline(1, ['', " \\-string"]) + set cpo-=\ + exe 'normal gg/[ \-]' .. "\<CR>n" + call assert_equal(3, col('.')) + set cpo+=\ + exe 'normal gg/[ \-]' .. "\<CR>n" + call assert_equal(2, col('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the '/' flag in 'cpo' is in the test_substitute.vim file + +" Test for the '{' flag in 'cpo' (the "{" and "}" commands stop at a { +" character at the start of a line) +func Test_cpo_brace() + throw 'Skipped: Nvim does not support cpoptions flag "{"' + let save_cpo = &cpo + new + call setline(1, ['', '{', ' int i;', '}', '']) + set cpo-={ + normal gg} + call assert_equal(5, line('.')) + normal G{ + call assert_equal(1, line('.')) + set cpo+={ + normal gg} + call assert_equal(2, line('.')) + normal G{ + call assert_equal(2, line('.')) + close! + let &cpo = save_cpo +endfunc + +" Test for the '.' flag in 'cpo' (:cd command fails if the current buffer is +" modified) +func Test_cpo_dot() + throw 'Skipped: Nvim does not support cpoptions flag "."' + let save_cpo = &cpo + new Xfoo + call setline(1, 'foo') + let save_dir = getcwd() + set cpo+=. + + " :cd should fail when buffer is modified and 'cpo' contains dot. + call assert_fails('cd ..', 'E747:') + call assert_equal(save_dir, getcwd()) + + " :cd with exclamation mark should succeed. + cd! .. + call assert_notequal(save_dir, getcwd()) + + " :cd should succeed when buffer has been written. + w! + exe 'cd ' .. fnameescape(save_dir) + call assert_equal(save_dir, getcwd()) + + call delete('Xfoo') + set cpo& + close! + let &cpo = save_cpo +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cscope.vim b/src/nvim/testdir/test_cscope.vim deleted file mode 100644 index 76ea35fa10..0000000000 --- a/src/nvim/testdir/test_cscope.vim +++ /dev/null @@ -1,344 +0,0 @@ -" Test for cscope commands. - -source check.vim -CheckFeature cscope -CheckFeature quickfix - -if !executable('cscope') - throw 'Skipped: cscope program missing' -endif - -func CscopeSetupOrClean(setup) - if a:setup - noa sp samples/memfile_test.c - saveas! Xmemfile_test.c - call system('cscope -bk -fXcscope.out Xmemfile_test.c') - call system('cscope -bk -fXcscope2.out Xmemfile_test.c') - cscope add Xcscope.out - set cscopequickfix=s-,g-,d-,c-,t-,e-,f-,i-,a- - else - cscope kill -1 - for file in ['Xcscope.out', 'Xcscope2.out', 'Xmemfile_test.c'] - call delete(file) - endfo - endif -endfunc - -func Test_cscopeWithCscopeConnections() - call CscopeSetupOrClean(1) - " Test: E568: duplicate cscope database not added - try - set nocscopeverbose - cscope add Xcscope.out - set cscopeverbose - catch - call assert_report('exception thrown') - endtry - call assert_fails('cscope add', 'E560') - call assert_fails('cscope add Xcscope.out', 'E568') - call assert_fails('cscope add doesnotexist.out', 'E563') - if has('unix') - call assert_fails('cscope add /dev/null', 'E564:') - endif - - " Test: Find this C-Symbol - for cmd in ['cs find s main', 'cs find 0 main'] - let a = execute(cmd) - " Test where it moves the cursor - call assert_equal('main(void)', getline('.')) - " Test the output of the :cs command - call assert_match('\n(1 of 1): <<main>> main(void )', a) - endfor - - " Test: Find this definition - for cmd in ['cs find g test_mf_hash', - \ 'cs find 1 test_mf_hash', - \ 'cs find 1 test_mf_hash'] " leading space ignored. - exe cmd - call assert_equal(['', '/*', ' * Test mf_hash_*() functions.', ' */', ' static void', 'test_mf_hash(void)', '{'], getline(line('.')-5, line('.')+1)) - endfor - - " Test: Find functions called by this function - for cmd in ['cs find d test_mf_hash', 'cs find 2 test_mf_hash'] - let a = execute(cmd) - call assert_match('\n(1 of 42): <<mf_hash_init>> mf_hash_init(&ht);', a) - call assert_equal(' mf_hash_init(&ht);', getline('.')) - endfor - - " Test: Find functions calling this function - for cmd in ['cs find c test_mf_hash', 'cs find 3 test_mf_hash'] - let a = execute(cmd) - call assert_match('\n(1 of 1): <<main>> test_mf_hash();', a) - call assert_equal(' test_mf_hash();', getline('.')) - endfor - - " Test: Find this text string - for cmd in ['cs find t Bram', 'cs find 4 Bram'] - let a = execute(cmd) - call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a) - call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.')) - endfor - - " Test: Find this egrep pattern - " test all matches returned by cscope - for cmd in ['cs find e ^\#includ.', 'cs find 6 ^\#includ.'] - let a = execute(cmd) - call assert_match('\n(1 of 3): <<<unknown>>> #include <assert.h>', a) - call assert_equal('#include <assert.h>', getline('.')) - cnext - call assert_equal('#include "main.c"', getline('.')) - cnext - call assert_equal('#include "memfile.c"', getline('.')) - call assert_fails('cnext', 'E553:') - endfor - - " Test: Find the same egrep pattern using lcscope this time. - let a = execute('lcs find e ^\#includ.') - call assert_match('\n(1 of 3): <<<unknown>>> #include <assert.h>', a) - call assert_equal('#include <assert.h>', getline('.')) - lnext - call assert_equal('#include "main.c"', getline('.')) - lnext - call assert_equal('#include "memfile.c"', getline('.')) - call assert_fails('lnext', 'E553:') - - " Test: Find this file - for cmd in ['cs find f Xmemfile_test.c', 'cs find 7 Xmemfile_test.c'] - enew - let a = execute(cmd) - call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+B') - call assert_equal('Xmemfile_test.c', @%) - endfor - - " Test: Find files #including this file - for cmd in ['cs find i assert.h', 'cs find 8 assert.h'] - enew - let a = execute(cmd) - let alines = split(a, '\n', 1) - call assert_equal('', alines[0]) - call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+B') - call assert_equal('(1 of 1): <<global>> #include <assert.h>', alines[2]) - call assert_equal('#include <assert.h>', getline('.')) - endfor - - " Test: Invalid find command - call assert_fails('cs find', 'E560:') - call assert_fails('cs find x', 'E560:') - - if has('float') - " Test: Find places where this symbol is assigned a value - " this needs a cscope >= 15.8 - " unfortunately, Travis has cscope version 15.7 - let cscope_version = systemlist('cscope --version')[0] - let cs_version = str2float(matchstr(cscope_version, '\d\+\(\.\d\+\)\?')) - if cs_version >= 15.8 - for cmd in ['cs find a item', 'cs find 9 item'] - let a = execute(cmd) - call assert_equal(['', '(1 of 4): <<test_mf_hash>> item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);'], split(a, '\n', 1)) - call assert_equal(' item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);', getline('.')) - cnext - call assert_equal(' item = mf_hash_find(&ht, key);', getline('.')) - cnext - call assert_equal(' item = mf_hash_find(&ht, key);', getline('.')) - cnext - call assert_equal(' item = mf_hash_find(&ht, key);', getline('.')) - endfor - endif - endif - - " Test: leading whitespace is not removed for cscope find text - let a = execute('cscope find t test_mf_hash') - call assert_equal(['', '(1 of 1): <<<unknown>>> test_mf_hash();'], split(a, '\n', 1)) - call assert_equal(' test_mf_hash();', getline('.')) - - " Test: test with scscope - let a = execute('scs find t Bram') - call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a) - call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.')) - - " Test: cscope help - for cmd in ['cs', 'cs help', 'cs xxx'] - let a = execute(cmd) - call assert_match('^cscope commands:\n', a) - call assert_match('\nadd :', a) - call assert_match('\nfind :', a) - call assert_match('\nhelp : Show this message', a) - call assert_match('\nkill : Kill a connection', a) - call assert_match('\nreset: Reinit all connections', a) - call assert_match('\nshow : Show connections', a) - endfor - let a = execute('scscope help') - call assert_match('This cscope command does not support splitting the window\.', a) - - " Test: reset connections - let a = execute('cscope reset') - call assert_match('\nAdded cscope database.*Xcscope.out (#0)', a) - call assert_match('\nAll cscope databases reset', a) - - " Test: cscope show - let a = execute('cscope show') - call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a) - - " Test: cstag and 'csto' option - set csto=0 - let a = execute('cstag TEST_COUNT') - call assert_match('(1 of 1): <<TEST_COUNT>> #define TEST_COUNT 50000', a) - call assert_equal('#define TEST_COUNT 50000', getline('.')) - call assert_fails('cstag DOES_NOT_EXIST', 'E257:') - set csto=1 - let a = execute('cstag index_to_key') - call assert_match('(1 of 1): <<index_to_key>> #define index_to_key(i) ((i) ^ 15167)', a) - call assert_equal('#define index_to_key(i) ((i) ^ 15167)', getline('.')) - call assert_fails('cstag DOES_NOT_EXIST', 'E257:') - call assert_fails('cstag', 'E562:') - let save_tags = &tags - set tags= - call assert_fails('cstag DOES_NOT_EXIST', 'E257:') - let a = execute('cstag index_to_key') - call assert_match('(1 of 1): <<index_to_key>> #define index_to_key(i) ((i) ^ 15167)', a) - let &tags = save_tags - - " Test: 'cst' option - set nocst - call assert_fails('tag TEST_COUNT', 'E426:') - set cst - let a = execute('tag TEST_COUNT') - call assert_match('(1 of 1): <<TEST_COUNT>> #define TEST_COUNT 50000', a) - call assert_equal('#define TEST_COUNT 50000', getline('.')) - let a = execute('tags') - call assert_match('1 1 TEST_COUNT\s\+\d\+\s\+#define index_to_key', a) - - " Test: 'cscoperelative' - call mkdir('Xcscoperelative') - cd Xcscoperelative - let a = execute('cs find g test_mf_hash') - call assert_notequal('test_mf_hash(void)', getline('.')) - set cscoperelative - let a = execute('cs find g test_mf_hash') - call assert_equal('test_mf_hash(void)', getline('.')) - set nocscoperelative - cd .. - call delete('Xcscoperelative', 'd') - - " Test: E259: no match found - call assert_fails('cscope find g DOES_NOT_EXIST', 'E259:') - - " Test: this should trigger call to cs_print_tags() - " Unclear how to check result though, we just exercise the code. - set cst cscopequickfix=s0 - call feedkeys(":cs find s main\<CR>", 't') - - " Test: cscope kill - call assert_fails('cscope kill', 'E560:') - call assert_fails('cscope kill 2', 'E261:') - call assert_fails('cscope kill xxx', 'E261:') - - let a = execute('cscope kill 0') - call assert_match('cscope connection 0 closed', a) - - cscope add Xcscope.out - let a = execute('cscope kill Xcscope.out') - call assert_match('cscope connection Xcscope.out closed', a) - - cscope add Xcscope.out . - let a = execute('cscope kill -1') - call assert_match('cscope connection .*Xcscope.out closed', a) - let a = execute('cscope kill -1') - call assert_equal('', a) - - " Test: 'csprg' option - call assert_equal('cscope', &csprg) - set csprg=doesnotexist - call assert_fails('cscope add Xcscope2.out', 'E609:') - set csprg=cscope - - " Test: multiple cscope connections - cscope add Xcscope.out - cscope add Xcscope2.out . -C - let a = execute('cscope show') - call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a) - call assert_match('\n 1 \d\+.*Xcscope2.out\s*\.', a) - - " Test: test Ex command line completion - call feedkeys(":cs \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"cs add find help kill reset show', @:) - - call feedkeys(":scs \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"scs find', @:) - - call feedkeys(":cs find \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"cs find a c d e f g i s t', @:) - - call feedkeys(":cs kill \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"cs kill -1 0 1', @:) - - call feedkeys(":cs add Xcscope\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"cs add Xcscope.out Xcscope2.out', @:) - - " Test: cscope_connection() - call assert_equal(cscope_connection(), 1) - call assert_equal(cscope_connection(0, 'out'), 1) - call assert_equal(cscope_connection(0, 'xxx'), 1) - - call assert_equal(cscope_connection(1, 'out'), 1) - call assert_equal(cscope_connection(1, 'xxx'), 0) - - call assert_equal(cscope_connection(2, 'out'), 0) - call assert_equal(cscope_connection(2, getcwd() .. '/Xcscope.out', 1), 1) - - call assert_equal(cscope_connection(3, 'xxx', '..'), 0) - call assert_equal(cscope_connection(3, 'out', 'xxx'), 0) - call assert_equal(cscope_connection(3, 'out', '.'), 1) - - call assert_equal(cscope_connection(4, 'out', '.'), 0) - - call assert_equal(cscope_connection(5, 'out'), 0) - call assert_equal(cscope_connection(-1, 'out'), 0) - - call CscopeSetupOrClean(0) -endfunc - -" Test ":cs add {dir}" (add the {dir}/cscope.out database) -func Test_cscope_add_dir() - call mkdir('Xcscopedir', 'p') - - " Cscope doesn't handle symlinks, so this needs to be resolved in case a - " shadow directory is being used. - let memfile = resolve('./samples/memfile_test.c') - call system('cscope -bk -fXcscopedir/cscope.out ' . memfile) - - cs add Xcscopedir - let a = execute('cscope show') - let lines = split(a, "\n", 1) - call assert_equal(3, len(lines)) - call assert_equal(' # pid database name prepend path', lines[0]) - call assert_equal('', lines[1]) - call assert_match('^ 0 \d\+.*Xcscopedir/cscope.out\s\+<none>$', lines[2]) - - cs kill -1 - call delete('Xcscopedir/cscope.out') - call assert_fails('cs add Xcscopedir', 'E563:') - - call delete('Xcscopedir', 'd') -endfunc - -func Test_cscopequickfix() - set cscopequickfix=s-,g-,d+,c-,t+,e-,f0,i-,a- - call assert_equal('s-,g-,d+,c-,t+,e-,f0,i-,a-', &cscopequickfix) - - call assert_fails('set cscopequickfix=x-', 'E474:') - call assert_fails('set cscopequickfix=s', 'E474:') - call assert_fails('set cscopequickfix=s7', 'E474:') - call assert_fails('set cscopequickfix=s-a', 'E474:') -endfunc - -func Test_withoutCscopeConnection() - call assert_equal(cscope_connection(), 0) - - call assert_fails('cscope find s main', 'E567:') - let a = execute('cscope show') - call assert_match('no cscope connections', a) -endfunc - - -" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim index 3b8a5f27ad..bb8e7cd5c5 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -1,7 +1,10 @@ " Tests for cursor() and other functions that get/set the cursor position +source check.vim + func Test_wrong_arguments() call assert_fails('call cursor(1. 3)', 'E474:') + call assert_fails('call cursor(v:_null_list)', 'E474:') endfunc func Test_move_cursor() @@ -22,7 +25,7 @@ func Test_move_cursor() call cursor(3, 0) call assert_equal([3, 1, 0, 1], getcurpos()[1:]) " below last line goes to last line - call cursor(9, 1) + eval [9, 1]->cursor() call assert_equal([4, 1, 0, 1], getcurpos()[1:]) " pass string arguments call cursor('3', '3') @@ -32,7 +35,7 @@ func Test_move_cursor() call cursor(1, 1, 1) call assert_equal([1, 1, 1], getcurpos()[1:3]) - call assert_equal(-1, cursor(-1, -1)) + call assert_fails('call cursor(-1, -1)', 'E475:') quit! endfunc @@ -82,34 +85,85 @@ func Test_screenpos() let winid = win_getid() let [winrow, wincol] = win_screenpos(winid) call assert_equal({'row': winrow, - \ 'col': wincol + 0, - \ 'curscol': wincol + 7, - \ 'endcol': wincol + 7}, winid->screenpos(1, 1)) + \ 'col': wincol + 0, + \ 'curscol': wincol + 7, + \ 'endcol': wincol + 7}, winid->screenpos(1, 1)) call assert_equal({'row': winrow, - \ 'col': wincol + 13, - \ 'curscol': wincol + 13, - \ 'endcol': wincol + 13}, winid->screenpos(1, 7)) + \ 'col': wincol + 13, + \ 'curscol': wincol + 13, + \ 'endcol': wincol + 13}, winid->screenpos(1, 7)) call assert_equal({'row': winrow + 2, - \ 'col': wincol + 1, - \ 'curscol': wincol + 1, - \ 'endcol': wincol + 1}, screenpos(winid, 2, 22)) + \ 'col': wincol + 1, + \ 'curscol': wincol + 1, + \ 'endcol': wincol + 1}, screenpos(winid, 2, 22)) setlocal number call assert_equal({'row': winrow + 3, - \ 'col': wincol + 9, - \ 'curscol': wincol + 9, - \ 'endcol': wincol + 9}, screenpos(winid, 2, 22)) + \ 'col': wincol + 9, + \ 'curscol': wincol + 9, + \ 'endcol': wincol + 9}, screenpos(winid, 2, 22)) + + let wininfo = getwininfo(winid)[0] + call setline(3, ['x']->repeat(wininfo.height)) + call setline(line('$') + 1, 'x'->repeat(wininfo.width * 3)) + setlocal nonumber display=lastline so=0 + exe "normal G\<C-Y>\<C-Y>" + redraw + call assert_equal({'row': winrow + wininfo.height - 1, + \ 'col': wincol + 7, + \ 'curscol': wincol + 7, + \ 'endcol': wincol + 7}, winid->screenpos(line('$'), 8)) + call assert_equal({'row': 0, 'col': 0, 'curscol': 0, 'endcol': 0}, + \ winid->screenpos(line('$'), 22)) + close call assert_equal({}, screenpos(999, 1, 1)) + bwipe! + set display& - call assert_equal({'col': 1, 'row': 1, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1)) + call assert_equal(#{col: 1, row: 1, endcol: 1, curscol: 1}, screenpos(win_getid(), 1, 1)) " nmenu WinBar.TEST : setlocal winbar=TEST - call assert_equal({'col': 1, 'row': 2, 'endcol': 1, 'curscol': 1}, screenpos(win_getid(), 1, 1)) + call assert_equal(#{col: 1, row: 2, endcol: 1, curscol: 1}, screenpos(win_getid(), 1, 1)) " nunmenu WinBar.TEST setlocal winbar& endfunc +func Test_screenpos_fold() + CheckFeature folding + + enew! + call setline(1, range(10)) + 3,5fold + redraw + call assert_equal(2, screenpos(1, 2, 1).row) + call assert_equal(#{col: 1, row: 3, endcol: 1, curscol: 1}, screenpos(1, 3, 1)) + call assert_equal(#{col: 1, row: 3, endcol: 1, curscol: 1}, screenpos(1, 4, 1)) + call assert_equal(#{col: 1, row: 3, endcol: 1, curscol: 1}, screenpos(1, 5, 1)) + setlocal number + call assert_equal(#{col: 5, row: 3, endcol: 5, curscol: 5}, screenpos(1, 3, 1)) + call assert_equal(#{col: 5, row: 3, endcol: 5, curscol: 5}, screenpos(1, 4, 1)) + call assert_equal(#{col: 5, row: 3, endcol: 5, curscol: 5}, screenpos(1, 5, 1)) + call assert_equal(4, screenpos(1, 6, 1).row) + bwipe! +endfunc + +func Test_screenpos_diff() + CheckFeature diff + + enew! + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + vnew + call setline(1, ['a', 'b', 'c', 'g', 'h', 'i']) + windo diffthis + wincmd w + call assert_equal(#{col: 3, row: 7, endcol: 3, curscol: 3}, screenpos(0, 4, 1)) + + windo diffoff + bwipe! + bwipe! +endfunc + func Test_screenpos_number() rightbelow new rightbelow 73vsplit @@ -121,6 +175,9 @@ func Test_screenpos_number() let pos = screenpos(winid, 1, 66) call assert_equal(winrow, pos.row) call assert_equal(wincol + 66 + 3, pos.col) + + call assert_fails('echo screenpos(0, 2, 1)', 'E966:') + close bwipe! endfunc @@ -241,8 +298,9 @@ endfunc " Test for the charcol() function func Test_charcol() - call assert_fails('call charcol({})', 'E731:') - call assert_equal(0, charcol(0)) + call assert_fails('call charcol({})', 'E1222:') + call assert_fails('call charcol(".", [])', 'E1210:') + call assert_fails('call charcol(0)', 'E1222:') new call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) @@ -298,6 +356,25 @@ func Test_charcol() call assert_equal([1, 10, 2, 10, 7], g:InsertCurrentCol) iunmap <F3> + " Test for getting the column number in another window. + let winid = win_getid() + new + call win_execute(winid, 'normal 1G') + call assert_equal(1, charcol('.', winid)) + call assert_equal(1, charcol('$', winid)) + call win_execute(winid, 'normal 2G6l') + call assert_equal(7, charcol('.', winid)) + call assert_equal(10, charcol('$', winid)) + + " calling from another tab page also works + tabnew + call assert_equal(7, charcol('.', winid)) + call assert_equal(10, charcol('$', winid)) + tabclose + + " unknown window ID + call assert_equal(0, charcol('.', 10001)) + %bw! endfunc @@ -353,8 +430,14 @@ func Test_setcursorcharpos() normal G call setcursorcharpos([1, 1]) call assert_equal([1, 1], [line('.'), col('.')]) + call setcursorcharpos([2, 7, 0]) call assert_equal([2, 9], [line('.'), col('.')]) + call setcursorcharpos([0, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcursorcharpos(0, 7, 0) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcursorcharpos(3, 4) call assert_equal([3, 1], [line('.'), col('.')]) call setcursorcharpos([3, 1]) @@ -373,4 +456,26 @@ func Test_setcursorcharpos() %bw! endfunc +" Test for virtcol2col() +func Test_virtcol2col() + new + call setline(1, ["a\tb\tc"]) + call assert_equal(1, virtcol2col(0, 1, 1)) + call assert_equal(2, virtcol2col(0, 1, 2)) + call assert_equal(2, virtcol2col(0, 1, 8)) + call assert_equal(3, virtcol2col(0, 1, 9)) + call assert_equal(4, virtcol2col(0, 1, 10)) + call assert_equal(4, virtcol2col(0, 1, 16)) + call assert_equal(5, virtcol2col(0, 1, 17)) + call assert_equal(-1, virtcol2col(10, 1, 1)) + call assert_equal(-1, virtcol2col(0, 10, 1)) + call assert_equal(-1, virtcol2col(0, -1, 1)) + call assert_equal(-1, virtcol2col(0, 1, -1)) + call assert_equal(5, virtcol2col(0, 1, 20)) + call assert_fails('echo virtcol2col("0", 1, 20)', 'E1210:') + call assert_fails('echo virtcol2col(0, "1", 20)', 'E1210:') + call assert_fails('echo virtcol2col(0, 1, "1")', 'E1210:') + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cursorline.vim b/src/nvim/testdir/test_cursorline.vim index e85e9304a3..70f39a8601 100644 --- a/src/nvim/testdir/test_cursorline.vim +++ b/src/nvim/testdir/test_cursorline.vim @@ -3,26 +3,26 @@ source check.vim source screendump.vim -function! s:screen_attr(lnum) abort +func s:screen_attr(lnum) abort return map(range(1, 8), 'screenattr(a:lnum, v:val)') -endfunction +endfunc -function! s:test_windows(h, w) abort +func s:test_windows(h, w) abort call NewWindow(a:h, a:w) -endfunction +endfunc -function! s:close_windows() abort +func s:close_windows() abort call CloseWindow() -endfunction +endfunc -function! s:new_hi() abort +func s:new_hi() abort redir => save_hi silent! hi CursorLineNr redir END let save_hi = join(split(substitute(save_hi, '\s*xxx\s*', ' ', ''), "\n"), '') exe 'hi' save_hi 'ctermbg=0 guibg=Black' return save_hi -endfunction +endfunc func Test_cursorline_highlight1() let save_hi = s:new_hi() @@ -319,7 +319,7 @@ func Test_cursorline_cursorbind_horizontal_scroll() let lines =<< trim END call setline(1, 'aa bb cc dd ee ff gg hh ii jj kk ll mm' .. - \ ' nn oo pp qq rr ss tt uu vv ww xx yy zz') + \ ' nn oo pp qq rr ss tt uu vv ww xx yy zz') set nowrap " The following makes the cursor apparent on the screen dump set sidescroll=1 cursorcolumn diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim index e038c0096a..f5177c8fb2 100644 --- a/src/nvim/testdir/test_debugger.vim +++ b/src/nvim/testdir/test_debugger.vim @@ -15,14 +15,18 @@ func CheckCWD() endfunc command! -nargs=0 -bar CheckCWD call CheckCWD() +" "options" argument can contain: +" 'msec' - time to wait for a match +" 'match' - "pattern" to use "lines" as pattern instead of text func CheckDbgOutput(buf, lines, options = {}) " Verify the expected output let lnum = 20 - len(a:lines) + let msec = get(a:options, 'msec', 1000) for l in a:lines if get(a:options, 'match', 'equal') ==# 'pattern' - call WaitForAssert({-> assert_match(l, term_getline(a:buf, lnum))}, 200) + call WaitForAssert({-> assert_match(l, term_getline(a:buf, lnum))}, msec) else - call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}, 200) + call WaitForAssert({-> assert_equal(l, term_getline(a:buf, lnum))}, msec) endif let lnum += 1 endfor @@ -32,7 +36,7 @@ endfunc " If the expected output argument is supplied, then check for it. func RunDbgCmd(buf, cmd, ...) call term_sendkeys(a:buf, a:cmd . "\r") - call term_wait(a:buf) + call term_wait(a:buf, 20) if a:0 != 0 let options = #{match: 'equal'} @@ -198,7 +202,7 @@ func Test_Debugger() " Start a debug session, so that reading the last line from the terminal " works properly. - call RunDbgCmd(buf, ':debug echo Foo()') + call RunDbgCmd(buf, ':debug echo Foo()', ['cmd: echo Foo()']) " No breakpoints call RunDbgCmd(buf, 'breakl', ['No breakpoints defined']) @@ -814,9 +818,10 @@ func Test_Backtrace_CmdLine() \ '-S Xtest1.vim -c "debug call GlobalFunction()"', \ {'wait_for_ruler': 0}) - " Need to wait for the vim-in-terminal to be ready + " Need to wait for the vim-in-terminal to be ready. + " With valgrind this can take quite long. call CheckDbgOutput(buf, ['command line', - \ 'cmd: call GlobalFunction()']) + \ 'cmd: call GlobalFunction()'], #{msec: 5000}) " At this point the ontly thing in the stack is the cmdline call RunDbgCmd(buf, 'backtrace', [ @@ -967,14 +972,14 @@ func Test_debug_backtrace_level() " set a breakpoint and source file1.vim let buf = RunVimInTerminal( \ '-c "breakadd file 1 Xtest1.vim" -S Xtest1.vim', - \ #{ wait_for_ruler: 0 } ) + \ #{wait_for_ruler: 0}) call CheckDbgOutput(buf, [ \ 'Breakpoint in "' .. file1 .. '" line 1', \ 'Entering Debug mode. Type "cont" to continue.', \ 'command line..script ' .. file1, \ 'line 1: let s:file1_var = ''file1''' - \ ]) + \ ], #{msec: 5000}) " step through the initial declarations call RunDbgCmd(buf, 'step', [ 'line 2: let g:global_var = ''global''' ] ) @@ -1140,7 +1145,6 @@ func Test_breakpt_endif_intr() let caught_intr = 0 debuggreedy call feedkeys(":call F()\<CR>quit\<CR>", "xt") - call F() catch /^Vim:Interrupt$/ call assert_match('\.F, line 4', v:throwpoint) let caught_intr = 1 @@ -1171,7 +1175,6 @@ func Test_breakpt_else_intr() let caught_intr = 0 debuggreedy call feedkeys(":call F()\<CR>quit\<CR>", "xt") - call F() catch /^Vim:Interrupt$/ call assert_match('\.F, line 4', v:throwpoint) let caught_intr = 1 @@ -1200,7 +1203,6 @@ func Test_breakpt_endwhile_intr() let caught_intr = 0 debuggreedy call feedkeys(":call F()\<CR>quit\<CR>", "xt") - call F() catch /^Vim:Interrupt$/ call assert_match('\.F, line 4', v:throwpoint) let caught_intr = 1 @@ -1212,38 +1214,24 @@ func Test_breakpt_endwhile_intr() delfunc F endfunc -" Test for setting a breakpoint on an :endtry where an exception is pending to -" be processed and then quit the script. This should generate an interrupt and -" the thrown exception should be ignored. -func Test_breakpt_endtry_intr() - func F() - try - let g:Xpath ..= 'a' - throw "abc" - endtry - invalid_command +" Test for setting a breakpoint on a script local function +func Test_breakpt_scriptlocal_func() + let g:Xpath = '' + func s:G() + let g:Xpath ..= 'a' endfunc - let g:Xpath = '' - breakadd func 4 F - try - let caught_intr = 0 - let caught_abc = 0 - debuggreedy - call feedkeys(":call F()\<CR>quit\<CR>", "xt") - call F() - catch /abc/ - let caught_abc = 1 - catch /^Vim:Interrupt$/ - call assert_match('\.F, line 4', v:throwpoint) - let caught_intr = 1 - endtry + let funcname = expand("<SID>") .. "G" + exe "breakadd func 1 " .. funcname + debuggreedy + redir => output + call feedkeys(":call " .. funcname .. "()\<CR>c\<CR>", "xt") + redir END 0debuggreedy - call assert_equal(1, caught_intr) - call assert_equal(0, caught_abc) + call assert_match('Breakpoint in "' .. funcname .. '" line 1', output) call assert_equal('a', g:Xpath) breakdel * - delfunc F + exe "delfunc " .. funcname endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_delete.vim b/src/nvim/testdir/test_delete.vim index b23a3bd025..6b49f153c6 100644 --- a/src/nvim/testdir/test_delete.vim +++ b/src/nvim/testdir/test_delete.vim @@ -1,5 +1,7 @@ " Test for delete(). +source check.vim + func Test_file_delete() split Xfile call setline(1, ['a', 'b']) @@ -41,9 +43,7 @@ func Test_recursive_delete() endfunc func Test_symlink_delete() - if !has('unix') - return - endif + CheckUnix split Xfile call setline(1, ['a', 'b']) wq @@ -56,9 +56,7 @@ func Test_symlink_delete() endfunc func Test_symlink_dir_delete() - if !has('unix') - return - endif + CheckUnix call mkdir('Xdir1') silent !ln -s Xdir1 Xlink call assert_true(isdirectory('Xdir1')) @@ -70,9 +68,7 @@ func Test_symlink_dir_delete() endfunc func Test_symlink_recursive_delete() - if !has('unix') - return - endif + CheckUnix call mkdir('Xdir3') call mkdir('Xdir3/subdir') call mkdir('Xdir4') diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index ea453b7174..0049398776 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -1,4 +1,5 @@ " Tests for diff mode + source shared.vim source screendump.vim source check.vim @@ -243,6 +244,36 @@ func Test_diffput_two() bwipe! b endfunc +" Test for :diffget/:diffput with a range that is inside a diff chunk +func Test_diffget_diffput_range() + call setline(1, range(1, 10)) + new + call setline(1, range(11, 20)) + windo diffthis + 3,5diffget + call assert_equal(['13', '14', '15'], getline(3, 5)) + call setline(1, range(1, 10)) + 4,8diffput + wincmd p + call assert_equal(['13', '4', '5', '6', '7', '8', '19'], getline(3, 9)) + %bw! +endfunc + +" Test for :diffget/:diffput with an empty buffer and a non-empty buffer +func Test_diffget_diffput_empty_buffer() + %d _ + new + call setline(1, 'one') + windo diffthis + diffget + call assert_equal(['one'], getline(1, '$')) + %d _ + diffput + wincmd p + call assert_equal([''], getline(1, '$')) + %bw! +endfunc + " :diffput and :diffget completes names of buffers which " are in diff mode and which are different then current buffer. " No completion when the current window is not in diff mode. @@ -621,9 +652,7 @@ func Test_diff_move_to() endfunc func Test_diffexpr() - if !executable('diff') - return - endif + CheckExecutable diff func DiffExpr() " Prepend some text to check diff type detection @@ -647,17 +676,31 @@ func Test_diffexpr() call assert_equal(normattr, screenattr(1, 1)) call assert_equal(normattr, screenattr(2, 1)) call assert_notequal(normattr, screenattr(3, 1)) + diffoff! + " Try using an non-existing function for 'diffexpr'. + set diffexpr=NewDiffFunc() + call assert_fails('windo diffthis', ['E117:', 'E97:']) diffoff! + + " Using a script-local function + func s:NewDiffExpr() + endfunc + set diffexpr=s:NewDiffExpr() + call assert_equal(expand('<SID>') .. 'NewDiffExpr()', &diffexpr) + set diffexpr=<SID>NewDiffExpr() + call assert_equal(expand('<SID>') .. 'NewDiffExpr()', &diffexpr) + %bwipe! set diffexpr& diffopt& + delfunc DiffExpr + delfunc s:NewDiffExpr endfunc func Test_diffpatch() " The patch program on MS-Windows may fail or hang. - if !executable('patch') || !has('unix') - return - endif + CheckExecutable patch + CheckUnix new insert *************** @@ -744,17 +787,13 @@ func Test_diff_hlID() call diff_hlID(-1, 1)->synIDattr("name")->assert_equal("") - call assert_equal(diff_hlID(1, 1), hlID("DiffChange")) call diff_hlID(1, 1)->synIDattr("name")->assert_equal("DiffChange") - call assert_equal(diff_hlID(1, 2), hlID("DiffText")) call diff_hlID(1, 2)->synIDattr("name")->assert_equal("DiffText") call diff_hlID(2, 1)->synIDattr("name")->assert_equal("") - call assert_equal(diff_hlID(3, 1), hlID("DiffAdd")) call diff_hlID(3, 1)->synIDattr("name")->assert_equal("DiffAdd") - call diff_hlID(4, 1)->synIDattr("name")->assert_equal("") + eval 4->diff_hlID(1)->synIDattr("name")->assert_equal("") wincmd w - call assert_equal(diff_hlID(1, 1), hlID("DiffChange")) call assert_equal(synIDattr(diff_hlID(1, 1), "name"), "DiffChange") call assert_equal(synIDattr(diff_hlID(2, 1), "name"), "") call assert_equal(synIDattr(diff_hlID(3, 1), "name"), "") @@ -1202,6 +1241,42 @@ func Test_diff_maintains_change_mark() delfunc DiffMaintainsChangeMark endfunc +" Test for 'patchexpr' +func Test_patchexpr() + let g:patch_args = [] + func TPatch() + call add(g:patch_args, readfile(v:fname_in)) + call add(g:patch_args, readfile(v:fname_diff)) + call writefile(['output file'], v:fname_out) + endfunc + set patchexpr=TPatch() + + call writefile(['input file'], 'Xinput') + call writefile(['diff file'], 'Xdiff') + %bwipe! + edit Xinput + diffpatch Xdiff + call assert_equal('output file', getline(1)) + call assert_equal('Xinput.new', bufname()) + call assert_equal(2, winnr('$')) + call assert_true(&diff) + + " Using a script-local function + func s:NewPatchExpr() + endfunc + set patchexpr=s:NewPatchExpr() + call assert_equal(expand('<SID>') .. 'NewPatchExpr()', &patchexpr) + set patchexpr=<SID>NewPatchExpr() + call assert_equal(expand('<SID>') .. 'NewPatchExpr()', &patchexpr) + + call delete('Xinput') + call delete('Xdiff') + set patchexpr& + delfunc TPatch + delfunc s:NewPatchExpr + %bwipe! +endfunc + func Test_diff_rnu() CheckScreendump @@ -1295,6 +1370,89 @@ func Test_diff_filler_cursorcolumn() call delete('Xtest_diff_cuc') endfunc +" Test for adding/removing lines inside diff chunks, between diff chunks +" and before diff chunks +func Test_diff_modify_chunks() + enew! + let w2_id = win_getid() + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + new + let w1_id = win_getid() + call setline(1, ['a', '2', '3', 'd', 'e', 'f', '7', '8', 'i']) + windo diffthis + + " remove a line between two diff chunks and create a new diff chunk + call win_gotoid(w2_id) + 5d + call win_gotoid(w1_id) + call diff_hlID(5, 1)->synIDattr('name')->assert_equal('DiffAdd') + + " add a line between two diff chunks + call win_gotoid(w2_id) + normal! 4Goe + call win_gotoid(w1_id) + call diff_hlID(4, 1)->synIDattr('name')->assert_equal('') + call diff_hlID(5, 1)->synIDattr('name')->assert_equal('') + + " remove all the lines in a diff chunk. + call win_gotoid(w2_id) + 7,8d + call win_gotoid(w1_id) + let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', 'DiffText', 'DiffText', '', '', '', 'DiffAdd', + \ 'DiffAdd', ''], hl) + + " remove lines from one diff chunk to just before the next diff chunk + call win_gotoid(w2_id) + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + 2,6d + call win_gotoid(w1_id) + let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', 'DiffText', 'DiffText', 'DiffAdd', 'DiffAdd', + \ 'DiffAdd', 'DiffAdd', 'DiffAdd', ''], hl) + + " remove lines just before the top of a diff chunk + call win_gotoid(w2_id) + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + 5,6d + call win_gotoid(w1_id) + let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', 'DiffText', 'DiffText', '', 'DiffText', 'DiffText', + \ 'DiffAdd', 'DiffAdd', ''], hl) + + " remove line after the end of a diff chunk + call win_gotoid(w2_id) + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + 4d + call win_gotoid(w1_id) + let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', 'DiffText', 'DiffText', 'DiffAdd', '', '', 'DiffText', + \ 'DiffText', ''], hl) + + " remove lines starting from the end of one diff chunk and ending inside + " another diff chunk + call win_gotoid(w2_id) + call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']) + 4,7d + call win_gotoid(w1_id) + let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', 'DiffText', 'DiffText', 'DiffText', 'DiffAdd', + \ 'DiffAdd', 'DiffAdd', 'DiffAdd', ''], hl) + + " removing the only remaining diff chunk should make the files equal + call win_gotoid(w2_id) + call setline(1, ['a', '2', '3', 'x', 'd', 'e', 'f', 'x', '7', '8', 'i']) + 8d + let hl = range(1, 10)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', '', '', 'DiffAdd', '', '', '', '', '', ''], hl) + call win_gotoid(w2_id) + 4d + call win_gotoid(w1_id) + let hl = range(1, 9)->map({_, lnum -> diff_hlID(lnum, 1)->synIDattr('name')}) + call assert_equal(['', '', '', '', '', '', '', '', ''], hl) + + %bw! +endfunc func Test_diff_binary() CheckScreendump diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index acc34e5e7c..f08dff8605 100644 --- a/src/nvim/testdir/test_digraph.vim +++ b/src/nvim/testdir/test_digraph.vim @@ -211,6 +211,8 @@ func Test_digraphs() call Put_Dig("00") call Put_Dig("el") call assert_equal(['␀', 'ü', '∞', 'l'], getline(line('.')-3,line('.'))) + call assert_fails('exe "digraph a\<Esc> 100"', 'E104:') + call assert_fails('exe "digraph \<Esc>a 100"', 'E104:') call assert_fails('digraph xy z', 'E39:') call assert_fails('digraph x', 'E1214:') bw! @@ -491,6 +493,17 @@ func Test_show_digraph_cp1251() bwipe! endfunc +" Test for error in a keymap file +func Test_loadkeymap_error() + if !has('keymap') + return + endif + call assert_fails('loadkeymap', 'E105:') + call writefile(['loadkeymap', 'a'], 'Xkeymap') + call assert_fails('source Xkeymap', 'E791:') + call delete('Xkeymap') +endfunc + " Test for the characters displayed on the screen when entering a digraph func Test_entering_digraph() CheckRunVimInTerminal diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index 6938abbc28..b642f39c9f 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -195,8 +195,6 @@ func Test_edit_long_file_name() call VerifyScreenDump(buf, 'Test_long_file_name_1', {}) - call term_sendkeys(buf, ":q\<cr>") - " clean up call StopVimInTerminal(buf) call delete(longName) @@ -228,7 +226,6 @@ endfunc " Test for scrolling that modifies buffer during visual block func Test_visual_block_scroll() - " See test/functional/legacy/visual_mode_spec.lua CheckScreendump let lines =<< trim END @@ -239,7 +236,7 @@ func Test_visual_block_scroll() END let filename = 'Xvisualblockmodifiedscroll' - call writefile(lines, filename) + call writefile(lines, filename, 'D') let buf = RunVimInTerminal('-S '.filename, #{rows: 7}) call term_sendkeys(buf, "V\<C-D>\<C-D>") @@ -247,11 +244,40 @@ func Test_visual_block_scroll() call VerifyScreenDump(buf, 'Test_display_visual_block_scroll', {}) call StopVimInTerminal(buf) - call delete(filename) +endfunc + +" Test for clearing paren highlight when switching buffers +func Test_matchparen_clear_highlight() + CheckScreendump + + let lines =<< trim END + source $VIMRUNTIME/plugin/matchparen.vim + set hidden + call setline(1, ['()']) + normal 0 + + func OtherBuffer() + enew + exe "normal iaa\<Esc>0" + endfunc + END + call writefile(lines, 'XMatchparenClear', 'D') + let buf = RunVimInTerminal('-S XMatchparenClear', #{rows: 5}) + call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_1', {}) + + call term_sendkeys(buf, ":call OtherBuffer()\<CR>:\<Esc>") + call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_2', {}) + + call term_sendkeys(buf, "\<C-^>:\<Esc>") + call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_1', {}) + + call term_sendkeys(buf, "\<C-^>:\<Esc>") + call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_2', {}) + + call StopVimInTerminal(buf) endfunc func Test_display_scroll_at_topline() - " See test/functional/legacy/display_spec.lua CheckScreendump let buf = RunVimInTerminal('', #{cols: 20}) @@ -309,6 +335,88 @@ func Test_eob_fillchars() close endfunc +" Test for 'foldopen', 'foldclose' and 'foldsep' in 'fillchars' +func Test_fold_fillchars() + new + set fdc=2 foldenable foldmethod=manual + call setline(1, ['one', 'two', 'three', 'four', 'five']) + 2,4fold + " First check for the default setting for a closed fold + let lines = ScreenLines([1, 3], 8) + let expected = [ + \ ' one ', + \ '+ +-- 3', + \ ' five ' + \ ] + call assert_equal(expected, lines) + normal 2Gzo + " check the characters for an open fold + let lines = ScreenLines([1, 5], 8) + let expected = [ + \ ' one ', + \ '- two ', + \ '| three ', + \ '| four ', + \ ' five ' + \ ] + call assert_equal(expected, lines) + + " change the setting + set fillchars=vert:\|,fold:-,eob:~,foldopen:[,foldclose:],foldsep:- + + " check the characters for an open fold + let lines = ScreenLines([1, 5], 8) + let expected = [ + \ ' one ', + \ '[ two ', + \ '- three ', + \ '- four ', + \ ' five ' + \ ] + call assert_equal(expected, lines) + + " check the characters for a closed fold + normal 2Gzc + let lines = ScreenLines([1, 3], 8) + let expected = [ + \ ' one ', + \ '] +-- 3', + \ ' five ' + \ ] + call assert_equal(expected, lines) + + %bw! + set fillchars& fdc& foldmethod& foldenable& +endfunc + +func Test_local_fillchars() + CheckScreendump + + let lines =<< trim END + call setline(1, ['window 1']->repeat(3)) + setlocal fillchars=stl:1,stlnc:a,vert:=,eob:x + vnew + call setline(1, ['window 2']->repeat(3)) + setlocal fillchars=stl:2,stlnc:b,vert:+,eob:y + new + wincmd J + call setline(1, ['window 3']->repeat(3)) + setlocal fillchars=stl:3,stlnc:c,vert:<,eob:z + vnew + call setline(1, ['window 4']->repeat(3)) + setlocal fillchars=stl:4,stlnc:d,vert:>,eob:o + END + call writefile(lines, 'Xdisplayfillchars') + let buf = RunVimInTerminal('-S Xdisplayfillchars', #{rows: 12}) + call VerifyScreenDump(buf, 'Test_display_fillchars_1', {}) + + call term_sendkeys(buf, ":wincmd k\r") + call VerifyScreenDump(buf, 'Test_display_fillchars_2', {}) + + call StopVimInTerminal(buf) + call delete('Xdisplayfillchars') +endfunc + func Test_display_linebreak_breakat() new vert resize 25 @@ -325,30 +433,49 @@ func Test_display_linebreak_breakat() let &breakat=_breakat endfunc -func Test_display_lastline() - CheckScreendump - +func Run_Test_display_lastline(euro) let lines =<< trim END - call setline(1, ['aaa', 'b'->repeat(100)]) + call setline(1, ['aaa', 'b'->repeat(200)]) set display=truncate + vsplit 100wincmd < END - call writefile(lines, 'XdispLastline') + if a:euro != '' + let lines[2] = 'set fillchars=vert:\|,lastline:€' + endif + call writefile(lines, 'XdispLastline', 'D') let buf = RunVimInTerminal('-S XdispLastline', #{rows: 10}) - call VerifyScreenDump(buf, 'Test_display_lastline_1', {}) + call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}1', {}) call term_sendkeys(buf, ":set display=lastline\<CR>") - call VerifyScreenDump(buf, 'Test_display_lastline_2', {}) + call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}2', {}) call term_sendkeys(buf, ":100wincmd >\<CR>") - call VerifyScreenDump(buf, 'Test_display_lastline_3', {}) + call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}3', {}) call term_sendkeys(buf, ":set display=truncate\<CR>") - call VerifyScreenDump(buf, 'Test_display_lastline_4', {}) + call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}4', {}) + + call term_sendkeys(buf, ":close\<CR>") + call term_sendkeys(buf, ":3split\<CR>") + call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}5', {}) + + call term_sendkeys(buf, ":close\<CR>") + call term_sendkeys(buf, ":2vsplit\<CR>") + call VerifyScreenDump(buf, $'Test_display_lastline_{a:euro}6', {}) call StopVimInTerminal(buf) - call delete('XdispLastline') +endfunc + +func Test_display_lastline() + CheckScreendump + + call Run_Test_display_lastline('') + call Run_Test_display_lastline('euro_') + + call assert_fails(':set fillchars=lastline:', 'E474:') + call assert_fails(':set fillchars=lastline:〇', 'E474:') endfunc diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index e26bbdc5be..fd54f77ccb 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -330,6 +330,16 @@ func Test_edit_11_indentexpr() set cinkeys&vim indentkeys&vim set nocindent indentexpr= delfu Do_Indent + + " Using a script-local function + func s:NewIndentExpr() + endfunc + set indentexpr=s:NewIndentExpr() + call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr) + set indentexpr=<SID>NewIndentExpr() + call assert_equal(expand('<SID>') .. 'NewIndentExpr()', &indentexpr) + set indentexpr& + bw! endfunc @@ -713,23 +723,32 @@ endfunc func Test_edit_CTRL_N() " Check keyword completion - new - set complete=. - call setline(1, ['INFER', 'loWER', '', '', ]) - call cursor(3, 1) - call feedkeys("Ai\<c-n>\<cr>\<esc>", "tnix") - call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') - call assert_equal(['INFER', 'loWER', 'i', 'LO', '', ''], getline(1, '$')) - %d - call setline(1, ['INFER', 'loWER', '', '', ]) - call cursor(3, 1) - set ignorecase infercase - call feedkeys("Ii\<c-n>\<cr>\<esc>", "tnix") - call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') - call assert_equal(['INFER', 'loWER', 'infer', 'LOWER', '', ''], getline(1, '$')) - - set noignorecase noinfercase complete& - bw! + " for e in ['latin1', 'utf-8'] + for e in ['utf-8'] + exe 'set encoding=' .. e + new + set complete=. + call setline(1, ['INFER', 'loWER', '', '', ]) + call cursor(3, 1) + call feedkeys("Ai\<c-n>\<cr>\<esc>", "tnix") + call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['INFER', 'loWER', 'i', 'LO', '', ''], getline(1, '$'), e) + %d + call setline(1, ['INFER', 'loWER', '', '', ]) + call cursor(3, 1) + set ignorecase infercase + call feedkeys("Ii\<c-n>\<cr>\<esc>", "tnix") + call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['INFER', 'loWER', 'infer', 'LOWER', '', ''], getline(1, '$'), e) + set noignorecase noinfercase + %d + call setline(1, ['one word', 'two word']) + exe "normal! Goo\<C-P>\<C-X>\<C-P>" + call assert_equal('one word', getline(3)) + %d + set complete& + bw! + endfor endfunc func Test_edit_CTRL_O() @@ -893,6 +912,24 @@ func Test_edit_CTRL_T() bw! endfunc +" Test thesaurus completion with different encodings +func Test_thesaurus_complete_with_encoding() + call writefile(['angry furious mad enraged'], 'Xthesaurus') + set thesaurus=Xthesaurus + " for e in ['latin1', 'utf-8'] + for e in ['utf-8'] + exe 'set encoding=' .. e + new + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<cr>\<esc>", 'tnix') + call assert_equal(['mad', ''], getline(1, '$')) + bw! + endfor + set thesaurus= + call delete('Xthesaurus') +endfunc + " Test 'thesaurusfunc' func MyThesaurus(findstart, base) let mythesaurus = [ @@ -1201,15 +1238,11 @@ func Test_edit_MOUSE() call assert_equal(24, line('w0')) call assert_equal([0, 24, 2, 0], getpos('.')) - " call test_setmouse(4, 3) - call nvim_input_mouse('left', 'press', '', 0, 3, 2) " set mouse position - call getchar() " discard mouse event but keep mouse position + call Ntest_setmouse(4, 3) call feedkeys("A\<LeftMouse>\<esc>", 'tnix') call assert_equal([0, 27, 2, 0], getpos('.')) set mousemodel=extend - " call test_setmouse(5, 3) - call nvim_input_mouse('right', 'press', '', 0, 4, 2) " set mouse position - call getchar() " discard mouse event but keep mouse position + call Ntest_setmouse(5, 3) call feedkeys("A\<RightMouse>\<esc>\<esc>", 'tnix') call assert_equal([0, 28, 2, 0], getpos('.')) set mousemodel& @@ -1410,9 +1443,7 @@ endfunc func Test_edit_rightleft() " Cursor in rightleft mode moves differently - if !exists("+rightleft") - return - endif + CheckFeature rightleft call NewWindow(10, 20) call setline(1, ['abc', 'def', 'ghi']) call cursor(1, 2) @@ -1457,6 +1488,13 @@ func Test_edit_rightleft() \" ihg", \" ~"] call assert_equal(join(expect, "\n"), join(lines, "\n")) + %d _ + " call test_override('redraw_flag', 1) + " call test_override('char_avail', 1) + call feedkeys("a\<C-V>x41", "xt") + redraw! + call assert_equal(repeat(' ', 19) .. 'A', Screenline(1)) + " call test_override('ALL', 0) set norightleft bw! endfunc @@ -1701,40 +1739,6 @@ func Test_edit_illegal_filename() close! endfunc -" Test for inserting text in a line with only spaces ('H' flag in 'cpoptions') -func Test_edit_cpo_H() - throw 'Skipped: Nvim does not support cpoptions flag "H"' - new - call setline(1, ' ') - normal! Ia - call assert_equal(' a', getline(1)) - set cpo+=H - call setline(1, ' ') - normal! Ia - call assert_equal(' a ', getline(1)) - set cpo-=H - close! -endfunc - -" Test for inserting tab in virtual replace mode ('L' flag in 'cpoptions') -func Test_edit_cpo_L() - new - call setline(1, 'abcdefghijklmnopqr') - exe "normal 0gR\<Tab>" - call assert_equal("\<Tab>ijklmnopqr", getline(1)) - set cpo+=L - set list - call setline(1, 'abcdefghijklmnopqr') - exe "normal 0gR\<Tab>" - call assert_equal("\<Tab>cdefghijklmnopqr", getline(1)) - set nolist - call setline(1, 'abcdefghijklmnopqr') - exe "normal 0gR\<Tab>" - call assert_equal("\<Tab>ijklmnopqr", getline(1)) - set cpo-=L - %bw! -endfunc - " Test for editing a directory func Test_edit_is_a_directory() CheckEnglish @@ -1842,7 +1846,8 @@ endfunc " Test for editing a file without read permission func Test_edit_file_no_read_perm() CheckUnix - CheckNotBSD + CheckNotRoot + call writefile(['one', 'two'], 'Xfile') call setfperm('Xfile', '-w-------') new @@ -1868,17 +1873,117 @@ func Test_edit_insertmode_ex_edit() call writefile(lines, 'Xtest_edit_insertmode_ex_edit') let buf = RunVimInTerminal('-S Xtest_edit_insertmode_ex_edit', #{rows: 6}) - call TermWait(buf, 50) - call assert_match('^-- INSERT --\s*$', term_getline(buf, 6)) + call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, 6))}) call term_sendkeys(buf, "\<C-B>\<C-L>") - call TermWait(buf, 50) - call assert_notmatch('^-- INSERT --\s*$', term_getline(buf, 6)) + call WaitForAssert({-> assert_notmatch('^-- INSERT --\s*$', term_getline(buf, 6))}) " clean up call StopVimInTerminal(buf) call delete('Xtest_edit_insertmode_ex_edit') endfunc +" Pressing escape in 'insertmode' should beep +" FIXME: Execute this later, when using valgrind it makes the next test +" Test_edit_insertmode_ex_edit() fail. +func Test_z_edit_insertmode_esc_beeps() + new + " set insertmode + " call assert_beeps("call feedkeys(\"one\<Esc>\", 'xt')") + set insertmode& + " unsupported "CTRL-G l" command should beep in insert mode. + call assert_beeps("normal i\<C-G>l") + bwipe! +endfunc + +" Test for 'hkmap' and 'hkmapp' +func Test_edit_hkmap() + CheckFeature rightleft + if has('win32') && !has('gui') + " Test fails on the MS-Windows terminal version + return + endif + new + + set revins hkmap + let str = 'abcdefghijklmnopqrstuvwxyz' + let str ..= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + let str ..= '`/'',.;' + call feedkeys('i' .. str, 'xt') + let expected = "óõú,.;" + let expected ..= "ZYXWVUTSRQPONMLKJIHGFEDCBA" + let expected ..= "æèñ'äåàãø/ôíîöêìçïéòë÷âáðù" + call assert_equal(expected, getline(1)) + + %d + set revins hkmap hkmapp + let str = 'abcdefghijklmnopqrstuvwxyz' + let str ..= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + call feedkeys('i' .. str, 'xt') + let expected = "õYXWVUTSRQóOïíLKJIHGFEDêBA" + let expected ..= "öòXùåèúæø'ôñðîì÷çéäâóǟãëáà" + call assert_equal(expected, getline(1)) + + set revins& hkmap& hkmapp& + close! +endfunc + +" Test for 'allowrevins' and using CTRL-_ in insert mode +func Test_edit_allowrevins() + CheckFeature rightleft + new + set allowrevins + call feedkeys("iABC\<C-_>DEF\<C-_>GHI", 'xt') + call assert_equal('ABCFEDGHI', getline(1)) + set allowrevins& + close! +endfunc + +" Test for inserting a register in insert mode using CTRL-R +func Test_edit_insert_reg() + throw 'Skipped: use test/functional/legacy/edit_spec.lua' + new + let g:Line = '' + func SaveFirstLine() + let g:Line = Screenline(1) + return 'r' + endfunc + inoremap <expr> <buffer> <F2> SaveFirstLine() + call test_override('redraw_flag', 1) + call test_override('char_avail', 1) + let @r = 'sample' + call feedkeys("a\<C-R>=SaveFirstLine()\<CR>", "xt") + call assert_equal('"', g:Line) + call test_override('ALL', 0) + close! +endfunc + +" When a character is inserted at the last position of the last line in a +" window, the window contents should be scrolled one line up. If the top line +" is part of a fold, then the entire fold should be scrolled up. +func Test_edit_lastline_scroll() + new + let h = winheight(0) + let lines = ['one', 'two', 'three'] + let lines += repeat(['vim'], h - 4) + call setline(1, lines) + call setline(h, repeat('x', winwidth(0) - 1)) + call feedkeys("GAx", 'xt') + redraw! + call assert_equal(h - 1, winline()) + call assert_equal(2, line('w0')) + + " scroll with a fold + 1,2fold + normal gg + call setline(h + 1, repeat('x', winwidth(0) - 1)) + call feedkeys("GAx", 'xt') + redraw! + call assert_equal(h - 1, winline()) + call assert_equal(3, line('w0')) + + close! +endfunc + func Test_edit_browse() " in the GUI this opens a file picker, we only test the terminal behavior CheckNotGui @@ -1904,4 +2009,76 @@ func Test_read_invalid() set encoding=utf-8 endfunc +" Test for the 'revins' option +func Test_edit_revins() + CheckFeature rightleft + new + set revins + exe "normal! ione\ttwo three" + call assert_equal("eerht owt\teno", getline(1)) + call setline(1, "one\ttwo three") + normal! gg$bi a + call assert_equal("one\ttwo a three", getline(1)) + exe "normal! $bi\<BS>\<BS>" + call assert_equal("one\ttwo a ree", getline(1)) + exe "normal! 0wi\<C-W>" + call assert_equal("one\t a ree", getline(1)) + exe "normal! 0wi\<C-U>" + call assert_equal("one\t ", getline(1)) + " newline in insert mode starts at the end of the line + call setline(1, 'one two three') + exe "normal! wi\nfour" + call assert_equal(['one two three', 'ruof'], getline(1, '$')) + set revins& + bw! +endfunc + +" Test for getting the character of the line below after "p" +func Test_edit_put_CTRL_E() + " set encoding=latin1 + new + let @" = '' + sil! norm orggRx + sil! norm pr + call assert_equal(['r', 'r'], getline(1, 2)) + bwipe! + set encoding=utf-8 +endfunc + +" Test toggling of input method. See :help i_CTRL-^ +func Test_edit_CTRL_hat() + CheckFeature xim + + " FIXME: test fails with Motif GUI. + " test also fails when running in the GUI. + CheckFeature gui_gtk + CheckNotGui + + new + + call assert_equal(0, &iminsert) + call feedkeys("i\<C-^>", 'xt') + call assert_equal(2, &iminsert) + call feedkeys("i\<C-^>", 'xt') + call assert_equal(0, &iminsert) + + bwipe! +endfunc + +" Weird long file name was going over the end of NameBuff +func Test_edit_overlong_file_name() + CheckUnix + + file 0000000000000000000000000000 + file %%%%%%%%%%%%%%%%%%%%%%%%%% + file %%%%%% + set readonly + set ls=2 + + redraw! + set noreadonly ls& + bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_escaped_glob.vim b/src/nvim/testdir/test_escaped_glob.vim index 1a4fd8bdab..9f53c76a2c 100644 --- a/src/nvim/testdir/test_escaped_glob.vim +++ b/src/nvim/testdir/test_escaped_glob.vim @@ -1,7 +1,7 @@ " Test whether glob()/globpath() return correct results with certain escaped " characters. -function SetUp() +func SetUp() " consistent sorting of file names set nofileignorecase endfunction diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index eff1376d3c..46482c34a1 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -1,5 +1,8 @@ " Tests for various eval things. +source view_util.vim +source shared.vim + function s:foo() abort try return [] == 0 @@ -17,13 +20,8 @@ func Test_nocatch_restore_silent_emsg() throw 1 catch endtry - echoerr 'wrong' - let c1 = nr2char(screenchar(&lines, 1)) - let c2 = nr2char(screenchar(&lines, 2)) - let c3 = nr2char(screenchar(&lines, 3)) - let c4 = nr2char(screenchar(&lines, 4)) - let c5 = nr2char(screenchar(&lines, 5)) - call assert_equal('wrong', c1 . c2 . c3 . c4 . c5) + echoerr 'wrong again' + call assert_equal('wrong again', ScreenLine(&lines)) endfunc func Test_mkdir_p() @@ -87,22 +85,54 @@ func Test_for_over_null_string() let &enc = save_enc endfunc +func Test_for_invalid_line_count() + let lines =<< trim END + 111111111111111111111111 for line in ['one'] + endfor + END + call writefile(lines, 'XinvalidFor') + " only test that this doesn't crash + call RunVim([], [], '-u NONE -e -s -S XinvalidFor -c qa') + + call delete('XinvalidFor') +endfunc + func Test_readfile_binary() new call setline(1, ['one', 'two', 'three']) setlocal ff=dos - silent write XReadfile - let lines = readfile('XReadfile') + silent write XReadfile_bin + let lines = 'XReadfile_bin'->readfile() call assert_equal(['one', 'two', 'three'], lines) - let lines = readfile('XReadfile', '', 2) + let lines = readfile('XReadfile_bin', '', 2) call assert_equal(['one', 'two'], lines) - let lines = readfile('XReadfile', 'b') + let lines = readfile('XReadfile_bin', 'b') call assert_equal(["one\r", "two\r", "three\r", ""], lines) - let lines = readfile('XReadfile', 'b', 2) + let lines = readfile('XReadfile_bin', 'b', 2) call assert_equal(["one\r", "two\r"], lines) bwipe! - call delete('XReadfile') + call delete('XReadfile_bin') +endfunc + +func Test_readfile_binary_empty() + call writefile([], 'Xempty-file') + " This used to compare uninitialized memory in Vim <= 8.2.4065 + call assert_equal([''], readfile('Xempty-file', 'b')) + call delete('Xempty-file') +endfunc + +func Test_readfile_bom() + call writefile(["\ufeffFOO", "FOO\ufeffBAR"], 'XReadfile_bom') + call assert_equal(['FOO', 'FOOBAR'], readfile('XReadfile_bom')) + call delete('XReadfile_bom') +endfunc + +func Test_readfile_max() + call writefile(range(1, 4), 'XReadfile_max') + call assert_equal(['1', '2'], readfile('XReadfile_max', '', 2)) + call assert_equal(['3', '4'], readfile('XReadfile_max', '', -2)) + call delete('XReadfile_max') endfunc func Test_let_errmsg() @@ -332,6 +362,11 @@ func Test_curly_assignment() unlet g:gvar endfunc +func Test_deep_recursion() + " this was running out of stack + call assert_fails("exe 'if ' .. repeat('(', 1002)", 'E1169: Expression too recursive: ((') +endfunc + " K_SPECIAL in the modified character used be escaped, which causes " double-escaping with feedkeys() or as the return value of an <expr> mapping, " and doesn't match what getchar() returns, diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index 2f734cba26..93100732ed 100644 --- a/src/nvim/testdir/test_ex_mode.vim +++ b/src/nvim/testdir/test_ex_mode.vim @@ -97,7 +97,6 @@ func Test_Ex_substitute() call term_sendkeys(buf, ":vi\<CR>") call WaitForAssert({-> assert_match('foo bar', term_getline(buf, 1))}, 1000) - call term_sendkeys(buf, ":q!\n") call StopVimInTerminal(buf) endfunc diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index dac7a6989d..44bed890f5 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -78,6 +78,14 @@ func Test_file_cmd() call assert_fails('3file', 'E474:') call assert_fails('0,0file', 'E474:') call assert_fails('0file abc', 'E474:') + if !has('win32') + " Change the name of the buffer to the same name + new Xfile1 + file Xfile1 + call assert_equal('Xfile1', @%) + call assert_equal('Xfile1', @#) + bw! + endif endfunc " Test for the :drop command @@ -230,7 +238,6 @@ endfunc " Test for the :language command func Test_language_cmd() CheckNotMSWindows " FIXME: why does this fail on Windows CI? - CheckNotBSD " FIXME: why does this fail on OpenBSD CI? CheckFeature multi_lang call assert_fails('language ctype non_existing_lang', 'E197:') @@ -477,20 +484,23 @@ func Test_winsize_cmd() endfunc " Test for the :redir command +" NOTE: if you run tests as root this will fail. Don't run tests as root! func Test_redir_cmd() call assert_fails('redir @@', 'E475:') call assert_fails('redir abc', 'E475:') + call assert_fails('redir => 1abc', 'E474:') + call assert_fails('redir => a b', 'E488:') + call assert_fails('redir => abc[1]', 'E121:') + let b = 0zFF + call assert_fails('redir =>> b', 'E734:') + unlet b + if has('unix') + " Redirecting to a directory name call mkdir('Xdir') call assert_fails('redir > Xdir', 'E17:') call delete('Xdir', 'd') endif - if !has('bsd') - call writefile([], 'Xfile') - call setfperm('Xfile', 'r--r--r--') - call assert_fails('redir! > Xfile', 'E190:') - call delete('Xfile') - endif " Test for redirecting to a register redir @q> | echon 'clean ' | redir END @@ -503,6 +513,16 @@ func Test_redir_cmd() call assert_equal('blue sky', color) endfunc +func Test_redir_cmd_readonly() + CheckNotRoot + + " Redirecting to a read-only file + call writefile([], 'Xfile') + call setfperm('Xfile', 'r--r--r--') + call assert_fails('redir! > Xfile', 'E190:') + call delete('Xfile') +endfunc + " Test for the :filetype command func Test_filetype_cmd() call assert_fails('filetype abc', 'E475:') @@ -568,10 +588,12 @@ endfunc " Test for the :verbose command func Test_verbose_cmd() - call assert_equal([' verbose=1'], split(execute('verbose set vbs'), "\n")) + set verbose=3 + call assert_match(' verbose=1\n\s*Last set from ', execute('verbose set vbs'), "\n") call assert_equal([' verbose=0'], split(execute('0verbose set vbs'), "\n")) - let l = execute("4verbose set verbose | set verbose") - call assert_equal([' verbose=4', ' verbose=0'], split(l, "\n")) + set verbose=0 + call assert_match(' verbose=4\n\s*Last set from .*\n verbose=0', + \ execute("4verbose set verbose | set verbose")) endfunc " Test for the :delete command and the related abbreviated commands @@ -661,12 +683,24 @@ func Sandbox_tests() if has('unix') call assert_fails('cd `pwd`', 'E48:') endif + " some options cannot be changed in a sandbox + call assert_fails('set exrc', 'E48:') + call assert_fails('set cdpath', 'E48:') + if has('xim') && has('gui_gtk') + call assert_fails('set imstyle', 'E48:') + endif endfunc func Test_sandbox() sandbox call Sandbox_tests() endfunc +func Test_command_not_implemented_E319() + if !has('mzscheme') + call assert_fails('mzscheme', 'E319:') + endif +endfunc + func Test_not_break_expression_register() call setreg('=', '1+1') if 0 diff --git a/src/nvim/testdir/test_exists.vim b/src/nvim/testdir/test_exists.vim index 471c77853d..62c66192ef 100644 --- a/src/nvim/testdir/test_exists.vim +++ b/src/nvim/testdir/test_exists.vim @@ -68,6 +68,10 @@ func Test_exists() " Existing environment variable let $EDITOR_NAME = 'Vim Editor' call assert_equal(1, exists('$EDITOR_NAME')) + if has('unix') + " ${name} environment variables are supported only on Unix-like systems + call assert_equal(1, exists('${VIM}')) + endif " Non-existing environment variable call assert_equal(0, exists('$NON_ENV_VAR')) @@ -323,3 +327,5 @@ endfunc func Test_exists_funcarg() call FuncArg_Tests("arg1", "arg2") endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_exit.vim b/src/nvim/testdir/test_exit.vim index 37be293950..6dbfb7047c 100644 --- a/src/nvim/testdir/test_exit.vim +++ b/src/nvim/testdir/test_exit.vim @@ -117,6 +117,7 @@ func Test_exit_error_reading_input() CheckNotMSWindows " The early exit causes memory not to be freed somehow CheckNotAsan + CheckNotValgrind call writefile([":au VimLeave * call writefile(['l = ' .. v:exiting], 'Xtestout')", ":tabnew", "q:"], 'Xscript', 'b') diff --git a/src/nvim/testdir/test_expand.vim b/src/nvim/testdir/test_expand.vim index ce414e4b11..4f5bb67d21 100644 --- a/src/nvim/testdir/test_expand.vim +++ b/src/nvim/testdir/test_expand.vim @@ -90,14 +90,26 @@ func Test_expandcmd() " Test for expression expansion `= let $FOO= "blue" call assert_equal("blue sky", expandcmd("`=$FOO .. ' sky'`")) + let x = expandcmd("`=axbycz`") + call assert_equal('`=axbycz`', x) + call assert_fails('let x = expandcmd("`=axbycz`", #{errmsg: 1})', 'E121:') + let x = expandcmd("`=axbycz`", #{abc: []}) + call assert_equal('`=axbycz`', x) " Test for env variable with spaces let $FOO= "foo bar baz" call assert_equal("e foo bar baz", expandcmd("e $FOO")) - if has('unix') - " test for using the shell to expand a command argument - call assert_equal('{1..4}', expandcmd('{1..4}')) + if has('unix') && executable('bash') + " test for using the shell to expand a command argument. + " only bash supports the {..} syntax + set shell=bash + let x = expandcmd('{1..4}') + call assert_equal('{1..4}', x) + call assert_fails("let x = expandcmd('{1..4}', #{errmsg: v:true})", 'E77:') + let x = expandcmd('{1..4}', #{error: v:true}) + call assert_equal('{1..4}', x) + set shell& endif unlet $FOO @@ -116,13 +128,21 @@ func Test_source_sfile() :call assert_equal('edit <cword>', expandcmd("edit <cword>")) :call assert_equal('edit <cexpr>', expandcmd("edit <cexpr>")) :call assert_fails('autocmd User MyCmd echo "<sfile>"', 'E498:') + : + :call assert_equal('', expand('<script>')) + :verbose echo expand('<script>') + :call add(v:errors, v:errmsg) + :verbose echo expand('<sfile>') + :call add(v:errors, v:errmsg) :call writefile(v:errors, 'Xresult') :qall! - [SCRIPT] call writefile(lines, 'Xscript') if RunVim([], [], '--clean -s Xscript') - call assert_equal([], readfile('Xresult')) + call assert_equal([ + \ 'E1274: No script file name to substitute for "<script>"', + \ 'E498: no :source file name to substitute for "<sfile>"'], + \ readfile('Xresult')) endif call delete('Xscript') call delete('Xresult') @@ -147,4 +167,63 @@ func Test_expandcmd_shell_nonomatch() call assert_equal('$*', expandcmd('$*')) endfunc +func Test_expand_script_source() + let lines0 =<< trim [SCRIPT] + call extend(g:script_level, [expand('<script>:t')]) + so Xscript1 + func F0() + call extend(g:func_level, [expand('<script>:t')]) + endfunc + + au User * call extend(g:au_level, [expand('<script>:t')]) + [SCRIPT] + + let lines1 =<< trim [SCRIPT] + call extend(g:script_level, [expand('<script>:t')]) + so Xscript2 + func F1() + call extend(g:func_level, [expand('<script>:t')]) + endfunc + + au User * call extend(g:au_level, [expand('<script>:t')]) + [SCRIPT] + + let lines2 =<< trim [SCRIPT] + call extend(g:script_level, [expand('<script>:t')]) + func F2() + call extend(g:func_level, [expand('<script>:t')]) + endfunc + + au User * call extend(g:au_level, [expand('<script>:t')]) + [SCRIPT] + + call writefile(lines0, 'Xscript0') + call writefile(lines1, 'Xscript1') + call writefile(lines2, 'Xscript2') + + " Check the expansion of <script> at different levels. + let g:script_level = [] + let g:func_level = [] + let g:au_level = [] + + so Xscript0 + call F0() + call F1() + call F2() + doautocmd User + + call assert_equal(['Xscript0', 'Xscript1', 'Xscript2'], g:script_level) + call assert_equal(['Xscript0', 'Xscript1', 'Xscript2'], g:func_level) + call assert_equal(['Xscript2', 'Xscript1', 'Xscript0'], g:au_level) + + unlet g:script_level g:func_level + delfunc F0 + delfunc F1 + delfunc F2 + + call delete('Xscript0') + call delete('Xscript1') + call delete('Xscript2') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expand_func.vim b/src/nvim/testdir/test_expand_func.vim index df01d84f19..454d76f0aa 100644 --- a/src/nvim/testdir/test_expand_func.vim +++ b/src/nvim/testdir/test_expand_func.vim @@ -107,10 +107,15 @@ endfunc func Test_expand() new - call assert_equal("", expand('%:S')) + call assert_equal("", expand('%:S')) call assert_equal('3', '<slnum>'->expand()) call assert_equal(['4'], expand('<slnum>', v:false, v:true)) " Don't add any line above this, otherwise <slnum> will change. + call assert_equal("", expand('%')) + set verbose=1 + call assert_equal("", expand('%')) + set verbose=0 + call assert_equal("", expand('%:p')) quit endfunc @@ -134,6 +139,7 @@ func Test_expand_wildignore() call assert_equal('test_expand_func.vim', expand('test_expand_func.vim', 1)) call assert_equal(['test_expand_func.vim'], \ expand('test_expand_func.vim', 1, 1)) + call assert_fails("call expand('*', [])", 'E745:') set wildignore& endfunc diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 5b10e691e5..47f7f5eb0e 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -1,5 +1,7 @@ " Tests for expressions. +source check.vim + func Test_equal() let base = {} func base.method() @@ -44,6 +46,7 @@ func Test_dict() call assert_equal('zero', d[0]) call assert_true(has_key(d, '')) call assert_true(has_key(d, 'a')) + call assert_fails("let i = has_key([], 'a')", 'E715:') let d[''] = 'none' let d['a'] = 'aaa' @@ -62,6 +65,8 @@ func Test_strgetchar() call assert_equal(-1, strgetchar('axb', -1)) call assert_equal(-1, strgetchar('axb', 3)) call assert_equal(-1, strgetchar('', 0)) + call assert_fails("let c=strgetchar([], 1)", 'E730:') + call assert_fails("let c=strgetchar('axb', [])", 'E745:') endfunc func Test_strcharpart() @@ -76,22 +81,27 @@ func Test_strcharpart() call assert_equal('', strcharpart('axb', -2, 2)) call assert_equal('a', strcharpart('axb', -1, 2)) + + call assert_equal('edit', "editor"[-10:3]) +endfunc + +func Test_getreg_empty_list() + call assert_equal('', getreg('x')) + call assert_equal([], getreg('x', 1, 1)) + let x = getreg('x', 1, 1) + let y = x + call add(x, 'foo') + call assert_equal(['foo'], y) + call assert_fails('call getreg([])', 'E730:') endfunc func Test_loop_over_null_list() - let null_list = submatch(1, 1) + let null_list = v:_null_list for i in null_list call assert_report('should not get here') endfor endfunc -func Test_compare_null_dict() - call assert_fails('let x = v:_null_dict[10]') - call assert_equal({}, {}) - call assert_equal(v:_null_dict, v:_null_dict) - call assert_notequal({}, v:_null_dict) -endfunc - func Test_set_reg_null_list() call setreg('x', v:_null_list) endfunc @@ -478,49 +488,6 @@ function Test_max_min_errors() call assert_fails('call min(v:true)', 'min()') endfunc -func Test_substitute_expr() - let g:val = 'XXX' - call assert_equal('XXX', substitute('yyy', 'y*', '\=g:val', '')) - call assert_equal('XXX', substitute('yyy', 'y*', {-> g:val}, '')) - call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', - \ '\=nr2char("0x" . submatch(1))', 'g')) - call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', - \ {-> nr2char("0x" . submatch(1))}, 'g')) - - call assert_equal('231', substitute('123', '\(.\)\(.\)\(.\)', - \ {-> submatch(2) . submatch(3) . submatch(1)}, '')) - - func Recurse() - return substitute('yyy', 'y\(.\)y', {-> submatch(1)}, '') - endfunc - " recursive call works - call assert_equal('-y-x-', substitute('xxx', 'x\(.\)x', {-> '-' . Recurse() . '-' . submatch(1) . '-'}, '')) -endfunc - -func Test_invalid_submatch() - " This was causing invalid memory access in Vim-7.4.2232 and older - call assert_fails("call substitute('x', '.', {-> submatch(10)}, '')", 'E935:') -endfunc - -func Test_substitute_expr_arg() - call assert_equal('123456789-123456789=', substitute('123456789', - \ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', - \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) - - call assert_equal('123456-123456=789', substitute('123456789', - \ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)', - \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) - - call assert_equal('123456789-123456789x=', substitute('123456789', - \ '\(.\)\(.\)\(.*\)', - \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) - - call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:') - call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:') - call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:') - call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:') -endfunc - func Test_function_with_funcref() let s:f = function('type') let s:fref = function(s:f) @@ -529,6 +496,14 @@ func Test_function_with_funcref() call assert_fails("call function('foo()')", 'E475:') call assert_fails("call function('foo()')", 'foo()') + call assert_fails("function('')", 'E129:') + + let Len = {s -> strlen(s)} + call assert_equal(6, Len('foobar')) + let name = string(Len) + " can evaluate "function('<lambda>99')" + call execute('let Ref = ' .. name) + call assert_equal(4, Ref('text')) endfunc func Test_funcref() @@ -547,6 +522,22 @@ func Test_funcref() call assert_fails('echo funcref("{")', 'E475:') let OneByRef = funcref("One", repeat(["foo"], 20)) call assert_fails('let OneByRef = funcref("One", repeat(["foo"], 21))', 'E118:') + call assert_fails('echo function("min") =~ function("min")', 'E694:') +endfunc + +" Test for calling function() and funcref() outside of a Vim script context. +func Test_function_outside_script() + let cleanup =<< trim END + call writefile([execute('messages')], 'Xtest.out') + qall + END + call writefile(cleanup, 'Xverify.vim') + call RunVim([], [], "-c \"echo function('s:abc')\" -S Xverify.vim") + call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0]) + call RunVim([], [], "-c \"echo funcref('s:abc')\" -S Xverify.vim") + call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0]) + call delete('Xtest.out') + call delete('Xverify.vim') endfunc func Test_setmatches() @@ -560,6 +551,7 @@ func Test_setmatches() endif eval set->setmatches() call assert_equal(exp, getmatches()) + call assert_fails('let m = setmatches([], [])', 'E745:') endfunc func Test_empty_concatenate() @@ -586,3 +578,99 @@ func Test_eval_after_if() if 0 | eval SetVal('a') | endif | call SetVal('b') call assert_equal('b', s:val) endfunc + +func Test_divide_by_zero() + " only tests that this doesn't crash, the result is not important + echo 0 / 0 + echo 0 / 0 / -1 +endfunc + +" Test for command-line completion of expressions +func Test_expr_completion() + CheckFeature cmdline_compl + for cmd in [ + \ 'let a = ', + \ 'const a = ', + \ 'if', + \ 'elseif', + \ 'while', + \ 'for', + \ 'echo', + \ 'echon', + \ 'execute', + \ 'echomsg', + \ 'echoerr', + \ 'call', + \ 'return', + \ 'cexpr', + \ 'caddexpr', + \ 'cgetexpr', + \ 'lexpr', + \ 'laddexpr', + \ 'lgetexpr'] + call feedkeys(":" . cmd . " getl\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"' . cmd . ' getline(', getreg(':')) + endfor + + " completion for the expression register + call feedkeys(":\"\<C-R>=float2\t\"\<C-B>\"\<CR>", 'xt') + call assert_equal('"float2nr("', @=) + + " completion for window local variables + let w:wvar1 = 10 + let w:wvar2 = 10 + call feedkeys(":echo w:wvar\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"echo w:wvar1 w:wvar2', @:) + unlet w:wvar1 w:wvar2 + + " completion for tab local variables + let t:tvar1 = 10 + let t:tvar2 = 10 + call feedkeys(":echo t:tvar\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"echo t:tvar1 t:tvar2', @:) + unlet t:tvar1 t:tvar2 + + " completion for variables + let g:tvar1 = 1 + let g:tvar2 = 2 + call feedkeys(":let g:tv\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"let g:tvar1 g:tvar2', @:) + " completion for variables after a || + call feedkeys(":echo 1 || g:tv\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"echo 1 || g:tvar1 g:tvar2', @:) + + " completion for options + call feedkeys(":echo &compat\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"echo &compatible', @:) + call feedkeys(":echo 1 && &compat\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"echo 1 && &compatible', @:) + call feedkeys(":echo &g:equala\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"echo &g:equalalways', @:) + + " completion for string + call feedkeys(":echo \"Hello\\ World\"\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"echo \"Hello\\ World\"\<C-A>", @:) + call feedkeys(":echo 'Hello World'\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"echo 'Hello World'\<C-A>", @:) + + " completion for command after a | + call feedkeys(":echo 'Hello' | cwin\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"echo 'Hello' | cwindow", @:) + + " completion for environment variable + let $X_VIM_TEST_COMPLETE_ENV = 'foo' + call feedkeys(":let $X_VIM_TEST_COMPLETE_E\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('"let $X_VIM_TEST_COMPLETE_ENV', @:) + unlet $X_VIM_TEST_COMPLETE_ENV +endfunc + +" Test for errors in expression evaluation +func Test_expr_eval_error() + call assert_fails("let i = 'abc' . []", 'E730:') + call assert_fails("let l = [] + 10", 'E745:') + call assert_fails("let v = 10 + []", 'E745:') + call assert_fails("let v = 10 / []", 'E745:') + call assert_fails("let v = -{}", 'E728:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim index b77f02afd1..fef0eb732f 100644 --- a/src/nvim/testdir/test_filechanged.vim +++ b/src/nvim/testdir/test_filechanged.vim @@ -140,7 +140,8 @@ func Test_FileChangedShell_edit() endfunc func Test_FileChangedShell_edit_dialog() - throw 'Skipped: requires a UI to be active' + " requires a UI to be active + throw 'Skipped: use test/functional/legacy/filechanged_spec.lua' CheckNotGui CheckUnix " Using low level feedkeys() does not work on MS-Windows. @@ -190,7 +191,8 @@ func Test_FileChangedShell_edit_dialog() endfunc func Test_file_changed_dialog() - throw 'Skipped: requires a UI to be active' + " requires a UI to be active + throw 'Skipped: use test/functional/legacy/filechanged_spec.lua' CheckUnix CheckNotGui au! FileChangedShell diff --git a/src/nvim/testdir/test_fileformat.vim b/src/nvim/testdir/test_fileformat.vim index 81127ea59a..8d727a68c4 100644 --- a/src/nvim/testdir/test_fileformat.vim +++ b/src/nvim/testdir/test_fileformat.vim @@ -31,6 +31,15 @@ func Test_fileformat_autocommand() bw! endfunc +func Test_fileformat_nomodifiable() + new + setlocal nomodifiable + + call assert_fails('set fileformat=latin1', 'E21:') + + bw +endfunc + " Convert the contents of a file into a literal string func s:file2str(fname) let b = readfile(a:fname, 'B') diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index e3a8370661..cddb1349f5 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -88,15 +88,17 @@ let s:filename_checks = { \ 'bindzone': ['named.root', '/bind/db.file', '/named/db.file', 'any/bind/db.file', 'any/named/db.file'], \ 'bitbake': ['file.bb', 'file.bbappend', 'file.bbclass', 'build/conf/local.conf', 'meta/conf/layer.conf', 'build/conf/bbappend.conf', 'meta-layer/conf/distro/foo.conf'], \ 'blank': ['file.bl'], + \ 'blueprint': ['file.blp'], \ 'bsdl': ['file.bsd', 'file.bsdl'], \ 'bst': ['file.bst'], - \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE'], + \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE', 'WORKSPACE.bzlmod'], \ 'bzr': ['bzr_log.any', 'bzr_log.file'], \ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c', 'some-enlightenment/file.cfg'], \ 'cabal': ['file.cabal'], \ 'cabalconfig': ['cabal.config'], \ 'cabalproject': ['cabal.project', 'cabal.project.local'], \ 'calendar': ['calendar', '/.calendar/file', '/share/calendar/any/calendar.file', '/share/calendar/calendar.file', 'any/share/calendar/any/calendar.file', 'any/share/calendar/calendar.file'], + \ 'capnp': ['file.capnp'], \ 'catalog': ['catalog', 'sgml.catalogfile', 'sgml.catalog', 'sgml.catalog-file'], \ 'cdl': ['file.cdl'], \ 'cdrdaoconf': ['/etc/cdrdao.conf', '/etc/defaults/cdrdao', '/etc/default/cdrdao', '.cdrdao', 'any/etc/cdrdao.conf', 'any/etc/default/cdrdao', 'any/etc/defaults/cdrdao'], @@ -107,6 +109,7 @@ let s:filename_checks = { \ 'ch': ['file.chf'], \ 'chaiscript': ['file.chai'], \ 'chaskell': ['file.chs'], + \ 'chatito': ['file.chatito'], \ 'chill': ['file..ch'], \ 'chordpro': ['file.chopro', 'file.crd', 'file.cho', 'file.crdpro', 'file.chordpro'], \ 'cl': ['file.eni'], @@ -120,10 +123,11 @@ let s:filename_checks = { \ 'conaryrecipe': ['file.recipe'], \ 'conf': ['auto.master'], \ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file', 'any/etc/hostname.file'], - \ 'confini': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'mpv.conf'], + \ 'confini': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'mpv.conf', 'any/.aws/config', 'any/.aws/credentials'], \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'], \ 'cook': ['file.cook'], \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'], + \ 'cqlang': ['file.cql'], \ 'crm': ['file.crm'], \ 'crontab': ['crontab', 'crontab.file', '/etc/cron.d/file', 'any/etc/cron.d/file'], \ 'cs': ['file.cs', 'file.csx'], @@ -159,7 +163,7 @@ let s:filename_checks = { \ 'dnsmasq': ['/etc/dnsmasq.conf', '/etc/dnsmasq.d/file', 'any/etc/dnsmasq.conf', 'any/etc/dnsmasq.d/file'], \ 'dockerfile': ['Containerfile', 'Dockerfile', 'dockerfile', 'file.Dockerfile', 'file.dockerfile', 'Dockerfile.debian', 'Containerfile.something'], \ 'dosbatch': ['file.bat'], - \ 'dosini': ['.editorconfig', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'], + \ 'dosini': ['/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'], \ 'dot': ['file.dot', 'file.gv'], \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'], \ 'dtd': ['file.dtd'], @@ -171,6 +175,7 @@ let s:filename_checks = { \ 'dylanlid': ['file.lid'], \ 'ecd': ['file.ecd'], \ 'edif': ['file.edf', 'file.edif', 'file.edo'], + \ 'editorconfig': ['.editorconfig'], \ 'eelixir': ['file.eex', 'file.leex'], \ 'elinks': ['elinks.conf'], \ 'elixir': ['file.ex', 'file.exs', 'mix.lock'], @@ -201,6 +206,7 @@ let s:filename_checks = { \ 'fpcmake': ['file.fpc'], \ 'framescript': ['file.fsl'], \ 'freebasic': ['file.fb'], + \ 'fsh': ['file.fsh'], \ 'fsharp': ['file.fs', 'file.fsi', 'file.fsx'], \ 'fstab': ['fstab', 'mtab'], \ 'fusion': ['file.fusion'], @@ -209,11 +215,14 @@ let s:filename_checks = { \ 'gdmo': ['file.mo', 'file.gdmo'], \ 'gdresource': ['file.tscn', 'file.tres'], \ 'gdscript': ['file.gd'], + \ 'gdshader': ['file.gdshader', 'file.shader'], \ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'], \ 'gemtext': ['file.gmi', 'file.gemini'], \ 'gift': ['file.gift'], + \ 'gitattributes': ['file.git/info/attributes', '.gitattributes', '/.config/git/attributes', '/etc/gitattributes', '/usr/local/etc/gitattributes', 'some.git/info/attributes'], \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'], \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', 'any/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'], + \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'], \ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'], \ 'gitrebase': ['git-rebase-todo'], \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], @@ -224,6 +233,7 @@ let s:filename_checks = { \ 'gnuplot': ['file.gpi', '.gnuplot'], \ 'go': ['file.go'], \ 'gomod': ['go.mod'], + \ 'gosum': ['go.sum'], \ 'gowork': ['go.work'], \ 'gp': ['file.gp', '.gprc'], \ 'gpg': ['/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel', 'any/.gnupg/gpg.conf', 'any/.gnupg/options', 'any/usr/any/gnupg/options.skel'], @@ -235,6 +245,7 @@ let s:filename_checks = { \ 'grub': ['/boot/grub/menu.lst', '/boot/grub/grub.conf', '/etc/grub.conf', 'any/boot/grub/grub.conf', 'any/boot/grub/menu.lst', 'any/etc/grub.conf'], \ 'gsp': ['file.gsp'], \ 'gtkrc': ['.gtkrc', 'gtkrc', '.gtkrc-file', 'gtkrc-file'], + \ 'gyp': ['file.gyp', 'file.gypi'], \ 'hack': ['file.hack', 'file.hackpartial'], \ 'haml': ['file.haml'], \ 'hamster': ['file.hsm'], @@ -250,6 +261,7 @@ let s:filename_checks = { \ 'hex': ['file.hex', 'file.h32'], \ 'hgcommit': ['hg-editor-file.txt'], \ 'hjson': ['file.hjson'], + \ 'hlsplaylist': ['file.m3u', 'file.m3u8'], \ 'hog': ['file.hog', 'snort.conf', 'vision.conf'], \ 'hollywood': ['file.hws'], \ 'hoon': ['file.hoon'], @@ -274,16 +286,18 @@ let s:filename_checks = { \ 'jam': ['file.jpl', 'file.jpr', 'JAM-file.file', 'JAM.file', 'Prl-file.file', 'Prl.file'], \ 'java': ['file.java', 'file.jav'], \ 'javacc': ['file.jj', 'file.jjt'], - \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'], + \ 'javascript': ['file.js', 'file.jsm', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'], \ 'javascript.glimmer': ['file.gjs'], \ 'javascriptreact': ['file.jsx'], \ 'jess': ['file.clp'], \ 'jgraph': ['file.jgr'], + \ 'jq': ['file.jq'], \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'], - \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file'], - \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.babelrc', '.eslintrc', '.prettierrc', '.firebaserc', 'file.slnf'], + \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file', 'org.eclipse.xyz.prefs'], + \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.prettierrc', '.firebaserc', 'file.slnf'], \ 'json5': ['file.json5'], - \ 'jsonc': ['file.jsonc'], + \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json'], + \ 'jsonnet': ['file.jsonnet', 'file.libsonnet'], \ 'jsp': ['file.jsp'], \ 'julia': ['file.jl'], \ 'kconfig': ['Kconfig', 'Kconfig.debug', 'Kconfig.file'], @@ -320,8 +334,9 @@ let s:filename_checks = { \ 'lpc': ['file.lpc', 'file.ulpc'], \ 'lsl': ['file.lsl'], \ 'lss': ['file.lss'], - \ 'lua': ['file.lua', 'file.rockspec', 'file.nse'], + \ 'lua': ['file.lua', 'file.rockspec', 'file.nse', '.luacheckrc'], \ 'lynx': ['lynx.cfg'], + \ 'lyrics': ['file.lrc'], \ 'm3build': ['m3makefile', 'm3overrides'], \ 'm3quake': ['file.quake', 'cm3.cfg'], \ 'm4': ['file.at'], @@ -340,6 +355,7 @@ let s:filename_checks = { \ 'maxima': ['file.demo', 'file.dmt', 'file.dm1', 'file.dm2', 'file.dm3', \ 'file.wxm', 'maxima-init.mac'], \ 'mel': ['file.mel'], + \ 'mermaid': ['file.mmd', 'file.mmdc', 'file.mermaid'], \ 'meson': ['meson.build', 'meson_options.txt'], \ 'messages': ['/log/auth', '/log/cron', '/log/daemon', '/log/debug', '/log/kern', '/log/lpr', '/log/mail', '/log/messages', '/log/news/news', '/log/syslog', '/log/user', \ '/log/auth.log', '/log/cron.log', '/log/daemon.log', '/log/debug.log', '/log/kern.log', '/log/lpr.log', '/log/mail.log', '/log/messages.log', '/log/news/news.log', '/log/syslog.log', '/log/user.log', @@ -357,7 +373,7 @@ let s:filename_checks = { \ 'mmp': ['file.mmp'], \ 'modconf': ['/etc/modules.conf', '/etc/modules', '/etc/conf.modules', '/etc/modprobe.file', 'any/etc/conf.modules', 'any/etc/modprobe.file', 'any/etc/modules', 'any/etc/modules.conf'], \ 'modula2': ['file.m2', 'file.mi'], - \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig'], + \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig', 'file.lm3'], \ 'monk': ['file.isc', 'file.monk', 'file.ssc', 'file.tsc'], \ 'moo': ['file.moo'], \ 'moonscript': ['file.moon'], @@ -378,12 +394,14 @@ let s:filename_checks = { \ 'neomuttrc': ['Neomuttrc', '.neomuttrc', '.neomuttrc-file', '/.neomutt/neomuttrc', '/.neomutt/neomuttrc-file', 'Neomuttrc', 'Neomuttrc-file', 'any/.neomutt/neomuttrc', 'any/.neomutt/neomuttrc-file', 'neomuttrc', 'neomuttrc-file'], \ 'netrc': ['.netrc'], \ 'nginx': ['file.nginx', 'nginxfile.conf', 'filenginx.conf', 'any/etc/nginx/file', 'any/usr/local/nginx/conf/file', 'any/nginx/file.conf'], + \ 'nim': ['file.nim', 'file.nims', 'file.nimble'], \ 'ninja': ['file.ninja'], \ 'nix': ['file.nix'], \ 'nqc': ['file.nqc'], \ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'], \ 'nsis': ['file.nsi', 'file.nsh'], \ 'obj': ['file.obj'], + \ 'obse': ['file.obl', 'file.obse', 'file.oblivion', 'file.obscript'], \ 'ocaml': ['file.ml', 'file.mli', 'file.mll', 'file.mly', '.ocamlinit', 'file.mlt', 'file.mlp', 'file.mlip', 'file.mli.cppo', 'file.ml.cppo'], \ 'occam': ['file.occ'], \ 'octave': ['octaverc', '.octaverc', 'octave.conf'], @@ -391,6 +409,7 @@ let s:filename_checks = { \ 'opam': ['opam', 'file.opam', 'file.opam.template'], \ 'openroad': ['file.or'], \ 'openscad': ['file.scad'], + \ 'openvpn': ['file.ovpn', '/etc/openvpn/client/client.conf', '/usr/share/openvpn/examples/server.conf'], \ 'opl': ['file.OPL', 'file.OPl', 'file.OpL', 'file.Opl', 'file.oPL', 'file.oPl', 'file.opL', 'file.opl'], \ 'ora': ['file.ora'], \ 'org': ['file.org', 'file.org_archive'], @@ -403,10 +422,10 @@ let s:filename_checks = { \ 'pccts': ['file.g'], \ 'pcmk': ['file.pcmk'], \ 'pdf': ['file.pdf'], - \ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc'], + \ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc', '.latexmkrc', 'latexmkrc'], \ 'pf': ['pf.conf'], - \ 'pfmain': ['main.cf'], - \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp', 'file.phpt'], + \ 'pfmain': ['main.cf', 'main.cf.proto'], + \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp', 'file.phpt', 'file.theme'], \ 'pike': ['file.pike', 'file.pmod'], \ 'pilrc': ['file.rcp'], \ 'pine': ['.pinerc', 'pinerc', '.pinercex', 'pinercex'], @@ -417,6 +436,7 @@ let s:filename_checks = { \ 'plsql': ['file.pls', 'file.plsql'], \ 'po': ['file.po', 'file.pot'], \ 'pod': ['file.pod'], + \ 'poefilter': ['file.filter'], \ 'poke': ['file.pk'], \ 'postscr': ['file.ps', 'file.pfa', 'file.afm', 'file.eps', 'file.epsf', 'file.epsi', 'file.ai'], \ 'pov': ['file.pov'], @@ -443,7 +463,7 @@ let s:filename_checks = { \ 'ql': ['file.ql', 'file.qll'], \ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'], \ 'quarto': ['file.qmd'], - \ 'r': ['file.r'], + \ 'r': ['file.r', '.Rprofile', 'Rprofile', 'Rprofile.site'], \ 'radiance': ['file.rad', 'file.mat'], \ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'], \ 'raml': ['file.raml'], @@ -507,9 +527,11 @@ let s:filename_checks = { \ 'slrnrc': ['.slrnrc'], \ 'slrnsc': ['file.score'], \ 'sm': ['sendmail.cf'], + \ 'smali': ['file.smali'], \ 'smarty': ['file.tpl'], \ 'smcl': ['file.hlp', 'file.ihlp', 'file.smcl'], \ 'smith': ['file.smt', 'file.smith'], + \ 'smithy': ['file.smithy'], \ 'sml': ['file.sml'], \ 'snobol4': ['file.sno', 'file.spt'], \ 'solidity': ['file.sol'], @@ -525,13 +547,15 @@ let s:filename_checks = { \ 'squid': ['squid.conf'], \ 'squirrel': ['file.nut'], \ 'srec': ['file.s19', 'file.s28', 'file.s37', 'file.mot', 'file.srec'], + \ 'srt': ['file.srt'], + \ 'ssa': ['file.ass', 'file.ssa'], \ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf', 'any/.ssh/config', 'any/.ssh/file.conf'], \ 'sshdconfig': ['sshd_config', '/etc/ssh/sshd_config.d/file.conf', 'any/etc/ssh/sshd_config.d/file.conf'], \ 'st': ['file.st'], \ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'], \ 'stp': ['file.stp'], \ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp', '/etc/sudoers', 'any/etc/sudoers.d/file'], - \ 'supercollider': ['file.quark'], + \ 'supercollider': ['file.quark'], \ 'surface': ['file.sface'], \ 'svelte': ['file.svelte'], \ 'svg': ['file.svg'], @@ -557,6 +581,7 @@ let s:filename_checks = { \ 'texmf': ['texmf.cnf'], \ 'text': ['file.text', 'file.txt', 'README', 'LICENSE', 'COPYING', 'AUTHORS', '/usr/share/doc/bash-completion/AUTHORS', '/etc/apt/apt.conf.d/README', '/etc/Muttrc.d/README'], \ 'tf': ['file.tf', '.tfrc', 'tfrc'], + \ 'thrift': ['file.thrift'], \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'], \ 'tilde': ['file.t.html'], \ 'tla': ['file.tla'], @@ -572,6 +597,7 @@ let s:filename_checks = { \ 'tssop': ['file.tssop'], \ 'tsv': ['file.tsv'], \ 'twig': ['file.twig'], + \ 'typescript': ['file.mts', 'file.cts'], \ 'typescript.glimmer': ['file.gts'], \ 'typescriptreact': ['file.tsx'], \ 'uc': ['file.uc'], @@ -588,11 +614,16 @@ let s:filename_checks = { \ 'usw2kagtlog': ['usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'], \ 'vala': ['file.vala'], \ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'], + \ 'vdf': ['file.vdf'], + \ 'vdmpp': ['file.vpp', 'file.vdmpp'], + \ 'vdmrt': ['file.vdmrt'], + \ 'vdmsl': ['file.vdm', 'file.vdmsl'], \ 'vera': ['file.vr', 'file.vri', 'file.vrh'], \ 'verilog': ['file.v'], \ 'verilogams': ['file.va', 'file.vams'], \ 'vgrindefs': ['vgrindefs'], \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123', 'file.vho', 'some.vhdl_1', 'some.vhdl_1-file'], + \ 'vhs': ['file.tape'], \ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc', 'some-vimrc', 'some-vimrc-file', 'vimrc', 'vimrc-file'], \ 'viminfo': ['.viminfo', '_viminfo'], \ 'vmasm': ['file.mar'], @@ -601,6 +632,7 @@ let s:filename_checks = { \ 'vroom': ['file.vroom'], \ 'vue': ['file.vue'], \ 'wast': ['file.wast', 'file.wat'], + \ 'wdl': ['file.wdl'], \ 'webmacro': ['file.wm'], \ 'wget': ['.wgetrc', 'wgetrc'], \ 'wget2': ['.wget2rc', 'wget2rc'], @@ -623,12 +655,13 @@ let s:filename_checks = { \ 'xsd': ['file.xsd'], \ 'xslt': ['file.xsl', 'file.xslt'], \ 'yacc': ['file.yy', 'file.yxx', 'file.y++'], - \ 'yaml': ['file.yaml', 'file.yml'], + \ 'yaml': ['file.yaml', 'file.yml', '.clang-format', '.clang-tidy'], \ 'yang': ['file.yang'], \ 'z8a': ['file.z8a'], \ 'zig': ['file.zig'], \ 'zimbu': ['file.zu'], \ 'zimbutempl': ['file.zut'], + \ 'zir': ['file.zir'], \ 'zsh': ['.zprofile', '/etc/zprofile', '.zfbfmarks', 'file.zsh', '.zcompdump', '.zlogin', '.zlogout', '.zshenv', '.zshrc', '.zcompdump-file', '.zlog', '.zlog-file', '.zsh', '.zsh-file', 'any/etc/zprofile', 'zlog', 'zlog-file', 'zsh', 'zsh-file'], \ \ 'help': [$VIMRUNTIME . '/doc/help.txt'], @@ -682,6 +715,13 @@ let s:script_checks = { \ ['__libc_start_main and something']], \ 'clojure': [['#!/path/clojure']], \ 'scala': [['#!/path/scala']], + \ 'sh': [['#!/path/sh'], + \ ['#!/path/bash'], + \ ['#!/path/bash2'], + \ ['#!/path/dash'], + \ ['#!/path/ksh'], + \ ['#!/path/ksh93']], + \ 'csh': [['#!/path/csh']], \ 'tcsh': [['#!/path/tcsh']], \ 'zsh': [['#!/path/zsh']], \ 'tcl': [['#!/path/tclsh'], @@ -918,7 +958,9 @@ func Test_d_file() call assert_equal('d', &filetype) bwipe! + " clean up filetype off + call delete('Xfile.d') endfunc func Test_dat_file() @@ -1346,7 +1388,7 @@ func Test_mod_file() unlet g:filetype_mod bwipe! - " RAPID header start with a line containing only "%%%", + " RAPID header start with a line containing only "%%%", " but is not always present. call writefile(['%%%'], 'modfile.mod') split modfile.mod @@ -1362,7 +1404,7 @@ func Test_mod_file() bwipe! call delete('modfile.Mod') - " RAPID is not case sensitive, embedded spaces, sysmodule, + " RAPID is not case sensitive, embedded spaces, sysmodule, " file starts with empty line(s). call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'modfile.MOD') split modfile.MOD @@ -1490,7 +1532,7 @@ func Test_prg_file() unlet g:filetype_prg bwipe! - " RAPID header start with a line containing only "%%%", + " RAPID header start with a line containing only "%%%", " but is not always present. call writefile(['%%%'], 'prgfile.prg') split prgfile.prg @@ -1506,7 +1548,7 @@ func Test_prg_file() bwipe! call delete('prgfile.Prg') - " RAPID is not case sensitive, embedded spaces, sysmodule, + " RAPID is not case sensitive, embedded spaces, sysmodule, " file starts with empty line(s). call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'prgfile.PRG') split prgfile.PRG @@ -1521,13 +1563,6 @@ endfunc func Test_sc_file() filetype on - " SC file methods are defined 'Class : Method' - call writefile(['SCNvimDocRenderer : SCDocHTMLRenderer {'], 'srcfile.sc') - split srcfile.sc - call assert_equal('supercollider', &filetype) - bwipe! - call delete('srcfile.sc') - " SC classes are defined with '+ Class {}' call writefile(['+ SCNvim {', '*methodArgs {|method|'], 'srcfile.sc') split srcfile.sc @@ -1617,7 +1652,7 @@ func Test_sys_file() unlet g:filetype_sys bwipe! - " RAPID header start with a line containing only "%%%", + " RAPID header start with a line containing only "%%%", " but is not always present. call writefile(['%%%'], 'sysfile.sys') split sysfile.sys @@ -1633,7 +1668,7 @@ func Test_sys_file() bwipe! call delete('sysfile.Sys') - " RAPID is not case sensitive, embedded spaces, sysmodule, + " RAPID is not case sensitive, embedded spaces, sysmodule, " file starts with empty line(s). call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'sysfile.SYS') split sysfile.SYS @@ -1647,17 +1682,45 @@ endfunc func Test_tex_file() filetype on - " only tests one case, should do more + call writefile(['%& pdflatex'], 'Xfile.tex') + split Xfile.tex + call assert_equal('tex', &filetype) + bwipe + + call writefile(['\newcommand{\test}{some text}'], 'Xfile.tex') + split Xfile.tex + call assert_equal('tex', &filetype) + bwipe + + " tex_flavor is unset + call writefile(['%& plain'], 'Xfile.tex') + split Xfile.tex + call assert_equal('plaintex', &filetype) + bwipe + + let g:tex_flavor = 'plain' + call writefile(['just some text'], 'Xfile.tex') + split Xfile.tex + call assert_equal('plaintex', &filetype) + bwipe + let lines =<< trim END - % This is a sentence. + % This is a comment. - This is a sentence. + \usemodule[translate] END - call writefile(lines, "Xfile.tex") + call writefile(lines, 'Xfile.tex') split Xfile.tex - call assert_equal('plaintex', &filetype) + call assert_equal('context', &filetype) bwipe + let g:tex_flavor = 'context' + call writefile(['just some text'], 'Xfile.tex') + split Xfile.tex + call assert_equal('context', &filetype) + bwipe + unlet g:tex_flavor + call delete('Xfile.tex') filetype off endfunc @@ -1748,6 +1811,11 @@ func Test_cls_file() call assert_equal('tex', &filetype) bwipe! + call writefile(['\NeedsTeXFormat{LaTeX2e}'], 'Xfile.cls') + split Xfile.cls + call assert_equal('tex', &filetype) + bwipe! + " Rexx call writefile(['# rexx'], 'Xfile.cls') @@ -1820,6 +1888,44 @@ func Test_sig_file() filetype off endfunc +" Test dist#ft#FTsil() +func Test_sil_file() + filetype on + + split Xfile.sil + call assert_equal('sil', &filetype) + bwipe! + + let lines =<< trim END + // valid + let protoErasedPathA = \ABCProtocol.a + + // also valid + let protoErasedPathA = + \ABCProtocol.a + END + call writefile(lines, 'Xfile.sil') + + split Xfile.sil + call assert_equal('sil', &filetype) + bwipe! + + " SILE + + call writefile(['% some comment'], 'Xfile.sil') + split Xfile.sil + call assert_equal('sile', &filetype) + bwipe! + + call writefile(['\begin[papersize=a6]{document}foo\end{document}'], 'Xfile.sil') + split Xfile.sil + call assert_equal('sile', &filetype) + bwipe! + + call delete('Xfile.sil') + filetype off +endfunc + func Test_inc_file() filetype on @@ -1899,4 +2005,36 @@ func Test_inc_file() filetype off endfunc +func Test_lsl_file() + filetype on + + call writefile(['looks like Linden Scripting Language'], 'Xfile.lsl') + split Xfile.lsl + call assert_equal('lsl', &filetype) + bwipe! + + " Test dist#ft#FTlsl() + + let g:filetype_lsl = 'larch' + split Xfile.lsl + call assert_equal('larch', &filetype) + bwipe! + unlet g:filetype_lsl + + " Larch Shared Language + + call writefile(['% larch comment'], 'Xfile.lsl') + split Xfile.lsl + call assert_equal('larch', &filetype) + bwipe! + + call writefile(['foo: trait'], 'Xfile.lsl') + split Xfile.lsl + call assert_equal('larch', &filetype) + bwipe! + + call delete('Xfile.lsl') + filetype off +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim index 1cd3a2287b..c75177ea39 100644 --- a/src/nvim/testdir/test_filter_map.vim +++ b/src/nvim/testdir/test_filter_map.vim @@ -86,6 +86,13 @@ func Test_map_filter_fails() call assert_fails('call filter([1], "42 +")', 'E15:') call assert_fails("let l = map('abc', '\"> \" . v:val')", 'E896:') call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:') + call assert_fails("let l = filter([1, 2, 3], '{}')", 'E728:') + call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:') + call assert_fails("let l = filter([1, 2], {})", 'E731:') + call assert_equal(v:_null_list, filter(v:_null_list, 0)) + call assert_equal(v:_null_dict, filter(v:_null_dict, 0)) + call assert_equal(v:_null_list, map(v:_null_list, '"> " .. v:val')) + call assert_equal(v:_null_dict, map(v:_null_dict, '"> " .. v:val')) endfunc func Test_map_and_modify() diff --git a/src/nvim/testdir/test_fixeol.vim b/src/nvim/testdir/test_fixeol.vim index 32cb059e26..41d47d6a06 100644 --- a/src/nvim/testdir/test_fixeol.vim +++ b/src/nvim/testdir/test_fixeol.vim @@ -1,16 +1,17 @@ -" Tests for 'fixeol' and 'eol' +" Tests for 'fixeol', 'eof' and 'eol' + func Test_fixeol() " first write two test files – with and without trailing EOL " use Unix fileformat for consistency set ff=unix enew! - call setline('.', 'with eol') + call setline('.', 'with eol or eof') w! XXEol enew! - set noeol nofixeol - call setline('.', 'without eol') + set noeof noeol nofixeol + call setline('.', 'without eol or eof') w! XXNoEol - set eol fixeol + set eol eof fixeol bwipe XXEol XXNoEol " try editing files with 'fixeol' disabled @@ -33,16 +34,85 @@ func Test_fixeol() w >>XXTestEol w >>XXTestNoEol - call assert_equal(['with eol', 'END'], readfile('XXEol')) - call assert_equal(['without eolEND'], readfile('XXNoEol')) - call assert_equal(['with eol', 'stays eol', 'END'], readfile('XXTestEol')) - call assert_equal(['without eol', 'stays withoutEND'], + call assert_equal(['with eol or eof', 'END'], readfile('XXEol')) + call assert_equal(['without eol or eofEND'], readfile('XXNoEol')) + call assert_equal(['with eol or eof', 'stays eol', 'END'], readfile('XXTestEol')) + call assert_equal(['without eol or eof', 'stays withoutEND'], \ readfile('XXTestNoEol')) call delete('XXEol') call delete('XXNoEol') call delete('XXTestEol') call delete('XXTestNoEol') - set ff& fixeol& eol& + set ff& fixeol& eof& eol& + enew! +endfunc + +func Test_eof() + let data = 0z68656c6c6f.0d0a.776f726c64 " "hello\r\nworld" + + " 1. Eol, Eof + " read + call writefile(data + 0z0d0a.1a, 'XXEolEof') + e! XXEolEof + call assert_equal(['hello', 'world'], getline(1, 2)) + call assert_equal([1, 1], [&eol, &eof]) + " write + set fixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXEolEof')) + set nofixeol + w! + call assert_equal(data + 0z0d0a.1a, readblob('XXEolEof')) + + " 2. NoEol, Eof + " read + call writefile(data + 0z1a, 'XXNoEolEof') + e! XXNoEolEof + call assert_equal(['hello', 'world'], getline(1, 2)) + call assert_equal([0, 1], [&eol, &eof]) + " write + set fixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXNoEolEof')) + set nofixeol + w! + call assert_equal(data + 0z1a, readblob('XXNoEolEof')) + + " 3. Eol, NoEof + " read + call writefile(data + 0z0d0a, 'XXEolNoEof') + e! XXEolNoEof + call assert_equal(['hello', 'world'], getline(1, 2)) + call assert_equal([1, 0], [&eol, &eof]) + " write + set fixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXEolNoEof')) + set nofixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXEolNoEof')) + + " 4. NoEol, NoEof + " read + call writefile(data, 'XXNoEolNoEof') + e! XXNoEolNoEof + call assert_equal(['hello', 'world'], getline(1, 2)) + call assert_equal([0, 0], [&eol, &eof]) + " write + set fixeol + w! + call assert_equal(data + 0z0d0a, readblob('XXNoEolNoEof')) + set nofixeol + w! + call assert_equal(data, readblob('XXNoEolNoEof')) + + call delete('XXEolEof') + call delete('XXNoEolEof') + call delete('XXEolNoEof') + call delete('XXNoEolNoEof') + set ff& fixeol& eof& eol& enew! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_fnamemodify.vim b/src/nvim/testdir/test_fnamemodify.vim index 5ae2a5ee17..258a2093bd 100644 --- a/src/nvim/testdir/test_fnamemodify.vim +++ b/src/nvim/testdir/test_fnamemodify.vim @@ -11,6 +11,7 @@ func Test_fnamemodify() call assert_equal('/', fnamemodify('.', ':p')[-1:]) call assert_equal('r', fnamemodify('.', ':p:h')[-1:]) call assert_equal('t', fnamemodify('test.out', ':p')[-1:]) + call assert_equal($HOME .. "/foo" , fnamemodify('~/foo', ':p')) call assert_equal('test.out', fnamemodify('test.out', ':.')) call assert_equal('a', fnamemodify('../testdir/a', ':.')) call assert_equal('~/testdir/test.out', fnamemodify('test.out', ':~')) @@ -95,4 +96,9 @@ func Test_fnamemodify_er() call assert_equal('', fnamemodify(v:_null_string, v:_null_string)) endfunc +func Test_fnamemodify_fail() + call assert_fails('call fnamemodify({}, ":p")', 'E731:') + call assert_fails('call fnamemodify("x", {})', 'E731:') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 327f0f73f2..19415286ad 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -1,5 +1,6 @@ " Test for folding +source check.vim source view_util.vim source screendump.vim @@ -71,6 +72,54 @@ func Test_address_fold() quit! endfunc +func Test_address_offsets() + " check the help for :range-closed-fold + enew + call setline(1, [ + \ '1 one', + \ '2 two', + \ '3 three', + \ '4 four FOLDED', + \ '5 five FOLDED', + \ '6 six', + \ '7 seven', + \ '8 eight', + \]) + set foldmethod=manual + normal 4Gvjzf + 3,4+2yank + call assert_equal([ + \ '3 three', + \ '4 four FOLDED', + \ '5 five FOLDED', + \ '6 six', + \ '7 seven', + \ ], getreg(0,1,1)) + + enew! + call setline(1, [ + \ '1 one', + \ '2 two', + \ '3 three FOLDED', + \ '4 four FOLDED', + \ '5 five FOLDED', + \ '6 six FOLDED', + \ '7 seven', + \ '8 eight', + \]) + normal 3Gv3jzf + 2,4-1yank + call assert_equal([ + \ '2 two', + \ '3 three FOLDED', + \ '4 four FOLDED', + \ '5 five FOLDED', + \ '6 six FOLDED', + \ ], getreg(0,1,1)) + + bwipe! +endfunc + func Test_indent_fold() new call setline(1, ['', 'a', ' b', ' c']) @@ -93,10 +142,44 @@ func Test_indent_fold2() bw! endfunc +" Test for fold indent with indents greater than 'foldnestmax' +func Test_indent_fold_max() + new + setlocal foldmethod=indent + setlocal shiftwidth=2 + " 'foldnestmax' default value is 20 + call setline(1, "\t\t\t\t\t\ta") + call assert_equal(20, foldlevel(1)) + setlocal foldnestmax=10 + call assert_equal(10, foldlevel(1)) + setlocal foldnestmax=-1 + call assert_equal(0, foldlevel(1)) + bw! +endfunc + +func Test_indent_fold_tabstop() + call setline(1, ['0', ' 1', ' 1', "\t2", "\t2"]) + setlocal shiftwidth=4 + setlocal foldcolumn=1 + setlocal foldlevel=2 + setlocal foldmethod=indent + redraw + call assert_equal('2 2', ScreenLines(5, 10)[0]) + vsplit + windo diffthis + botright new + " This 'tabstop' value should not be used for folding in other buffers. + setlocal tabstop=4 + diffoff! + redraw + call assert_equal('2 2', ScreenLines(5, 10)[0]) + + bwipe! + bwipe! +endfunc + func Test_manual_fold_with_filter() - if !executable('cat') - return - endif + CheckExecutable cat for type in ['manual', 'marker'] exe 'set foldmethod=' . type new @@ -418,27 +501,6 @@ func Test_move_folds_around_indent() bw! endfunc -" test for patch 7.3.637 -" Cannot catch the error caused by a foldopen when there is no fold. -func Test_foldopen_exception() - enew! - let a = 'No error caught' - try - foldopen - catch - let a = matchstr(v:exception,'^[^ ]*') - endtry - call assert_equal('Vim(foldopen):E490:', a) - - let a = 'No error caught' - try - foobar - catch - let a = matchstr(v:exception,'^[^ ]*') - endtry - call assert_match('E492:', a) -endfunc - func Test_folddoopen_folddoclosed() new call setline(1, range(1, 9)) @@ -492,11 +554,24 @@ func Test_fold_error() bw! endfunc +func Test_foldtext_recursive() + new + call setline(1, ['{{{', 'some text', '}}}']) + setlocal foldenable foldmethod=marker foldtext=foldtextresult(v\:foldstart) + " This was crashing because of endless recursion. + 2foldclose + redraw + call assert_equal(1, foldlevel(2)) + call assert_equal(1, foldclosed(2)) + call assert_equal(3, foldclosedend(2)) + bwipe! +endfunc + " Various fold related tests " Basic test if a fold can be created, opened, moving to the end and closed func Test_fold_manual() - enew! + new set fdm=manual let content = ['1 aa', '2 bb', '3 cc'] @@ -511,13 +586,67 @@ func Test_fold_manual() normal zc call assert_equal('1 aa', getline(foldclosed('.'))) + " Create a fold inside a closed fold after setting 'foldlevel' + %d _ + call setline(1, range(1, 5)) + 1,5fold + normal zR + 2,4fold + set foldlevel=1 + 3fold + call assert_equal([1, 3, 3, 3, 1], map(range(1, 5), {->foldlevel(v:val)})) + set foldlevel& + + " Create overlapping folds (at the start and at the end) + normal zE + 2,3fold + normal zR + 3,4fold + call assert_equal([0, 2, 2, 1, 0], map(range(1, 5), {->foldlevel(v:val)})) + normal zE + 3,4fold + normal zR + 2,3fold + call assert_equal([0, 1, 2, 2, 0], map(range(1, 5), {->foldlevel(v:val)})) + + " Create a nested fold across two non-adjoining folds + %d _ + call setline(1, range(1, 7)) + 1,2fold + normal zR + 4,5fold + normal zR + 6,7fold + normal zR + 1,5fold + call assert_equal([2, 2, 1, 2, 2, 1, 1], + \ map(range(1, 7), {->foldlevel(v:val)})) + + " A newly created nested fold should be closed + %d _ + call setline(1, range(1, 6)) + 1,6fold + normal zR + 3,4fold + normal zR + 2,5fold + call assert_equal([1, 2, 3, 3, 2, 1], map(range(1, 6), {->foldlevel(v:val)})) + call assert_equal(2, foldclosed(4)) + call assert_equal(5, foldclosedend(4)) + + " Test zO, zC and zA on a line with no folds. + normal zE + call assert_fails('normal zO', 'E490:') + call assert_fails('normal zC', 'E490:') + call assert_fails('normal zA', 'E490:') + set fdm& - enew! + bw! endfunc " test folding with markers. func Test_fold_marker() - enew! + new set fdm=marker fdl=1 fdc=3 let content = ['4 dd {{{', '5 ee {{{ }}}', '6 ff }}}'] @@ -531,13 +660,22 @@ func Test_fold_marker() normal kYpj call assert_equal(0, foldlevel('.')) + " Use only closing fold marker (without and with a count) + set fdl& + %d _ + call setline(1, ['one }}}', 'two']) + call assert_equal([0, 0], [foldlevel(1), foldlevel(2)]) + %d _ + call setline(1, ['one }}}4', 'two']) + call assert_equal([4, 3], [foldlevel(1), foldlevel(2)]) + set fdm& fdl& fdc& - enew! + bw! endfunc " test create fold markers with C filetype func Test_fold_create_marker_in_C() - enew! + bw! set fdm=marker fdl=9 set filetype=c @@ -562,12 +700,12 @@ func Test_fold_create_marker_in_C() endfor set fdm& fdl& - enew! + bw! endfunc " test folding with indent func Test_fold_indent() - enew! + new set fdm=indent sw=2 let content = ['1 aa', '2 bb', '3 cc'] @@ -579,16 +717,14 @@ func Test_fold_indent() call assert_equal(1, foldlevel('.')) set fdm& sw& - enew! + bw! endfunc " test syntax folding func Test_fold_syntax() - if !has('syntax') - return - endif + CheckFeature syntax - enew! + new set fdm=syntax fdl=0 syn region Hup start="dd" end="ii" fold contains=Fd1,Fd2,Fd3 @@ -612,7 +748,7 @@ func Test_fold_syntax() syn clear Fd1 Fd2 Fd3 Hup set fdm& fdl& - enew! + bw! endfunc func Flvl() @@ -631,7 +767,7 @@ endfun " test expression folding func Test_fold_expr() - enew! + new set fdm=expr fde=Flvl() let content = ['1 aa', @@ -659,14 +795,14 @@ func Test_fold_expr() call assert_equal(0, foldlevel('.')) set fdm& fde& - enew! + bw! endfunc " Bug with fdm=indent and moving folds " Moving a fold a few times, messes up the folds below the moved fold. " Fixed by 7.4.700 func Test_fold_move() - enew! + new set fdm=indent sw=2 fdl=0 let content = ['', '', 'Line1', ' Line2', ' Line3', @@ -686,24 +822,33 @@ func Test_fold_move() call assert_equal('+-- 2 lines: Line8', 10->foldtextresult()) set fdm& sw& fdl& - enew! + bw! endfunc -func Test_foldtext_recursive() +" test for patch 7.3.637 +" Cannot catch the error caused by a foldopen when there is no fold. +func Test_foldopen_exception() new - call setline(1, ['{{{', 'some text', '}}}']) - setlocal foldenable foldmethod=marker foldtext=foldtextresult(v\:foldstart) - " This was crashing because of endless recursion. - 2foldclose - redraw - call assert_equal(1, foldlevel(2)) - call assert_equal(1, foldclosed(2)) - call assert_equal(3, foldclosedend(2)) - bwipe! + let a = 'No error caught' + try + foldopen + catch + let a = matchstr(v:exception,'^[^ ]*') + endtry + call assert_equal('Vim(foldopen):E490:', a) + + let a = 'No error caught' + try + foobar + catch + let a = matchstr(v:exception,'^[^ ]*') + endtry + call assert_match('E492:', a) + bw! endfunc func Test_fold_last_line_with_pagedown() - enew! + new set fdm=manual let expect = '+-- 11 lines: 9---' @@ -723,13 +868,11 @@ func Test_fold_last_line_with_pagedown() call assert_equal(expect, ScreenLines(1, len(expect))[0]) set fdm& - enew! + bw! endfunc func Test_folds_with_rnu() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump call writefile([ \ 'set fdm=marker rnu foldcolumn=2', @@ -816,6 +959,55 @@ func Test_fold_delete_first_line() set foldmethod& endfunc +" Add a test for deleting the outer fold of a nested fold and promoting the +" inner folds to one level up with already a fold at that level following the +" nested fold. +func Test_fold_delete_recursive_fold() + new + call setline(1, range(1, 7)) + 2,3fold + normal zR + 4,5fold + normal zR + 1,5fold + normal zR + 6,7fold + normal zR + normal 1Gzd + normal 1Gzj + call assert_equal(2, line('.')) + normal zj + call assert_equal(4, line('.')) + normal zj + call assert_equal(6, line('.')) + bw! +endfunc + +" Test for errors in 'foldexpr' +func Test_fold_expr_error() + new + call setline(1, ['one', 'two', 'three']) + " In a window with no folds, foldlevel() should return 0 + call assert_equal(0, foldlevel(1)) + + " Return a list from the expression + set foldexpr=[] + set foldmethod=expr + for i in range(3) + call assert_equal(0, foldlevel(i)) + endfor + + " expression error + set foldexpr=[{] + set foldmethod=expr + for i in range(3) + call assert_equal(0, foldlevel(i)) + endfor + + set foldmethod& foldexpr& + close! +endfunc + func Test_undo_fold_deletion() new set fdm=marker @@ -899,6 +1091,264 @@ func Test_fold_relative_move() call assert_equal(2, winline()) set fdm& sw& wrap& tw& + bw! +endfunc + +" Test for calling foldlevel() from a fold expression +let g:FoldLevels = [] +func FoldExpr1(lnum) + let f = [a:lnum] + for i in range(1, line('$')) + call add(f, foldlevel(i)) + endfor + call add(g:FoldLevels, f) + return getline(a:lnum)[0] == "\t" +endfunc + +func Test_foldexpr_foldlevel() + new + call setline(1, ['one', "\ttwo", "\tthree"]) + setlocal foldmethod=expr + setlocal foldexpr=FoldExpr1(v:lnum) + setlocal foldenable + setlocal foldcolumn=3 + redraw! + call assert_equal([[1, -1, -1, -1], [2, -1, -1, -1], [3, 0, 1, -1]], + \ g:FoldLevels) + set foldmethod& foldexpr& foldenable& foldcolumn& + bw! +endfunc + +" Test for returning different values from a fold expression +func FoldExpr2(lnum) + if a:lnum == 1 || a:lnum == 4 + return -2 + elseif a:lnum == 2 + return 'a1' + elseif a:lnum == 3 + return 's4' + endif + return '=' +endfunc + +func Test_foldexpr_2() + new + call setline(1, ['one', 'two', 'three', 'four']) + setlocal foldexpr=FoldExpr2(v:lnum) + setlocal foldmethod=expr + call assert_equal([0, 1, 1, 0], [foldlevel(1), foldlevel(2), foldlevel(3), + \ foldlevel(4)]) + bw! +endfunc + +" Test for the 'foldclose' option +func Test_foldclose_opt() + CheckScreendump + + let lines =<< trim END + set foldmethod=manual foldclose=all foldopen=all + call setline(1, ['one', 'two', 'three', 'four']) + 2,3fold + func XsaveFoldLevels() + redraw! + call writefile([json_encode([foldclosed(1), foldclosed(2), foldclosed(3), + \ foldclosed(4)])], 'Xoutput', 'a') + endfunc + END + call writefile(lines, 'Xscript') + let rows = 10 + let buf = RunVimInTerminal('-S Xscript', {'rows': rows}) + call term_wait(buf) + call term_sendkeys(buf, ":set noruler\n") + call term_wait(buf) + call term_sendkeys(buf, ":call XsaveFoldLevels()\n") + call term_sendkeys(buf, "2G") + call WaitForAssert({-> assert_equal('two', term_getline(buf, 2))}) + call term_sendkeys(buf, ":call XsaveFoldLevels()\n") + call term_sendkeys(buf, "4G") + call WaitForAssert({-> assert_equal('four', term_getline(buf, 3))}) + call term_sendkeys(buf, ":call XsaveFoldLevels()\n") + call term_sendkeys(buf, "3G") + call WaitForAssert({-> assert_equal('three', term_getline(buf, 3))}) + call term_sendkeys(buf, ":call XsaveFoldLevels()\n") + call term_sendkeys(buf, "1G") + call WaitForAssert({-> assert_equal('four', term_getline(buf, 3))}) + call term_sendkeys(buf, ":call XsaveFoldLevels()\n") + call term_sendkeys(buf, "2G") + call WaitForAssert({-> assert_equal('two', term_getline(buf, 2))}) + call term_sendkeys(buf, "k") + call WaitForAssert({-> assert_equal('four', term_getline(buf, 3))}) + + " clean up + call StopVimInTerminal(buf) + + call assert_equal(['[-1,2,2,-1]', '[-1,-1,-1,-1]', '[-1,2,2,-1]', + \ '[-1,-1,-1,-1]', '[-1,2,2,-1]'], readfile('Xoutput')) + call delete('Xscript') + call delete('Xoutput') +endfunc + +" Test for foldtextresult() +func Test_foldtextresult() + new + call assert_equal('', foldtextresult(-1)) + call assert_equal('', foldtextresult(0)) + call assert_equal('', foldtextresult(1)) + call setline(1, ['one', 'two', 'three', 'four']) + 2,3fold + call assert_equal('', foldtextresult(1)) + call assert_equal('+-- 2 lines: two', foldtextresult(2)) + setlocal foldtext= + call assert_equal('+-- 2 lines folded ', foldtextresult(2)) + + " Fold text for a C comment fold + %d _ + setlocal foldtext& + call setline(1, ['', '/*', ' * Comment', ' */', '']) + 2,4fold + call assert_equal('+-- 3 lines: Comment', foldtextresult(2)) + + bw! +endfunc + +" Test for merging two recursive folds when an intermediate line with no fold +" is removed +func Test_fold_merge_recursive() + new + call setline(1, [' one', ' two', 'xxxx', ' three', + \ ' four', "\tfive"]) + setlocal foldmethod=indent shiftwidth=2 + 3d_ + %foldclose + call assert_equal([1, 5], [foldclosed(5), foldclosedend(1)]) + bw! +endfunc + +" Test for moving a line which is the start of a fold from a recursive fold to +" outside. The fold length should reduce. +func Test_fold_move_foldlevel() + new + call setline(1, ['a{{{', 'b{{{', 'c{{{', 'd}}}', 'e}}}', 'f}}}', 'g']) + setlocal foldmethod=marker + normal zR + call assert_equal([3, 2, 1], [foldlevel(4), foldlevel(5), foldlevel(6)]) + 3move 7 + call assert_equal([2, 1, 0], [foldlevel(3), foldlevel(4), foldlevel(5)]) + call assert_equal(1, foldlevel(7)) + + " Move a line from outside a fold to inside the fold. + %d _ + call setline(1, ['a', 'b{{{', 'c}}}']) + normal zR + 1move 2 + call assert_equal([1, 1, 1], [foldlevel(1), foldlevel(2), foldlevel(3)]) + + " Move the start of one fold to inside another fold + %d _ + call setline(1, ['a', 'b{{{', 'c}}}', 'd{{{', 'e}}}']) + normal zR + call assert_equal([0, 1, 1, 1, 1], [foldlevel(1), foldlevel(2), + \ foldlevel(3), foldlevel(4), foldlevel(5)]) + 1,2move 4 + call assert_equal([0, 1, 1, 2, 2], [foldlevel(1), foldlevel(2), + \ foldlevel(3), foldlevel(4), foldlevel(5)]) + + bw! +endfunc + +" Test for using zj and zk to move downwards and upwards to the start and end +" of the next fold. +" Test for using [z and ]z in a closed fold to jump to the beginning and end +" of the fold. +func Test_fold_jump() + new + call setline(1, ["\t1", "\t2", "\t\t3", "\t\t4", "\t\t\t5", "\t\t\t6", "\t\t7", "\t\t8", "\t9", "\t10"]) + setlocal foldmethod=indent + normal zR + normal zj + call assert_equal(3, line('.')) + normal zj + call assert_equal(5, line('.')) + call assert_beeps('normal zj') + call assert_equal(5, line('.')) + call assert_beeps('normal 9Gzj') + call assert_equal(9, line('.')) + normal Gzk + call assert_equal(8, line('.')) + normal zk + call assert_equal(6, line('.')) + call assert_beeps('normal zk') + call assert_equal(6, line('.')) + call assert_beeps('normal 2Gzk') + call assert_equal(2, line('.')) + + " Using [z or ]z in a closed fold should not move the cursor + %d _ + call setline(1, ["1", "\t2", "\t3", "\t4", "\t5", "\t6", "7"]) + normal zR4Gzc + call assert_equal(4, line('.')) + call assert_beeps('normal [z') + call assert_equal(4, line('.')) + call assert_beeps('normal ]z') + call assert_equal(4, line('.')) + bw! +endfunc + +" Test for using a script-local function for 'foldexpr' +func Test_foldexpr_scriptlocal_func() + func! s:FoldFunc() + let g:FoldLnum = v:lnum + endfunc + new | only + call setline(1, 'abc') + let g:FoldLnum = 0 + set foldmethod=expr foldexpr=s:FoldFunc() + redraw! + call assert_equal(expand('<SID>') .. 'FoldFunc()', &foldexpr) + call assert_equal(1, g:FoldLnum) + set foldmethod& foldexpr= + bw! + new | only + call setline(1, 'abc') + let g:FoldLnum = 0 + set foldmethod=expr foldexpr=<SID>FoldFunc() + redraw! + call assert_equal(expand('<SID>') .. 'FoldFunc()', &foldexpr) + call assert_equal(1, g:FoldLnum) + set foldmethod& foldexpr= + delfunc s:FoldFunc + bw! +endfunc + +" Test for using a script-local function for 'foldtext' +func Test_foldtext_scriptlocal_func() + func! s:FoldText() + let g:FoldTextArgs = [v:foldstart, v:foldend] + return foldtext() + endfunc + new | only + call setline(1, range(50)) + let g:FoldTextArgs = [] + set foldmethod=manual + set foldtext=s:FoldText() + norm! 4Gzf4j + redraw! + call assert_equal(expand('<SID>') .. 'FoldText()', &foldtext) + call assert_equal([4, 8], g:FoldTextArgs) + set foldtext& + bw! + new | only + call setline(1, range(50)) + let g:FoldTextArgs = [] + set foldmethod=manual + set foldtext=<SID>FoldText() + norm! 8Gzf4j + redraw! + call assert_equal(expand('<SID>') .. 'FoldText()', &foldtext) + call assert_equal([8, 12], g:FoldTextArgs) + set foldtext& + bw! + delfunc s:FoldText endfunc " Make sure a fold containing a nested fold is split correctly when using @@ -989,4 +1439,61 @@ func Test_indent_append_blank_small_fold_close() bw! endfunc +func Test_sort_closed_fold() + CheckExecutable sort + + call setline(1, [ + \ 'Section 1', + \ ' how', + \ ' now', + \ ' brown', + \ ' cow', + \ 'Section 2', + \ ' how', + \ ' now', + \ ' brown', + \ ' cow', + \]) + setlocal foldmethod=indent sw=3 + normal 2G + + " The "!!" expands to ".,.+3" and must only sort four lines + call feedkeys("!!sort\<CR>", 'xt') + call assert_equal([ + \ 'Section 1', + \ ' brown', + \ ' cow', + \ ' how', + \ ' now', + \ 'Section 2', + \ ' how', + \ ' now', + \ ' brown', + \ ' cow', + \ ], getline(1, 10)) + + bwipe! +endfunc + +func Test_indent_with_L_command() + " The "L" command moved the cursor to line zero, causing the text saved for + " undo to use line number -1, which caused trouble for undo later. + new + sil! norm 8R
V{zf8=Lu + bwipe! +endfunc + +" Make sure that when there is a fold at the bottom of the buffer and a newline +" character is appended to the line, the fold gets expanded (instead of the new +" line not being part of the fold). +func Test_expand_fold_at_bottom_of_buffer() + new + " create a fold on the only line + fold + execute "normal A\<CR>" + call assert_equal([1, 1], range(1, 2)->map('foldlevel(v:val)')) + + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 44b6f0373e..500c30c76b 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -2,6 +2,9 @@ source shared.vim source check.vim +source term_util.vim +source screendump.vim +source vim9.vim " Must be done first, since the alternate buffer must be unset. func Test_00_bufexists() @@ -19,6 +22,27 @@ func Test_00_bufexists() call assert_equal(0, bufexists('Xfoo')) endfunc +func Test_has() + throw 'Skipped: Nvim has removed some features' + call assert_equal(1, has('eval')) + call assert_equal(1, has('eval', 1)) + + if has('unix') + call assert_equal(1, or(has('ttyin'), 1)) + call assert_equal(0, and(has('ttyout'), 0)) + call assert_equal(1, has('multi_byte_encoding')) + endif + call assert_equal(1, has('vcon', 1)) + call assert_equal(1, has('mouse_gpm_enabled', 1)) + + call assert_equal(0, has('nonexistent')) + call assert_equal(0, has('nonexistent', 1)) + + " Will we ever have patch 9999? + let ver = 'patch-' .. v:version / 100 .. '.' .. v:version % 100 .. '.9999' + call assert_equal(0, has(ver)) +endfunc + func Test_empty() call assert_equal(1, empty('')) call assert_equal(0, empty('a')) @@ -67,9 +91,11 @@ func Test_len() call assert_equal(2, len('ab')) call assert_equal(0, len([])) + call assert_equal(0, len(v:_null_list)) call assert_equal(2, len([2, 1])) call assert_equal(0, len({})) + call assert_equal(0, len(v:_null_dict)) call assert_equal(2, len({'a': 1, 'b': 2})) " call assert_fails('call len(v:none)', 'E701:') @@ -87,6 +113,10 @@ func Test_max() call assert_fails('call max(1)', 'E712:') " call assert_fails('call max(v:none)', 'E712:') + + " check we only get one error + call assert_fails('call max([#{}, [1]])', ['E728:', 'E728:']) + call assert_fails('call max(#{a: {}, b: [1]})', ['E728:', 'E728:']) endfunc func Test_min() @@ -100,6 +130,11 @@ func Test_min() call assert_fails('call min(1)', 'E712:') " call assert_fails('call min(v:none)', 'E712:') + call assert_fails('call min([1, {}])', 'E728:') + + " check we only get one error + call assert_fails('call min([[1], #{}])', ['E745:', 'E745:']) + call assert_fails('call min(#{a: [1], b: #{}})', ['E745:', 'E745:']) endfunc func Test_strwidth() @@ -188,7 +223,7 @@ func Test_str2nr() if has('float') call assert_fails('call str2nr(1.2)', 'E806:') endif - call assert_fails('call str2nr(10, [])', 'E474:') + call assert_fails('call str2nr(10, [])', 'E745:') endfunc func Test_strftime() @@ -381,6 +416,8 @@ func Test_strpart() call assert_equal('abcdefg', 'abcdefg'->strpart(-2)) call assert_equal('fg', strpart('abcdefg', 5, 4)) call assert_equal('defg', strpart('abcdefg', 3)) + call assert_equal('', strpart('abcdefg', 10)) + call assert_fails("let s=strpart('abcdef', [])", 'E745:') call assert_equal('lép', strpart('éléphant', 2, 4)) call assert_equal('léphant', strpart('éléphant', 2)) @@ -462,6 +499,12 @@ func Test_tolower() " invalid memory. call tolower("\xC0\x80\xC0") call tolower("123\xC0\x80\xC0") + + " Test in latin1 encoding + let save_enc = &encoding + " set encoding=latin1 + call assert_equal("abc", tolower("ABC")) + let &encoding = save_enc endfunc func Test_toupper() @@ -533,11 +576,27 @@ func Test_toupper() " invalid memory. call toupper("\xC0\x80\xC0") call toupper("123\xC0\x80\xC0") + + " Test in latin1 encoding + let save_enc = &encoding + " set encoding=latin1 + call assert_equal("ABC", toupper("abc")) + let &encoding = save_enc endfunc func Test_tr() call assert_equal('foo', tr('bar', 'bar', 'foo')) call assert_equal('zxy', 'cab'->tr('abc', 'xyz')) + call assert_fails("let s=tr([], 'abc', 'def')", 'E730:') + call assert_fails("let s=tr('abc', [], 'def')", 'E730:') + call assert_fails("let s=tr('abc', 'abc', [])", 'E730:') + call assert_fails("let s=tr('abcd', 'abcd', 'def')", 'E475:') + " set encoding=latin1 + call assert_fails("let s=tr('abcd', 'abcd', 'def')", 'E475:') + call assert_equal('hEllO', tr('hello', 'eo', 'EO')) + call assert_equal('hello', tr('hello', 'xy', 'ab')) + call assert_fails('call tr("abc", "123", "₁₂")', 'E475:') + set encoding=utf8 endfunc " Tests for the mode() function @@ -752,13 +811,41 @@ func Test_mode() delfunction OperatorFunc endfunc +" Test for append() func Test_append() enew! split call append(0, ["foo"]) + call append(1, []) + call append(1, v:_null_list) + call assert_equal(['foo', ''], getline(1, '$')) split only undo + undo + + " Using $ instead of '$' must give an error + call assert_fails("call append($, 'foobar')", 'E116:') + + call assert_fails("call append({}, '')", ['E728:', 'E728:']) +endfunc + +" Test for setline() +func Test_setline() + new + call setline(0, ["foo"]) + call setline(0, []) + call setline(0, v:_null_list) + call setline(1, ["bar"]) + call setline(1, []) + call setline(1, v:_null_list) + call setline(2, []) + call setline(2, v:_null_list) + call setline(3, []) + call setline(3, v:_null_list) + call setline(2, ["baz"]) + call assert_equal(['bar', 'baz'], getline(1, '$')) + close! endfunc func Test_getbufvar() @@ -792,6 +879,10 @@ func Test_getbufvar() call assert_equal(0, getbufvar(bnr, '&autoindent')) call assert_equal(0, getbufvar(bnr, '&autoindent', 1)) + " Set and get a buffer-local variable + call setbufvar(bnr, 'bufvar_test', ['one', 'two']) + call assert_equal(['one', 'two'], getbufvar(bnr, 'bufvar_test')) + " Open new window with forced option values set fileformats=unix,dos new ++ff=dos ++bin ++enc=iso-8859-2 @@ -830,6 +921,8 @@ func Test_stridx() call assert_equal(-1, stridx('hello', 'l', 10)) call assert_equal(2, stridx('hello', 'll')) call assert_equal(-1, stridx('hello', 'hello world')) + call assert_fails("let n=stridx('hello', [])", 'E730:') + call assert_fails("let n=stridx([], 'l')", 'E730:') endfunc func Test_strridx() @@ -846,6 +939,8 @@ func Test_strridx() call assert_equal(-1, strridx('hello', 'l', -1)) call assert_equal(2, strridx('hello', 'll')) call assert_equal(-1, strridx('hello', 'hello world')) + call assert_fails("let n=strridx('hello', [])", 'E730:') + call assert_fails("let n=strridx([], 'l')", 'E730:') endfunc func Test_match_func() @@ -855,6 +950,13 @@ func Test_match_func() call assert_equal(-1, match('testing', 'ing', 8)) call assert_equal(1, match(['vim', 'testing', 'execute'], 'ing')) call assert_equal(-1, match(['vim', 'testing', 'execute'], 'img')) + call assert_fails("let x=match('vim', [])", 'E730:') + call assert_equal(3, match(['a', 'b', 'c', 'a'], 'a', 1)) + call assert_equal(-1, match(['a', 'b', 'c', 'a'], 'a', 5)) + call assert_equal(4, match('testing', 'ing', -1)) + call assert_fails("let x=match('testing', 'ing', 0, [])", 'E745:') + call assert_equal(-1, match(v:_null_list, 2)) + call assert_equal(-1, match('abc', '\\%(')) endfunc func Test_matchend() @@ -961,6 +1063,7 @@ func Test_byte2line_line2byte() bw! endfunc +" Test for byteidx() and byteidxcomp() functions func Test_byteidx() let a = '.é.' " one char of two bytes call assert_equal(0, byteidx(a, 0)) @@ -980,6 +1083,7 @@ func Test_byteidx() call assert_equal(4, b->byteidx(2)) call assert_equal(5, b->byteidx(3)) call assert_equal(-1, b->byteidx(4)) + call assert_fails("call byteidx([], 0)", 'E730:') call assert_equal(0, b->byteidxcomp(0)) call assert_equal(1, b->byteidxcomp(1)) @@ -987,6 +1091,7 @@ func Test_byteidx() call assert_equal(4, b->byteidxcomp(3)) call assert_equal(5, b->byteidxcomp(4)) call assert_equal(-1, b->byteidxcomp(5)) + call assert_fails("call byteidxcomp([], 0)", 'E730:') endfunc " Test for charidx() @@ -997,7 +1102,9 @@ func Test_charidx() call assert_equal(2, charidx(a, 4)) call assert_equal(3, charidx(a, 7)) call assert_equal(-1, charidx(a, 8)) + call assert_equal(-1, charidx(a, -1)) call assert_equal(-1, charidx('', 0)) + call assert_equal(-1, charidx(v:_null_string, 0)) " count composing characters call assert_equal(0, charidx(a, 0, 1)) @@ -1010,8 +1117,8 @@ func Test_charidx() call assert_fails('let x = charidx([], 1)', 'E474:') call assert_fails('let x = charidx("abc", [])', 'E474:') call assert_fails('let x = charidx("abc", 1, [])', 'E474:') - call assert_fails('let x = charidx("abc", 1, -1)', 'E474:') - call assert_fails('let x = charidx("abc", 1, 2)', 'E474:') + call assert_fails('let x = charidx("abc", 1, -1)', 'E1023:') + call assert_fails('let x = charidx("abc", 1, 2)', 'E1023:') endfunc func Test_count() @@ -1096,6 +1203,10 @@ func Test_filewritable() call assert_equal(0, filewritable('doesnotexist')) + call mkdir('Xdir') + call assert_equal(2, filewritable('Xdir')) + call delete('Xdir', 'd') + call delete('Xfilewritable') bw! endfunc @@ -1179,6 +1290,7 @@ func Test_col() norm gg4|mx6|mY2| call assert_equal(2, col('.')) call assert_equal(7, col('$')) + call assert_equal(2, col('v')) call assert_equal(4, col("'x")) call assert_equal(6, col("'Y")) call assert_equal(2, [1, 2]->col()) @@ -1189,6 +1301,47 @@ func Test_col() call assert_equal(0, col([2, '$'])) call assert_equal(0, col([1, 100])) call assert_equal(0, col([1])) + call assert_equal(0, col(v:_null_list)) + call assert_fails('let c = col({})', 'E1222:') + call assert_fails('let c = col(".", [])', 'E1210:') + + " test for getting the visual start column + func T() + let g:Vcol = col('v') + return '' + endfunc + let g:Vcol = 0 + xmap <expr> <F2> T() + exe "normal gg3|ve\<F2>" + call assert_equal(3, g:Vcol) + xunmap <F2> + delfunc T + + " Test for the visual line start and end marks '< and '> + call setline(1, ['one', 'one two', 'one two three']) + "normal! ggVG + call feedkeys("ggVG\<Esc>", 'xt') + call assert_equal(1, col("'<")) + call assert_equal(14, col("'>")) + " Delete the last line of the visually selected region + $d + call assert_notequal(14, col("'>")) + + " Test with 'virtualedit' + set virtualedit=all + call cursor(1, 10) + call assert_equal(4, col('.')) + set virtualedit& + + " Test for getting the column number in another window + let winid = win_getid() + new + call win_execute(winid, 'normal 1G$') + call assert_equal(3, col('.', winid)) + call win_execute(winid, 'normal 2G') + call assert_equal(8, col('$', winid)) + call assert_equal(0, col('.', 5001)) + bw! endfunc @@ -1232,12 +1385,15 @@ endfunc " Test for the inputdialog() function func Test_inputdialog() - CheckNotGui - - call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<CR>", 'xt') - call assert_equal('xx', v) - call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<Esc>", 'xt') - call assert_equal('yy', v) + if has('gui_running') + call assert_fails('let v=inputdialog([], "xx")', 'E730:') + call assert_fails('let v=inputdialog("Q", [])', 'E730:') + else + call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<CR>", 'xt') + call assert_equal('xx', v) + call feedkeys(":let v=inputdialog('Q:', 'xx', 'yy')\<CR>\<Esc>", 'xt') + call assert_equal('yy', v) + endif endfunc " Test for inputlist() @@ -1270,26 +1426,39 @@ func Test_inputlist() call assert_equal(2, c) " Use mouse to make a selection - " call test_setmouse(&lines - 3, 2) - call nvim_input_mouse('left', 'press', '', 0, &lines - 4, 1) " set mouse position - call getchar() " discard mouse event but keep mouse position + call Ntest_setmouse(&lines - 3, 2) call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<LeftMouse>", 'tx') call assert_equal(1, c) " Mouse click outside of the list - " call test_setmouse(&lines - 6, 2) - call nvim_input_mouse('left', 'press', '', 0, &lines - 7, 1) " set mouse position - call getchar() " discard mouse event but keep mouse position + call Ntest_setmouse(&lines - 6, 2) call feedkeys(":let c = inputlist(['Select color:', '1. red', '2. green', '3. blue'])\<cr>\<LeftMouse>", 'tx') call assert_equal(-2, c) call assert_fails('call inputlist("")', 'E686:') endfunc +func Test_range_inputlist() + " flush out any garbage left in the buffer + while getchar(0) + endwhile + + call feedkeys(":let result = inputlist(range(10))\<CR>1\<CR>", 'x') + call assert_equal(1, result) + call feedkeys(":let result = inputlist(range(3, 10))\<CR>1\<CR>", 'x') + call assert_equal(1, result) + + unlet result +endfunc + func Test_balloon_show() CheckFeature balloon_eval " This won't do anything but must not crash either. call balloon_show('hi!') + if !has('gui_running') + call balloon_show(range(3)) + call balloon_show([]) + endif endfunc func Test_shellescape() @@ -1420,12 +1589,15 @@ func Test_trim() call assert_equal("vim", trim(" vim ", " ", 0)) call assert_equal("vim ", trim(" vim ", " ", 1)) call assert_equal(" vim", trim(" vim ", " ", 2)) - call assert_fails('call trim(" vim ", " ", [])', 'E745:') - call assert_fails('call trim(" vim ", " ", -1)', 'E475:') - call assert_fails('call trim(" vim ", " ", 3)', 'E475:') + call assert_fails('eval trim(" vim ", " ", [])', 'E745:') + call assert_fails('eval trim(" vim ", " ", -1)', 'E475:') + call assert_fails('eval trim(" vim ", " ", 3)', 'E475:') + call assert_fails('eval trim(" vim ", 0)', 'E475:') let chars = join(map(range(1, 0x20) + [0xa0], {n -> n->nr2char()}), '') call assert_equal("x", trim(chars . "x" . chars)) + + call assert_fails('let c=trim([])', 'E730:') endfunc " Test for reg_recording() and reg_executing() @@ -1534,13 +1706,12 @@ func Test_getchar() call assert_equal(0, getchar(0)) call setline(1, 'xxxx') - " call test_setmouse(1, 3) - " let v:mouse_win = 9 - " let v:mouse_winid = 9 - " let v:mouse_lnum = 9 - " let v:mouse_col = 9 - " call feedkeys("\<S-LeftMouse>", '') - call nvim_input_mouse('left', 'press', 'S', 0, 0, 2) + call Ntest_setmouse(1, 3) + let v:mouse_win = 9 + let v:mouse_winid = 9 + let v:mouse_lnum = 9 + let v:mouse_col = 9 + call feedkeys("\<S-LeftMouse>", '') call assert_equal("\<S-LeftMouse>", getchar()) call assert_equal(1, v:mouse_win) call assert_equal(win_getid(1), v:mouse_winid) @@ -1598,11 +1769,11 @@ func Test_libcall_libcallnr() call assert_equal(4, 'abcd'->libcallnr(libc, 'strlen')) call assert_equal(char2nr('A'), char2nr('a')->libcallnr(libc, 'toupper')) - call assert_fails("call libcall(libc, 'Xdoesnotexist_', '')", 'E364:') - call assert_fails("call libcallnr(libc, 'Xdoesnotexist_', '')", 'E364:') + call assert_fails("call libcall(libc, 'Xdoesnotexist_', '')", ['', 'E364:']) + call assert_fails("call libcallnr(libc, 'Xdoesnotexist_', '')", ['', 'E364:']) - call assert_fails("call libcall('Xdoesnotexist_', 'getenv', 'HOME')", 'E364:') - call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", 'E364:') + call assert_fails("call libcall('Xdoesnotexist_', 'getenv', 'HOME')", ['', 'E364:']) + call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", ['', 'E364:']) endfunc sandbox function Fsandbox() @@ -1619,6 +1790,10 @@ func Test_func_sandbox() call assert_fails('call Fsandbox()', 'E48:') delfunc Fsandbox + + " From a sandbox try to set a predefined variable (which cannot be modified + " from a sandbox) + call assert_fails('sandbox let v:lnum = 10', 'E794:') endfunc func EditAnotherFile() @@ -1697,6 +1872,62 @@ func Test_platform_name() endif endfunc +" Test confirm({msg} [, {choices} [, {default} [, {type}]]]) +func Test_confirm() + " requires a UI to be active + throw 'Skipped: use test/functional/vimscript/input_spec.lua' + CheckUnix + CheckNotGui + + call feedkeys('o', 'L') + let a = confirm('Press O to proceed') + call assert_equal(1, a) + + call feedkeys('y', 'L') + let a = 'Are you sure?'->confirm("&Yes\n&No") + call assert_equal(1, a) + + call feedkeys('n', 'L') + let a = confirm('Are you sure?', "&Yes\n&No") + call assert_equal(2, a) + + " confirm() should return 0 when pressing CTRL-C. + call feedkeys("\<C-C>", 'L') + let a = confirm('Are you sure?', "&Yes\n&No") + call assert_equal(0, a) + + " <Esc> requires another character to avoid it being seen as the start of an + " escape sequence. Zero should be harmless. + eval "\<Esc>0"->feedkeys('L') + let a = confirm('Are you sure?', "&Yes\n&No") + call assert_equal(0, a) + + " Default choice is returned when pressing <CR>. + call feedkeys("\<CR>", 'L') + let a = confirm('Are you sure?', "&Yes\n&No") + call assert_equal(1, a) + + call feedkeys("\<CR>", 'L') + let a = confirm('Are you sure?', "&Yes\n&No", 2) + call assert_equal(2, a) + + call feedkeys("\<CR>", 'L') + let a = confirm('Are you sure?', "&Yes\n&No", 0) + call assert_equal(0, a) + + " Test with the {type} 4th argument + for type in ['Error', 'Question', 'Info', 'Warning', 'Generic'] + call feedkeys('y', 'L') + let a = confirm('Are you sure?', "&Yes\n&No\n", 1, type) + call assert_equal(1, a) + endfor + + call assert_fails('call confirm([])', 'E730:') + call assert_fails('call confirm("Are you sure?", [])', 'E730:') + call assert_fails('call confirm("Are you sure?", "&Yes\n&No\n", [])', 'E745:') + call assert_fails('call confirm("Are you sure?", "&Yes\n&No\n", 0, [])', 'E730:') +endfunc + func Test_readdir() call mkdir('Xdir') call writefile([], 'Xdir/foo.txt') @@ -1724,7 +1955,7 @@ func Test_readdir() let files = readdir('Xdir', {x -> len(add(l, x)) == 2 ? -1 : 1}) call assert_equal(1, len(files)) - call delete('Xdir', 'rf') + eval 'Xdir'->delete('rf') endfunc func Test_delete_rf() @@ -1756,6 +1987,7 @@ func Test_call() call assert_equal(3, 'len'->call([123])) call assert_fails("call call('len', 123)", 'E714:') call assert_equal(0, call('', [])) + call assert_equal(0, call('len', v:_null_list)) function Mylen() dict return len(self.data) @@ -1763,10 +1995,15 @@ func Test_call() let mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")} eval mydict.len->call([], mydict)->assert_equal(4) call assert_fails("call call('Mylen', [], 0)", 'E715:') + call assert_fails('call foo', 'E107:') endfunc func Test_char2nr() call assert_equal(12354, char2nr('あ', 1)) + call assert_equal(120, 'x'->char2nr()) + " set encoding=latin1 + call assert_equal(120, 'x'->char2nr()) + set encoding=utf-8 endfunc func Test_charclass() @@ -1819,17 +2056,329 @@ func Test_bufadd_bufload() exe 'bwipe ' .. buf2 call assert_equal(0, bufexists(buf2)) + " When 'buftype' is "nofile" then bufload() does not read the file. + " Other values too. + for val in [['nofile', 0], + \ ['nowrite', 1], + \ ['acwrite', 1], + \ ['quickfix', 0], + \ ['help', 1], + "\ ['terminal', 0], + \ ['prompt', 0], + "\ ['popup', 0], + \ ] + bwipe! XotherName + let buf = bufadd('XotherName') + call setbufvar(buf, '&bt', val[0]) + call bufload(buf) + call assert_equal(val[1] ? ['some', 'text'] : [''], getbufline(buf, 1, '$'), val[0]) + endfor + bwipe someName bwipe XotherName call assert_equal(0, bufexists('someName')) call delete('XotherName') endfunc +func Test_range() + " destructuring + let [x, y] = range(2) + call assert_equal([0, 1], [x, y]) + + " index + call assert_equal(4, range(1, 10)[3]) + + " add() + call assert_equal([0, 1, 2, 3], add(range(3), 3)) + call assert_equal([0, 1, 2, [0, 1, 2]], add([0, 1, 2], range(3))) + call assert_equal([0, 1, 2, [0, 1, 2]], add(range(3), range(3))) + + " append() + new + call append('.', range(5)) + call assert_equal(['', '0', '1', '2', '3', '4'], getline(1, '$')) + bwipe! + + " appendbufline() + new + call appendbufline(bufnr(''), '.', range(5)) + call assert_equal(['0', '1', '2', '3', '4', ''], getline(1, '$')) + bwipe! + + " call() + func TwoArgs(a, b) + return [a:a, a:b] + endfunc + call assert_equal([0, 1], call('TwoArgs', range(2))) + + " col() + new + call setline(1, ['foo', 'bar']) + call assert_equal(2, col(range(1, 2))) + bwipe! + + " complete() + execute "normal! a\<C-r>=[complete(col('.'), range(10)), ''][1]\<CR>" + " complete_info() + execute "normal! a\<C-r>=[complete(col('.'), range(10)), ''][1]\<CR>\<C-r>=[complete_info(range(5)), ''][1]\<CR>" + + " copy() + call assert_equal([1, 2, 3], copy(range(1, 3))) + + " count() + call assert_equal(0, count(range(0), 3)) + call assert_equal(0, count(range(2), 3)) + call assert_equal(1, count(range(5), 3)) + + " cursor() + new + call setline(1, ['aaa', 'bbb', 'ccc']) + call cursor(range(1, 2)) + call assert_equal([2, 1], [col('.'), line('.')]) + bwipe! + + " deepcopy() + call assert_equal([1, 2, 3], deepcopy(range(1, 3))) + + " empty() + call assert_true(empty(range(0))) + call assert_false(empty(range(2))) + + " execute() + new + call setline(1, ['aaa', 'bbb', 'ccc']) + call execute(range(3)) + call assert_equal(2, line('.')) + bwipe! + + " extend() + call assert_equal([1, 2, 3, 4], extend([1], range(2, 4))) + call assert_equal([1, 2, 3, 4], extend(range(1, 1), range(2, 4))) + call assert_equal([1, 2, 3, 4], extend(range(1, 1), [2, 3, 4])) + + " filter() + call assert_equal([1, 3], filter(range(5), 'v:val % 2')) + + " funcref() + call assert_equal([0, 1], funcref('TwoArgs', range(2))()) + + " function() + call assert_equal([0, 1], function('TwoArgs', range(2))()) + + " garbagecollect() + let thelist = [1, range(2), 3] + let otherlist = range(3) + call test_garbagecollect_now() + + " get() + call assert_equal(4, get(range(1, 10), 3)) + call assert_equal(-1, get(range(1, 10), 42, -1)) + + " index() + call assert_equal(1, index(range(1, 5), 2)) + call assert_fails("echo index([1, 2], 1, [])", 'E745:') + + " insert() + call assert_equal([42, 1, 2, 3, 4, 5], insert(range(1, 5), 42)) + call assert_equal([42, 1, 2, 3, 4, 5], insert(range(1, 5), 42, 0)) + call assert_equal([1, 42, 2, 3, 4, 5], insert(range(1, 5), 42, 1)) + call assert_equal([1, 2, 3, 4, 42, 5], insert(range(1, 5), 42, 4)) + call assert_equal([1, 2, 3, 4, 42, 5], insert(range(1, 5), 42, -1)) + call assert_equal([1, 2, 3, 4, 5, 42], insert(range(1, 5), 42, 5)) + + " join() + call assert_equal('0 1 2 3 4', join(range(5))) + + " json_encode() + " call assert_equal('[0,1,2,3]', json_encode(range(4))) + call assert_equal('[0, 1, 2, 3]', json_encode(range(4))) + + " len() + call assert_equal(0, len(range(0))) + call assert_equal(2, len(range(2))) + call assert_equal(5, len(range(0, 12, 3))) + call assert_equal(4, len(range(3, 0, -1))) + + " list2str() + call assert_equal('ABC', list2str(range(65, 67))) + call assert_fails('let s = list2str(5)', 'E474:') + + " lock() + let thelist = range(5) + lockvar thelist + + " map() + call assert_equal([0, 2, 4, 6, 8], map(range(5), 'v:val * 2')) + + " match() + call assert_equal(3, match(range(5), 3)) + + " matchaddpos() + highlight MyGreenGroup ctermbg=green guibg=green + call matchaddpos('MyGreenGroup', range(line('.'), line('.'))) + + " matchend() + call assert_equal(4, matchend(range(5), '4')) + call assert_equal(3, matchend(range(1, 5), '4')) + call assert_equal(-1, matchend(range(1, 5), '42')) + + " matchstrpos() + call assert_equal(['4', 4, 0, 1], matchstrpos(range(5), '4')) + call assert_equal(['4', 3, 0, 1], matchstrpos(range(1, 5), '4')) + call assert_equal(['', -1, -1, -1], matchstrpos(range(1, 5), '42')) + + " max() reverse() + call assert_equal(0, max(range(0))) + call assert_equal(0, max(range(10, 9))) + call assert_equal(9, max(range(10))) + call assert_equal(18, max(range(0, 20, 3))) + call assert_equal(20, max(range(20, 0, -3))) + call assert_equal(99999, max(range(100000))) + call assert_equal(99999, max(range(99999, 0, -1))) + call assert_equal(99999, max(reverse(range(100000)))) + call assert_equal(99999, max(reverse(range(99999, 0, -1)))) + + " min() reverse() + call assert_equal(0, min(range(0))) + call assert_equal(0, min(range(10, 9))) + call assert_equal(5, min(range(5, 10))) + call assert_equal(5, min(range(5, 10, 3))) + call assert_equal(2, min(range(20, 0, -3))) + call assert_equal(0, min(range(100000))) + call assert_equal(0, min(range(99999, 0, -1))) + call assert_equal(0, min(reverse(range(100000)))) + call assert_equal(0, min(reverse(range(99999, 0, -1)))) + + " remove() + call assert_equal(1, remove(range(1, 10), 0)) + call assert_equal(2, remove(range(1, 10), 1)) + call assert_equal(9, remove(range(1, 10), 8)) + call assert_equal(10, remove(range(1, 10), 9)) + call assert_equal(10, remove(range(1, 10), -1)) + call assert_equal([3, 4, 5], remove(range(1, 10), 2, 4)) + + " repeat() + call assert_equal([0, 1, 2, 0, 1, 2], repeat(range(3), 2)) + call assert_equal([0, 1, 2], repeat(range(3), 1)) + call assert_equal([], repeat(range(3), 0)) + call assert_equal([], repeat(range(5, 4), 2)) + call assert_equal([], repeat(range(5, 4), 0)) + + " reverse() + call assert_equal([2, 1, 0], reverse(range(3))) + call assert_equal([0, 1, 2, 3], reverse(range(3, 0, -1))) + call assert_equal([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], reverse(range(10))) + call assert_equal([20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10], reverse(range(10, 20))) + call assert_equal([16, 13, 10], reverse(range(10, 18, 3))) + call assert_equal([19, 16, 13, 10], reverse(range(10, 19, 3))) + call assert_equal([19, 16, 13, 10], reverse(range(10, 20, 3))) + call assert_equal([11, 14, 17, 20], reverse(range(20, 10, -3))) + call assert_equal([], reverse(range(0))) + + " TODO: setpos() + " new + " call setline(1, repeat([''], bufnr(''))) + " call setline(bufnr('') + 1, repeat('x', bufnr('') * 2 + 6)) + " call setpos('x', range(bufnr(''), bufnr('') + 3)) + " bwipe! + + " setreg() + call setreg('a', range(3)) + call assert_equal("0\n1\n2\n", getreg('a')) + + " settagstack() + call settagstack(1, #{items : range(4)}) + + " sign_define() + call assert_fails("call sign_define(range(5))", "E715:") + call assert_fails("call sign_placelist(range(5))", "E715:") + + " sign_undefine() + " call assert_fails("call sign_undefine(range(5))", "E908:") + call assert_fails("call sign_undefine(range(5))", "E155:") + + " sign_unplacelist() + call assert_fails("call sign_unplacelist(range(5))", "E715:") + + " sort() + call assert_equal([0, 1, 2, 3, 4, 5], sort(range(5, 0, -1))) + + " string() + call assert_equal('[0, 1, 2, 3, 4]', string(range(5))) + + " taglist() with 'tagfunc' + func TagFunc(pattern, flags, info) + return range(10) + endfunc + set tagfunc=TagFunc + call assert_fails("call taglist('asdf')", 'E987:') + set tagfunc= + + " term_start() + if has('terminal') && has('termguicolors') + call assert_fails('call term_start(range(3, 4))', 'E474:') + let g:terminal_ansi_colors = range(16) + if has('win32') + let cmd = "cmd /c dir" + else + let cmd = "ls" + endif + call assert_fails('call term_start("' .. cmd .. '", #{term_finish: "close"})', 'E475:') + unlet g:terminal_ansi_colors + endif + + " type() + call assert_equal(v:t_list, type(range(5))) + + " uniq() + call assert_equal([0, 1, 2, 3, 4], uniq(range(5))) + + " errors + call assert_fails('let x=range(2, 8, 0)', 'E726:') + call assert_fails('let x=range(3, 1)', 'E727:') + call assert_fails('let x=range(1, 3, -2)', 'E727:') + call assert_fails('let x=range([])', 'E745:') + call assert_fails('let x=range(1, [])', 'E745:') + call assert_fails('let x=range(1, 4, [])', 'E745:') +endfunc + +func Test_garbagecollect_now_fails() + let v:testing = 0 + call assert_fails('call test_garbagecollect_now()', 'E1142:') + let v:testing = 1 +endfunc + " Test for the eval() function func Test_eval() call assert_fails("call eval('5 a')", 'E488:') endfunc +" Test for the keytrans() function +func Test_keytrans() + call assert_equal('<Space>', keytrans(' ')) + call assert_equal('<lt>', keytrans('<')) + call assert_equal('<lt>Tab>', keytrans('<Tab>')) + call assert_equal('<Tab>', keytrans("\<Tab>")) + call assert_equal('<C-V>', keytrans("\<C-V>")) + call assert_equal('<BS>', keytrans("\<BS>")) + call assert_equal('<Home>', keytrans("\<Home>")) + call assert_equal('<C-Home>', keytrans("\<C-Home>")) + call assert_equal('<M-Home>', keytrans("\<M-Home>")) + call assert_equal('<C-Space>', keytrans("\<C-Space>")) + call assert_equal('<M-Space>', keytrans("\<*M-Space>")) + call assert_equal('<M-x>', "\<*M-x>"->keytrans()) + call assert_equal('<C-I>', "\<*C-I>"->keytrans()) + call assert_equal('<S-3>', "\<*S-3>"->keytrans()) + call assert_equal('π', 'π'->keytrans()) + call assert_equal('<M-π>', "\<M-π>"->keytrans()) + call assert_equal('ě', 'ě'->keytrans()) + call assert_equal('<M-ě>', "\<M-ě>"->keytrans()) + call assert_equal('', ''->keytrans()) + call assert_equal('', v:_null_string->keytrans()) + call assert_fails('call keytrans(1)', 'E1174:') + call assert_fails('call keytrans()', 'E119:') +endfunc + " Test for the nr2char() function func Test_nr2char() " set encoding=latin1 @@ -1842,6 +2391,13 @@ func Test_nr2char() call assert_equal("\x80\xfc\b" .. nr2char(0x40000000), eval('"\<M-' .. nr2char(0x40000000) .. '>"')) endfunc +" Test for screenattr(), screenchar() and screenchars() functions +func Test_screen_functions() + call assert_equal(-1, screenattr(-1, -1)) + call assert_equal(-1, screenchar(-1, -1)) + call assert_equal([], screenchars(-1, -1)) +endfunc + " Test for getcurpos() and setpos() func Test_getcurpos_setpos() new @@ -1872,9 +2428,7 @@ endfunc func Test_getmousepos() enew! call setline(1, "\t\t\t1234") - " call test_setmouse(1, 1) - call nvim_input_mouse('left', 'press', '', 0, 0, 0) - call getchar() " wait for and consume the mouse press + call Ntest_setmouse(1, 1) call assert_equal(#{ \ screenrow: 1, \ screencol: 1, @@ -1884,9 +2438,7 @@ func Test_getmousepos() \ line: 1, \ column: 1, \ }, getmousepos()) - " call test_setmouse(1, 25) - call nvim_input_mouse('left', 'press', '', 0, 0, 24) - call getchar() " wait for and consume the mouse press + call Ntest_setmouse(1, 25) call assert_equal(#{ \ screenrow: 1, \ screencol: 25, @@ -1896,9 +2448,7 @@ func Test_getmousepos() \ line: 1, \ column: 4, \ }, getmousepos()) - " call test_setmouse(1, 50) - call nvim_input_mouse('left', 'press', '', 0, 0, 49) - call getchar() " wait for and consume the mouse press + call Ntest_setmouse(1, 50) call assert_equal(#{ \ screenrow: 1, \ screencol: 50, @@ -1911,9 +2461,7 @@ func Test_getmousepos() " If the mouse is positioned past the last buffer line, "line" and "column" " should act like it's positioned on the last buffer line. - " call test_setmouse(2, 25) - call nvim_input_mouse('left', 'press', '', 0, 1, 24) - call getchar() " wait for and consume the mouse press + call Ntest_setmouse(2, 25) call assert_equal(#{ \ screenrow: 2, \ screencol: 25, @@ -1923,9 +2471,7 @@ func Test_getmousepos() \ line: 1, \ column: 4, \ }, getmousepos()) - " call test_setmouse(2, 50) - call nvim_input_mouse('left', 'press', '', 0, 1, 49) - call getchar() " wait for and consume the mouse press + call Ntest_setmouse(2, 50) call assert_equal(#{ \ screenrow: 2, \ screencol: 50, @@ -1938,6 +2484,28 @@ func Test_getmousepos() bwipe! endfunc +" Test for glob() +func Test_glob() + call assert_equal('', glob(v:_null_string)) + call assert_equal('', globpath(v:_null_string, v:_null_string)) + call assert_fails("let x = globpath(&rtp, 'syntax/c.vim', [])", 'E745:') + + call writefile([], 'Xglob1') + call writefile([], 'XGLOB2') + set wildignorecase + " Sort output of glob() otherwise we end up with different + " ordering depending on whether file system is case-sensitive. + call assert_equal(['XGLOB2', 'Xglob1'], sort(glob('Xglob[12]', 0, 1))) + " wildignorecase shall be applied even when the pattern contains no wildcards. + call assert_equal('XGLOB2', glob('xglob2')) + set wildignorecase& + + call delete('Xglob1') + call delete('XGLOB2') + + call assert_fails("call glob('*', 0, {})", 'E728:') +endfunc + func HasDefault(msg = 'msg') return a:msg endfunc @@ -1946,4 +2514,46 @@ func Test_default_arg_value() call assert_equal('msg', HasDefault()) endfunc +" Test for gettext() +func Test_gettext() + call assert_fails('call gettext(1)', 'E475:') +endfunc + +func Test_builtin_check() + call assert_fails('let g:["trim"] = {x -> " " .. x}', 'E704:') + call assert_fails('let g:.trim = {x -> " " .. x}', 'E704:') + call assert_fails('let l:["trim"] = {x -> " " .. x}', 'E704:') + call assert_fails('let l:.trim = {x -> " " .. x}', 'E704:') + let lines =<< trim END + vim9script + var s:trim = (x) => " " .. x + END + call CheckScriptFailure(lines, 'E704:') + + call assert_fails('call extend(g:, #{foo: { -> "foo" }})', 'E704:') + let g:bar = 123 + call extend(g:, #{bar: { -> "foo" }}, "keep") + call assert_fails('call extend(g:, #{bar: { -> "foo" }}, "force")', 'E704:') + unlet g:bar + + call assert_fails('call extend(l:, #{foo: { -> "foo" }})', 'E704:') + let bar = 123 + call extend(l:, #{bar: { -> "foo" }}, "keep") + call assert_fails('call extend(l:, #{bar: { -> "foo" }}, "force")', 'E704:') + unlet bar + + call assert_fails('call extend(g:, #{foo: function("extend")})', 'E704:') + let g:bar = 123 + call extend(g:, #{bar: function("extend")}, "keep") + call assert_fails('call extend(g:, #{bar: function("extend")}, "force")', 'E704:') + unlet g:bar + + call assert_fails('call extend(l:, #{foo: function("extend")})', 'E704:') + let bar = 123 + call extend(l:, #{bar: function("extend")}, "keep") + call assert_fails('call extend(l:, #{bar: function("extend")}, "force")', 'E704:') + unlet bar +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_getcwd.vim b/src/nvim/testdir/test_getcwd.vim index a75583cd2c..073f8303dc 100644 --- a/src/nvim/testdir/test_getcwd.vim +++ b/src/nvim/testdir/test_getcwd.vim @@ -24,7 +24,7 @@ endfunc " Do all test in a separate window to avoid E211 when we recursively " delete the Xtopdir directory during cleanup -function SetUp() +func SetUp() set visualbell set nocp viminfo+=nviminfo diff --git a/src/nvim/testdir/test_getvar.vim b/src/nvim/testdir/test_getvar.vim index 5a96548893..e6b6341fce 100644 --- a/src/nvim/testdir/test_getvar.vim +++ b/src/nvim/testdir/test_getvar.vim @@ -133,11 +133,20 @@ func Test_get_lambda() call assert_equal([], get(l:L, 'args')) endfunc +func s:FooBar() +endfunc + " get({func}, {what} [, {default}]) func Test_get_func() let l:F = function('tr') call assert_equal('tr', get(l:F, 'name')) call assert_equal(l:F, get(l:F, 'func')) + + let Fb_func = function('s:FooBar') + call assert_match('<SNR>\d\+_FooBar', get(Fb_func, 'name')) + let Fb_ref = funcref('s:FooBar') + call assert_match('<SNR>\d\+_FooBar', get(Fb_ref, 'name')) + call assert_equal({'func has': 'no dict'}, get(l:F, 'dict', {'func has': 'no dict'})) call assert_equal(0, get(l:F, 'dict')) call assert_equal([], get(l:F, 'args')) diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index b2bb189688..e369645328 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -226,6 +226,32 @@ func Test_gf_includeexpr() delfunc IncFunc endfunc +" Test for using a script-local function for 'includeexpr' +func Test_includeexpr_scriptlocal_func() + func! s:IncludeFunc() + let g:IncludeFname = v:fname + return '' + endfunc + set includeexpr=s:IncludeFunc() + call assert_equal(expand('<SID>') .. 'IncludeFunc()', &includeexpr) + new | only + call setline(1, 'TestFile1') + let g:IncludeFname = '' + call assert_fails('normal! gf', 'E447:') + call assert_equal('TestFile1', g:IncludeFname) + bw! + set includeexpr=<SID>IncludeFunc() + call assert_equal(expand('<SID>') .. 'IncludeFunc()', &includeexpr) + new | only + call setline(1, 'TestFile2') + let g:IncludeFname = '' + call assert_fails('normal! gf', 'E447:') + call assert_equal('TestFile2', g:IncludeFname) + set includeexpr& + delfunc s:IncludeFunc + bw! +endfunc + " Check that expanding directories can handle more than 255 entries. func Test_gf_subdirs_wildcard() let cwd = getcwd() diff --git a/src/nvim/testdir/test_global.vim b/src/nvim/testdir/test_global.vim index cb6851250c..44a8784348 100644 --- a/src/nvim/testdir/test_global.vim +++ b/src/nvim/testdir/test_global.vim @@ -39,7 +39,7 @@ endfunc func Test_global_error() call assert_fails('g\\a', 'E10:') call assert_fails('g', 'E148:') - call assert_fails('g/\(/y', 'E476:') + call assert_fails('g/\(/y', 'E54:') endfunc " Test for printing lines using :g with different search patterns @@ -72,6 +72,18 @@ func Test_global_print() close! endfunc +func Test_global_empty_pattern() + " populate history + silent g/hello/ + + redir @a + g// + redir END + + call assert_match('Pattern not found: hello', @a) + " ^~~~~ this was previously empty +endfunc + " Test for global command with newline character func Test_global_newline() new @@ -91,6 +103,7 @@ endfunc " Test for interrupting :global using Ctrl-C func Test_interrupt_global() CheckRunVimInTerminal + let lines =<< trim END cnoremap ; <Cmd>sleep 10<CR> call setline(1, repeat(['foo'], 5)) @@ -100,14 +113,14 @@ func Test_interrupt_global() call term_sendkeys(buf, ":g/foo/norm :\<C-V>;\<CR>") " Wait for :sleep to start - call term_wait(buf) + call TermWait(buf, 100) call term_sendkeys(buf, "\<C-C>") call WaitForAssert({-> assert_match('Interrupted', term_getline(buf, 6))}, 1000) " Also test in Ex mode call term_sendkeys(buf, "gQg/foo/norm :\<C-V>;\<CR>") " Wait for :sleep to start - call term_wait(buf) + call TermWait(buf, 100) call term_sendkeys(buf, "\<C-C>") call WaitForAssert({-> assert_match('Interrupted', term_getline(buf, 5))}, 1000) diff --git a/src/nvim/testdir/test_gui.vim b/src/nvim/testdir/test_gui.vim new file mode 100644 index 0000000000..c3f1f3163a --- /dev/null +++ b/src/nvim/testdir/test_gui.vim @@ -0,0 +1,43 @@ + +func Test_colorscheme() + " call assert_equal('16777216', &t_Co) + + let colorscheme_saved = exists('g:colors_name') ? g:colors_name : 'default' + let g:color_count = 0 + augroup TestColors + au! + au ColorScheme * let g:color_count += 1 + \ | let g:after_colors = g:color_count + \ | let g:color_after = expand('<amatch>') + au ColorSchemePre * let g:color_count += 1 + \ | let g:before_colors = g:color_count + \ | let g:color_pre = expand('<amatch>') + augroup END + + colorscheme torte + redraw! + call assert_equal('dark', &background) + call assert_equal(1, g:before_colors) + call assert_equal(2, g:after_colors) + call assert_equal('torte', g:color_pre) + call assert_equal('torte', g:color_after) + call assert_equal("\ntorte", execute('colorscheme')) + + let a = substitute(execute('hi Search'), "\n\\s\\+", ' ', 'g') + " FIXME: temporarily check less while the colorscheme changes + " call assert_match("\nSearch xxx term=reverse cterm=reverse ctermfg=196 ctermbg=16 gui=reverse guifg=#ff0000 guibg=#000000", a) + " call assert_match("\nSearch xxx term=reverse ", a) + + call assert_fails('colorscheme does_not_exist', 'E185:') + call assert_equal('does_not_exist', g:color_pre) + call assert_equal('torte', g:color_after) + + exec 'colorscheme' colorscheme_saved + augroup TestColors + au! + augroup END + unlet g:color_count g:after_colors g:before_colors + redraw! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_hardcopy.vim b/src/nvim/testdir/test_hardcopy.vim deleted file mode 100644 index e390bd5cc8..0000000000 --- a/src/nvim/testdir/test_hardcopy.vim +++ /dev/null @@ -1,204 +0,0 @@ -" Test :hardcopy - -source check.vim - -func Test_printoptions() - edit test_hardcopy.vim - syn on - - for opt in ['left:5in,right:10pt,top:8mm,bottom:2pc', - \ 'left:2in,top:30pt,right:16mm,bottom:3pc', - \ 'header:3,syntax:y,number:y,wrap:n', - \ 'header:3,syntax:n,number:y,wrap:y', - \ 'header:0,syntax:a,number:y,wrap:y', - \ 'duplex:short,collate:n,jobsplit:y,portrait:n', - \ 'duplex:long,collate:y,jobsplit:n,portrait:y', - \ 'duplex:off,collate:y,jobsplit:y,portrait:y', - \ 'paper:10x14', - \ 'paper:A3', - \ 'paper:A4', - \ 'paper:A5', - \ 'paper:B4', - \ 'paper:B5', - \ 'paper:executive', - \ 'paper:folio', - \ 'paper:ledger', - \ 'paper:legal', - \ 'paper:letter', - \ 'paper:quarto', - \ 'paper:statement', - \ 'paper:tabloid', - \ 'formfeed:y', - \ ''] - exe 'set printoptions=' .. opt - if has('postscript') - 1,50hardcopy > Xhardcopy_printoptions - let lines = readfile('Xhardcopy_printoptions') - call assert_true(len(lines) > 20, opt) - call assert_true(lines[0] =~ 'PS-Adobe', opt) - call delete('Xhardcopy_printoptions') - endif - endfor - - call assert_fails('set printoptions=paper', 'E550:') - call assert_fails('set printoptions=shredder:on', 'E551:') - call assert_fails('set printoptions=left:no', 'E552:') - set printoptions& - bwipe -endfunc - -func Test_printmbfont() - " Print a help page which contains tabs, underlines (etc) to recover more code. - help syntax.txt - syn on - - for opt in [':WadaMin-Regular,b:WadaMin-Bold,i:WadaMin-Italic,o:WadaMin-Bold-Italic,c:yes,a:no', - \ ''] - exe 'set printmbfont=' .. opt - if has('postscript') - hardcopy > Xhardcopy_printmbfont - let lines = readfile('Xhardcopy_printmbfont') - call assert_true(len(lines) > 20, opt) - call assert_true(lines[0] =~ 'PS-Adobe', opt) - call delete('Xhardcopy_printmbfont') - endif - endfor - set printmbfont& - bwipe -endfunc - -func Test_printmbcharset() - CheckFeature postscript - - " digraph.txt has plenty of non-latin1 characters. - help digraph.txt - set printmbcharset=ISO10646 printencoding=utf-8 - for courier in ['yes', 'no'] - for ascii in ['yes', 'no'] - exe 'set printmbfont=r:WadaMin-Regular,b:WadaMin-Bold,i:WadaMin-Italic,o:WadaMin-BoldItalic' - \ .. ',c:' .. courier .. ',a:' .. ascii - hardcopy > Xhardcopy_printmbcharset - let lines = readfile('Xhardcopy_printmbcharset') - call assert_true(len(lines) > 20) - call assert_true(lines[0] =~ 'PS-Adobe') - endfor - endfor - - set printmbcharset=does-not-exist printencoding=utf-8 printmbfont=r:WadaMin-Regular - call assert_fails('hardcopy > Xhardcopy_printmbcharset', 'E456:') - - set printmbcharset=GB_2312-80 printencoding=utf-8 printmbfont=r:WadaMin-Regular - call assert_fails('hardcopy > Xhardcopy_printmbcharset', 'E673:') - - set printmbcharset=ISO10646 printencoding=utf-8 printmbfont= - call assert_fails('hardcopy > Xhardcopy_printmbcharset', 'E675:') - - call delete('Xhardcopy_printmbcharset') - set printmbcharset& printencoding& printmbfont& - bwipe -endfunc - -func Test_printexpr() - CheckFeature postscript - - " Not a very useful printexpr value, but enough to test - " hardcopy with 'printexpr'. - function PrintFile(fname) - call writefile(['Test printexpr: ' .. v:cmdarg], - \ 'Xhardcopy_printexpr') - call delete(a:fname) - return 0 - endfunc - set printexpr=PrintFile(v:fname_in) - - help help - hardcopy dummy args - call assert_equal(['Test printexpr: dummy args'], - \ readfile('Xhardcopy_printexpr')) - call delete('Xhardcopy_printexpr') - - " Function returns 1 to test print failure. - function PrintFails(fname) - call delete(a:fname) - return 1 - endfunc - set printexpr=PrintFails(v:fname_in) - call assert_fails('hardcopy', 'E365:') - - set printexpr& - bwipe -endfunc - -func Test_errors() - CheckFeature postscript - - edit test_hardcopy.vim - call assert_fails('hardcopy >', 'E324:') - bwipe -endfunc - -func Test_dark_background() - edit test_hardcopy.vim - syn on - - for bg in ['dark', 'light'] - exe 'set background=' .. bg - - if has('postscript') - hardcopy > Xhardcopy_dark_background - let lines = readfile('Xhardcopy_dark_background') - call assert_true(len(lines) > 20) - call assert_true(lines[0] =~ 'PS-Adobe') - call delete('Xhardcopy_dark_background') - endif - endfor - - set background& - bwipe -endfun - -func Test_empty_buffer() - CheckFeature postscript - - new - call assert_equal("\nNo text to be printed", execute('hardcopy')) - bwipe -endfunc - -func Test_printheader_parsing() - " Only test that this doesn't throw an error. - set printheader=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P - set printheader=%<%f%h%m%r%=%b\ 0x%B\ \ %l,%c%V\ %P - set printheader=%<%f%=\ [%1*%M%*%n%R%H]\ %-19(%3l,%02c%03V%)%O'%02b' - set printheader=...%r%{VarExists('b:gzflag','\ [GZ]')}%h... - set printheader= - set printheader& -endfunc - -func Test_fname_with_spaces() - CheckFeature postscript - - split t\ e\ s\ t.txt - call setline(1, ['just', 'some', 'text']) - hardcopy > %.ps - call assert_true(filereadable('t e s t.txt.ps')) - call delete('t e s t.txt.ps') - bwipe! -endfunc - -func Test_illegal_byte() - CheckFeature postscript - if &enc != 'utf-8' - return - endif - - new - " conversion of 0xff will fail, this used to cause a crash - call setline(1, "\xff") - hardcopy >Xpstest - - bwipe! - call delete('Xpstest') -endfunc - -" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_help.vim b/src/nvim/testdir/test_help.vim index dbb36facee..08dd3dcb9a 100644 --- a/src/nvim/testdir/test_help.vim +++ b/src/nvim/testdir/test_help.vim @@ -1,5 +1,23 @@ " Tests for :help +source check.vim +source vim9.vim + +func SetUp() + let s:vimruntime = $VIMRUNTIME + let s:runtimepath = &runtimepath + " Set $VIMRUNTIME to $BUILD_DIR/runtime and remove the original $VIMRUNTIME + " path from &runtimepath so that ":h local-additions" won't pick up builtin + " help files. + let $VIMRUNTIME = expand($BUILD_DIR) .. '/runtime' + set runtimepath-=../../../runtime +endfunc + +func TearDown() + let $VIMRUNTIME = s:vimruntime + let &runtimepath = s:runtimepath +endfunc + func Test_help_restore_snapshot() help set buftype= @@ -79,16 +97,42 @@ func Test_help_local_additions() call writefile(['*mydoc-ext.txt* my extended awesome doc'], 'Xruntime/doc/mydoc-ext.txt') let rtp_save = &rtp set rtp+=./Xruntime - help - 1 - call search('mydoc.txt') - call assert_equal('|mydoc.txt| my awesome doc', getline('.')) - 1 - call search('mydoc-ext.txt') - call assert_equal('|mydoc-ext.txt| my extended awesome doc', getline('.')) + help local-additions + let lines = getline(line(".") + 1, search("^$") - 1) + call assert_equal([ + \ '|mydoc-ext.txt| my extended awesome doc', + \ '|mydoc.txt| my awesome doc' + \ ], lines) + call delete('Xruntime/doc/mydoc-ext.txt') + close + + call mkdir('Xruntime-ja/doc', 'p') + call writefile(["local-additions\thelp.jax\t/*local-additions*"], 'Xruntime-ja/doc/tags-ja') + call writefile(['*help.txt* This is jax file', '', + \ 'LOCAL ADDITIONS: *local-additions*', ''], 'Xruntime-ja/doc/help.jax') + call writefile(['*work.txt* This is jax file'], 'Xruntime-ja/doc/work.jax') + call writefile(['*work2.txt* This is jax file'], 'Xruntime-ja/doc/work2.jax') + set rtp+=./Xruntime-ja + + help local-additions@en + let lines = getline(line(".") + 1, search("^$") - 1) + call assert_equal([ + \ '|mydoc.txt| my awesome doc' + \ ], lines) + close + + help local-additions@ja + let lines = getline(line(".") + 1, search("^$") - 1) + call assert_equal([ + \ '|mydoc.txt| my awesome doc', + \ '|help.txt| This is jax file', + \ '|work.txt| This is jax file', + \ '|work2.txt| This is jax file', + \ ], lines) close call delete('Xruntime', 'rf') + call delete('Xruntime-ja', 'rf') let &rtp = rtp_save endfunc @@ -98,6 +142,7 @@ func Test_help_completion() endfunc " Test for the :helptags command +" NOTE: if you run tests as root this will fail. Don't run tests as root! func Test_helptag_cmd() call mkdir('Xdir/a/doc', 'p') @@ -111,28 +156,12 @@ func Test_helptag_cmd() call assert_equal(["help-tags\ttags\t1"], readfile('Xdir/tags')) call delete('Xdir/tags') - " The following tests fail on FreeBSD for some reason - if has('unix') && !has('bsd') - " Read-only tags file - call mkdir('Xdir/doc', 'p') - call writefile([''], 'Xdir/doc/tags') - call writefile([], 'Xdir/doc/sample.txt') - call setfperm('Xdir/doc/tags', 'r-xr--r--') - call assert_fails('helptags Xdir/doc', 'E152:', getfperm('Xdir/doc/tags')) - - let rtp = &rtp - let &rtp = 'Xdir' - helptags ALL - let &rtp = rtp - - call delete('Xdir/doc/tags') - - " No permission to read the help file - call setfperm('Xdir/a/doc/sample.txt', '-w-------') - call assert_fails('helptags Xdir', 'E153:', getfperm('Xdir/a/doc/sample.txt')) - call delete('Xdir/a/doc/sample.txt') - call delete('Xdir/tags') - endif + " Test parsing tags + call writefile(['*tag1*', 'Example: >', ' *notag*', 'Example end: *tag2*'], + \ 'Xdir/a/doc/sample.txt') + helptags Xdir + call assert_equal(["tag1\ta/doc/sample.txt\t/*tag1*", + \ "tag2\ta/doc/sample.txt\t/*tag2*"], readfile('Xdir/tags')) " Duplicate tags in the help file call writefile(['*tag1*', '*tag1*', '*tag2*'], 'Xdir/a/doc/sample.txt') @@ -141,6 +170,43 @@ func Test_helptag_cmd() call delete('Xdir', 'rf') endfunc +func Test_helptag_cmd_readonly() + CheckUnix + CheckNotRoot + + " Read-only tags file + call mkdir('Xdir/doc', 'p') + call writefile([''], 'Xdir/doc/tags') + call writefile([], 'Xdir/doc/sample.txt') + call setfperm('Xdir/doc/tags', 'r-xr--r--') + call assert_fails('helptags Xdir/doc', 'E152:', getfperm('Xdir/doc/tags')) + + let rtp = &rtp + let &rtp = 'Xdir' + helptags ALL + let &rtp = rtp + + call delete('Xdir/doc/tags') + + " No permission to read the help file + call mkdir('Xdir/b/doc', 'p') + call writefile([], 'Xdir/b/doc/sample.txt') + call setfperm('Xdir/b/doc/sample.txt', '-w-------') + call assert_fails('helptags Xdir', 'E153:', getfperm('Xdir/b/doc/sample.txt')) + call delete('Xdir', 'rf') +endfunc + +" Test for setting the 'helpheight' option in the help window +func Test_help_window_height() + let &cmdheight = &lines - 23 + set helpheight=10 + help + set helpheight=14 + call assert_equal(14, winheight(0)) + set helpheight& cmdheight=1 + close +endfunc + func Test_help_long_argument() try exe 'help \%' .. repeat('0', 1021) @@ -149,5 +215,15 @@ func Test_help_long_argument() endtry endfunc +func Test_help_using_visual_match() + let lines =<< trim END + call setline(1, ' ') + /^ + exe "normal \<C-V>\<C-V>" + h5\%V] + END + call CheckScriptFailure(lines, 'E149:') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index e84726bbfc..eae1a241e3 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -1,17 +1,5 @@ " Tests for :help! {subject} -func SetUp() - " v:progpath is …/build/bin/nvim and we need …/build/runtime - " to be added to &rtp - let builddir = fnamemodify(exepath(v:progpath), ':h:h') - let s:rtp = &rtp - let &rtp .= printf(',%s/runtime', builddir) -endfunc - -func TearDown() - let &rtp = s:rtp -endfunc - func Test_help_tagjump() help call assert_equal("help", &filetype) @@ -38,18 +26,18 @@ func Test_help_tagjump() call assert_true(getline('.') =~ '\*quotestar\*') helpclose - " The test result is different in vim. There ":help ??" will jump to the - " falsy operator ??, which hasn't been ported to neovim yet. Instead, neovim - " jumps to the tag "g??". This test result needs to be changed if neovim - " ports the falsy operator. - help ?? + " help sm?le + help ch?ckhealth call assert_equal("help", &filetype) - call assert_true(getline('.') =~ '\*g??\*') + " call assert_true(getline('.') =~ '\*:smile\*') + call assert_true(getline('.') =~ '\*:checkhealth\*') helpclose - help ch?ckhealth + help ?? call assert_equal("help", &filetype) - call assert_true(getline('.') =~ '\*:checkhealth\*') + " *??* tag needs patch 8.2.1794 + " call assert_true(getline('.') =~ '\*??\*') + call assert_true(getline('.') =~ '\*g??\*') helpclose help :? diff --git a/src/nvim/testdir/test_hide.vim b/src/nvim/testdir/test_hide.vim index 41b1a4ad7c..b3ce395523 100644 --- a/src/nvim/testdir/test_hide.vim +++ b/src/nvim/testdir/test_hide.vim @@ -1,6 +1,6 @@ " Tests for :hide command/modifier and 'hidden' option -function SetUp() +func SetUp() let s:save_hidden = &hidden let s:save_bufhidden = &bufhidden let s:save_autowrite = &autowrite diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim index efdf44a0d6..8a102f2e65 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -536,9 +536,7 @@ func Test_termguicolors() endfunc func Test_cursorline_after_yank() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump call writefile([ \ 'set cul rnu', @@ -578,9 +576,7 @@ func Test_put_before_cursorline() endfunc func Test_cursorline_with_visualmode() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump call writefile([ \ 'set cul', @@ -722,7 +718,7 @@ func Test_1_highlight_Normalgroup_exists() elseif has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3') " expect is DEFAULT_FONT of gui_gtk_x11.c call assert_match('hi Normal\s*font=Monospace 10', hlNormal) - elseif has('gui_motif') || has('gui_athena') + elseif has('gui_motif') " expect is DEFAULT_FONT of gui_x11.c call assert_match('hi Normal\s*font=7x13', hlNormal) elseif has('win32') @@ -731,7 +727,8 @@ func Test_1_highlight_Normalgroup_exists() endif endfunc -function Test_no_space_before_xxx() +" Do this test last, sometimes restoring the columns doesn't work +func Test_z_no_space_before_xxx() " Note: we need to create this highlight group in the test because it does not exist in Neovim execute('hi StatusLineTermNC ctermfg=green') let l:org_columns = &columns @@ -739,13 +736,23 @@ function Test_no_space_before_xxx() let l:hi_StatusLineTermNC = join(split(execute('hi StatusLineTermNC'))) call assert_match('StatusLineTermNC xxx', l:hi_StatusLineTermNC) let &columns = l:org_columns -endfunction +endfunc " Test for :highlight command errors func Test_highlight_cmd_errors() if has('gui_running') || has('nvim') + " This test doesn't fail in the MS-Windows console version. + call assert_fails('hi Xcomment ctermfg=fg', 'E419:') + call assert_fails('hi Xcomment ctermfg=bg', 'E420:') call assert_fails('hi ' .. repeat('a', 201) .. ' ctermfg=black', 'E1249:') endif + + " Try using a very long terminal code. Define a dummy terminal code for this + " test. + let &t_fo = "\<Esc>1;" + let c = repeat("t_fo,", 100) . "t_fo" + " call assert_fails('exe "hi Xgroup1 start=" . c', 'E422:') + let &t_fo = "" endfunc " Test for using RGB color values in a highlight group @@ -812,11 +819,11 @@ func Test_highlight_clear_restores_context() let patContextDefault = fnamemodify(scriptContextDefault, ':t') .. ' line 1' let patContextRelink = fnamemodify(scriptContextRelink, ':t') .. ' line 2' - exec "source" scriptContextDefault + exec 'source ' .. scriptContextDefault let hlContextDefault = execute("verbose hi Context") call assert_match(patContextDefault, hlContextDefault) - exec "source" scriptContextRelink + exec 'source ' .. scriptContextRelink let hlContextRelink = execute("verbose hi Context") call assert_match(patContextRelink, hlContextRelink) diff --git a/src/nvim/testdir/test_history.vim b/src/nvim/testdir/test_history.vim index feb521e232..f1c31dee04 100644 --- a/src/nvim/testdir/test_history.vim +++ b/src/nvim/testdir/test_history.vim @@ -95,6 +95,23 @@ function Test_History() call assert_fails('call histnr([])', 'E730:') call assert_fails('history xyz', 'E488:') call assert_fails('history ,abc', 'E488:') + call assert_fails('call histdel(":", "\\%(")', 'E53:') +endfunction + +function Test_history_truncates_long_entry() + " History entry short enough to fit on the screen should not be truncated. + call histadd(':', 'echo x' .. repeat('y', &columns - 17) .. 'z') + let a = execute('history : -1') + + call assert_match("^\n # cmd history\n" + \ .. "> *\\d\\+ echo x" .. repeat('y', &columns - 17) .. 'z$', a) + + " Long history entry should be truncated to fit on the screen, with, '...' + " inserted in the string to indicate the that there is truncation. + call histadd(':', 'echo x' .. repeat('y', &columns - 16) .. 'z') + let a = execute('history : -1') + call assert_match("^\n # cmd history\n" + \ .. "> *\\d\\+ echo xy\\+\.\.\.y\\+z$", a) endfunction function Test_Search_history_window() diff --git a/src/nvim/testdir/test_increment.vim b/src/nvim/testdir/test_increment.vim index 2559654f25..3c2b88ef9f 100644 --- a/src/nvim/testdir/test_increment.vim +++ b/src/nvim/testdir/test_increment.vim @@ -476,6 +476,10 @@ func Test_visual_increment_20() exec "norm! \<C-A>" call assert_equal(["b"], getline(1, '$')) call assert_equal([0, 1, 1, 0], getpos('.')) + " decrement a and A and increment z and Z + call setline(1, ['a', 'A', 'z', 'Z']) + exe "normal 1G\<C-X>2G\<C-X>3G\<C-A>4G\<C-A>" + call assert_equal(['a', 'A', 'z', 'Z'], getline(1, '$')) endfunc " 21) block-wise increment on part of hexadecimal @@ -566,12 +570,14 @@ endfunc " 1) <ctrl-a> " 0b11111111111111111111111111111111 func Test_visual_increment_26() - set nrformats+=alpha + set nrformats+=bin call setline(1, ["0b11111111111111111111111111111110"]) exec "norm! \<C-V>$\<C-A>" call assert_equal(["0b11111111111111111111111111111111"], getline(1, '$')) call assert_equal([0, 1, 1, 0], getpos('.')) - set nrformats-=alpha + exec "norm! \<C-V>$\<C-X>" + call assert_equal(["0b11111111111111111111111111111110"], getline(1, '$')) + set nrformats-=bin endfunc " 27) increment with 'rightreft', if supported @@ -772,7 +778,6 @@ func Test_normal_increment_03() endfunc func Test_increment_empty_line() - new call setline(1, ['0', '0', '0', '0', '0', '0', '']) exe "normal Gvgg\<C-A>" call assert_equal(['1', '1', '1', '1', '1', '1', ''], getline(1, 7)) @@ -783,8 +788,13 @@ func Test_increment_empty_line() exe "normal! c\<C-A>l" exe "normal! c\<C-X>l" call assert_equal('one two', getline(1)) +endfunc - bwipe! +" Try incrementing/decrementing a non-digit/alpha character +func Test_increment_special_char() + call setline(1, '!') + call assert_beeps("normal \<C-A>") + call assert_beeps("normal \<C-X>") endfunc " Try incrementing/decrementing a number when nrformats contains unsigned @@ -867,4 +877,21 @@ func Test_normal_increment_with_virtualedit() set virtualedit& endfunc +" Test for incrementing a signed hexadecimal and octal number +func Test_normal_increment_signed_hexoct_nr() + new + " negative sign before a hex number should be ignored + call setline(1, ["-0x9"]) + exe "norm \<C-A>" + call assert_equal(["-0xa"], getline(1, '$')) + exe "norm \<C-X>" + call assert_equal(["-0x9"], getline(1, '$')) + call setline(1, ["-007"]) + exe "norm \<C-A>" + call assert_equal(["-010"], getline(1, '$')) + exe "norm \<C-X>" + call assert_equal(["-007"], getline(1, '$')) + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index 179218e48a..ec1379a378 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -1,5 +1,8 @@ +" Test for insert completion + source screendump.vim source check.vim +source vim9.vim " Test for insert expansion func Test_ins_complete() @@ -44,11 +47,11 @@ func Test_ins_complete() exe "normal o\<C-X>\<C-P>\<C-P>\<C-X>\<C-X>\<C-N>\<C-X>\<C-N>\<C-N>" call assert_equal('run1 run2', getline('.')) - set cpt=.,w,i + set cpt=.,\ ,w,i " i-add-expands and switches to local exe "normal OM\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-X>\<C-X>\<C-P>" call assert_equal("Makefile\tto\trun3", getline('.')) - " add-expands lines (it would end in an empty line if it didn't ignored + " add-expands lines (it would end in an empty line if it didn't ignore " itself) exe "normal o\<C-X>\<C-L>\<C-X>\<C-L>\<C-P>\<C-P>" call assert_equal("Makefile\tto\trun3", getline('.')) @@ -68,6 +71,11 @@ func Test_ins_complete() call assert_equal('Xtest11.one', getline('.')) normal ddk + " Test for expanding a non-existing filename + exe "normal oa1b2X3Y4\<C-X>\<C-F>" + call assert_equal('a1b2X3Y4', getline('.')) + normal ddk + set cpt=w " checks make_cyclic in other window exe "normal oST\<C-N>\<C-P>\<C-P>\<C-P>\<C-P>" @@ -183,17 +191,17 @@ func s:CompleteDone_CompleteFuncDict( findstart, base ) endif return { - \ 'words': [ - \ { - \ 'word': 'aword', - \ 'abbr': 'wrd', - \ 'menu': 'extra text', - \ 'info': 'words are cool', - \ 'kind': 'W', - \ 'user_data': ['one', 'two'] - \ } - \ ] - \ } + \ 'words': [ + \ { + \ 'word': 'aword', + \ 'abbr': 'wrd', + \ 'menu': 'extra text', + \ 'info': 'words are cool', + \ 'kind': 'W', + \ 'user_data': ['one', 'two'] + \ } + \ ] + \ } endfunc func s:CompleteDone_CheckCompletedItemNone() @@ -253,16 +261,16 @@ func s:CompleteDone_CompleteFuncDictNoUserData(findstart, base) endif return { - \ 'words': [ - \ { - \ 'word': 'aword', - \ 'abbr': 'wrd', - \ 'menu': 'extra text', - \ 'info': 'words are cool', - \ 'kind': 'W' - \ } - \ ] - \ } + \ 'words': [ + \ { + \ 'word': 'aword', + \ 'abbr': 'wrd', + \ 'menu': 'extra text', + \ 'info': 'words are cool', + \ 'kind': 'W', + \ } + \ ] + \ } endfunc func s:CompleteDone_CheckCompletedItemDictNoUserData() @@ -573,6 +581,33 @@ func Test_pum_with_folds_two_tabs() call delete('Xpumscript') endfunc +func Test_pum_with_preview_win() + CheckScreendump + + let lines =<< trim END + funct Omni_test(findstart, base) + if a:findstart + return col(".") - 1 + endif + return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}] + endfunc + set omnifunc=Omni_test + set completeopt+=longest + END + + call writefile(lines, 'Xpreviewscript') + let buf = RunVimInTerminal('-S Xpreviewscript', #{rows: 12}) + call term_wait(buf, 100) + call term_sendkeys(buf, "Gi\<C-X>\<C-O>") + call term_wait(buf, 200) + call term_sendkeys(buf, "\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_with_preview_win', {}) + + call term_sendkeys(buf, "\<Esc>") + call StopVimInTerminal(buf) + call delete('Xpreviewscript') +endfunc + " Test for inserting the tag search pattern in insert mode func Test_ins_compl_tag_sft() call writefile([ @@ -682,6 +717,42 @@ func Test_complete_func_error() call assert_equal([], complete_info(['items']).items) endfunc +" Test for recursively starting completion mode using complete() +func Test_recursive_complete_func() + func ListColors() + call complete(5, ["red", "blue"]) + return '' + endfunc + new + call setline(1, ['a1', 'a2']) + set complete=. + exe "normal Goa\<C-X>\<C-L>\<C-R>=ListColors()\<CR>\<C-N>" + call assert_equal('a2blue', getline(3)) + delfunc ListColors + bw! +endfunc + +" Test for using complete() with completeopt+=longest +func Test_complete_with_longest() + new + inoremap <buffer> <f3> <cmd>call complete(1, ["iaax", "iaay", "iaaz"])<cr> + + " default: insert first match + set completeopt& + call setline(1, ['i']) + exe "normal Aa\<f3>\<esc>" + call assert_equal('iaax', getline(1)) + + " with longest: insert longest prefix + set completeopt+=longest + call setline(1, ['i']) + exe "normal Aa\<f3>\<esc>" + call assert_equal('iaa', getline(1)) + set completeopt& + bwipe! +endfunc + + " Test for completing words following a completed word in a line func Test_complete_wrapscan() " complete words from another buffer @@ -721,6 +792,17 @@ func Test_complete_across_line() close! endfunc +" Test for completing words with a '.' at the end of a word. +func Test_complete_joinspaces() + new + call setline(1, ['one two.', 'three. four']) + set joinspaces + exe "normal Goon\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>" + call assert_equal("one two. three. four", getline(3)) + set joinspaces& + bw! +endfunc + " Test for using CTRL-L to add one character when completing matching func Test_complete_add_onechar() new @@ -741,6 +823,39 @@ func Test_complete_add_onechar() close! endfunc +" Test for using CTRL-X CTRL-L to complete whole lines lines +func Test_complete_wholeline() + new + " complete one-line + call setline(1, ['a1', 'a2']) + exe "normal ggoa\<C-X>\<C-L>" + call assert_equal(['a1', 'a1', 'a2'], getline(1, '$')) + " go to the next match (wrapping around the buffer) + exe "normal 2GCa\<C-X>\<C-L>\<C-N>" + call assert_equal(['a1', 'a', 'a2'], getline(1, '$')) + " go to the next match + exe "normal 2GCa\<C-X>\<C-L>\<C-N>\<C-N>" + call assert_equal(['a1', 'a2', 'a2'], getline(1, '$')) + exe "normal 2GCa\<C-X>\<C-L>\<C-N>\<C-N>\<C-N>" + call assert_equal(['a1', 'a1', 'a2'], getline(1, '$')) + " repeat the test using CTRL-L + " go to the next match (wrapping around the buffer) + exe "normal 2GCa\<C-X>\<C-L>\<C-L>" + call assert_equal(['a1', 'a2', 'a2'], getline(1, '$')) + " go to the next match + exe "normal 2GCa\<C-X>\<C-L>\<C-L>\<C-L>" + call assert_equal(['a1', 'a', 'a2'], getline(1, '$')) + exe "normal 2GCa\<C-X>\<C-L>\<C-L>\<C-L>\<C-L>" + call assert_equal(['a1', 'a1', 'a2'], getline(1, '$')) + %d + " use CTRL-X CTRL-L to add one more line + call setline(1, ['a1', 'b1']) + setlocal complete=. + exe "normal ggOa\<C-X>\<C-L>\<C-X>\<C-L>\<C-X>\<C-L>" + call assert_equal(['a1', 'b1', '', 'a1', 'b1'], getline(1, '$')) + bw! +endfunc + " Test insert completion with 'cindent' (adjust the indent) func Test_complete_with_cindent() new @@ -829,6 +944,25 @@ func Test_complete_stop() close! endfunc +" Test for typing CTRL-R in insert completion mode to insert a register +" content. +func Test_complete_reginsert() + new + call setline(1, ['a1', 'a12', 'a123', 'a1234']) + + " if a valid CTRL-X mode key is returned from <C-R>=, then it should be + " processed. Otherwise, CTRL-X mode should be stopped and the key should be + " inserted. + exe "normal Goa\<C-P>\<C-R>=\"\\<C-P>\"\<CR>" + call assert_equal('a123', getline(5)) + let @r = "\<C-P>\<C-P>" + exe "normal GCa\<C-P>\<C-R>r" + call assert_equal('a12', getline(5)) + exe "normal GCa\<C-P>\<C-R>=\"x\"\<CR>" + call assert_equal('a1234x', getline(5)) + bw! +endfunc + func Test_issue_7021() CheckMSWindows @@ -842,6 +976,322 @@ func Test_issue_7021() set completeslash= endfunc +" Test for 'longest' setting in 'completeopt' with latin1 and utf-8 encodings +func Test_complete_longest_match() + " for e in ['latin1', 'utf-8'] + for e in ['utf-8'] + exe 'set encoding=' .. e + new + set complete=. + set completeopt=menu,longest + call setline(1, ['pfx_a1', 'pfx_a12', 'pfx_a123', 'pfx_b1']) + exe "normal Gopfx\<C-P>" + call assert_equal('pfx_', getline(5)) + bw! + endfor + + " Test for completing additional words with longest match set + new + call setline(1, ['abc1', 'abd2']) + exe "normal Goab\<C-P>\<C-X>\<C-P>" + call assert_equal('ab', getline(3)) + bw! + set complete& completeopt& +endfunc + +" Test for removing the first displayed completion match and selecting the +" match just before that. +func Test_complete_erase_firstmatch() + new + call setline(1, ['a12', 'a34', 'a56']) + set complete=. + exe "normal Goa\<C-P>\<BS>\<BS>3\<CR>" + call assert_equal('a34', getline('$')) + set complete& + bw! +endfunc + +" Test for completing words from unloaded buffers +func Test_complete_from_unloadedbuf() + call writefile(['abc'], "Xfile1") + call writefile(['def'], "Xfile2") + edit Xfile1 + edit Xfile2 + new | close + enew + bunload Xfile1 Xfile2 + set complete=u + " complete from an unloaded buffer + exe "normal! ia\<C-P>" + call assert_equal('abc', getline(1)) + exe "normal! od\<C-P>" + call assert_equal('def', getline(2)) + set complete& + %bw! + call delete("Xfile1") + call delete("Xfile2") +endfunc + +" Test for completing whole lines from unloaded buffers +func Test_complete_wholeline_unloadedbuf() + call writefile(['a line1', 'a line2', 'a line3'], "Xfile1") + edit Xfile1 + enew + set complete=u + exe "normal! ia\<C-X>\<C-L>\<C-P>" + call assert_equal('a line2', getline(1)) + %d + " completing from an unlisted buffer should fail + bdel Xfile1 + exe "normal! ia\<C-X>\<C-L>\<C-P>" + call assert_equal('a', getline(1)) + set complete& + %bw! + call delete("Xfile1") +endfunc + +" Test for completing words from unlisted buffers +func Test_complete_from_unlistedbuf() + call writefile(['abc'], "Xfile1") + call writefile(['def'], "Xfile2") + edit Xfile1 + edit Xfile2 + new | close + bdel Xfile1 Xfile2 + set complete=U + " complete from an unlisted buffer + exe "normal! ia\<C-P>" + call assert_equal('abc', getline(1)) + exe "normal! od\<C-P>" + call assert_equal('def', getline(2)) + set complete& + %bw! + call delete("Xfile1") + call delete("Xfile2") +endfunc + +" Test for completing whole lines from unlisted buffers +func Test_complete_wholeline_unlistedbuf() + call writefile(['a line1', 'a line2', 'a line3'], "Xfile1") + edit Xfile1 + enew + set complete=U + " completing from a unloaded buffer should fail + exe "normal! ia\<C-X>\<C-L>\<C-P>" + call assert_equal('a', getline(1)) + %d + bdel Xfile1 + exe "normal! ia\<C-X>\<C-L>\<C-P>" + call assert_equal('a line2', getline(1)) + set complete& + %bw! + call delete("Xfile1") +endfunc + +" Test for adding a multibyte character using CTRL-L in completion mode +func Test_complete_mbyte_char_add() + new + set complete=. + call setline(1, 'abė') + exe "normal! oa\<C-P>\<BS>\<BS>\<C-L>\<C-L>" + call assert_equal('abė', getline(2)) + " Test for a leader with multibyte character + %d + call setline(1, 'abėĕ') + exe "normal! oabė\<C-P>" + call assert_equal('abėĕ', getline(2)) + bw! +endfunc + +" Test for using <C-X><C-P> for local expansion even if 'complete' is set to +" not to complete matches from the local buffer. Also test using multiple +" <C-X> to cancel the current completion mode. +func Test_complete_local_expansion() + new + set complete=t + call setline(1, ['abc', 'def']) + exe "normal! Go\<C-X>\<C-P>" + call assert_equal("def", getline(3)) + exe "normal! Go\<C-P>" + call assert_equal("", getline(4)) + exe "normal! Go\<C-X>\<C-N>" + call assert_equal("abc", getline(5)) + exe "normal! Go\<C-N>" + call assert_equal("", getline(6)) + + " use multiple <C-X> to cancel the previous completion mode + exe "normal! Go\<C-P>\<C-X>\<C-P>" + call assert_equal("", getline(7)) + exe "normal! Go\<C-P>\<C-X>\<C-X>\<C-P>" + call assert_equal("", getline(8)) + exe "normal! Go\<C-P>\<C-X>\<C-X>\<C-X>\<C-P>" + call assert_equal("abc", getline(9)) + + " interrupt the current completion mode + set completeopt=menu,noinsert + exe "normal! Go\<C-X>\<C-F>\<C-X>\<C-X>\<C-P>\<C-Y>" + call assert_equal("abc", getline(10)) + + " when only one <C-X> is used to interrupt, do normal expansion + exe "normal! Go\<C-X>\<C-F>\<C-X>\<C-P>" + call assert_equal("", getline(11)) + set completeopt& + + " using two <C-X> in non-completion mode and restarting the same mode + exe "normal! God\<C-X>\<C-X>\<C-P>\<C-X>\<C-X>\<C-P>\<C-Y>" + call assert_equal("def", getline(12)) + + " test for adding a match from the original empty text + %d + call setline(1, 'abc def g') + exe "normal! o\<C-X>\<C-P>\<C-N>\<C-X>\<C-P>" + call assert_equal('def', getline(2)) + exe "normal! 0C\<C-X>\<C-N>\<C-P>\<C-X>\<C-N>" + call assert_equal('abc', getline(2)) + + bw! +endfunc + +" Test for undoing changes after a insert-mode completion +func Test_complete_undo() + new + set complete=. + " undo with 'ignorecase' + call setline(1, ['ABOVE', 'BELOW']) + set ignorecase + exe "normal! Goab\<C-G>u\<C-P>" + call assert_equal("ABOVE", getline(3)) + undo + call assert_equal("ab", getline(3)) + set ignorecase& + %d + " undo with longest match + set completeopt=menu,longest + call setline(1, ['above', 'about']) + exe "normal! Goa\<C-G>u\<C-P>" + call assert_equal("abo", getline(3)) + undo + call assert_equal("a", getline(3)) + set completeopt& + %d + " undo for line completion + call setline(1, ['above that change', 'below that change']) + exe "normal! Goabove\<C-G>u\<C-X>\<C-L>" + call assert_equal("above that change", getline(3)) + undo + call assert_equal("above", getline(3)) + + bw! +endfunc + +" Test for completing a very long word +func Test_complete_long_word() + set complete& + new + call setline(1, repeat('x', 950) .. ' one two three') + exe "normal! Gox\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>" + call assert_equal(repeat('x', 950) .. ' one two three', getline(2)) + %d + " should fail when more than 950 characters are in a word + call setline(1, repeat('x', 951) .. ' one two three') + exe "normal! Gox\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>\<C-X>\<C-P>" + call assert_equal(repeat('x', 951), getline(2)) + + " Test for adding a very long word to an existing completion + %d + call setline(1, ['abc', repeat('x', 1016) .. '012345']) + exe "normal! Goab\<C-P>\<C-X>\<C-P>" + call assert_equal('abc ' .. repeat('x', 1016) .. '0123', getline(3)) + bw! +endfunc + +" Test for some fields in the complete items used by complete() +func Test_complete_items() + func CompleteItems(idx) + let items = [[#{word: "one", dup: 1, user_data: 'u1'}, #{word: "one", dup: 1, user_data: 'u2'}], + \ [#{word: "one", dup: 0, user_data: 'u3'}, #{word: "one", dup: 0, user_data: 'u4'}], + \ [#{word: "one", icase: 1, user_data: 'u7'}, #{word: "oNE", icase: 1, user_data: 'u8'}], + \ [#{user_data: 'u9'}], + \ [#{word: "", user_data: 'u10'}], + \ [#{word: "", empty: 1, user_data: 'u11'}]] + call complete(col('.'), items[a:idx]) + return '' + endfunc + new + exe "normal! i\<C-R>=CompleteItems(0)\<CR>\<C-N>\<C-Y>" + call assert_equal('u2', v:completed_item.user_data) + call assert_equal('one', getline(1)) + exe "normal! o\<C-R>=CompleteItems(1)\<CR>\<C-Y>" + call assert_equal('u3', v:completed_item.user_data) + call assert_equal('one', getline(2)) + exe "normal! o\<C-R>=CompleteItems(1)\<CR>\<C-N>" + call assert_equal('', getline(3)) + set completeopt=menu,noinsert + exe "normal! o\<C-R>=CompleteItems(2)\<CR>one\<C-N>\<C-Y>" + call assert_equal('oNE', getline(4)) + call assert_equal('u8', v:completed_item.user_data) + set completeopt& + exe "normal! o\<C-R>=CompleteItems(3)\<CR>" + call assert_equal('', getline(5)) + exe "normal! o\<C-R>=CompleteItems(4)\<CR>" + call assert_equal('', getline(6)) + exe "normal! o\<C-R>=CompleteItems(5)\<CR>" + call assert_equal('', getline(7)) + call assert_equal('u11', v:completed_item.user_data) + " pass invalid argument to complete() + let cmd = "normal! o\<C-R>=complete(1, [[]])\<CR>" + call assert_fails('exe cmd', 'E730:') + bw! + delfunc CompleteItems +endfunc + +" Test for the "refresh" item in the dict returned by an insert completion +" function +func Test_complete_item_refresh_always() + let g:CallCount = 0 + func! Tcomplete(findstart, base) + if a:findstart + " locate the start of the word + let line = getline('.') + let start = col('.') - 1 + while start > 0 && line[start - 1] =~ '\a' + let start -= 1 + endwhile + return start + else + let g:CallCount += 1 + let res = ["update1", "update12", "update123"] + return #{words: res, refresh: 'always'} + endif + endfunc + new + set completeopt=menu,longest + set completefunc=Tcomplete + exe "normal! iup\<C-X>\<C-U>\<BS>\<BS>\<BS>\<BS>\<BS>" + call assert_equal('up', getline(1)) + call assert_equal(2, g:CallCount) + set completeopt& + set completefunc& + bw! + delfunc Tcomplete +endfunc + +" Test for completing from a thesaurus file without read permission +func Test_complete_unreadable_thesaurus_file() + CheckUnix + CheckNotRoot + + call writefile(['about', 'above'], 'Xfile') + call setfperm('Xfile', '---r--r--') + new + set complete=sXfile + exe "normal! ia\<C-P>" + call assert_equal('a', getline(1)) + bw! + call delete('Xfile') + set complete& +endfunc + " Test to ensure 'Scanning...' messages are not recorded in messages history func Test_z1_complete_no_history() new @@ -857,7 +1307,7 @@ endfunc " A mapping is not used for the key after CTRL-X. func Test_no_mapping_for_ctrl_x_key() new - inoremap <C-K> <Cmd>let was_mapped = 'yes'<CR> + inoremap <buffer> <C-K> <Cmd>let was_mapped = 'yes'<CR> setlocal dictionary=README.txt call feedkeys("aexam\<C-X>\<C-K> ", 'xt') call assert_equal('example ', getline(1)) @@ -865,6 +1315,813 @@ func Test_no_mapping_for_ctrl_x_key() bwipe! endfunc +" Test for different ways of setting the 'completefunc' option +func Test_completefunc_callback() + func CompleteFunc1(callnr, findstart, base) + call add(g:CompleteFunc1Args, [a:callnr, a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + func CompleteFunc2(findstart, base) + call add(g:CompleteFunc2Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + + let lines =<< trim END + #" Test for using a global function name + LET &completefunc = 'g:CompleteFunc2' + new + call setline(1, 'global') + LET g:CompleteFunc2Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'global']], g:CompleteFunc2Args) + bw! + + #" Test for using a function() + set completefunc=function('g:CompleteFunc1',\ [10]) + new + call setline(1, 'one') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[10, 1, ''], [10, 0, 'one']], g:CompleteFunc1Args) + bw! + + #" Using a funcref variable to set 'completefunc' + VAR Fn = function('g:CompleteFunc1', [11]) + LET &completefunc = Fn + new + call setline(1, 'two') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[11, 1, ''], [11, 0, 'two']], g:CompleteFunc1Args) + bw! + + #" Using string(funcref_variable) to set 'completefunc' + LET Fn = function('g:CompleteFunc1', [12]) + LET &completefunc = string(Fn) + new + call setline(1, 'two') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[12, 1, ''], [12, 0, 'two']], g:CompleteFunc1Args) + bw! + + #" Test for using a funcref() + set completefunc=funcref('g:CompleteFunc1',\ [13]) + new + call setline(1, 'three') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[13, 1, ''], [13, 0, 'three']], g:CompleteFunc1Args) + bw! + + #" Using a funcref variable to set 'completefunc' + LET Fn = funcref('g:CompleteFunc1', [14]) + LET &completefunc = Fn + new + call setline(1, 'four') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[14, 1, ''], [14, 0, 'four']], g:CompleteFunc1Args) + bw! + + #" Using a string(funcref_variable) to set 'completefunc' + LET Fn = funcref('g:CompleteFunc1', [15]) + LET &completefunc = string(Fn) + new + call setline(1, 'four') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[15, 1, ''], [15, 0, 'four']], g:CompleteFunc1Args) + bw! + + #" Test for using a lambda function with set + VAR optval = "LSTART a, b LMIDDLE CompleteFunc1(16, a, b) LEND" + LET optval = substitute(optval, ' ', '\\ ', 'g') + exe "set completefunc=" .. optval + new + call setline(1, 'five') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[16, 1, ''], [16, 0, 'five']], g:CompleteFunc1Args) + bw! + + #" Set 'completefunc' to a lambda expression + LET &completefunc = LSTART a, b LMIDDLE CompleteFunc1(17, a, b) LEND + new + call setline(1, 'six') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[17, 1, ''], [17, 0, 'six']], g:CompleteFunc1Args) + bw! + + #" Set 'completefunc' to string(lambda_expression) + LET &completefunc = 'LSTART a, b LMIDDLE CompleteFunc1(18, a, b) LEND' + new + call setline(1, 'six') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[18, 1, ''], [18, 0, 'six']], g:CompleteFunc1Args) + bw! + + #" Set 'completefunc' to a variable with a lambda expression + VAR Lambda = LSTART a, b LMIDDLE CompleteFunc1(19, a, b) LEND + LET &completefunc = Lambda + new + call setline(1, 'seven') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:CompleteFunc1Args) + bw! + + #" Set 'completefunc' to a string(variable with a lambda expression) + LET Lambda = LSTART a, b LMIDDLE CompleteFunc1(20, a, b) LEND + LET &completefunc = string(Lambda) + new + call setline(1, 'seven') + LET g:CompleteFunc1Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:CompleteFunc1Args) + bw! + + #" Test for using a lambda function with incorrect return value + LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND + LET &completefunc = Lambda + new + call setline(1, 'eight') + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + bw! + + #" Test for clearing the 'completefunc' option + set completefunc='' + set completefunc& + call assert_fails("set completefunc=function('abc')", "E700:") + call assert_fails("set completefunc=funcref('abc')", "E700:") + + #" set 'completefunc' to a non-existing function + set completefunc=CompleteFunc2 + call setline(1, 'five') + call assert_fails("set completefunc=function('NonExistingFunc')", 'E700:') + call assert_fails("LET &completefunc = function('NonExistingFunc')", 'E700:') + LET g:CompleteFunc2Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'five']], g:CompleteFunc2Args) + bw! + END + call CheckLegacyAndVim9Success(lines) + + " Test for using a script-local function name + func s:CompleteFunc3(findstart, base) + call add(g:CompleteFunc3Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + set completefunc=s:CompleteFunc3 + new + call setline(1, 'script1') + let g:CompleteFunc3Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script1']], g:CompleteFunc3Args) + bw! + + let &completefunc = 's:CompleteFunc3' + new + call setline(1, 'script2') + let g:CompleteFunc3Args = [] + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script2']], g:CompleteFunc3Args) + bw! + delfunc s:CompleteFunc3 + + " invalid return value + let &completefunc = {a -> 'abc'} + call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + + " Using Vim9 lambda expression in legacy context should fail + " set completefunc=(a,\ b)\ =>\ CompleteFunc1(21,\ a,\ b) + new | only + let g:CompleteFunc1Args = [] + " call assert_fails('call feedkeys("A\<C-X>\<C-U>\<Esc>", "x")', 'E117:') + call assert_equal([], g:CompleteFunc1Args) + + " set 'completefunc' to a partial with dict. This used to cause a crash. + func SetCompleteFunc() + let params = {'complete': function('g:DictCompleteFunc')} + let &completefunc = params.complete + endfunc + func g:DictCompleteFunc(_) dict + endfunc + call SetCompleteFunc() + new + call SetCompleteFunc() + bw + call test_garbagecollect_now() + new + set completefunc= + wincmd w + set completefunc= + %bw! + delfunc g:DictCompleteFunc + delfunc SetCompleteFunc + + " Vim9 tests + let lines =<< trim END + vim9script + + def Vim9CompleteFunc(callnr: number, findstart: number, base: string): any + add(g:Vim9completeFuncArgs, [callnr, findstart, base]) + return findstart ? 0 : [] + enddef + + # Test for using a def function with completefunc + set completefunc=function('Vim9CompleteFunc',\ [60]) + new | only + setline(1, 'one') + g:Vim9completeFuncArgs = [] + feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9completeFuncArgs) + bw! + + # Test for using a global function name + &completefunc = g:CompleteFunc2 + new | only + setline(1, 'two') + g:CompleteFunc2Args = [] + feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'two']], g:CompleteFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalCompleteFunc(findstart: number, base: string): any + add(g:LocalCompleteFuncArgs, [findstart, base]) + return findstart ? 0 : [] + enddef + &completefunc = s:LocalCompleteFunc + new | only + setline(1, 'three') + g:LocalCompleteFuncArgs = [] + feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'three']], g:LocalCompleteFuncArgs) + bw! + END + call CheckScriptSuccess(lines) + + " cleanup + set completefunc& + delfunc CompleteFunc1 + delfunc CompleteFunc2 + unlet g:CompleteFunc1Args g:CompleteFunc2Args + %bw! +endfunc + +" Test for different ways of setting the 'omnifunc' option +func Test_omnifunc_callback() + func OmniFunc1(callnr, findstart, base) + call add(g:OmniFunc1Args, [a:callnr, a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + func OmniFunc2(findstart, base) + call add(g:OmniFunc2Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + + let lines =<< trim END + #" Test for using a function name + LET &omnifunc = 'g:OmniFunc2' + new + call setline(1, 'zero') + LET g:OmniFunc2Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'zero']], g:OmniFunc2Args) + bw! + + #" Test for using a function() + set omnifunc=function('g:OmniFunc1',\ [10]) + new + call setline(1, 'one') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[10, 1, ''], [10, 0, 'one']], g:OmniFunc1Args) + bw! + + #" Using a funcref variable to set 'omnifunc' + VAR Fn = function('g:OmniFunc1', [11]) + LET &omnifunc = Fn + new + call setline(1, 'two') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[11, 1, ''], [11, 0, 'two']], g:OmniFunc1Args) + bw! + + #" Using a string(funcref_variable) to set 'omnifunc' + LET Fn = function('g:OmniFunc1', [12]) + LET &omnifunc = string(Fn) + new + call setline(1, 'two') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[12, 1, ''], [12, 0, 'two']], g:OmniFunc1Args) + bw! + + #" Test for using a funcref() + set omnifunc=funcref('g:OmniFunc1',\ [13]) + new + call setline(1, 'three') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[13, 1, ''], [13, 0, 'three']], g:OmniFunc1Args) + bw! + + #" Use let to set 'omnifunc' to a funcref + LET Fn = funcref('g:OmniFunc1', [14]) + LET &omnifunc = Fn + new + call setline(1, 'four') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[14, 1, ''], [14, 0, 'four']], g:OmniFunc1Args) + bw! + + #" Using a string(funcref) to set 'omnifunc' + LET Fn = funcref("g:OmniFunc1", [15]) + LET &omnifunc = string(Fn) + new + call setline(1, 'four') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[15, 1, ''], [15, 0, 'four']], g:OmniFunc1Args) + bw! + + #" Test for using a lambda function with set + VAR optval = "LSTART a, b LMIDDLE OmniFunc1(16, a, b) LEND" + LET optval = substitute(optval, ' ', '\\ ', 'g') + exe "set omnifunc=" .. optval + new + call setline(1, 'five') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[16, 1, ''], [16, 0, 'five']], g:OmniFunc1Args) + bw! + + #" Set 'omnifunc' to a lambda expression + LET &omnifunc = LSTART a, b LMIDDLE OmniFunc1(17, a, b) LEND + new + call setline(1, 'six') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[17, 1, ''], [17, 0, 'six']], g:OmniFunc1Args) + bw! + + #" Set 'omnifunc' to a string(lambda_expression) + LET &omnifunc = 'LSTART a, b LMIDDLE OmniFunc1(18, a, b) LEND' + new + call setline(1, 'six') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[18, 1, ''], [18, 0, 'six']], g:OmniFunc1Args) + bw! + + #" Set 'omnifunc' to a variable with a lambda expression + VAR Lambda = LSTART a, b LMIDDLE OmniFunc1(19, a, b) LEND + LET &omnifunc = Lambda + new + call setline(1, 'seven') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:OmniFunc1Args) + bw! + + #" Set 'omnifunc' to a string(variable with a lambda expression) + LET Lambda = LSTART a, b LMIDDLE OmniFunc1(20, a, b) LEND + LET &omnifunc = string(Lambda) + new + call setline(1, 'seven') + LET g:OmniFunc1Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:OmniFunc1Args) + bw! + + #" Test for using a lambda function with incorrect return value + LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND + LET &omnifunc = Lambda + new + call setline(1, 'eight') + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + bw! + + #" Test for clearing the 'omnifunc' option + set omnifunc='' + set omnifunc& + call assert_fails("set omnifunc=function('abc')", "E700:") + call assert_fails("set omnifunc=funcref('abc')", "E700:") + + #" set 'omnifunc' to a non-existing function + set omnifunc=OmniFunc2 + call setline(1, 'nine') + call assert_fails("set omnifunc=function('NonExistingFunc')", 'E700:') + call assert_fails("LET &omnifunc = function('NonExistingFunc')", 'E700:') + LET g:OmniFunc2Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'nine']], g:OmniFunc2Args) + bw! + END + call CheckLegacyAndVim9Success(lines) + + " Test for using a script-local function name + func s:OmniFunc3(findstart, base) + call add(g:OmniFunc3Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + set omnifunc=s:OmniFunc3 + new + call setline(1, 'script1') + let g:OmniFunc3Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script1']], g:OmniFunc3Args) + bw! + + let &omnifunc = 's:OmniFunc3' + new + call setline(1, 'script2') + let g:OmniFunc3Args = [] + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script2']], g:OmniFunc3Args) + bw! + delfunc s:OmniFunc3 + + " invalid return value + let &omnifunc = {a -> 'abc'} + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + + " Using Vim9 lambda expression in legacy context should fail + " set omnifunc=(a,\ b)\ =>\ OmniFunc1(21,\ a,\ b) + new | only + let g:OmniFunc1Args = [] + " call assert_fails('call feedkeys("A\<C-X>\<C-O>\<Esc>", "x")', 'E117:') + call assert_equal([], g:OmniFunc1Args) + + " set 'omnifunc' to a partial with dict. This used to cause a crash. + func SetOmniFunc() + let params = {'omni': function('g:DictOmniFunc')} + let &omnifunc = params.omni + endfunc + func g:DictOmniFunc(_) dict + endfunc + call SetOmniFunc() + new + call SetOmniFunc() + bw + call test_garbagecollect_now() + new + set omnifunc= + wincmd w + set omnifunc= + %bw! + delfunc g:DictOmniFunc + delfunc SetOmniFunc + + " Vim9 tests + let lines =<< trim END + vim9script + + def Vim9omniFunc(callnr: number, findstart: number, base: string): any + add(g:Vim9omniFunc_Args, [callnr, findstart, base]) + return findstart ? 0 : [] + enddef + + # Test for using a def function with omnifunc + set omnifunc=function('Vim9omniFunc',\ [60]) + new | only + setline(1, 'one') + g:Vim9omniFunc_Args = [] + feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9omniFunc_Args) + bw! + + # Test for using a global function name + &omnifunc = g:OmniFunc2 + new | only + setline(1, 'two') + g:OmniFunc2Args = [] + feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'two']], g:OmniFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalOmniFunc(findstart: number, base: string): any + add(g:LocalOmniFuncArgs, [findstart, base]) + return findstart ? 0 : [] + enddef + &omnifunc = s:LocalOmniFunc + new | only + setline(1, 'three') + g:LocalOmniFuncArgs = [] + feedkeys("A\<C-X>\<C-O>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'three']], g:LocalOmniFuncArgs) + bw! + END + call CheckScriptSuccess(lines) + + " cleanup + set omnifunc& + delfunc OmniFunc1 + delfunc OmniFunc2 + unlet g:OmniFunc1Args g:OmniFunc2Args + %bw! +endfunc + +" Test for different ways of setting the 'thesaurusfunc' option +func Test_thesaurusfunc_callback() + func TsrFunc1(callnr, findstart, base) + call add(g:TsrFunc1Args, [a:callnr, a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + func TsrFunc2(findstart, base) + call add(g:TsrFunc2Args, [a:findstart, a:base]) + return a:findstart ? 0 : ['sunday'] + endfunc + + let lines =<< trim END + #" Test for using a function name + LET &thesaurusfunc = 'g:TsrFunc2' + new + call setline(1, 'zero') + LET g:TsrFunc2Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'zero']], g:TsrFunc2Args) + bw! + + #" Test for using a function() + set thesaurusfunc=function('g:TsrFunc1',\ [10]) + new + call setline(1, 'one') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[10, 1, ''], [10, 0, 'one']], g:TsrFunc1Args) + bw! + + #" Using a funcref variable to set 'thesaurusfunc' + VAR Fn = function('g:TsrFunc1', [11]) + LET &thesaurusfunc = Fn + new + call setline(1, 'two') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[11, 1, ''], [11, 0, 'two']], g:TsrFunc1Args) + bw! + + #" Using a string(funcref_variable) to set 'thesaurusfunc' + LET Fn = function('g:TsrFunc1', [12]) + LET &thesaurusfunc = string(Fn) + new + call setline(1, 'two') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[12, 1, ''], [12, 0, 'two']], g:TsrFunc1Args) + bw! + + #" Test for using a funcref() + set thesaurusfunc=funcref('g:TsrFunc1',\ [13]) + new + call setline(1, 'three') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[13, 1, ''], [13, 0, 'three']], g:TsrFunc1Args) + bw! + + #" Using a funcref variable to set 'thesaurusfunc' + LET Fn = funcref('g:TsrFunc1', [14]) + LET &thesaurusfunc = Fn + new + call setline(1, 'four') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[14, 1, ''], [14, 0, 'four']], g:TsrFunc1Args) + bw! + + #" Using a string(funcref_variable) to set 'thesaurusfunc' + LET Fn = funcref('g:TsrFunc1', [15]) + LET &thesaurusfunc = string(Fn) + new + call setline(1, 'four') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[15, 1, ''], [15, 0, 'four']], g:TsrFunc1Args) + bw! + + #" Test for using a lambda function + VAR optval = "LSTART a, b LMIDDLE TsrFunc1(16, a, b) LEND" + LET optval = substitute(optval, ' ', '\\ ', 'g') + exe "set thesaurusfunc=" .. optval + new + call setline(1, 'five') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[16, 1, ''], [16, 0, 'five']], g:TsrFunc1Args) + bw! + + #" Test for using a lambda function with set + LET &thesaurusfunc = LSTART a, b LMIDDLE TsrFunc1(17, a, b) LEND + new + call setline(1, 'six') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[17, 1, ''], [17, 0, 'six']], g:TsrFunc1Args) + bw! + + #" Set 'thesaurusfunc' to a string(lambda expression) + LET &thesaurusfunc = 'LSTART a, b LMIDDLE TsrFunc1(18, a, b) LEND' + new + call setline(1, 'six') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[18, 1, ''], [18, 0, 'six']], g:TsrFunc1Args) + bw! + + #" Set 'thesaurusfunc' to a variable with a lambda expression + VAR Lambda = LSTART a, b LMIDDLE TsrFunc1(19, a, b) LEND + LET &thesaurusfunc = Lambda + new + call setline(1, 'seven') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[19, 1, ''], [19, 0, 'seven']], g:TsrFunc1Args) + bw! + + #" Set 'thesaurusfunc' to a string(variable with a lambda expression) + LET Lambda = LSTART a, b LMIDDLE TsrFunc1(20, a, b) LEND + LET &thesaurusfunc = string(Lambda) + new + call setline(1, 'seven') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[20, 1, ''], [20, 0, 'seven']], g:TsrFunc1Args) + bw! + + #" Test for using a lambda function with incorrect return value + LET Lambda = LSTART a, b LMIDDLE strlen(a) LEND + LET &thesaurusfunc = Lambda + new + call setline(1, 'eight') + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + bw! + + #" Test for clearing the 'thesaurusfunc' option + set thesaurusfunc='' + set thesaurusfunc& + call assert_fails("set thesaurusfunc=function('abc')", "E700:") + call assert_fails("set thesaurusfunc=funcref('abc')", "E700:") + + #" set 'thesaurusfunc' to a non-existing function + set thesaurusfunc=TsrFunc2 + call setline(1, 'ten') + call assert_fails("set thesaurusfunc=function('NonExistingFunc')", 'E700:') + call assert_fails("LET &thesaurusfunc = function('NonExistingFunc')", 'E700:') + LET g:TsrFunc2Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'ten']], g:TsrFunc2Args) + bw! + + #" Use a buffer-local value and a global value + set thesaurusfunc& + setlocal thesaurusfunc=function('g:TsrFunc1',\ [22]) + call setline(1, 'sun') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", "x") + call assert_equal('sun', getline(1)) + call assert_equal([[22, 1, ''], [22, 0, 'sun']], g:TsrFunc1Args) + new + call setline(1, 'sun') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", "x") + call assert_equal('sun', getline(1)) + call assert_equal([], g:TsrFunc1Args) + set thesaurusfunc=function('g:TsrFunc1',\ [23]) + wincmd w + call setline(1, 'sun') + LET g:TsrFunc1Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", "x") + call assert_equal('sun', getline(1)) + call assert_equal([[22, 1, ''], [22, 0, 'sun']], g:TsrFunc1Args) + :%bw! + END + call CheckLegacyAndVim9Success(lines) + + " Test for using a script-local function name + func s:TsrFunc3(findstart, base) + call add(g:TsrFunc3Args, [a:findstart, a:base]) + return a:findstart ? 0 : [] + endfunc + set tsrfu=s:TsrFunc3 + new + call setline(1, 'script1') + let g:TsrFunc3Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script1']], g:TsrFunc3Args) + bw! + + let &tsrfu = 's:TsrFunc3' + new + call setline(1, 'script2') + let g:TsrFunc3Args = [] + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + call assert_equal([[1, ''], [0, 'script2']], g:TsrFunc3Args) + bw! + delfunc s:TsrFunc3 + + " invalid return value + let &thesaurusfunc = {a -> 'abc'} + call feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + + " Using Vim9 lambda expression in legacy context should fail + " set thesaurusfunc=(a,\ b)\ =>\ TsrFunc1(21,\ a,\ b) + new | only + let g:TsrFunc1Args = [] + " call assert_fails('call feedkeys("A\<C-X>\<C-T>\<Esc>", "x")', 'E117:') + call assert_equal([], g:TsrFunc1Args) + bw! + + " set 'thesaurusfunc' to a partial with dict. This used to cause a crash. + func SetTsrFunc() + let params = {'thesaurus': function('g:DictTsrFunc')} + let &thesaurusfunc = params.thesaurus + endfunc + func g:DictTsrFunc(_) dict + endfunc + call SetTsrFunc() + new + call SetTsrFunc() + bw + call test_garbagecollect_now() + new + set thesaurusfunc= + wincmd w + %bw! + delfunc SetTsrFunc + + " set buffer-local 'thesaurusfunc' to a partial with dict. This used to + " cause a crash. + func SetLocalTsrFunc() + let params = {'thesaurus': function('g:DictTsrFunc')} + let &l:thesaurusfunc = params.thesaurus + endfunc + call SetLocalTsrFunc() + call test_garbagecollect_now() + call SetLocalTsrFunc() + set thesaurusfunc= + bw! + delfunc g:DictTsrFunc + delfunc SetLocalTsrFunc + + " Vim9 tests + let lines =<< trim END + vim9script + + def Vim9tsrFunc(callnr: number, findstart: number, base: string): any + add(g:Vim9tsrFunc_Args, [callnr, findstart, base]) + return findstart ? 0 : [] + enddef + + # Test for using a def function with thesaurusfunc + set thesaurusfunc=function('Vim9tsrFunc',\ [60]) + new | only + setline(1, 'one') + g:Vim9tsrFunc_Args = [] + feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + assert_equal([[60, 1, ''], [60, 0, 'one']], g:Vim9tsrFunc_Args) + bw! + + # Test for using a global function name + &thesaurusfunc = g:TsrFunc2 + new | only + setline(1, 'two') + g:TsrFunc2Args = [] + feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'two']], g:TsrFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalTsrFunc(findstart: number, base: string): any + add(g:LocalTsrFuncArgs, [findstart, base]) + return findstart ? 0 : [] + enddef + &thesaurusfunc = s:LocalTsrFunc + new | only + setline(1, 'three') + g:LocalTsrFuncArgs = [] + feedkeys("A\<C-X>\<C-T>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'three']], g:LocalTsrFuncArgs) + bw! + END + call CheckScriptSuccess(lines) + + " cleanup + set thesaurusfunc& + delfunc TsrFunc1 + delfunc TsrFunc2 + unlet g:TsrFunc1Args g:TsrFunc2Args + %bw! +endfunc + func FooBarComplete(findstart, base) if a:findstart return col('.') - 1 @@ -884,4 +2141,52 @@ func Test_complete_smartindent() delfunction! FooBarComplete endfunc +func Test_complete_overrun() + " this was going past the end of the copied text + new + sil norm si0s0 + bwipe! +endfunc + +func Test_infercase_very_long_line() + " this was truncating the line when inferring case + new + let longLine = "blah "->repeat(300) + let verylongLine = "blah "->repeat(400) + call setline(1, verylongLine) + call setline(2, longLine) + set ic infercase + exe "normal 2Go\<C-X>\<C-L>\<Esc>" + call assert_equal(longLine, getline(3)) + + " check that the too long text is NUL terminated + %del + norm o + norm 1987ax + exec "norm ox\<C-X>\<C-L>" + call assert_equal(repeat('x', 1987), getline(3)) + + bwipe! + set noic noinfercase +endfunc + +func Test_ins_complete_add() + " this was reading past the end of allocated memory + new + norm o + norm 7o + sil! norm o + + bwipe! +endfunc + +func Test_ins_complete_end_of_line() + " this was reading past the end of the line + new + norm 8oý + sil! norm o + + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index c1fe47d1c9..025eb016a8 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -62,7 +62,8 @@ endfunc function Test_lambda_fails() call assert_equal(3, {a, b -> a + b}(1, 2)) call assert_fails('echo {a, a -> a + a}(1, 2)', 'E853:') - call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E15:') + call assert_fails('echo {a, b -> a + b)}(1, 2)', 'E451:') + echo assert_fails('echo 10->{a -> a + 2}', 'E107:') endfunc func Test_not_lambda() @@ -125,7 +126,7 @@ func Test_lambda_closure_counter() endfunc let l:F = s:foo() - call garbagecollect() + call test_garbagecollect_now() call assert_equal(1, l:F()) call assert_equal(2, l:F()) call assert_equal(3, l:F()) @@ -208,9 +209,9 @@ func Test_lambda_circular_reference() endfunc call s:Foo() - call garbagecollect() + call test_garbagecollect_now() let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile - call garbagecollect() + call test_garbagecollect_now() endfunc func Test_lambda_combination() @@ -239,11 +240,16 @@ func Test_closure_counter() endfunc let l:F = s:foo() - call garbagecollect() + call test_garbagecollect_now() call assert_equal(1, l:F()) call assert_equal(2, l:F()) call assert_equal(3, l:F()) call assert_equal(4, l:F()) + + call assert_match("^\n function <SNR>\\d\\+_bar() closure" + \ .. "\n1 let x += 1" + \ .. "\n2 return x" + \ .. "\n endfunction$", execute('func s:bar')) endfunc func Test_closure_unlet() @@ -257,7 +263,7 @@ func Test_closure_unlet() endfunc call assert_false(has_key(s:foo(), 'x')) - call garbagecollect() + call test_garbagecollect_now() endfunc func LambdaFoo() @@ -294,7 +300,7 @@ func Test_named_function_closure() endfunc call Afoo() call assert_equal(14, s:Abar()) - call garbagecollect() + call test_garbagecollect_now() call assert_equal(14, s:Abar()) endfunc @@ -308,3 +314,21 @@ func Test_lambda_error() " This was causing a crash call assert_fails('ec{@{->{d->()()', 'E15') endfunc + +func Test_closure_error() + let l =<< trim END + func F1() closure + return 1 + endfunc + END + call writefile(l, 'Xscript') + let caught_932 = 0 + try + source Xscript + catch /E932:/ + let caught_932 = 1 + endtry + call assert_equal(1, caught_932) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_langmap.vim b/src/nvim/testdir/test_langmap.vim index 4f831aa40b..aaed77e109 100644 --- a/src/nvim/testdir/test_langmap.vim +++ b/src/nvim/testdir/test_langmap.vim @@ -49,6 +49,41 @@ func Test_langmap() call feedkeys(';', 'tx') call assert_equal(5, col('.')) + set langmap=RL + let g:counter = 0 + nnoremap L;L <Cmd>let g:counter += 1<CR> + nnoremap <C-L> <Cmd>throw 'This mapping should not be triggered'<CR> + + " 'langmap' is applied to keys without modifiers when matching a mapping + call feedkeys('R;R', 'tx') + call assert_equal(1, g:counter) + nunmap L;L + unlet g:counter + + delete + call assert_equal('', getline(1)) + undo + call assert_equal('Hello World', getline(1)) + " 'langmap' does not change Ctrl-R to Ctrl-L for consistency + call feedkeys("\<*C-R>", 'tx') + call assert_equal('', getline(1)) + + set langmap=6L + undo + setlocal bufhidden=hide + let oldbuf = bufnr() + enew + call assert_notequal(oldbuf, bufnr()) + " 'langmap' does not change Ctrl-6 to Ctrl-L for consistency + " Ctrl-6 becomes Ctrl-^ after merging the Ctrl modifier + call feedkeys("\<*C-6>", 'tx') + call assert_equal(oldbuf, bufnr()) + setlocal bufhidden& + + nunmap <C-L> + set langmap& quit! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_legacy_filetype.vim b/src/nvim/testdir/test_legacy_filetype.vim deleted file mode 100644 index 772faaadb0..0000000000 --- a/src/nvim/testdir/test_legacy_filetype.vim +++ /dev/null @@ -1,4 +0,0 @@ -let g:do_legacy_filetype = 1 -filetype on - -source test_filetype.vim diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim index 6cb736a38a..35745e9c6a 100644 --- a/src/nvim/testdir/test_let.vim +++ b/src/nvim/testdir/test_let.vim @@ -6,6 +6,10 @@ func Test_let() let Test104#numvar = function('tr') call assert_equal("function('tr')", string(Test104#numvar)) + let foo#tr = function('tr') + call assert_equal("function('tr')", string(foo#tr)) + unlet foo#tr + let a = 1 let b = 2 @@ -25,9 +29,62 @@ func Test_let() let s = "\na #1\nb #2" call assert_equal(s, out) + " Test for displaying a string variable + let s = 'vim' + let out = execute('let s') + let s = "\ns vim" + call assert_equal(s, out) + + " Test for displaying a list variable + let l = [1, 2] + let out = execute('let l') + let s = "\nl [1, 2]" + call assert_equal(s, out) + + " Test for displaying a dict variable + let d = {'k' : 'v'} + let out = execute('let d') + let s = "\nd {'k': 'v'}" + call assert_equal(s, out) + + " Test for displaying a function reference variable + let F = function('min') + let out = execute('let F') + let s = "\nF *min()" + call assert_equal(s, out) + let x = 0 if 0 | let x = 1 | endif call assert_equal(0, x) + + " Display a list item using an out of range index + let l = [10] + call assert_fails('let l[1]', 'E684:') + + " List special variable dictionaries + let g:Test_Global_Var = 5 + call assert_match("\nTest_Global_Var #5", execute('let g:')) + unlet g:Test_Global_Var + + let b:Test_Buf_Var = 8 + call assert_match("\nb:Test_Buf_Var #8", execute('let b:')) + unlet b:Test_Buf_Var + + let w:Test_Win_Var = 'foo' + call assert_equal("\nw:Test_Win_Var foo", execute('let w:')) + unlet w:Test_Win_Var + + let t:Test_Tab_Var = 'bar' + call assert_equal("\nt:Test_Tab_Var bar", execute('let t:')) + unlet t:Test_Tab_Var + + let s:Test_Script_Var = [7] + call assert_match("\ns:Test_Script_Var \\[7]", execute('let s:')) + unlet s:Test_Script_Var + + let l:Test_Local_Var = {'k' : 5} + call assert_match("\nl:Test_Local_Var {'k': 5}", execute('let l:')) + call assert_match("v:errors []", execute('let v:')) endfunc func s:set_arg1(a) abort @@ -201,16 +258,68 @@ func Test_let_option_error() let &fillchars = _w endfunc +" Errors with the :let statement func Test_let_errors() let s = 'abcd' call assert_fails('let s[1] = 5', 'E689:') let l = [1, 2, 3] call assert_fails('let l[:] = 5', 'E709:') + + call assert_fails('let x:lnum=5', ['E121:', 'E488:']) + call assert_fails('let v:=5', 'E461:') + call assert_fails('let [a]', 'E474:') + call assert_fails('let [a, b] = [', 'E697:') + call assert_fails('let [a, b] = [10, 20', 'E696:') + call assert_fails('let [a, b] = 10', 'E714:') + call assert_fails('let [a, , b] = [10, 20]', 'E475:') + call assert_fails('let [a, b&] = [10, 20]', 'E475:') + call assert_fails('let $ = 10', 'E475:') + call assert_fails('let $FOO[1] = "abc"', 'E18:') + call assert_fails('let &buftype[1] = "nofile"', 'E18:') + let s = "var" + let var = 1 + call assert_fails('let var += [1,2]', 'E734:') + call assert_fails('let {s}.1 = 2', 'E1203:') + call assert_fails('let a[1] = 5', 'E121:') + let l = [[1,2]] + call assert_fails('let l[:][0] = [5]', 'E708:') + let d = {'k' : 4} + call assert_fails('let d.# = 5', 'E488:') + call assert_fails('let d.m += 5', 'E716:') + call assert_fails('let m = d[{]', 'E15:') + let l = [1, 2] + call assert_fails('let l[2] = 0', 'E684:') + call assert_fails('let l[0:1] = [1, 2, 3]', 'E710:') + call assert_fails('let l[-2:-3] = [3, 4]', 'E684:') + call assert_fails('let l[0:4] = [5, 6]', 'E711:') + call assert_fails('let l -= 2', 'E734:') + call assert_fails('let l += 2', 'E734:') + call assert_fails('let g:["a;b"] = 10', 'E461:') + call assert_fails('let g:.min = function("max")', 'E704:') + call assert_fails('let g:cos = "" | let g:.cos = {-> 42}', 'E704:') + if has('channel') + let ch = test_null_channel() + call assert_fails('let ch += 1', 'E734:') + endif + call assert_fails('let name = "a" .. "b",', 'E488: Trailing characters: ,') + + " This test works only when the language is English + if v:lang == "C" || v:lang =~ '^[Ee]n' + call assert_fails('let [a ; b;] = [10, 20]', + \ 'Double ; in list of variables') + endif endfunc func Test_let_heredoc_fails() call assert_fails('let v =<< marker', 'E991:') + try + exe "let v =<< TEXT | abc | TEXT" + call assert_report('No exception thrown') + catch /E488:/ + catch + call assert_report("Caught exception: " .. v:exception) + endtry let text =<< trim END func WrongSyntax() @@ -243,6 +352,10 @@ func Test_let_heredoc_fails() call writefile(text, 'XheredocBadMarker') call assert_fails('source XheredocBadMarker', 'E221:') call delete('XheredocBadMarker') + + call writefile(['let v =<< TEXT', 'abc'], 'XheredocMissingMarker') + call assert_fails('source XheredocMissingMarker', 'E990:') + call delete('XheredocMissingMarker') endfunc func Test_let_heredoc_trim_no_indent_marker() @@ -361,3 +474,5 @@ E END call assert_equal([' x', ' \y', ' z'], [a, b, c]) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_lispwords.vim b/src/nvim/testdir/test_lispindent.vim index 4144fb0521..2d6060bba3 100644 --- a/src/nvim/testdir/test_lispwords.vim +++ b/src/nvim/testdir/test_lispindent.vim @@ -86,6 +86,37 @@ func Test_lisp_indent() set nolisp endfunc +func Test_lispindent_negative() + " in legacy script there is no error + call assert_equal(-1, lispindent(-1)) +endfunc + +func Test_lispindent_with_indentexpr() + enew + setl ai lisp nocin indentexpr=11 + exe "normal a(x\<CR>1\<CR>2)\<Esc>" + let expected = ['(x', ' 1', ' 2)'] + call assert_equal(expected, getline(1, 3)) + " with Lisp indenting the first line is not indented + normal 1G=G + call assert_equal(expected, getline(1, 3)) + + %del + setl lispoptions=expr:1 indentexpr=5 + exe "normal a(x\<CR>1\<CR>2)\<Esc>" + let expected_expr = ['(x', ' 1', ' 2)'] + call assert_equal(expected_expr, getline(1, 3)) + normal 2G2<<=G + call assert_equal(expected_expr, getline(1, 3)) + + setl lispoptions=expr:0 + " with Lisp indenting the first line is not indented + normal 1G3<<=G + call assert_equal(expected, getline(1, 3)) + + bwipe! +endfunc + func Test_lisp_indent_works() " This was reading beyond the end of the line new diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index 2f4e1db4a1..9ecd83265a 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -31,6 +31,12 @@ func Test_list_slice() call assert_equal([1, 'as''d', [1, 2, function('strlen')]], l[:-2]) call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[0:8]) call assert_equal([], l[8:-1]) + call assert_equal([], l[0:-10]) + " perform an operation on a list slice + let l = [1, 2, 3] + let l[:1] += [1, 2] + let l[2:] -= [1] + call assert_equal([2, 4, 2], l) endfunc " List identity @@ -104,6 +110,8 @@ func Test_list_range_assign() let l = [0] let l[:] = [1, 2] call assert_equal([1, 2], l) + let l[-4:-1] = [5, 6] + call assert_equal([5, 6], l) endfunc " Test removing items in list @@ -143,6 +151,20 @@ func Test_list_func_remove() call assert_fails("call remove(l, l)", 'E745:') endfunc +" List add() function +func Test_list_add() + let l = [] + call add(l, 1) + call add(l, [2, 3]) + call add(l, []) + call add(l, v:_null_list) + call add(l, {'k' : 3}) + call add(l, {}) + call add(l, v:_null_dict) + call assert_equal([1, [2, 3], [], [], {'k' : 3}, {}, {}], l) + " call assert_equal(1, add(v:_null_list, 4)) +endfunc + " Tests for Dictionary type func Test_dict() @@ -165,6 +187,26 @@ func Test_dict() call assert_equal({'c': 'ccc', '1': 99, 'b': [1, 2, function('strlen')], '3': 33, '-1': {'a': 1}}, d) call filter(d, 'v:key =~ ''[ac391]''') call assert_equal({'c': 'ccc', '1': 99, '3': 33, '-1': {'a': 1}}, d) + + " duplicate key + call assert_fails("let d = {'k' : 10, 'k' : 20}", 'E721:') + " missing comma + call assert_fails("let d = {'k' : 10 'k' : 20}", 'E722:') + " missing curly brace + call assert_fails("let d = {'k' : 10,", 'E723:') + " invalid key + call assert_fails('let d = #{++ : 10}', 'E15:') + " wrong type for key + call assert_fails('let d={[] : 10}', 'E730:') + " undefined variable as value + call assert_fails("let d={'k' : i}", 'E121:') + + " allow key starting with number at the start, not a curly expression + call assert_equal({'1foo': 77}, #{1foo: 77}) + + " #{expr} is not a curly expression + let x = 'x' + call assert_equal(#{g: x}, #{g:x}) endfunc " Dictionary identity @@ -247,6 +289,16 @@ func Test_dict_func() call assert_equal('xxx3', Fn('xxx')) endfunc +func Test_dict_assign() + let d = {} + let d.1 = 1 + let d._ = 2 + call assert_equal({'1': 1, '_': 2}, d) + + let n = 0 + call assert_fails('let n.key = 3', 'E1203: Dot can only be used on a dictionary: n.key = 3') +endfunc + " Function in script-local List or Dict func Test_script_local_dict_func() let g:dict = {} @@ -259,7 +311,7 @@ func Test_script_local_dict_func() unlet g:dict endfunc -" Test removing items in la dictionary +" Test removing items in a dictionary func Test_dict_func_remove() let d = {1:'a', 2:'b', 3:'c'} call assert_equal('b', remove(d, 2)) @@ -294,17 +346,18 @@ func Test_dict_deepcopy() let l = [4, d, 6] let d[3] = l let dc = deepcopy(d) - call assert_fails('call deepcopy(d, 1)', 'E698') + call assert_fails('call deepcopy(d, 1)', 'E698:') let l2 = [0, l, l, 3] let l[1] = l2 let l3 = deepcopy(l2) call assert_true(l3[1] is l3[2]) + call assert_fails("call deepcopy([1, 2], 2)", 'E1023:') endfunc " Locked variables func Test_list_locked_var() let expected = [ - \ [['0000-000', 'ppppppp'], + \ [['1000-000', 'ppppppF'], \ ['0000-000', 'ppppppp'], \ ['0000-000', 'ppppppp']], \ [['1000-000', 'ppppppF'], @@ -331,7 +384,7 @@ func Test_list_locked_var() exe "unlockvar " . depth . " l" endif let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]") - call assert_equal(expected[depth][u][0], ps) + call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth) let ps = '' try let l[1][1][0] = 99 @@ -375,15 +428,20 @@ func Test_list_locked_var() catch let ps .= 'F' endtry - call assert_equal(expected[depth][u][1], ps) + call assert_equal(expected[depth][u][1], ps, 'depth: ' .. depth) endfor endfor + call assert_fails("let x=islocked('a b')", 'E488:') + let mylist = [1, 2, 3] + call assert_fails("let x = islocked('mylist[1:2]')", 'E786:') + let mydict = {'k' : 'v'} + call assert_fails("let x = islocked('mydict.a')", 'E716:') endfunc " Unletting locked variables func Test_list_locked_var_unlet() let expected = [ - \ [['0000-000', 'ppppppp'], + \ [['1000-000', 'ppppppp'], \ ['0000-000', 'ppppppp'], \ ['0000-000', 'ppppppp']], \ [['1000-000', 'ppFppFp'], @@ -411,7 +469,7 @@ func Test_list_locked_var_unlet() exe "unlockvar " . depth . " l" endif let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]") - call assert_equal(expected[depth][u][0], ps) + call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth) let ps = '' try unlet l[2]['6'][7] @@ -458,6 +516,11 @@ func Test_list_locked_var_unlet() call assert_equal(expected[depth][u][1], ps) endfor endfor + " Deleting a list range should fail if the range is locked + let l = [1, 2, 3, 4] + lockvar l[1:2] + call assert_fails('unlet l[1:2]', 'E741:') + unlet l endfunc " Locked variables and :unlet or list / dict functions @@ -567,6 +630,18 @@ func Test_let_lock_list() unlet l endfunc +" Locking part of the list +func Test_let_lock_list_items() + let l = [1, 2, 3, 4] + lockvar l[2:] + call assert_equal(0, islocked('l[0]')) + call assert_equal(1, islocked('l[2]')) + call assert_equal(1, islocked('l[3]')) + call assert_fails('let l[2] = 10', 'E741:') + call assert_fails('let l[3] = 20', 'E741:') + unlet l +endfunc + " lockvar/islocked() triggering script autoloading func Test_lockvar_script_autoload() let old_rtp = &rtp @@ -598,26 +673,35 @@ func Test_func_arg_list() call s:arg_list_test(1, 2, [3, 4], {5: 6}) endfunc +func Test_dict_item_locked() +endfunc + " Tests for reverse(), sort(), uniq() func Test_reverse_sort_uniq() let l = ['-0', 'A11', 2, 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5] call assert_equal(['-0', 'A11', 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5], uniq(copy(l))) call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(l)) call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(reverse(l))) - call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(l)) - call assert_equal([[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'], reverse(sort(l))) - call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(reverse(sort(l)))) - call assert_equal(['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]], uniq(sort(l))) - - let l=[7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four'] - call assert_equal([-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255], sort(copy(l), 'n')) - - let l=[7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []] - call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1)) - call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i')) - call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l))) + if has('float') + call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(l)) + call assert_equal([[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'], reverse(sort(l))) + call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(reverse(sort(l)))) + call assert_equal(['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]], uniq(sort(l))) + + let l = [7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four'] + call assert_equal([-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255], sort(copy(l), 'n')) + + let l = [7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []] + call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1)) + call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i')) + call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l))) + endif call assert_fails('call reverse("")', 'E899:') + call assert_fails('call uniq([1, 2], {x, y -> []})', 'E745:') + call assert_fails("call sort([1, 2], function('min'), 1)", "E715:") + call assert_fails("call sort([1, 2], function('invalid_func'))", "E700:") + call assert_fails("call sort([1, 2], function('min'))", "E118:") endfunc " reduce a list or a blob @@ -665,7 +749,7 @@ func Test_reduce() call assert_equal(42, reduce(v:_null_blob, function('add'), 42)) endfunc -" splitting a string to a List +" splitting a string to a List using split() func Test_str_split() call assert_equal(['aa', 'bb'], split(' aa bb ')) call assert_equal(['aa', 'bb'], split(' aa bb ', '\W\+', 0)) @@ -676,6 +760,9 @@ func Test_str_split() call assert_equal(['aa', '', 'bb', 'cc', ''], split('aa,,bb, cc,', ',\s*', 1)) call assert_equal(['a', 'b', 'c'], split('abc', '\zs')) call assert_equal(['', 'a', '', 'b', '', 'c', ''], split('abc', '\zs', 1)) + call assert_fails("call split('abc', [])", 'E730:') + call assert_fails("call split('abc', 'b', [])", 'E745:') + call assert_equal(['abc'], split('abc', '\\%(')) endfunc " compare recursively linked list and dict @@ -687,6 +774,12 @@ func Test_listdict_compare() call assert_true(d == d) call assert_false(l != deepcopy(l)) call assert_false(d != deepcopy(d)) + + " comparison errors + call assert_fails('echo [1, 2] =~ {}', 'E691:') + call assert_fails('echo [1, 2] =~ [1, 2]', 'E692:') + call assert_fails('echo {} =~ 5', 'E735:') + call assert_fails('echo {} =~ {}', 'E736:') endfunc " compare complex recursively linked list and dict @@ -860,6 +953,67 @@ func Test_scope_dict() call s:check_scope_dict('v', v:true) endfunc +" Test for deep nesting of lists (> 100) +func Test_deep_nested_list() + let deep_list = [] + let l = deep_list + for i in range(102) + let newlist = [] + call add(l, newlist) + let l = newlist + endfor + call add(l, 102) + + call assert_fails('let m = deepcopy(deep_list)', 'E698:') + call assert_fails('lockvar 110 deep_list', 'E743:') + call assert_fails('unlockvar 110 deep_list', 'E743:') + " Nvim implements :echo very differently + " call assert_fails('let x = execute("echo deep_list")', 'E724:') + call test_garbagecollect_now() + unlet deep_list +endfunc + +" Test for deep nesting of dicts (> 100) +func Test_deep_nested_dict() + let deep_dict = {} + let d = deep_dict + for i in range(102) + let newdict = {} + let d.k = newdict + let d = newdict + endfor + let d.k = 'v' + + call assert_fails('let m = deepcopy(deep_dict)', 'E698:') + call assert_fails('lockvar 110 deep_dict', 'E743:') + call assert_fails('unlockvar 110 deep_dict', 'E743:') + " Nvim implements :echo very differently + " call assert_fails('let x = execute("echo deep_dict")', 'E724:') + call test_garbagecollect_now() + unlet deep_dict +endfunc + +" List and dict indexing tests +func Test_listdict_index() + call assert_fails('echo function("min")[0]', 'E695:') + call assert_fails('echo v:true[0]', 'E909:') + let d = {'k' : 10} + call assert_fails('echo d.', 'E15:') + call assert_fails('echo d[1:2]', 'E719:') + call assert_fails("let v = [4, 6][{-> 1}]", 'E729:') + call assert_fails("let v = range(5)[2:[]]", 'E730:') + call assert_fails("let v = range(5)[2:{-> 2}(]", ['E15:', 'E116:']) + call assert_fails("let v = range(5)[2:3", 'E111:') + call assert_fails("let l = insert([1,2,3], 4, 10)", 'E684:') + call assert_fails("let l = insert([1,2,3], 4, -10)", 'E684:') + call assert_fails("let l = insert([1,2,3], 4, [])", 'E745:') + let l = [1, 2, 3] + call assert_fails("let l[i] = 3", 'E121:') + call assert_fails("let l[1.1] = 4", 'E806:') + call assert_fails("let l[:i] = [4, 5]", 'E121:') + call assert_fails("let l[:3.2] = [4, 5]", 'E806:') +endfunc + " Test for a null list func Test_null_list() let l = v:_null_list @@ -896,3 +1050,21 @@ func Test_null_list() call assert_equal(1, islocked('l')) unlockvar l endfunc + +" Test for a null dict +func Test_null_dict() + call assert_equal(v:_null_dict, v:_null_dict) + let d = v:_null_dict + call assert_equal({}, d) + call assert_equal(0, len(d)) + call assert_equal(1, empty(d)) + call assert_equal(0, items(d)) + call assert_equal(0, keys(d)) + call assert_equal(0, values(d)) + call assert_false(has_key(d, 'k')) + call assert_equal('{}', string(d)) + call assert_fails('let x = v:_null_dict[10]') + call assert_equal({}, {}) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_listlbr.vim b/src/nvim/testdir/test_listlbr.vim index affa0f96fa..1cbdba5d76 100644 --- a/src/nvim/testdir/test_listlbr.vim +++ b/src/nvim/testdir/test_listlbr.vim @@ -7,6 +7,7 @@ CheckOption linebreak CheckFeature conceal source view_util.vim +source screendump.vim function s:screen_lines(lnum, width) abort return ScreenLines(a:lnum, a:width) @@ -133,6 +134,45 @@ func Test_linebreak_with_visual_operations() call s:close_windows() endfunc +" Test that cursor is drawn at correct position after an operator when +" 'linebreak' is enabled. +func Test_linebreak_reset_restore() + CheckScreendump + + " f_wincol() calls validate_cursor() + let lines =<< trim END + set linebreak showcmd noshowmode formatexpr=wincol()-wincol() + call setline(1, repeat('a', &columns - 10) .. ' bbbbbbbbbb c') + END + call writefile(lines, 'XlbrResetRestore', 'D') + let buf = RunVimInTerminal('-S XlbrResetRestore', {'rows': 8}) + + call term_sendkeys(buf, '$v$') + call WaitForAssert({-> assert_equal(13, term_getcursor(buf)[1])}) + call term_sendkeys(buf, 'zo') + call WaitForAssert({-> assert_equal(12, term_getcursor(buf)[1])}) + + call term_sendkeys(buf, '$v$') + call WaitForAssert({-> assert_equal(13, term_getcursor(buf)[1])}) + call term_sendkeys(buf, 'gq') + call WaitForAssert({-> assert_equal(12, term_getcursor(buf)[1])}) + + call term_sendkeys(buf, "$\<C-V>$") + call WaitForAssert({-> assert_equal(13, term_getcursor(buf)[1])}) + call term_sendkeys(buf, 'I') + call WaitForAssert({-> assert_equal(12, term_getcursor(buf)[1])}) + + call term_sendkeys(buf, "\<Esc>$v$") + call WaitForAssert({-> assert_equal(13, term_getcursor(buf)[1])}) + call term_sendkeys(buf, 's') + call WaitForAssert({-> assert_equal(12, term_getcursor(buf)[1])}) + call VerifyScreenDump(buf, 'Test_linebreak_reset_restore_1', {}) + + " clean up + call term_sendkeys(buf, "\<Esc>") + call StopVimInTerminal(buf) +endfunc + func Test_virtual_block() call s:test_windows('setl sbr=+') call setline(1, [ diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim index c53c07d991..e297bdc228 100644 --- a/src/nvim/testdir/test_makeencoding.vim +++ b/src/nvim/testdir/test_makeencoding.vim @@ -107,3 +107,19 @@ func Test_make() lclose endfor endfunc + +" Test for an error file with a long line that needs an encoding conversion +func Test_longline_conversion() + new + call setline(1, ['Xfile:10:' .. repeat("\xe0", 2000)]) + write ++enc=latin1 Xerr.out + bw! + set errorformat& + set makeencoding=latin1 + cfile Xerr.out + call assert_equal(repeat("\u00e0", 2000), getqflist()[0].text) + call delete('Xerr.out') + set makeencoding& +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim index dad4c81a7b..f903f5b934 100644 --- a/src/nvim/testdir/test_maparg.vim +++ b/src/nvim/testdir/test_maparg.vim @@ -158,11 +158,11 @@ func Test_range_map() call assert_equal("abcd", getline(1)) endfunc -func One_mapset_test(keys) - exe 'nnoremap ' .. a:keys .. ' original<CR>' +func One_mapset_test(keys, rhs) + exe 'nnoremap ' .. a:keys .. ' ' .. a:rhs let orig = maparg(a:keys, 'n', 0, 1) call assert_equal(a:keys, orig.lhs) - call assert_equal('original<CR>', orig.rhs) + call assert_equal(a:rhs, orig.rhs) call assert_equal('n', orig.mode) exe 'nunmap ' .. a:keys @@ -172,15 +172,16 @@ func One_mapset_test(keys) call mapset('n', 0, orig) let d = maparg(a:keys, 'n', 0, 1) call assert_equal(a:keys, d.lhs) - call assert_equal('original<CR>', d.rhs) + call assert_equal(a:rhs, d.rhs) call assert_equal('n', d.mode) exe 'nunmap ' .. a:keys endfunc func Test_mapset() - call One_mapset_test('K') - call One_mapset_test('<F3>') + call One_mapset_test('K', 'original<CR>') + call One_mapset_test('<F3>', 'original<CR>') + call One_mapset_test('<F3>', '<lt>Nop>') " Check <> key conversion new @@ -203,6 +204,26 @@ func Test_mapset() iunmap K + " Test that <Nop> is restored properly + inoremap K <Nop> + call feedkeys("SK\<Esc>", 'xt') + call assert_equal('', getline(1)) + + let orig = maparg('K', 'i', 0, 1) + call assert_equal('K', orig.lhs) + call assert_equal('<Nop>', orig.rhs) + call assert_equal('i', orig.mode) + + inoremap K foo + call feedkeys("SK\<Esc>", 'xt') + call assert_equal('foo', getline(1)) + + call mapset('i', 0, orig) + call feedkeys("SK\<Esc>", 'xt') + call assert_equal('', getline(1)) + + iunmap K + " Test literal <CR> using a backslash let cpo_save = &cpo set cpo-=B @@ -248,6 +269,8 @@ func Test_mapset() bwipe! call assert_fails('call mapset([], v:false, {})', 'E730:') + call assert_fails('call mapset("i", 0, "")', 'E715:') + call assert_fails('call mapset("i", 0, {})', 'E460:') endfunc func Check_ctrlb_map(d, check_alt) diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index e1d0b9a9ba..5c5a65d4ca 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -395,7 +395,9 @@ func Test_motionforce_omap() endfunc func Test_error_in_map_expr() - if !has('terminal') || (has('win32') && has('gui_running')) + " Unlike CheckRunVimInTerminal this does work in a win32 console + CheckFeature terminal + if has('win32') && has('gui_running') throw 'Skipped: cannot run Vim in a terminal window' endif @@ -765,6 +767,11 @@ func Test_mapcomplete() mapclear endfunc +func GetAbbrText() + unabbr hola + return 'hello' +endfunc + " Test for <expr> in abbreviation func Test_expr_abbr() new @@ -777,10 +784,17 @@ func Test_expr_abbr() " invalid <expr> abbreviation abbr <expr> hte GetAbbr() call assert_fails('normal ihte ', 'E117:') - call assert_equal(' ', getline(1)) + call assert_equal('', getline(1)) unabbr <expr> hte - close! + " evaluating the expression deletes the abbreviation + abbr <expr> hola GetAbbrText() + call assert_equal('GetAbbrText()', maparg('hola', 'i', '1')) + call feedkeys("ahola \<Esc>", 'xt') + call assert_equal('hello ', getline('.')) + call assert_equal('', maparg('hola', 'i', '1')) + + bwipe! endfunc " Test for storing mappings in different modes in a vimrc file @@ -975,6 +989,21 @@ func Test_abbreviate_multi_byte() bwipe! endfunc +" Test for abbreviations with 'latin1' encoding +func Test_abbreviate_latin1_encoding() + " set encoding=latin1 + call assert_fails('abbr ab#$c ABC', 'E474:') + new + iabbr <buffer> #i #include + iabbr <buffer> ## #enddef + exe "normal i#i\<C-]>" + call assert_equal('#include', getline(1)) + exe "normal 0Di##\<C-]>" + call assert_equal('#enddef', getline(1)) + %bw! + set encoding=utf-8 +endfunc ++ " Test for <Plug> always being mapped, even when used with "noremap". func Test_plug_remap() let g:foo = 0 @@ -1006,15 +1035,14 @@ func Test_plug_remap() endfunc func Test_mouse_drag_mapped_start_select() - CheckFunction test_setmouse set mouse=a set selectmode=key,mouse func ClickExpr() - call test_setmouse(1, 1) + call Ntest_setmouse(1, 1) return "\<LeftMouse>" endfunc func DragExpr() - call test_setmouse(1, 2) + call Ntest_setmouse(1, 2) return "\<LeftDrag>" endfunc nnoremap <expr> <F2> ClickExpr() @@ -1034,16 +1062,39 @@ func Test_mouse_drag_mapped_start_select() set mouse& endfunc +func Test_mouse_drag_statusline() + set laststatus=2 + set mouse=a + func ClickExpr() + call Ntest_setmouse(&lines - 1, 1) + return "\<LeftMouse>" + endfunc + func DragExpr() + call Ntest_setmouse(&lines - 2, 1) + return "\<LeftDrag>" + endfunc + nnoremap <expr> <F2> ClickExpr() + nnoremap <expr> <F3> DragExpr() + + " this was causing a crash in win_drag_status_line() + call feedkeys("\<F2>:tabnew\<CR>\<F3>", 'tx') + + nunmap <F2> + nunmap <F3> + delfunc ClickExpr + delfunc DragExpr + set laststatus& mouse& +endfunc + " Test for mapping <LeftDrag> in Insert mode func Test_mouse_drag_insert_map() - CheckFunction test_setmouse set mouse=a func ClickExpr() - call test_setmouse(1, 1) + call Ntest_setmouse(1, 1) return "\<LeftMouse>" endfunc func DragExpr() - call test_setmouse(1, 2) + call Ntest_setmouse(1, 2) return "\<LeftDrag>" endfunc inoremap <expr> <F2> ClickExpr() @@ -1129,4 +1180,14 @@ func Test_map_after_timed_out_nop() call delete('Xtest_map_after_timed_out_nop') endfunc +func Test_using_past_typeahead() + nnoremap :00 0 + exe "norm :set \x80\xfb0=0\<CR>" + exe "sil norm :0\x0f\<C-U>\<CR>" + + exe "norm :set \x80\xfb0=\<CR>" + nunmap :00 +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index 74e63d9d69..a7ccca498c 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -91,6 +91,17 @@ func Test_setpos() call assert_equal([0, 1, 21341234, 0], getpos("'a")) call assert_equal(4, virtcol("'a")) + " Test with invalid buffer number, line number and column number + call cursor(2, 2) + call setpos('.', [-1, 1, 1, 0]) + call assert_equal([2, 2], [line('.'), col('.')]) + call setpos('.', [0, -1, 1, 0]) + call assert_equal([2, 2], [line('.'), col('.')]) + call setpos('.', [0, 1, -1, 0]) + call assert_equal([2, 2], [line('.'), col('.')]) + + call assert_fails("call setpos('ab', [0, 1, 1, 0])", 'E474:') + bwipe! call win_gotoid(twowin) bwipe! @@ -293,4 +304,17 @@ func Test_getmarklist() close! endfunc +" This was using freed memory +func Test_jump_mark_autocmd() + next 00 + edit 0 + sargument + au BufEnter 0 all + sil norm + + au! BufEnter + bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_match.vim b/src/nvim/testdir/test_match.vim index 70271aa32f..600b6132a9 100644 --- a/src/nvim/testdir/test_match.vim +++ b/src/nvim/testdir/test_match.vim @@ -2,6 +2,7 @@ " matchaddpos(), matcharg(), matchdelete(), and setmatches(). source screendump.vim +source check.vim function Test_match() highlight MyGroup1 term=bold ctermbg=red guibg=red @@ -35,8 +36,8 @@ function Test_match() let m1 = matchadd("MyGroup1", "TODO") let m2 = matchadd("MyGroup2", "FIXME", 42) let m3 = matchadd("MyGroup3", "XXX", 60, 17) - let ans = [{'group': 'MyGroup1', 'pattern': 'TODO', 'priority': 10, 'id': 4}, - \ {'group': 'MyGroup2', 'pattern': 'FIXME', 'priority': 42, 'id': 5}, + let ans = [{'group': 'MyGroup1', 'pattern': 'TODO', 'priority': 10, 'id': 1000}, + \ {'group': 'MyGroup2', 'pattern': 'FIXME', 'priority': 42, 'id': 1001}, \ {'group': 'MyGroup3', 'pattern': 'XXX', 'priority': 60, 'id': 17}] call assert_equal(ans, getmatches()) @@ -117,7 +118,7 @@ function Test_match() call clearmatches() call setline(1, 'abcdΣabcdef') - eval "MyGroup1"->matchaddpos([[1, 4, 2], [1, 9, 2]]) + eval "MyGroup1"->matchaddpos([[1, 4, 2], [1, 9, 2]], 10, 42) 1 redraw! let v1 = screenattr(1, 1) @@ -128,7 +129,7 @@ function Test_match() let v8 = screenattr(1, 8) let v9 = screenattr(1, 9) let v10 = screenattr(1, 10) - call assert_equal([{'group': 'MyGroup1', 'id': 11, 'priority': 10, 'pos1': [1, 4, 2], 'pos2': [1, 9, 2]}], getmatches()) + call assert_equal([{'group': 'MyGroup1', 'id': 42, 'priority': 10, 'pos1': [1, 4, 2], 'pos2': [1, 9, 2]}], getmatches()) call assert_notequal(v1, v4) call assert_equal(v5, v4) call assert_equal(v6, v1) @@ -142,7 +143,7 @@ function Test_match() let m=getmatches() call clearmatches() call setmatches(m) - call assert_equal([{'group': 'MyGroup1', 'id': 11, 'priority': 10, 'pos1': [1, 4, 2], 'pos2': [1,9, 2]}, {'group': 'MyGroup1', 'pattern': '\%2lmatchadd', 'priority': 10, 'id': 12}], getmatches()) + call assert_equal([{'group': 'MyGroup1', 'id': 42, 'priority': 10, 'pos1': [1, 4, 2], 'pos2': [1,9, 2]}, {'group': 'MyGroup1', 'pattern': '\%2lmatchadd', 'priority': 10, 'id': 1106}], getmatches()) highlight MyGroup1 NONE highlight MyGroup2 NONE @@ -159,12 +160,14 @@ endfunc func Test_matchadd_error() call clearmatches() " Nvim: not an error anymore: + " call assert_fails("call matchadd('GroupDoesNotExist', 'X')", 'E28:') call matchadd('GroupDoesNotExist', 'X') - call assert_equal([{'group': 'GroupDoesNotExist', 'pattern': 'X', 'priority': 10, 'id': 13}], getmatches()) - call assert_fails("call matchadd('Search', '\\(')", 'E475:') + call assert_equal([{'group': 'GroupDoesNotExist', 'pattern': 'X', 'priority': 10, 'id': 1206}], getmatches()) + call assert_fails("call matchadd('Search', '\\(')", 'E54:') call assert_fails("call matchadd('Search', 'XXX', 1, 123, 1)", 'E715:') call assert_fails("call matchadd('Error', 'XXX', 1, 3)", 'E798:') call assert_fails("call matchadd('Error', 'XXX', 1, 0)", 'E799:') + call assert_fails("call matchadd('Error', 'XXX', [], 0)", 'E745:') endfunc func Test_matchaddpos() @@ -219,6 +222,21 @@ func Test_matchaddpos() set hlsearch& endfunc +" Add 12 match positions (previously the limit was 8 positions). +func Test_matchaddpos_dump() + CheckScreendump + + let lines =<< trim END + call setline(1, ['1234567890123']->repeat(14)) + call matchaddpos('Search', range(1, 12)->map({i, v -> [v, v]})) + END + call writefile(lines, 'Xmatchaddpos', 'D') + let buf = RunVimInTerminal('-S Xmatchaddpos', #{rows: 14}) + call VerifyScreenDump(buf, 'Test_matchaddpos_1', {}) + + call StopVimInTerminal(buf) +endfunc + func Test_matchaddpos_otherwin() syntax on new @@ -237,8 +255,8 @@ func Test_matchaddpos_otherwin() let savematches = getmatches(winid) let expect = [ - \ {'group': 'Search', 'pattern': '4', 'priority': 10, 'id': 4}, - \ {'group': 'Error', 'id': 5, 'priority': 10, 'pos1': [1, 2, 1], 'pos2': [2, 2, 1]}, + \ {'group': 'Search', 'pattern': '4', 'priority': 10, 'id': 1000}, + \ {'group': 'Error', 'id': 1001, 'priority': 10, 'pos1': [1, 2, 1], 'pos2': [2, 2, 1]}, \] call assert_equal(expect, savematches) @@ -289,7 +307,10 @@ func Test_matchaddpos_error() call assert_fails("call matchaddpos('Error', [1], 1, 123, 1)", 'E715:') call assert_fails("call matchaddpos('Error', [1], 1, 5, {'window':12345})", 'E957:') " Why doesn't the following error have an error code E...? + " call assert_fails("call matchaddpos('Error', [{}])", 'E290:') call assert_fails("call matchaddpos('Error', [{}])", 'E5031:') + call assert_equal(-1, matchaddpos('Error', v:_null_list)) + call assert_fails("call matchaddpos('Error', [1], [], 1)", 'E745:') endfunc func OtherWindowCommon() @@ -306,9 +327,8 @@ func OtherWindowCommon() endfunc func Test_matchdelete_other_window() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckScreendump + let buf = OtherWindowCommon() call term_sendkeys(buf, ":call matchdelete(mid, winid)\<CR>") call VerifyScreenDump(buf, 'Test_matchdelete_1', {}) @@ -323,9 +343,7 @@ func Test_matchdelete_error() endfunc func Test_matchclear_other_window() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal let buf = OtherWindowCommon() call term_sendkeys(buf, ":call clearmatches(winid)\<CR>") call VerifyScreenDump(buf, 'Test_matchclear_1', {}) @@ -335,9 +353,7 @@ func Test_matchclear_other_window() endfunc func Test_matchadd_other_window() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal let buf = OtherWindowCommon() call term_sendkeys(buf, ":call matchadd('Search', 'Hello', 1, -1, #{window: winid})\<CR>") call term_sendkeys(buf, ":\<CR>") @@ -347,5 +363,80 @@ func Test_matchadd_other_window() call delete('XscriptMatchCommon') endfunc +func Test_match_in_linebreak() + CheckRunVimInTerminal + + let lines =<< trim END + set breakindent linebreak breakat+=] + call printf('%s]%s', repeat('x', 50), repeat('x', 70))->setline(1) + call matchaddpos('ErrorMsg', [[1, 51]]) + END + call writefile(lines, 'XscriptMatchLinebreak') + let buf = RunVimInTerminal('-S XscriptMatchLinebreak', #{rows: 10}) + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_match_linebreak', {}) + + call StopVimInTerminal(buf) + call delete('XscriptMatchLinebreak') +endfunc + +func Test_match_with_incsearch() + CheckRunVimInTerminal + + let lines =<< trim END + set incsearch + call setline(1, range(20)) + call matchaddpos('ErrorMsg', [3]) + END + call writefile(lines, 'XmatchWithIncsearch') + let buf = RunVimInTerminal('-S XmatchWithIncsearch', #{rows: 6}) + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_match_with_incsearch_1', {}) + + call term_sendkeys(buf, ":s/0") + call VerifyScreenDump(buf, 'Test_match_with_incsearch_2', {}) + + call term_sendkeys(buf, "\<CR>") + call StopVimInTerminal(buf) + call delete('XmatchWithIncsearch') +endfunc + +" Test for deleting matches outside of the screen redraw top/bottom lines +" This should cause a redraw of those lines. +func Test_matchdelete_redraw() + new + call setline(1, range(1, 500)) + call cursor(250, 1) + let m1 = matchaddpos('Search', [[250]]) + let m2 = matchaddpos('Search', [[10], [450]]) + redraw! + let m3 = matchaddpos('Search', [[240], [260]]) + call matchdelete(m2) + let m = getmatches() + call assert_equal(2, len(m)) + call assert_equal([250], m[0].pos1) + redraw! + call matchdelete(m1) + call assert_equal(1, len(getmatches())) + bw! +endfunc + +func Test_match_tab_with_linebreak() + CheckRunVimInTerminal + + let lines =<< trim END + set linebreak + call setline(1, "\tix") + call matchadd('ErrorMsg', '\t') + END + call writefile(lines, 'XscriptMatchTabLinebreak') + let buf = RunVimInTerminal('-S XscriptMatchTabLinebreak', #{rows: 10}) + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_match_tab_linebreak', {}) + + call StopVimInTerminal(buf) + call delete('XscriptMatchTabLinebreak') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/src/nvim/testdir/test_matchfuzzy.vim index c836bc87aa..b46550fbc3 100644 --- a/src/nvim/testdir/test_matchfuzzy.vim +++ b/src/nvim/testdir/test_matchfuzzy.vim @@ -6,9 +6,7 @@ source check.vim " Test for matchfuzzy() func Test_matchfuzzy() call assert_fails('call matchfuzzy(10, "abc")', 'E686:') - " Needs v8.2.1183; match the final error that's thrown for now - " call assert_fails('call matchfuzzy(["abc"], [])', 'E730:') - call assert_fails('call matchfuzzy(["abc"], [])', 'E475:') + call assert_fails('call matchfuzzy(["abc"], [])', 'E730:') call assert_fails("let x = matchfuzzy(v:_null_list, 'foo')", 'E686:') call assert_fails('call matchfuzzy(["abc"], v:_null_string)', 'E475:') call assert_equal([], matchfuzzy([], 'abc')) @@ -75,12 +73,9 @@ func Test_matchfuzzy() call assert_fails("let x = matchfuzzy(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') call assert_equal([], matchfuzzy(l, 'cam')) " Nvim's callback implementation is different, so E6000 is expected instead, - " but we need v8.2.1183 to assert it " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E921:') - " call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:') - call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E475:') - " call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:') - call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E475:') + call assert_fails("let x = matchfuzzy(l, 'cam', {'text_cb' : []})", 'E6000:') + call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : []})", 'E730:') call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E715:') call assert_fails("let x = matchfuzzy(l, 'foo', {'key' : v:_null_string})", 'E475:') " Nvim doesn't have null functions @@ -155,12 +150,9 @@ func Test_matchfuzzypos() call assert_fails("let x = matchfuzzypos(l, 'day', {'text_cb' : {a, b -> 1}})", 'E119:') call assert_equal([[], [], []], matchfuzzypos(l, 'cam')) " Nvim's callback implementation is different, so E6000 is expected instead, - " but we need v8.2.1183 to assert it " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E921:') - " call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:') - call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E475:') - " call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:') - call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E475:') + call assert_fails("let x = matchfuzzypos(l, 'cam', {'text_cb' : []})", 'E6000:') + call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : []})", 'E730:') call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E715:') call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : v:_null_string})", 'E475:') " Nvim doesn't have null functions diff --git a/src/nvim/testdir/test_menu.vim b/src/nvim/testdir/test_menu.vim index 75992d3313..a1121632e6 100644 --- a/src/nvim/testdir/test_menu.vim +++ b/src/nvim/testdir/test_menu.vim @@ -153,7 +153,7 @@ func Test_menu_errors() call assert_fails('menu Test.Foo.Bar', 'E327:') call assert_fails('cmenu Test.Foo', 'E328:') call assert_fails('emenu x Test.Foo', 'E475:') - call assert_fails('emenu Test.Foo.Bar', 'E334:') + call assert_fails('emenu Test.Foo.Bar', 'E327:') call assert_fails('menutranslate Test', 'E474:') silent! unmenu Foo @@ -252,6 +252,7 @@ func Test_menu_info() nmenu Test.abc <Nop> call assert_equal('<Nop>', menu_info('Test.abc').rhs) call assert_fails('call menu_info([])', 'E730:') + call assert_fails('call menu_info("", [])', 'E730:') nunmenu Test " Test for defining menus in different modes @@ -429,7 +430,7 @@ func Test_menu_special() nunmenu Test.Sign endfunc -" Test for "icon=filname" in a toolbar +" Test for "icon=filename" in a toolbar func Test_menu_icon() CheckFeature toolbar nmenu icon=myicon.xpm Toolbar.Foo :echo "Foo"<CR> @@ -481,6 +482,35 @@ func Test_popup_menu() unmenu PopUp endfunc +" Test for MenuPopup autocommand +func Test_autocmd_MenuPopup() + CheckNotGui + + set mouse=a + set mousemodel=popup + aunmenu * + autocmd MenuPopup * exe printf( + \ 'anoremenu PopUp.Foo <Cmd>let g:res = ["%s", "%s"]<CR>', + \ expand('<afile>'), expand('<amatch>')) + + call feedkeys("\<RightMouse>\<Down>\<CR>", 'tnix') + call assert_equal(['n', 'n'], g:res) + + call feedkeys("v\<RightMouse>\<Down>\<CR>\<Esc>", 'tnix') + call assert_equal(['v', 'v'], g:res) + + call feedkeys("gh\<RightMouse>\<Down>\<CR>\<Esc>", 'tnix') + call assert_equal(['s', 's'], g:res) + + call feedkeys("i\<RightMouse>\<Down>\<CR>\<Esc>", 'tnix') + call assert_equal(['i', 'i'], g:res) + + autocmd! MenuPopup + aunmenu PopUp.Foo + unlet g:res + set mouse& mousemodel& +endfunc + " Test for listing the menus using the :menu command func Test_show_menus() " In the GUI, tear-off menu items are present in the output below diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim index 3a607ff533..bfdebdac79 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -171,6 +171,38 @@ func Test_echospace() set ruler& showcmd& endfunc +func Test_warning_scroll() + CheckRunVimInTerminal + let lines =<< trim END + call test_override('ui_delay', 50) + set noruler + set readonly + undo + END + call writefile(lines, 'XTestWarningScroll', 'D') + let buf = RunVimInTerminal('', #{rows: 8}) + + " When the warning comes from a script, messages are scrolled so that the + " stacktrace is visible. + call term_sendkeys(buf, ":source XTestWarningScroll\n") + " only match the final colon in the line that shows the source + call WaitForAssert({-> assert_match(':$', term_getline(buf, 5))}) + call WaitForAssert({-> assert_equal('line 4:W10: Warning: Changing a readonly file', term_getline(buf, 6))}) + call WaitForAssert({-> assert_equal('Already at oldest change', term_getline(buf, 7))}) + call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 8))}) + call term_sendkeys(buf, "\n") + + " When the warning does not come from a script, messages are not scrolled. + call term_sendkeys(buf, ":enew\n") + call term_sendkeys(buf, ":set readonly\n") + call term_sendkeys(buf, 'u') + call WaitForAssert({-> assert_equal('W10: Warning: Changing a readonly file', term_getline(buf, 8))}) + call WaitForAssert({-> assert_equal('Already at oldest change', term_getline(buf, 8))}) + + " clean up + call StopVimInTerminal(buf) +endfunc + " Test more-prompt (see :help more-prompt). func Test_message_more() CheckRunVimInTerminal @@ -284,6 +316,66 @@ func Test_message_more() call StopVimInTerminal(buf) endfunc +" Test more-prompt scrollback +func Test_message_more_scrollback() + CheckRunVimInTerminal + + let lines =<< trim END + set t_ut= + hi Normal ctermfg=15 ctermbg=0 + for i in range(100) + echo i + endfor + END + call writefile(lines, 'XmoreScrollback', 'D') + let buf = RunVimInTerminal('-S XmoreScrollback', {'rows': 10}) + call VerifyScreenDump(buf, 'Test_more_scrollback_1', {}) + + call term_sendkeys(buf, 'f') + call TermWait(buf) + call term_sendkeys(buf, 'b') + call VerifyScreenDump(buf, 'Test_more_scrollback_2', {}) + + call term_sendkeys(buf, 'q') + call TermWait(buf) + call StopVimInTerminal(buf) +endfunc + +" Test verbose message before echo command +func Test_echo_verbose_system() + CheckRunVimInTerminal + CheckUnix + + let buf = RunVimInTerminal('', {'rows': 10}) + call term_sendkeys(buf, ":4 verbose echo system('seq 20')\<CR>") + " Note that the screendump is filtered to remove the name of the temp file + call VerifyScreenDump(buf, 'Test_verbose_system_1', {}) + + " display a page and go back, results in exactly the same view + call term_sendkeys(buf, ' ') + call TermWait(buf, 50) + call term_sendkeys(buf, 'b') + call VerifyScreenDump(buf, 'Test_verbose_system_1', {}) + + " do the same with 'cmdheight' set to 2 + call term_sendkeys(buf, 'q') + call TermWait(buf) + call term_sendkeys(buf, ":set ch=2\<CR>") + call TermWait(buf) + call term_sendkeys(buf, ":4 verbose echo system('seq 20')\<CR>") + call VerifyScreenDump(buf, 'Test_verbose_system_2', {}) + + call term_sendkeys(buf, ' ') + call TermWait(buf, 50) + call term_sendkeys(buf, 'b') + call VerifyScreenDump(buf, 'Test_verbose_system_2', {}) + + call term_sendkeys(buf, 'q') + call TermWait(buf) + call StopVimInTerminal(buf) +endfunc + + func Test_ask_yesno() CheckRunVimInTerminal let buf = RunVimInTerminal('', {'rows': 6}) @@ -325,14 +417,14 @@ func Test_quit_long_message() let content =<< trim END echom range(9999)->join("\x01") END - call writefile(content, 'Xtest_quit_message') - let buf = RunVimInTerminal('-S Xtest_quit_message', #{rows: 6}) + call writefile(content, 'Xtest_quit_message', 'D') + let buf = RunVimInTerminal('-S Xtest_quit_message', #{rows: 10, wait_for_ruler: 0}) + call WaitForAssert({-> assert_match('^-- More --', term_getline(buf, 10))}) call term_sendkeys(buf, "q") call VerifyScreenDump(buf, 'Test_quit_long_message', {}) " clean up call StopVimInTerminal(buf) - call delete('Xtest_quit_message') endfunc " this was missing a terminating NUL @@ -398,7 +490,7 @@ func Test_cmdheight_zero() " Check change/restore cmdheight when macro call feedkeys("qa", "xt") - call assert_equal(1, &cmdheight) + call assert_equal(0, &cmdheight) call feedkeys("q", "xt") call assert_equal(0, &cmdheight) diff --git a/src/nvim/testdir/test_method.vim b/src/nvim/testdir/test_method.vim index cdf688b857..ca3b736429 100644 --- a/src/nvim/testdir/test_method.vim +++ b/src/nvim/testdir/test_method.vim @@ -35,6 +35,7 @@ func Test_list_method() call assert_equal(v:t_list, l->type()) call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq()) call assert_fails('eval l->values()', 'E715:') + call assert_fails('echo []->len', 'E107:') endfunc func Test_dict_method() @@ -130,9 +131,9 @@ func Test_method_syntax() eval [1, 2, 3] \ ->sort( \ ) - call assert_fails('eval [1, 2, 3]-> sort()', 'E260:') + call assert_fails('eval [1, 2, 3]-> sort()', 'E15:') call assert_fails('eval [1, 2, 3]->sort ()', 'E274:') - call assert_fails('eval [1, 2, 3]-> sort ()', 'E260:') + call assert_fails('eval [1, 2, 3]-> sort ()', 'E15:') endfunc func Test_method_lambda() @@ -152,6 +153,22 @@ endfunc func Test_method_not_supported() call assert_fails('eval 123->changenr()', 'E276:') + call assert_fails('echo "abc"->invalidfunc()', 'E117:') + " Test for too many or too few arguments to a method + call assert_fails('let n="abc"->len(2)', 'E118:') + call assert_fails('let n=10->setwinvar()', 'E119:') endfunc -" vim: shiftwidth=2 sts=2 expandtab +" Test for passing optional arguments to methods +func Test_method_args() + let v:errors = [] + let n = 10->assert_inrange(1, 5, "Test_assert_inrange") + if v:errors[0] !~ 'Test_assert_inrange' + call assert_report(v:errors[0]) + else + " Test passed + let v:errors = [] + endif +endfunc + +" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index 8ec408e62e..972397cb91 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -851,9 +851,7 @@ func Test_mksession_shortmess_with_A() edit Xtestfile write let fname = swapname('%') - " readblob() needs patch 8.2.2343 - " let cont = readblob(fname) - let cont = readfile(fname, 'B') + let cont = readblob(fname) set sessionoptions-=options mksession Xtestsession bwipe! @@ -942,6 +940,19 @@ func Test_mkvimrc() endfor call s:ClearMappings() + + " the 'pastetoggle', 'wildchar' and 'wildcharm' option values should be + " stored as key names in the vimrc file + set pastetoggle=<F5> + set wildchar=<F6> + set wildcharm=<F7> + call assert_fails('mkvimrc Xtestvimrc') + mkvimrc! Xtestvimrc + call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set pastetoggle=<F5>')) + call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set wildchar=<F6>')) + call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set wildcharm=<F7>')) + set pastetoggle& wildchar& wildcharm& + call delete('Xtestvimrc') endfunc @@ -979,6 +990,38 @@ func Test_altfile() call assert_equal('Xtwoalt', bufname('#')) only bwipe! + call delete('Xtest_altfile') +endfunc + +" Test for creating views with manual folds +func Test_mkview_manual_fold() + call writefile(range(1,10), 'Xfile') + new Xfile + " create recursive folds + 5,6fold + 4,7fold + mkview Xview + normal zE + source Xview + call assert_equal([-1, 4, 4, 4, 4, -1], [foldclosed(3), foldclosed(4), + \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)]) + " open one level of fold + 4foldopen + mkview! Xview + normal zE + source Xview + call assert_equal([-1, -1, 5, 5, -1, -1], [foldclosed(3), foldclosed(4), + \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)]) + " open all the folds + %foldopen! + mkview! Xview + normal zE + source Xview + call assert_equal([-1, -1, -1, -1, -1, -1], [foldclosed(3), foldclosed(4), + \ foldclosed(5), foldclosed(6), foldclosed(7), foldclosed(8)]) + call delete('Xfile') + call delete('Xview') + bw! endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_modeline.vim b/src/nvim/testdir/test_modeline.vim index b3fe79f545..613722fdbd 100644 --- a/src/nvim/testdir/test_modeline.vim +++ b/src/nvim/testdir/test_modeline.vim @@ -1,5 +1,7 @@ " Tests for parsing the modeline. +source check.vim + func Test_modeline_invalid() " This was reading allocated memory in the past. call writefile(['vi:0', 'nothing'], 'Xmodeline') @@ -281,6 +283,88 @@ func Test_modeline_fails_modelineexpr() call s:modeline_fails('titlestring', 'titlestring=Something()', 'E992:') endfunc +func Test_modeline_setoption_verbose() + let modeline = &modeline + set modeline + + let lines =<< trim END + 1 vim:ts=2 + 2 two + 3 three + 4 four + 5 five + 6 six + 7 seven + 8 eight + END + call writefile(lines, 'Xmodeline') + edit Xmodeline + let info = split(execute('verbose set tabstop?'), "\n") + call assert_match('^\s*Last set from modeline line 1$', info[-1]) + bwipe! + + let lines =<< trim END + 1 one + 2 two + 3 three + 4 vim:ts=4 + 5 five + 6 six + 7 seven + 8 eight + END + call writefile(lines, 'Xmodeline') + edit Xmodeline + let info = split(execute('verbose set tabstop?'), "\n") + call assert_match('^\s*Last set from modeline line 4$', info[-1]) + bwipe! + + let lines =<< trim END + 1 one + 2 two + 3 three + 4 four + 5 five + 6 six + 7 seven + 8 vim:ts=8 + END + call writefile(lines, 'Xmodeline') + edit Xmodeline + let info = split(execute('verbose set tabstop?'), "\n") + call assert_match('^\s*Last set from modeline line 8$', info[-1]) + bwipe! + + let &modeline = modeline + call delete('Xmodeline') +endfunc + +" Test for the 'modeline' default value in compatible and non-compatible modes +" for root and non-root accounts +func Test_modeline_default() + " set compatible + " call assert_false(&modeline) + set nocompatible + call assert_equal(IsRoot() ? 0 : 1, &modeline) + " set compatible&vi + " call assert_false(&modeline) + set compatible&vim + call assert_equal(IsRoot() ? 0 : 1, &modeline) + set compatible& modeline& +endfunc + +" Some options cannot be set from the modeline when 'diff' option is set +func Test_modeline_diff_buffer() + call writefile(['vim: diff foldmethod=marker wrap'], 'Xfile') + set foldmethod& nowrap + new Xfile + call assert_equal('manual', &foldmethod) + call assert_false(&wrap) + set wrap& + call delete('Xfile') + bw +endfunc + func Test_modeline_disable() set modeline call writefile(['vim: sw=2', 'vim: nomodeline', 'vim: sw=3'], 'Xmodeline_disable') diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 2092b508ea..7c90b444e5 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -3,6 +3,7 @@ source shared.vim source check.vim source view_util.vim +source vim9.vim source screendump.vim func Setup_NewWindow() @@ -35,14 +36,14 @@ func CountSpaces(type, ...) else silent exe "normal! `[v`]y" endif - let g:a=strlen(substitute(@@, '[^ ]', '', 'g')) + let g:a = strlen(substitute(@@, '[^ ]', '', 'g')) let &selection = sel_save let @@ = reg_save endfunc func OpfuncDummy(type, ...) " for testing operatorfunc - let g:opt=&linebreak + let g:opt = &linebreak if a:0 " Invoked from Visual mode, use gv command. silent exe "normal! gvy" @@ -53,7 +54,7 @@ func OpfuncDummy(type, ...) endif " Create a new dummy window new - let g:bufnr=bufnr('%') + let g:bufnr = bufnr('%') endfunc func Test_normal00_optrans() @@ -120,6 +121,39 @@ func Test_normal01_keymodel() call feedkeys("Vkk\<Up>yy", 'tx') call assert_equal(['47', '48', '49', '50'], getreg(0, 0, 1)) + " Test for using special keys to start visual selection + %d + call setline(1, ['red fox tail', 'red fox tail', 'red fox tail']) + set keymodel=startsel + " Test for <S-PageUp> and <S-PageDown> + call cursor(1, 1) + call feedkeys("\<S-PageDown>y", 'xt') + call assert_equal([0, 1, 1, 0], getpos("'<")) + call assert_equal([0, 3, 1, 0], getpos("'>")) + call feedkeys("Gz\<CR>8|\<S-PageUp>y", 'xt') + call assert_equal([0, 2, 1, 0], getpos("'<")) + call assert_equal([0, 3, 8, 0], getpos("'>")) + " Test for <S-C-Home> and <S-C-End> + call cursor(2, 12) + call feedkeys("\<S-C-Home>y", 'xt') + call assert_equal([0, 1, 1, 0], getpos("'<")) + call assert_equal([0, 2, 12, 0], getpos("'>")) + call cursor(1, 4) + call feedkeys("\<S-C-End>y", 'xt') + call assert_equal([0, 1, 4, 0], getpos("'<")) + call assert_equal([0, 3, 13, 0], getpos("'>")) + " Test for <S-C-Left> and <S-C-Right> + call cursor(2, 5) + call feedkeys("\<S-C-Right>y", 'xt') + call assert_equal([0, 2, 5, 0], getpos("'<")) + call assert_equal([0, 2, 9, 0], getpos("'>")) + call cursor(2, 9) + call feedkeys("\<S-C-Left>y", 'xt') + call assert_equal([0, 2, 5, 0], getpos("'<")) + call assert_equal([0, 2, 9, 0], getpos("'>")) + + set keymodel& + " clean up bw! endfunc @@ -140,16 +174,15 @@ func Test_normal03_join() $ :j 10 call assert_equal('100', getline('.')) + call assert_beeps('normal GVJ') " clean up bw! endfunc +" basic filter test func Test_normal04_filter() - " basic filter test " only test on non windows platform - if has('win32') - return - endif + CheckNotMSWindows call Setup_NewWindow() 1 call feedkeys("!!sed -e 's/^/| /'\n", 'tx') @@ -222,12 +255,49 @@ func Test_normal_formatexpr_returns_nonzero() close! endfunc +" Test for using a script-local function for 'formatexpr' +func Test_formatexpr_scriptlocal_func() + func! s:Format() + let g:FormatArgs = [v:lnum, v:count] + endfunc + set formatexpr=s:Format() + call assert_equal(expand('<SID>') .. 'Format()', &formatexpr) + new | only + call setline(1, range(1, 40)) + let g:FormatArgs = [] + normal! 2GVjgq + call assert_equal([2, 2], g:FormatArgs) + bw! + set formatexpr=<SID>Format() + call assert_equal(expand('<SID>') .. 'Format()', &formatexpr) + new | only + call setline(1, range(1, 40)) + let g:FormatArgs = [] + normal! 4GVjgq + call assert_equal([4, 2], g:FormatArgs) + bw! + let &formatexpr = 's:Format()' + new | only + call setline(1, range(1, 40)) + let g:FormatArgs = [] + normal! 6GVjgq + call assert_equal([6, 2], g:FormatArgs) + bw! + let &formatexpr = '<SID>Format()' + new | only + call setline(1, range(1, 40)) + let g:FormatArgs = [] + normal! 8GVjgq + call assert_equal([8, 2], g:FormatArgs) + setlocal formatexpr= + delfunc s:Format + bw! +endfunc + +" basic test for formatprg func Test_normal06_formatprg() - " basic test for formatprg " only test on non windows platform - if has('win32') - return - endif + CheckNotMSWindows " uses sed to number non-empty lines call writefile(['#!/bin/sh', 'sed ''/./=''|sed ''/./{', 'N', 's/\n/ /', '}'''], 'Xsed_format.sh') @@ -240,16 +310,24 @@ func Test_normal06_formatprg() set formatprg=./Xsed_format.sh norm! gggqG call assert_equal(expected, getline(1, '$')) - bw! + %d - 10new call setline(1, text) set formatprg=donothing setlocal formatprg=./Xsed_format.sh norm! gggqG call assert_equal(expected, getline(1, '$')) - bw! + %d + + " Check for the command-line ranges added to 'formatprg' + set formatprg=cat + call setline(1, ['one', 'two', 'three', 'four', 'five']) + call feedkeys('gggqG', 'xt') + call assert_equal('.,$!cat', @:) + call feedkeys('2Ggq2j', 'xt') + call assert_equal('.,.+2!cat', @:) + bw! " clean up set formatprg= setlocal formatprg= @@ -263,18 +341,16 @@ func Test_normal07_internalfmt() 10new call setline(1, list) set tw=12 - norm! gggqG + norm! ggVGgq call assert_equal(['1 2 3', '4 5 6', '7 8 9', '10 11 '], getline(1, '$')) " clean up set tw=0 bw! endfunc +" basic tests for foldopen/folddelete func Test_normal08_fold() - " basic tests for foldopen/folddelete - if !has("folding") - return - endif + CheckFeature folding call Setup_NewWindow() 50 setl foldenable fdm=marker @@ -352,70 +428,6 @@ func Test_normal09a_operatorfunc() norm V10j,, call assert_equal(22, g:a) - " Use a lambda function for 'opfunc' - unmap <buffer> ,, - call cursor(1, 1) - let g:a=0 - nmap <buffer><silent> ,, :set opfunc={type\ ->\ CountSpaces(type)}<CR>g@ - vmap <buffer><silent> ,, :<C-U>call CountSpaces(visualmode(), 1)<CR> - 50 - norm V2j,, - call assert_equal(6, g:a) - norm V,, - call assert_equal(2, g:a) - norm ,,l - call assert_equal(0, g:a) - 50 - exe "norm 0\<c-v>10j2l,," - call assert_equal(11, g:a) - 50 - norm V10j,, - call assert_equal(22, g:a) - - " use a partial function for 'opfunc' - let g:OpVal = 0 - func! Test_opfunc1(x, y, type) - let g:OpVal = a:x + a:y - endfunc - set opfunc=function('Test_opfunc1',\ [5,\ 7]) - normal! g@l - call assert_equal(12, g:OpVal) - " delete the function and try to use g@ - delfunc Test_opfunc1 - call test_garbagecollect_now() - call assert_fails('normal! g@l', 'E117:') - set opfunc= - - " use a funcref for 'opfunc' - let g:OpVal = 0 - func! Test_opfunc2(x, y, type) - let g:OpVal = a:x + a:y - endfunc - set opfunc=funcref('Test_opfunc2',\ [4,\ 3]) - normal! g@l - call assert_equal(7, g:OpVal) - " delete the function and try to use g@ - delfunc Test_opfunc2 - call test_garbagecollect_now() - call assert_fails('normal! g@l', 'E933:') - set opfunc= - - " Try to use a function with two arguments for 'operatorfunc' - let g:OpVal = 0 - func! Test_opfunc3(x, y) - let g:OpVal = 4 - endfunc - set opfunc=Test_opfunc3 - call assert_fails('normal! g@l', 'E119:') - call assert_equal(0, g:OpVal) - set opfunc= - delfunc Test_opfunc3 - unlet g:OpVal - - " Try to use a lambda function with two arguments for 'operatorfunc' - set opfunc={x,\ y\ ->\ 'done'} - call assert_fails('normal! g@l', 'E119:') - " clean up unmap <buffer> ,, set opfunc= @@ -484,6 +496,227 @@ func Test_normal09c_operatorfunc() set operatorfunc= endfunc +" Test for different ways of setting the 'operatorfunc' option +func Test_opfunc_callback() + new + func OpFunc1(callnr, type) + let g:OpFunc1Args = [a:callnr, a:type] + endfunc + func OpFunc2(type) + let g:OpFunc2Args = [a:type] + endfunc + + let lines =<< trim END + #" Test for using a function name + LET &opfunc = 'g:OpFunc2' + LET g:OpFunc2Args = [] + normal! g@l + call assert_equal(['char'], g:OpFunc2Args) + + #" Test for using a function() + set opfunc=function('g:OpFunc1',\ [10]) + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([10, 'char'], g:OpFunc1Args) + + #" Using a funcref variable to set 'operatorfunc' + VAR Fn = function('g:OpFunc1', [11]) + LET &opfunc = Fn + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([11, 'char'], g:OpFunc1Args) + + #" Using a string(funcref_variable) to set 'operatorfunc' + LET Fn = function('g:OpFunc1', [12]) + LET &operatorfunc = string(Fn) + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([12, 'char'], g:OpFunc1Args) + + #" Test for using a funcref() + set operatorfunc=funcref('g:OpFunc1',\ [13]) + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([13, 'char'], g:OpFunc1Args) + + #" Using a funcref variable to set 'operatorfunc' + LET Fn = funcref('g:OpFunc1', [14]) + LET &opfunc = Fn + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([14, 'char'], g:OpFunc1Args) + + #" Using a string(funcref_variable) to set 'operatorfunc' + LET Fn = funcref('g:OpFunc1', [15]) + LET &opfunc = string(Fn) + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([15, 'char'], g:OpFunc1Args) + + #" Test for using a lambda function using set + VAR optval = "LSTART a LMIDDLE OpFunc1(16, a) LEND" + LET optval = substitute(optval, ' ', '\\ ', 'g') + exe "set opfunc=" .. optval + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([16, 'char'], g:OpFunc1Args) + + #" Test for using a lambda function using LET + LET &opfunc = LSTART a LMIDDLE OpFunc1(17, a) LEND + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([17, 'char'], g:OpFunc1Args) + + #" Set 'operatorfunc' to a string(lambda expression) + LET &opfunc = 'LSTART a LMIDDLE OpFunc1(18, a) LEND' + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([18, 'char'], g:OpFunc1Args) + + #" Set 'operatorfunc' to a variable with a lambda expression + VAR Lambda = LSTART a LMIDDLE OpFunc1(19, a) LEND + LET &opfunc = Lambda + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([19, 'char'], g:OpFunc1Args) + + #" Set 'operatorfunc' to a string(variable with a lambda expression) + LET Lambda = LSTART a LMIDDLE OpFunc1(20, a) LEND + LET &opfunc = string(Lambda) + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([20, 'char'], g:OpFunc1Args) + + #" Try to use 'operatorfunc' after the function is deleted + func g:TmpOpFunc1(type) + let g:TmpOpFunc1Args = [21, a:type] + endfunc + LET &opfunc = function('g:TmpOpFunc1') + delfunc g:TmpOpFunc1 + call test_garbagecollect_now() + LET g:TmpOpFunc1Args = [] + call assert_fails('normal! g@l', 'E117:') + call assert_equal([], g:TmpOpFunc1Args) + + #" Try to use a function with two arguments for 'operatorfunc' + func g:TmpOpFunc2(x, y) + let g:TmpOpFunc2Args = [a:x, a:y] + endfunc + set opfunc=TmpOpFunc2 + LET g:TmpOpFunc2Args = [] + call assert_fails('normal! g@l', 'E119:') + call assert_equal([], g:TmpOpFunc2Args) + delfunc TmpOpFunc2 + + #" Try to use a lambda function with two arguments for 'operatorfunc' + LET &opfunc = LSTART a, b LMIDDLE OpFunc1(22, b) LEND + LET g:OpFunc1Args = [] + call assert_fails('normal! g@l', 'E119:') + call assert_equal([], g:OpFunc1Args) + + #" Test for clearing the 'operatorfunc' option + set opfunc='' + set opfunc& + call assert_fails("set opfunc=function('abc')", "E700:") + call assert_fails("set opfunc=funcref('abc')", "E700:") + + #" set 'operatorfunc' to a non-existing function + LET &opfunc = function('g:OpFunc1', [23]) + call assert_fails("set opfunc=function('NonExistingFunc')", 'E700:') + call assert_fails("LET &opfunc = function('NonExistingFunc')", 'E700:') + LET g:OpFunc1Args = [] + normal! g@l + call assert_equal([23, 'char'], g:OpFunc1Args) + END + call CheckTransLegacySuccess(lines) + + " Test for using a script-local function name + func s:OpFunc3(type) + let g:OpFunc3Args = [a:type] + endfunc + set opfunc=s:OpFunc3 + let g:OpFunc3Args = [] + normal! g@l + call assert_equal(['char'], g:OpFunc3Args) + + let &opfunc = 's:OpFunc3' + let g:OpFunc3Args = [] + normal! g@l + call assert_equal(['char'], g:OpFunc3Args) + delfunc s:OpFunc3 + + " Using Vim9 lambda expression in legacy context should fail + " set opfunc=(a)\ =>\ OpFunc1(24,\ a) + let g:OpFunc1Args = [] + " call assert_fails('normal! g@l', 'E117:') + call assert_equal([], g:OpFunc1Args) + + " set 'operatorfunc' to a partial with dict. This used to cause a crash. + func SetOpFunc() + let operator = {'execute': function('OperatorExecute')} + let &opfunc = operator.execute + endfunc + func OperatorExecute(_) dict + endfunc + call SetOpFunc() + call test_garbagecollect_now() + set operatorfunc= + delfunc SetOpFunc + delfunc OperatorExecute + + " Vim9 tests + let lines =<< trim END + vim9script + + def g:Vim9opFunc(val: number, type: string): void + g:OpFunc1Args = [val, type] + enddef + + # Test for using a def function with opfunc + set opfunc=function('g:Vim9opFunc',\ [60]) + g:OpFunc1Args = [] + normal! g@l + assert_equal([60, 'char'], g:OpFunc1Args) + + # Test for using a global function name + &opfunc = g:OpFunc2 + g:OpFunc2Args = [] + normal! g@l + assert_equal(['char'], g:OpFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalOpFunc(type: string): void + g:LocalOpFuncArgs = [type] + enddef + &opfunc = s:LocalOpFunc + g:LocalOpFuncArgs = [] + normal! g@l + assert_equal(['char'], g:LocalOpFuncArgs) + bw! + END + call CheckScriptSuccess(lines) + + " setting 'opfunc' to a script local function outside of a script context + " should fail + let cleanup =<< trim END + call writefile([execute('messages')], 'Xtest.out') + qall + END + call writefile(cleanup, 'Xverify.vim') + call RunVim([], [], "-c \"set opfunc=s:abc\" -S Xverify.vim") + call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0]) + call delete('Xtest.out') + call delete('Xverify.vim') + + " cleanup + set opfunc& + delfunc OpFunc1 + delfunc OpFunc2 + unlet g:OpFunc1Args g:OpFunc2Args + %bw! +endfunc + func Test_normal10_expand() " Test for expand() 10new @@ -506,6 +739,14 @@ func Test_normal10_expand() call assert_equal(expected[i], expand('<cexpr>'), 'i == ' . i) endfor + " Test for <cexpr> in state.val and ptr->val + call setline(1, 'x = state.val;') + call cursor(1, 10) + call assert_equal('state.val', expand('<cexpr>')) + call setline(1, 'x = ptr->val;') + call cursor(1, 9) + call assert_equal('ptr->val', expand('<cexpr>')) + if executable('echo') " Test expand(`...`) i.e. backticks command expansion. " MS-Windows has a trailing space. @@ -514,11 +755,25 @@ func Test_normal10_expand() " Test expand(`=...`) i.e. backticks expression expansion call assert_equal('5', expand('`=2+3`')) + call assert_equal('3.14', expand('`=3.14`')) " clean up bw! endfunc +" Test for expand() in latin1 encoding +func Test_normal_expand_latin1() + new + let save_enc = &encoding + " set encoding=latin1 + call setline(1, 'val = item->color;') + call cursor(1, 11) + call assert_equal('color', expand("<cword>")) + call assert_equal('item->color', expand("<cexpr>")) + let &encoding = save_enc + bw! +endfunc + func Test_normal11_showcmd() " test for 'showcmd' 10new @@ -543,6 +798,13 @@ func Test_normal11_showcmd() redraw! call assert_match('1-3$', Screenline(&lines)) call feedkeys("v", 'xt') + " test for visually selecting the end of line + call setline(1, ["foobar"]) + call feedkeys("$vl", 'xt') + redraw! + call assert_match('2$', Screenline(&lines)) + call feedkeys("y", 'xt') + call assert_equal("r\n", @") bw! endfunc @@ -1024,6 +1286,22 @@ func Test_vert_scroll_cmds() close! endfunc +func Test_scroll_in_ex_mode() + " This was using invalid memory because w_botline was invalid. + let lines =<< trim END + diffsplit + norm os00( + call writefile(['done'], 'Xdone') + qa! + END + call writefile(lines, 'Xscript') + call assert_equal(1, RunVim([], [], '--clean -X -Z -e -s -S Xscript')) + call assert_equal(['done'], readfile('Xdone')) + + call delete('Xscript') + call delete('Xdone') +endfunc + " Test for the 'sidescroll' option func Test_sidescroll_opt() new @@ -1431,10 +1709,8 @@ func Test_normal18_z_fold() endfunc func Test_normal20_exmode() - if !has("unix") - " Reading from redirected file doesn't work on MS-Windows - return - endif + " Reading from redirected file doesn't work on MS-Windows + CheckNotMSWindows call writefile(['1a', 'foo', 'bar', '.', 'w! Xfile2', 'q!'], 'Xscript') call writefile(['1', '2'], 'Xfile') call system(GetVimCommand() .. ' -e -s < Xscript Xfile') @@ -1455,6 +1731,7 @@ func Test_normal21_nv_hat() edit Xfoo | %bw call assert_fails(':buffer #', 'E86') call assert_fails(':execute "normal! \<C-^>"', 'E23') + call assert_fails("normal i\<C-R>#", 'E23:') " Test for the expected behavior when switching between two named buffers. edit Xfoo | edit Xbar @@ -1586,7 +1863,7 @@ func Test_normal23_K() call setline(1, '---') call assert_fails('normal! ggv2lK', 'E349:') call setline(1, ['abc', 'xyz']) - call assert_fails("normal! gg2lv2h\<C-]>", 'E426:') + call assert_fails("normal! gg2lv2h\<C-]>", 'E433:') call assert_beeps("normal! ggVjK") " clean up @@ -2061,6 +2338,16 @@ func Test_normal30_changecase() call assert_equal(['aaaaaa', 'AAAAaa'], getline(1, 2)) set whichwrap& + " try changing the case with a double byte encoding (DBCS) + %bw! + let enc = &enc + " set encoding=cp932 + call setline(1, "\u8470") + normal ~ + normal gU$gu$gUgUg~g~gugu + call assert_equal("\u8470", getline(1)) + let &encoding = enc + " clean up bw! endfunc @@ -2152,6 +2439,19 @@ func Test_normal31_r_cmd() " r command should fail in operator pending mode call assert_beeps('normal! cr') + " replace a tab character in visual mode + %d + call setline(1, ["a\tb", "c\td", "e\tf"]) + normal gglvjjrx + call assert_equal(['axx', 'xxx', 'xxf'], getline(1, '$')) + + " replace with a multibyte character (with multiple composing characters) + %d + new + call setline(1, 'aaa') + exe "normal $ra\u0328\u0301" + call assert_equal("aaa\u0328\u0301", getline(1)) + " clean up set noautoindent bw! @@ -2176,9 +2476,7 @@ endfunc " Test for g`, g;, g,, g&, gv, gk, gj, gJ, g0, g^, g_, gm, g$, gM, g CTRL-G, " gi and gI commands func Test_normal33_g_cmd2() - if !has("jumplist") - return - endif + CheckFeature jumplist call Setup_NewWindow() " Test for g` clearjumps @@ -2637,7 +2935,6 @@ endfunc " Test for cw cW ce func Test_normal39_cw() " Test for cw and cW on whitespace - " and cpo+=w setting new set tw=0 call append(0, 'here are some words') @@ -2645,14 +2942,6 @@ func Test_normal39_cw() call assert_equal('hereZZZare some words', getline('.')) norm! 1gg0elcWYYY call assert_equal('hereZZZareYYYsome words', getline('.')) - " Nvim: no "w" flag in 'cpoptions'. - " set cpo+=w - " call setline(1, 'here are some words') - " norm! 1gg0elcwZZZ - " call assert_equal('hereZZZ are some words', getline('.')) - " norm! 1gg2elcWYYY - " call assert_equal('hereZZZ areYYY some words', getline('.')) - set cpo-=w norm! 2gg0cwfoo call assert_equal('foo', getline('.')) @@ -2812,24 +3101,6 @@ func Test_normal47_visual_buf_wipe() set nomodified endfunc -func Test_normal47_autocmd() - " disabled, does not seem to be possible currently - throw "Skipped: not possible to test cursorhold autocmd while waiting for input in normal_cmd" - new - call append(0, repeat('-',20)) - au CursorHold * call feedkeys('2l', '') - 1 - set updatetime=20 - " should delete 12 chars (d12l) - call feedkeys('d1', '!') - call assert_equal('--------', getline(1)) - - " clean up - au! CursorHold - set updatetime=4000 - bw! -endfunc - func Test_normal48_wincmd() new exe "norm! \<c-w>c" @@ -2847,9 +3118,8 @@ func Test_normal49_counts() endfunc func Test_normal50_commandline() - if !has("timers") || !has("cmdline_hist") - return - endif + CheckFeature timers + CheckFeature cmdline_hist func! DoTimerWork(id) call assert_equal('[Command Line]', bufname('')) " should fail, with E11, but does fail with E23? @@ -2878,9 +3148,7 @@ func Test_normal50_commandline() endfunc func Test_normal51_FileChangedRO() - if !has("autocmd") - return - endif + CheckFeature autocmd call writefile(['foo'], 'Xreadonly.log') new Xreadonly.log setl ro @@ -2895,9 +3163,7 @@ func Test_normal51_FileChangedRO() endfunc func Test_normal52_rl() - if !has("rightleft") - return - endif + CheckFeature rightleft new call setline(1, 'abcde fghij klmnopq') norm! 1gg$ @@ -2929,22 +3195,6 @@ func Test_normal52_rl() bw! endfunc -func Test_normal53_digraph() - if !has('digraphs') - return - endif - new - call setline(1, 'abcdefgh|') - exe "norm! 1gg0f\<c-k>!!" - call assert_equal(9, col('.')) - set cpo+=D - exe "norm! 1gg0f\<c-k>!!" - call assert_equal(1, col('.')) - - set cpo-=D - bw! -endfunc - func Test_normal54_Ctrl_bsl() new call setline(1, 'abcdefghijklmn') @@ -3265,46 +3515,6 @@ func Test_normal_gk_gj() set cpoptions& number& numberwidth& wrap& endfunc -" Test for cursor movement with '-' in 'cpoptions' -func Test_normal_cpo_minus() - throw 'Skipped: Nvim does not support cpoptions flag "-"' - new - call setline(1, ['foo', 'bar', 'baz']) - let save_cpo = &cpo - set cpo+=- - call assert_beeps('normal 10j') - call assert_equal(1, line('.')) - normal G - call assert_beeps('normal 10k') - call assert_equal(3, line('.')) - call assert_fails(10, 'E16:') - let &cpo = save_cpo - close! -endfunc - -" Test for displaying dollar when changing text ('$' flag in 'cpoptions') -func Test_normal_cpo_dollar() - throw 'Skipped: use test/functional/legacy/cpoptions_spec.lua' - new - let g:Line = '' - func SaveFirstLine() - let g:Line = Screenline(1) - return '' - endfunc - inoremap <expr> <buffer> <F2> SaveFirstLine() - call test_override('redraw_flag', 1) - set cpo+=$ - call setline(1, 'one two three') - redraw! - exe "normal c2w\<F2>vim" - call assert_equal('one tw$ three', g:Line) - call assert_equal('vim three', getline(1)) - set cpo-=$ - call test_override('ALL', 0) - delfunc SaveFirstLine - %bw! -endfunc - " Test for using : to run a multi-line Ex command in operator pending mode func Test_normal_yank_with_excmd() new @@ -3369,6 +3579,31 @@ func Test_normal_colon_op() close! endfunc +" Test for d and D commands +func Test_normal_delete_cmd() + new + " D in an empty line + call setline(1, '') + normal D + call assert_equal('', getline(1)) + " D in an empty line in virtualedit mode + set virtualedit=all + normal D + call assert_equal('', getline(1)) + set virtualedit& + " delete to a readonly register + call setline(1, ['abcd']) + call assert_beeps('normal ":d2l') + + " D and d with 'nomodifiable' + call setline(1, ['abcd']) + setlocal nomodifiable + call assert_fails('normal D', 'E21:') + call assert_fails('normal d$', 'E21:') + + close! +endfunc + " Test for deleting or changing characters across lines with 'whichwrap' " containing 's'. Should count <EOL> as one character. func Test_normal_op_across_lines() @@ -3476,6 +3711,27 @@ func Test_normal_percent_jump() close! endfunc +" Test for << and >> commands to shift text by 'shiftwidth' +func Test_normal_shift_rightleft() + new + call setline(1, ['one', '', "\t", ' two', "\tthree", ' four']) + set shiftwidth=2 tabstop=8 + normal gg6>> + call assert_equal([' one', '', "\t ", ' two', "\t three", "\tfour"], + \ getline(1, '$')) + normal ggVG2>> + call assert_equal([' one', '', "\t ", "\ttwo", + \ "\t three", "\t four"], getline(1, '$')) + normal gg6<< + call assert_equal([' one', '', "\t ", ' two', "\t three", + \ "\t four"], getline(1, '$')) + normal ggVG2<< + call assert_equal(['one', '', "\t", ' two', "\tthree", ' four'], + \ getline(1, '$')) + set shiftwidth& tabstop& + bw! +endfunc + " Some commands like yy, cc, dd, >>, << and !! accept a count after " typing the first letter of the command. func Test_normal_count_after_operator() @@ -3539,4 +3795,37 @@ func Test_normal_count_out_of_range() bwipe! endfunc +" Test that mouse shape is restored to Normal mode after failed "c" operation. +func Test_mouse_shape_after_failed_change() + CheckFeature mouseshape + CheckCanRunGui + + let lines =<< trim END + set mouseshape+=o:busy + setlocal nomodifiable + let g:mouse_shapes = [] + + func SaveMouseShape(timer) + let g:mouse_shapes += [getmouseshape()] + endfunc + + func SaveAndQuit(timer) + call writefile(g:mouse_shapes, 'Xmouseshapes') + quit + endfunc + + call timer_start(50, {_ -> feedkeys('c')}) + call timer_start(100, 'SaveMouseShape') + call timer_start(150, {_ -> feedkeys('c')}) + call timer_start(200, 'SaveMouseShape') + call timer_start(250, 'SaveAndQuit') + END + call writefile(lines, 'Xmouseshape.vim', 'D') + call RunVim([], [], "-g -S Xmouseshape.vim") + sleep 300m + call assert_equal(['busy', 'arrow'], readfile('Xmouseshapes')) + + call delete('Xmouseshapes') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index fdfc1c0f89..f51de94bac 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -22,6 +22,21 @@ func Test_whichwrap() set whichwrap=h,h,h call assert_equal('h', &whichwrap) + " For compatibility with Vim 3.0 and before, number values are also + " supported for 'whichwrap' + set whichwrap=1 + call assert_equal('b', &whichwrap) + set whichwrap=2 + call assert_equal('s', &whichwrap) + set whichwrap=4 + call assert_equal('h,l', &whichwrap) + set whichwrap=8 + call assert_equal('<,>', &whichwrap) + set whichwrap=16 + call assert_equal('[,]', &whichwrap) + set whichwrap=31 + call assert_equal('b,s,h,l,<,>,[,]', &whichwrap) + set whichwrap& endfunc @@ -142,6 +157,20 @@ func Test_path_keep_commas() set path& endfunc +func Test_path_too_long() + exe 'set path=' .. repeat('x', 10000) + call assert_fails('find x', 'E854:') + set path& +endfunc + +func Test_signcolumn() + CheckFeature signs + call assert_equal("auto", &signcolumn) + set signcolumn=yes + set signcolumn=no + call assert_fails('set signcolumn=nope') +endfunc + func Test_filetype_valid() set ft=valid_name call assert_equal("valid_name", &filetype) @@ -234,6 +263,7 @@ func Test_complete() new call feedkeys("i\<C-N>\<Esc>", 'xt') bwipe! + call assert_fails('set complete=ix', 'E535:') set complete& endfun @@ -247,7 +277,7 @@ func Test_set_completion() call feedkeys(":setglobal di\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"setglobal dictionary diff diffexpr diffopt digraph directory display', @:) - " Expand boolan options. When doing :set no<Tab> + " Expand boolean options. When doing :set no<Tab> " vim displays the options names without "no" but completion uses "no...". call feedkeys(":set nodi\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set nodiff digraph', @:) @@ -266,6 +296,15 @@ func Test_set_completion() call feedkeys(":set fileencodings:\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set fileencodings:ucs-bom,utf-8,default,latin1', @:) + " Expand key codes. + " call feedkeys(":set <H\<C-A>\<C-B>\"\<CR>", 'tx') + " call assert_equal('"set <Help> <Home>', @:) + + " Expand terminal options. + " call feedkeys(":set t_A\<C-A>\<C-B>\"\<CR>", 'tx') + " call assert_equal('"set t_AB t_AF t_AU t_AL', @:) + " call assert_fails('call feedkeys(":set <t_afoo>=\<C-A>\<CR>", "xt")', 'E474:') + " Expand directories. call feedkeys(":set cdpath=./\<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('./samples/ ', @:) @@ -361,10 +400,20 @@ func Test_set_errors() call assert_fails('set winminwidth=10 winwidth=9', 'E592:') call assert_fails("set showbreak=\x01", 'E595:') call assert_fails('set t_foo=', 'E846:') + call assert_fails('set tabstop??', 'E488:') + call assert_fails('set wrapscan!!', 'E488:') + call assert_fails('set tabstop&&', 'E488:') + call assert_fails('set wrapscan<<', 'E488:') + call assert_fails('set wrapscan=1', 'E474:') + call assert_fails('set autoindent@', 'E488:') + call assert_fails('set wildchar=<abc>', 'E474:') + call assert_fails('set cmdheight=1a', 'E521:') + call assert_fails('set invcmdheight', 'E474:') if has('python') || has('python3') call assert_fails('set pyxversion=6', 'E474:') endif call assert_fails("let &tabstop='ab'", 'E521:') + call assert_fails('set spellcapcheck=%\\(', 'E54:') call assert_fails('set sessionoptions=curdir,sesdir', 'E474:') call assert_fails('set foldmarker={{{,', 'E474:') call assert_fails('set sessionoptions=sesdir,curdir', 'E474:') @@ -384,6 +433,7 @@ func Test_set_errors() set nomodifiable call assert_fails('set fileencoding=latin1', 'E21:') set modifiable& + " call assert_fails('set t_#-&', 'E522:') endfunc func CheckWasSet(name) @@ -397,9 +447,8 @@ endfunc " Must be executed before other tests that set 'term'. func Test_000_term_option_verbose() - if has('nvim') || has('gui_running') - return - endif + throw "Skipped: Nvim does not support setting 'term'" + CheckNotGui call CheckWasNotSet('t_cm') @@ -431,32 +480,37 @@ func Test_copy_context() endfunc func Test_set_ttytype() - " Nvim does not support 'ttytype'. - if !has('nvim') && !has('gui_running') && has('unix') - " Setting 'ttytype' used to cause a double-free when exiting vim and - " when vim is compiled with -DEXITFREE. - set ttytype=ansi - call assert_equal('ansi', &ttytype) - call assert_equal(&ttytype, &term) - set ttytype=xterm - call assert_equal('xterm', &ttytype) - call assert_equal(&ttytype, &term) - try - set ttytype= - call assert_report('set ttytype= did not fail') - catch /E529/ - endtry - - " Some systems accept any terminal name and return dumb settings, - " check for failure of finding the entry and for missing 'cm' entry. - try - set ttytype=xxx - call assert_report('set ttytype=xxx did not fail') - catch /E522\|E437/ - endtry - - set ttytype& - call assert_equal(&ttytype, &term) + throw "Skipped: Nvim does not support 'ttytype'" + CheckUnix + CheckNotGui + + " Setting 'ttytype' used to cause a double-free when exiting vim and + " when vim is compiled with -DEXITFREE. + set ttytype=ansi + call assert_equal('ansi', &ttytype) + call assert_equal(&ttytype, &term) + set ttytype=xterm + call assert_equal('xterm', &ttytype) + call assert_equal(&ttytype, &term) + try + set ttytype= + call assert_report('set ttytype= did not fail') + catch /E529/ + endtry + + " Some systems accept any terminal name and return dumb settings, + " check for failure of finding the entry and for missing 'cm' entry. + try + set ttytype=xxx + call assert_report('set ttytype=xxx did not fail') + catch /E522\|E437/ + endtry + + set ttytype& + call assert_equal(&ttytype, &term) + + if has('gui') && !has('gui_running') + call assert_fails('set term=gui', 'E531:') endif endfunc @@ -774,7 +828,13 @@ func Test_shell() CheckUnix let save_shell = &shell set shell= - call assert_fails('shell', 'E91:') + let caught_e91 = 0 + try + shell + catch /E91:/ + let caught_e91 = 1 + endtry + call assert_equal(1, caught_e91) let &shell = save_shell endfunc @@ -833,6 +893,107 @@ func Test_debug_option() set debug& endfunc +" Test for the default CDPATH option +func Test_opt_default_cdpath() + let after =<< trim [CODE] + call assert_equal(',/path/to/dir1,/path/to/dir2', &cdpath) + call writefile(v:errors, 'Xtestout') + qall + [CODE] + if has('unix') + let $CDPATH='/path/to/dir1:/path/to/dir2' + else + let $CDPATH='/path/to/dir1;/path/to/dir2' + endif + if RunVim([], after, '') + call assert_equal([], readfile('Xtestout')) + call delete('Xtestout') + endif +endfunc + +" Test for setting keycodes using set +func Test_opt_set_keycode() + call assert_fails('set <t_k1=l', 'E474:') + call assert_fails('set <Home=l', 'E474:') + set <t_k9>=abcd + " call assert_equal('abcd', &t_k9) + set <t_k9>& + set <F9>=xyz + " call assert_equal('xyz', &t_k9) + set <t_k9>& +endfunc + +" Test for changing options in a sandbox +func Test_opt_sandbox() + for opt in ['backupdir', 'cdpath', 'exrc'] + call assert_fails('sandbox set ' .. opt .. '?', 'E48:') + endfor +endfunc + +" Test for setting an option with local value to global value +func Test_opt_local_to_global() + setglobal equalprg=gprg + setlocal equalprg=lprg + call assert_equal('gprg', &g:equalprg) + call assert_equal('lprg', &l:equalprg) + call assert_equal('lprg', &equalprg) + set equalprg< + call assert_equal('', &l:equalprg) + call assert_equal('gprg', &equalprg) + setglobal equalprg=gnewprg + setlocal equalprg=lnewprg + setlocal equalprg< + call assert_equal('gnewprg', &l:equalprg) + call assert_equal('gnewprg', &equalprg) + set equalprg& + + " Test for setting the global/local value of a boolean option + setglobal autoread + setlocal noautoread + call assert_false(&autoread) + set autoread< + call assert_true(&autoread) + setglobal noautoread + setlocal autoread + setlocal autoread< + call assert_false(&autoread) + set autoread& +endfunc + +func Test_set_in_sandbox() + " Some boolean options cannot be set in sandbox, some can. + call assert_fails('sandbox set modelineexpr', 'E48:') + sandbox set number + call assert_true(&number) + set number& + + " Some boolean options cannot be set in sandbox, some can. + if has('python') || has('python3') + call assert_fails('sandbox set pyxversion=3', 'E48:') + endif + sandbox set tabstop=4 + call assert_equal(4, &tabstop) + set tabstop& + + " Some string options cannot be set in sandbox, some can. + call assert_fails('sandbox set backupdir=/tmp', 'E48:') + sandbox set filetype=perl + call assert_equal('perl', &filetype) + set filetype& +endfunc + +" Test for incrementing, decrementing and multiplying a number option value +func Test_opt_num_op() + set shiftwidth=4 + set sw+=2 + call assert_equal(6, &sw) + set sw-=2 + call assert_equal(4, &sw) + set sw^=2 + call assert_equal(8, &sw) + set shiftwidth& +endfunc + " Test for setting option values using v:false and v:true func Test_opt_boolean() set number& @@ -969,6 +1130,93 @@ func Test_opt_reset_scroll() call delete('Xscroll') endfunc +" Check that VIM_POSIX env variable influences default value of 'cpo' and 'shm' +func Test_VIM_POSIX() + throw 'Skipped: Nvim does not support $VIM_POSIX' + let saved_VIM_POSIX = getenv("VIM_POSIX") + + call setenv('VIM_POSIX', "1") + let after =<< trim [CODE] + call writefile([&cpo, &shm], 'X_VIM_POSIX') + qall + [CODE] + if RunVim([], after, '') + call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>#{|&/\.;', + \ 'AS'], readfile('X_VIM_POSIX')) + endif + + call setenv('VIM_POSIX', v:null) + let after =<< trim [CODE] + call writefile([&cpo, &shm], 'X_VIM_POSIX') + qall + [CODE] + if RunVim([], after, '') + call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>;', + \ 'S'], readfile('X_VIM_POSIX')) + endif + + call delete('X_VIM_POSIX') + call setenv('VIM_POSIX', saved_VIM_POSIX) +endfunc + +" Test for setting an option to a Vi or Vim default +func Test_opt_default() + throw 'Skipped: Nvim has different defaults' + set formatoptions&vi + call assert_equal('vt', &formatoptions) + set formatoptions&vim + call assert_equal('tcq', &formatoptions) +endfunc + +" Test for the 'cmdheight' option +func Test_cmdheight() + %bw! + let ht = &lines + set cmdheight=9999 + call assert_equal(1, winheight(0)) + call assert_equal(ht - 1, &cmdheight) + set cmdheight& +endfunc + +" To specify a control character as a option value, '^' can be used +func Test_opt_control_char() + set wildchar=^v + call assert_equal("\<C-V>", nr2char(&wildchar)) + set wildcharm=^r + call assert_equal("\<C-R>", nr2char(&wildcharm)) + " Bug: This doesn't work for the 'cedit' and 'termwinkey' options + set wildchar& wildcharm& +endfunc + +" Test for the 'errorbells' option +func Test_opt_errorbells() + set errorbells + call assert_beeps('s/a1b2/x1y2/') + set noerrorbells +endfunc + +func Test_opt_scrolljump() + help + resize 10 + + " Test with positive 'scrolljump'. + set scrolljump=2 + norm! Lj + call assert_equal({'lnum':11, 'leftcol':0, 'col':0, 'topfill':0, + \ 'topline':3, 'coladd':0, 'skipcol':0, 'curswant':0}, + \ winsaveview()) + + " Test with negative 'scrolljump' (percentage of window height). + set scrolljump=-40 + norm! ggLj + call assert_equal({'lnum':11, 'leftcol':0, 'col':0, 'topfill':0, + \ 'topline':5, 'coladd':0, 'skipcol':0, 'curswant':0}, + \ winsaveview()) + + set scrolljump& + bw +endfunc + " Test for the 'cdhome' option func Test_opt_cdhome() if has('unix') || has('vms') @@ -990,6 +1238,30 @@ func Test_opt_cdhome() set cdhome& endfunc +func Test_set_completion_2() + CheckOption termguicolors + + " Test default option completion + set wildoptions= + call feedkeys(":set termg\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set termguicolors', @:) + + call feedkeys(":set notermg\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set notermguicolors', @:) + + " Test fuzzy option completion + set wildoptions=fuzzy + call feedkeys(":set termg\<C-A>\<C-B>\"\<CR>", 'tx') + " Nvim doesn't have 'termencoding' + " call assert_equal('"set termguicolors termencoding', @:) + call assert_equal('"set termguicolors', @:) + + call feedkeys(":set notermg\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set notermguicolors', @:) + + set wildoptions= +endfunc + func Test_switchbuf_reset() set switchbuf=useopen sblast @@ -1003,4 +1275,30 @@ func Test_switchbuf_reset() only! endfunc +" :set empty string for global 'keywordprg' falls back to ":help" +func Test_keywordprg_empty() + let k = &keywordprg + set keywordprg=man + call assert_equal('man', &keywordprg) + set keywordprg= + call assert_equal(':help', &keywordprg) + set keywordprg=man + call assert_equal('man', &keywordprg) + call assert_equal("\n keywordprg=:help", execute('set kp= kp?')) + let &keywordprg = k +endfunc + +" check that the very first buffer created does not have 'endoffile' set +func Test_endoffile_default() + let after =<< trim [CODE] + call writefile([execute('set eof?')], 'Xtestout') + qall! + [CODE] + if RunVim([], after, '') + call assert_equal(["\nnoendoffile"], readfile('Xtestout')) + endif + call delete('Xtestout') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index 8c90f21600..3020668f1b 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -82,6 +82,9 @@ func Test_partial_dict() let dict = {"tr": function('tr', ['hello', 'h', 'H'])} call assert_equal("Hello", dict.tr()) + + call assert_fails("let F=function('setloclist', 10)", "E923:") + call assert_fails("let F=function('setloclist', [], [])", "E922:") endfunc func Test_partial_implicit() @@ -354,3 +357,5 @@ func Test_compare_partials() call assert_true(F1 isnot# F1d1) " Partial /= non-partial call assert_true(d1.f1 isnot# d1.f1) " handle_subscript creates new partial each time endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_paste.vim b/src/nvim/testdir/test_paste.vim new file mode 100644 index 0000000000..dad3c2c6a0 --- /dev/null +++ b/src/nvim/testdir/test_paste.vim @@ -0,0 +1,76 @@ + +" Test for 'pastetoggle' +func Test_pastetoggle() + new + set pastetoggle=<F4> + set nopaste + call feedkeys("iHello\<F4>", 'xt') + call assert_true(&paste) + call feedkeys("i\<F4>", 'xt') + call assert_false(&paste) + call assert_equal('Hello', getline(1)) + " command-line completion for 'pastetoggle' value + call feedkeys(":set pastetoggle=\<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"set pastetoggle=<F4>', @:) + set pastetoggle& + bwipe! +endfunc + +" Test for restoring option values when 'paste' is disabled +func Test_paste_opt_restore() + set autoindent expandtab ruler showmatch + if has('rightleft') + set revins hkmap + endif + set smarttab softtabstop=3 textwidth=27 wrapmargin=12 + if has('vartabs') + set varsofttabstop=10,20 + endif + + " enabling 'paste' should reset the above options + set paste + call assert_false(&autoindent) + call assert_false(&expandtab) + if has('rightleft') + call assert_false(&revins) + call assert_false(&hkmap) + endif + call assert_false(&ruler) + call assert_false(&showmatch) + call assert_false(&smarttab) + call assert_equal(0, &softtabstop) + call assert_equal(0, &textwidth) + call assert_equal(0, &wrapmargin) + if has('vartabs') + call assert_equal('', &varsofttabstop) + endif + + " disabling 'paste' should restore the option values + set nopaste + call assert_true(&autoindent) + call assert_true(&expandtab) + if has('rightleft') + call assert_true(&revins) + call assert_true(&hkmap) + endif + call assert_true(&ruler) + call assert_true(&showmatch) + call assert_true(&smarttab) + call assert_equal(3, &softtabstop) + call assert_equal(27, &textwidth) + call assert_equal(12, &wrapmargin) + if has('vartabs') + call assert_equal('10,20', &varsofttabstop) + endif + + set autoindent& expandtab& ruler& showmatch& + if has('rightleft') + set revins& hkmap& + endif + set smarttab& softtabstop& textwidth& wrapmargin& + if has('vartabs') + set varsofttabstop& + endif +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 3d1e3fa6db..791cce4431 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -359,7 +359,7 @@ func Test_completefunc_opens_new_window_one() /^one call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E565:') call assert_equal(winid, win_getid()) - call assert_equal('oneDEF', getline(1)) + call assert_equal('onedef', getline(1)) q! endfunc @@ -384,9 +384,7 @@ func Test_completefunc_opens_new_window_two() /^two call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E565:') call assert_equal(winid, win_getid()) - " v8.2.1919 hasn't been ported yet - " call assert_equal('twodef', getline(1)) - call assert_equal('twoDEF', getline(1)) + call assert_equal('twodef', getline(1)) q! endfunc @@ -677,9 +675,9 @@ func Test_complete_CTRLN_startofbuffer() endfunc func Test_popup_and_window_resize() - if !has('terminal') || has('gui_running') - return - endif + CheckFeature terminal + CheckNotGui + let h = winheight(0) if h < 15 return @@ -864,7 +862,6 @@ func Test_popup_position() endfunc func Test_popup_command() - CheckScreendump CheckFeature menu menu Test.Foo Foo @@ -872,6 +869,23 @@ func Test_popup_command() call assert_fails('popup Test.Foo.X', 'E327:') call assert_fails('popup Foo', 'E337:') unmenu Test.Foo +endfunc + +func Test_popup_command_dump() + CheckFeature menu + CheckScreendump + + let script =<< trim END + func StartTimer() + call timer_start(100, {-> ChangeMenu()}) + endfunc + func ChangeMenu() + aunmenu PopUp.&Paste + nnoremenu 1.40 PopUp.&Paste :echomsg "pasted"<CR> + echomsg 'changed' + endfunc + END + call writefile(script, 'XtimerScript') let lines =<< trim END one two three four five @@ -879,12 +893,12 @@ func Test_popup_command() one more two three four five END call writefile(lines, 'Xtest') - let buf = RunVimInTerminal('Xtest', {}) + let buf = RunVimInTerminal('-S XtimerScript Xtest', {}) call term_sendkeys(buf, ":source $VIMRUNTIME/menu.vim\<CR>") call term_sendkeys(buf, "/X\<CR>:popup PopUp\<CR>") call VerifyScreenDump(buf, 'Test_popup_command_01', {}) - " Select a word + " go to the Paste entry in the menu call term_sendkeys(buf, "jj") call VerifyScreenDump(buf, 'Test_popup_command_02', {}) @@ -893,8 +907,20 @@ func Test_popup_command() call VerifyScreenDump(buf, 'Test_popup_command_03', {}) call term_sendkeys(buf, "\<Esc>") + + " Set a timer to change a menu entry while it's displayed. The text should + " not change but the command does. Making the screendump also verifies that + " "changed" shows up, which means the timer triggered + call term_sendkeys(buf, "/X\<CR>:call StartTimer() | popup PopUp\<CR>") + call VerifyScreenDump(buf, 'Test_popup_command_04', {}) + + " Select the Paste entry, executes the changed menu item. + call term_sendkeys(buf, "jj\<CR>") + call VerifyScreenDump(buf, 'Test_popup_command_05', {}) + call StopVimInTerminal(buf) call delete('Xtest') + call delete('XtimerScript') endfunc func Test_popup_complete_backwards() @@ -948,9 +974,9 @@ func Test_complete_o_tab() endfunc func Test_menu_only_exists_in_terminal() - if !exists(':tlmenu') || has('gui_running') - return - endif + CheckCommand tlmenu + CheckNotGui + tlnoremenu &Edit.&Paste<Tab>"+gP <C-W>"+ aunmenu * try @@ -1129,15 +1155,15 @@ func Test_CompleteChanged() autocmd! AAAAA_Group set complete& completeopt& - delfunc! OnPumchange + delfunc! OnPumChange bw! endfunc -function! GetPumPosition() +func GetPumPosition() call assert_true( pumvisible() ) let g:pum_pos = pum_getpos() return '' -endfunction +endfunc func Test_pum_getpos() new diff --git a/src/nvim/testdir/test_preview.vim b/src/nvim/testdir/test_preview.vim index 6c4ae414d3..b7b908e761 100644 --- a/src/nvim/testdir/test_preview.vim +++ b/src/nvim/testdir/test_preview.vim @@ -1,5 +1,8 @@ " Tests for the preview window +source check.vim +CheckFeature quickfix + func Test_Psearch() " this used to cause ml_get errors help @@ -13,6 +16,8 @@ func Test_Psearch() endfunc func Test_window_preview() + CheckFeature quickfix + " Open a preview window pedit Xa call assert_equal(2, winnr('$')) @@ -32,6 +37,8 @@ func Test_window_preview() endfunc func Test_window_preview_from_help() + CheckFeature quickfix + filetype on call writefile(['/* some C code */'], 'Xpreview.c') help diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim index fdb6f13e2b..9165f7bace 100644 --- a/src/nvim/testdir/test_profile.vim +++ b/src/nvim/testdir/test_profile.vim @@ -40,8 +40,8 @@ func Test_profile_func() call writefile(lines, 'Xprofile_func.vim') call system(GetVimCommand() \ . ' -es --clean' - \ . ' -c "so Xprofile_func.vim"' - \ . ' -c "qall!"') + \ . ' --cmd "so Xprofile_func.vim"' + \ . ' --cmd "qall!"') call assert_equal(0, v:shell_error) let lines = readfile('Xprofile_func.log') @@ -403,6 +403,47 @@ func Test_profile_completion() call feedkeys(":profile start test_prof\<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('^"profile start.* test_profile\.vim', @:) + + call feedkeys(":profile file test_prof\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match('"profile file test_profile\.vim', @:) + call feedkeys(":profile file test_prof\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match('"profile file test_profile\.vim', @:) + call feedkeys(":profile file test_prof \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match('"profile file test_prof ', @:) + call feedkeys(":profile file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_match('"profile file X1B2C3', @:) + + func Xprof_test() + endfunc + call feedkeys(":profile func Xprof\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profile func Xprof_test', @:) + call feedkeys(":profile func Xprof\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profile func Xprof_test', @:) + call feedkeys(":profile func Xprof \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profile func Xprof ', @:) + call feedkeys(":profile func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profile func X1B2C3', @:) + + call feedkeys(":profdel \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel file func', @:) + call feedkeys(":profdel fu\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel func', @:) + call feedkeys(":profdel he\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel he', @:) + call feedkeys(":profdel here \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel here ', @:) + call feedkeys(":profdel file test_prof\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel file test_profile.vim', @:) + call feedkeys(":profdel file X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel file X1B2C3', @:) + call feedkeys(":profdel func Xprof\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel func Xprof_test', @:) + call feedkeys(":profdel func Xprof_test \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel func Xprof_test ', @:) + call feedkeys(":profdel func X1B2C3\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"profdel func X1B2C3', @:) + + delfunc Xprof_test endfunc func Test_profile_errors() @@ -475,7 +516,7 @@ func Test_profdel_func() call Foo3() [CODE] call writefile(lines, 'Xprofile_file.vim') - call system(GetVimCommandClean() . ' -es -c "so Xprofile_file.vim" -c q') + call system(GetVimCommandClean() . ' -es --cmd "so Xprofile_file.vim" --cmd q') call assert_equal(0, v:shell_error) let lines = readfile('Xprofile_file.log') diff --git a/src/nvim/testdir/test_prompt_buffer.vim b/src/nvim/testdir/test_prompt_buffer.vim index 8f94a8572b..b8f6c5240c 100644 --- a/src/nvim/testdir/test_prompt_buffer.vim +++ b/src/nvim/testdir/test_prompt_buffer.vim @@ -180,6 +180,8 @@ func Test_prompt_buffer_edit() call assert_beeps('normal! S') call assert_beeps("normal! \<C-A>") call assert_beeps("normal! \<C-X>") + call assert_beeps("normal! dp") + call assert_beeps("normal! do") " pressing CTRL-W in the prompt buffer should trigger the window commands call assert_equal(1, winnr()) exe "normal A\<C-W>\<C-W>" @@ -223,7 +225,7 @@ func Test_prompt_buffer_getbufinfo() %bwipe! endfunc -function! Test_prompt_while_writing_to_hidden_buffer() +func Test_prompt_while_writing_to_hidden_buffer() throw 'skipped: TODO' call CanTestPromptBuffer() CheckUnix diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index f6d573d76b..8dc4173d60 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1,6 +1,7 @@ " Test for the quickfix feature. source check.vim +source vim9.vim CheckFeature quickfix source screendump.vim @@ -85,6 +86,12 @@ func s:setup_commands(cchar) endif endfunc +" This must be run before any error lists are created. +func Test_AA_cc_no_errors() + call assert_fails('cc', 'E42:') + call assert_fails('ll', 'E42:') +endfunc + " Tests for the :clist and :llist commands func XlistTests(cchar) call s:setup_commands(a:cchar) @@ -98,9 +105,15 @@ func XlistTests(cchar) call assert_true(v:errmsg ==# 'E42: No Errors') " Populate the list and then try - Xgetexpr ['non-error 1', 'Xtestfile1:1:3:Line1', - \ 'non-error 2', 'Xtestfile2:2:2:Line2', - \ 'non-error| 3', 'Xtestfile3:3:1:Line3'] + let lines =<< trim END + non-error 1 + Xtestfile1:1:3:Line1 + non-error 2 + Xtestfile2:2:2:Line2 + non-error| 3 + Xtestfile3:3:1:Line3 + END + Xgetexpr lines " List only valid entries let l = split(execute('Xlist', ''), "\n") @@ -255,6 +268,7 @@ func XwindowTests(cchar) " Opening the location list window without any errors should fail if a:cchar == 'l' call assert_fails('lopen', 'E776:') + call assert_fails('lwindow', 'E776:') endif " Create a list with no valid entries @@ -265,8 +279,12 @@ func XwindowTests(cchar) call assert_true(winnr('$') == 1) " Create a list with valid entries - Xgetexpr ['Xtestfile1:1:3:Line1', 'Xtestfile2:2:2:Line2', - \ 'Xtestfile3:3:1:Line3'] + let lines =<< trim END + Xtestfile1:1:3:Line1 + Xtestfile2:2:2:Line2 + Xtestfile3:3:1:Line3 + END + Xgetexpr lines " Open the window Xwindow @@ -329,8 +347,12 @@ func XwindowTests(cchar) if a:cchar == 'c' " Opening the quickfix window in multiple tab pages should reuse the " quickfix buffer - Xgetexpr ['Xtestfile1:1:3:Line1', 'Xtestfile2:2:2:Line2', - \ 'Xtestfile3:3:1:Line3'] + let lines =<< trim END + Xtestfile1:1:3:Line1 + Xtestfile2:2:2:Line2 + Xtestfile3:3:1:Line3 + END + Xgetexpr lines Xopen let qfbufnum = bufnr('%') tabnew @@ -365,14 +387,16 @@ func Test_copenHeight_tabline() set tabline& showtabline& endfunc - " Tests for the :cfile, :lfile, :caddfile, :laddfile, :cgetfile and :lgetfile " commands. func XfileTests(cchar) call s:setup_commands(a:cchar) - call writefile(['Xtestfile1:700:10:Line 700', - \ 'Xtestfile2:800:15:Line 800'], 'Xqftestfile1') + let lines =<< trim END + Xtestfile1:700:10:Line 700 + Xtestfile2:800:15:Line 800 + END + call writefile(lines, 'Xqftestfile1') enew! Xfile Xqftestfile1 @@ -396,8 +420,11 @@ func XfileTests(cchar) call assert_true(len(l) == 3 && \ l[2].lnum == 900 && l[2].col == 30 && l[2].text ==# 'Line 900') - call writefile(['Xtestfile1:222:77:Line 222', - \ 'Xtestfile2:333:88:Line 333'], 'Xqftestfile1') + let lines =<< trim END + Xtestfile1:222:77:Line 222 + Xtestfile2:333:88:Line 333 + END + call writefile(lines, 'Xqftestfile1') enew! Xgetfile Xqftestfile1 @@ -427,8 +454,11 @@ func XbufferTests(cchar) call s:setup_commands(a:cchar) enew! - silent! call setline(1, ['Xtestfile7:700:10:Line 700', - \ 'Xtestfile8:800:15:Line 800']) + let lines =<< trim END + Xtestfile7:700:10:Line 700 + Xtestfile8:800:15:Line 800 + END + silent! call setline(1, lines) Xbuffer! let l = g:Xgetlist() call assert_true(len(l) == 2 && @@ -436,8 +466,11 @@ func XbufferTests(cchar) \ l[1].lnum == 800 && l[1].col == 15 && l[1].text ==# 'Line 800') enew! - silent! call setline(1, ['Xtestfile9:900:55:Line 900', - \ 'Xtestfile10:950:66:Line 950']) + let lines =<< trim END + Xtestfile9:900:55:Line 900 + Xtestfile10:950:66:Line 950 + END + silent! call setline(1, lines) Xgetbuffer let l = g:Xgetlist() call assert_true(len(l) == 2 && @@ -445,8 +478,11 @@ func XbufferTests(cchar) \ l[1].lnum == 950 && l[1].col == 66 && l[1].text ==# 'Line 950') enew! - silent! call setline(1, ['Xtestfile11:700:20:Line 700', - \ 'Xtestfile12:750:25:Line 750']) + let lines =<< trim END + Xtestfile11:700:20:Line 700 + Xtestfile12:750:25:Line 750 + END + silent! call setline(1, lines) Xaddbuffer let l = g:Xgetlist() call assert_true(len(l) == 4 && @@ -516,12 +552,15 @@ func Xtest_browse(cchar) call s:create_test_file('Xqftestfile1') call s:create_test_file('Xqftestfile2') - Xgetexpr ['Xqftestfile1:5:Line5', - \ 'Xqftestfile1:6:Line6', - \ 'Xqftestfile2:10:Line10', - \ 'Xqftestfile2:11:Line11', - \ 'RegularLine1', - \ 'RegularLine2'] + let lines =<< trim END + Xqftestfile1:5:Line5 + Xqftestfile1:6:Line6 + Xqftestfile2:10:Line10 + Xqftestfile2:11:Line11 + RegularLine1 + RegularLine2 + END + Xgetexpr lines Xfirst call assert_fails('-5Xcc', 'E16:') @@ -571,10 +610,13 @@ func Xtest_browse(cchar) call assert_equal(5, line('.')) " Jumping to an error from the error window using cc command - Xgetexpr ['Xqftestfile1:5:Line5', - \ 'Xqftestfile1:6:Line6', - \ 'Xqftestfile2:10:Line10', - \ 'Xqftestfile2:11:Line11'] + let lines =<< trim END + Xqftestfile1:5:Line5 + Xqftestfile1:6:Line6 + Xqftestfile2:10:Line10 + Xqftestfile2:11:Line11 + END + Xgetexpr lines Xopen 10Xcc call assert_equal(11, line('.')) @@ -699,7 +741,7 @@ func s:test_xhelpgrep(cchar) " Search for non existing help string call assert_fails('Xhelpgrep a1b2c3', 'E480:') " Invalid regular expression - call assert_fails('Xhelpgrep \@<!', 'E480:') + call assert_fails('Xhelpgrep \@<!', 'E866:') endfunc func Test_helpgrep() @@ -708,6 +750,35 @@ func Test_helpgrep() call s:test_xhelpgrep('l') endfunc +" When running the :helpgrep command, if an autocmd modifies the 'cpoptions' +" value, then Vim crashes. (issue fixed by 7.2b-004 and 8.2.4453) +func Test_helpgrep_restore_cpo_aucmd() + let save_cpo = &cpo + augroup QF_Test + au! + autocmd BufNew * set cpo=acd + augroup END + + helpgrep quickfix + call assert_equal('acd', &cpo) + %bw! + + set cpo&vim + augroup QF_Test + au! + autocmd BufReadPost * set cpo= + augroup END + + helpgrep buffer + call assert_equal('', &cpo) + + augroup QF_Test + au! + augroup END + %bw! + let &cpo = save_cpo +endfunc + func Test_errortitle() augroup QfBufWinEnter au! @@ -1073,20 +1144,21 @@ func s:dir_stack_tests(cchar) let save_efm=&efm set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f' - let lines = ["Entering dir 'dir1/a'", - \ 'habits2.txt:1:Nine Healthy Habits', - \ "Entering dir 'b'", - \ 'habits3.txt:2:0 Hours of television', - \ 'habits2.txt:7:5 Small meals', - \ "Entering dir 'dir1/c'", - \ 'habits4.txt:3:1 Hour of exercise', - \ "Leaving dir 'dir1/c'", - \ "Leaving dir 'dir1/a'", - \ 'habits1.txt:4:2 Liters of water', - \ "Entering dir 'dir2'", - \ 'habits5.txt:5:3 Cups of hot green tea', - \ "Leaving dir 'dir2'" - \] + let lines =<< trim END + Entering dir 'dir1/a' + habits2.txt:1:Nine Healthy Habits + Entering dir 'b' + habits3.txt:2:0 Hours of television + habits2.txt:7:5 Small meals + Entering dir 'dir1/c' + habits4.txt:3:1 Hour of exercise + Leaving dir 'dir1/c' + Leaving dir 'dir1/a' + habits1.txt:4:2 Liters of water + Entering dir 'dir2' + habits5.txt:5:3 Cups of hot green tea + Leaving dir 'dir2' + END Xexpr "" for l in lines @@ -1120,19 +1192,19 @@ func Test_efm_dirstack() call mkdir('dir1/c') call mkdir('dir2') - let lines = ["Nine Healthy Habits", - \ "0 Hours of television", - \ "1 Hour of exercise", - \ "2 Liters of water", - \ "3 Cups of hot green tea", - \ "4 Short mental breaks", - \ "5 Small meals", - \ "6 AM wake up time", - \ "7 Minutes of laughter", - \ "8 Hours of sleep (at least)", - \ "9 PM end of the day and off to bed" - \ ] - + let lines =<< trim END + Nine Healthy Habits + 0 Hours of television + 1 Hour of exercise + 2 Liters of water + 3 Cups of hot green tea + 4 Short mental breaks + 5 Small meals + 6 AM wake up time + 7 Minutes of laughter + 8 Hours of sleep (at least) + 9 PM end of the day and off to bed + END call writefile(lines, 'habits1.txt') call writefile(lines, 'dir1/a/habits2.txt') call writefile(lines, 'dir1/a/b/habits3.txt') @@ -1158,7 +1230,13 @@ func Xefm_ignore_continuations(cchar) \ '%-Wignored %m %l,' . \ '%+Cmore ignored %m %l,' . \ '%Zignored end' - Xgetexpr ['ignored warning 1', 'more ignored continuation 2', 'ignored end', 'error resync 4'] + let lines =<< trim END + ignored warning 1 + more ignored continuation 2 + ignored end + error resync 4 + END + Xgetexpr lines let l = map(g:Xgetlist(), '[v:val.text, v:val.valid, v:val.lnum, v:val.type]') call assert_equal([['resync', 1, 4, 'E']], l) @@ -1204,8 +1282,14 @@ func Xinvalid_efm_Tests(cchar) set efm= call assert_fails('Xexpr "abc.txt:1:Hello world"', 'E378:') + " Empty directory name. When there is an error in parsing new entries, make + " sure the previous quickfix list is made the current list. + set efm& + cexpr ["one", "two"] + let qf_id = getqflist(#{id: 0}).id set efm=%DEntering\ dir\ abc,%f:%l:%m call assert_fails('Xexpr ["Entering dir abc", "abc.txt:1:Hello world"]', 'E379:') + call assert_equal(qf_id, getqflist(#{id: 0}).id) let &efm = save_efm endfunc @@ -1396,8 +1480,14 @@ func Test_efm_error_type() " error type set efm=%f:%l:%t:%m - cexpr ["Xfile1:10:E:msg1", "Xfile1:20:W:msg2", "Xfile1:30:I:msg3", - \ "Xfile1:40:N:msg4", "Xfile1:50:R:msg5"] + let lines =<< trim END + Xfile1:10:E:msg1 + Xfile1:20:W:msg2 + Xfile1:30:I:msg3 + Xfile1:40:N:msg4 + Xfile1:50:R:msg5 + END + cexpr lines let output = split(execute('clist'), "\n") call assert_equal([ \ ' 1 Xfile1:10 error: msg1', @@ -1408,8 +1498,14 @@ func Test_efm_error_type() " error type and a error number set efm=%f:%l:%t:%n:%m - cexpr ["Xfile1:10:E:2:msg1", "Xfile1:20:W:4:msg2", "Xfile1:30:I:6:msg3", - \ "Xfile1:40:N:8:msg4", "Xfile1:50:R:3:msg5"] + let lines =<< trim END + Xfile1:10:E:2:msg1 + Xfile1:20:W:4:msg2 + Xfile1:30:I:6:msg3 + Xfile1:40:N:8:msg4 + Xfile1:50:R:3:msg5 + END + cexpr lines let output = split(execute('clist'), "\n") call assert_equal([ \ ' 1 Xfile1:10 error 2: msg1', @@ -1434,8 +1530,13 @@ func Test_efm_end_lnum_col() " multiple lines set efm=%A%n)%m,%Z%f:%l-%e:%c-%k - cexpr ["1)msg1", "Xfile1:14-24:1-2", - \ "2)msg2", "Xfile1:24-34:3-4"] + let lines =<< trim END + 1)msg1 + Xfile1:14-24:1-2 + 2)msg2 + Xfile1:24-34:3-4 + END + cexpr lines let output = split(execute('clist'), "\n") call assert_equal([ \ ' 1 Xfile1:14-24 col 1-2 error 1: msg1', @@ -1459,7 +1560,7 @@ func XquickfixChangedByAutocmd(cchar) endfunc endif - augroup testgroup + augroup QF_Test au! autocmd BufReadCmd test_changed.txt call ReadFunc() augroup END @@ -1473,7 +1574,24 @@ func XquickfixChangedByAutocmd(cchar) endfor call assert_fails('Xrewind', ErrorNr . ':') - augroup! testgroup + augroup QF_Test + au! + augroup END + + if a:cchar == 'c' + cexpr ["Xtest1:1:Line"] + cwindow + only + augroup QF_Test + au! + autocmd WinEnter * call setqflist([], 'f') + augroup END + call assert_fails('exe "normal \<CR>"', 'E925:') + augroup QF_Test + au! + augroup END + endif + %bw! endfunc func Test_quickfix_was_changed_by_autocmd() @@ -1611,6 +1729,9 @@ func SetXlistTests(cchar, bnum) \ " {'bufnr':999, 'lnum':5}])", 'E92:') call g:Xsetlist([[1, 2,3]]) call assert_equal(0, len(g:Xgetlist())) + call assert_fails('call g:Xsetlist([], [])', 'E928:') + call g:Xsetlist([v:_null_dict]) + call assert_equal([], g:Xgetlist()) endfunc func Test_setqflist() @@ -1825,12 +1946,12 @@ func Test_cgetfile_on_long_lines() " Problematic values if the line is longer than 4096 bytes. Then 1024 bytes " are read at a time. for len in [4078, 4079, 4080, 5102, 5103, 5104, 6126, 6127, 6128, 7150, 7151, 7152] - let lines = [ - \ '/tmp/file1:1:1:aaa', - \ '/tmp/file2:1:1:%s', - \ '/tmp/file3:1:1:bbb', - \ '/tmp/file4:1:1:ccc', - \ ] + let lines =<< trim END + /tmp/file1:1:1:aaa + /tmp/file2:1:1:%s + /tmp/file3:1:1:bbb + /tmp/file4:1:1:ccc + END let lines[1] = substitute(lines[1], '%s', repeat('x', len), '') call writefile(lines, 'Xcqetfile.txt') cgetfile Xcqetfile.txt @@ -1857,12 +1978,15 @@ func Test_switchbuf() let file1_winid = win_getid() new Xqftestfile2 let file2_winid = win_getid() - cgetexpr ['Xqftestfile1:5:Line5', - \ 'Xqftestfile1:6:Line6', - \ 'Xqftestfile2:10:Line10', - \ 'Xqftestfile2:11:Line11', - \ 'Xqftestfile3:15:Line15', - \ 'Xqftestfile3:16:Line16'] + let lines =<< trim END + Xqftestfile1:5:Line5 + Xqftestfile1:6:Line6 + Xqftestfile2:10:Line10 + Xqftestfile2:11:Line11 + Xqftestfile3:15:Line15 + Xqftestfile3:16:Line16 + END + cgetexpr lines new let winid = win_getid() @@ -2524,21 +2648,23 @@ func Test_Autocmd() silent! cexpr non_existing_func() silent! caddexpr non_existing_func() silent! cgetexpr non_existing_func() - let l = ['precexpr', - \ 'postcexpr', - \ 'precaddexpr', - \ 'postcaddexpr', - \ 'precgetexpr', - \ 'postcgetexpr', - \ 'precexpr', - \ 'postcexpr', - \ 'precaddexpr', - \ 'postcaddexpr', - \ 'precgetexpr', - \ 'postcgetexpr', - \ 'precexpr', - \ 'precaddexpr', - \ 'precgetexpr'] + let l =<< trim END + precexpr + postcexpr + precaddexpr + postcaddexpr + precgetexpr + postcgetexpr + precexpr + postcexpr + precaddexpr + postcaddexpr + precgetexpr + postcgetexpr + precexpr + precaddexpr + precgetexpr + END call assert_equal(l, g:acmds) let g:acmds = [] @@ -2556,15 +2682,17 @@ func Test_Autocmd() exe 'silent! cgetbuffer ' . bnum exe 'silent! caddbuffer ' . bnum enew! - let l = ['precbuffer', - \ 'postcbuffer', - \ 'precgetbuffer', - \ 'postcgetbuffer', - \ 'precaddbuffer', - \ 'postcaddbuffer', - \ 'precbuffer', - \ 'precgetbuffer', - \ 'precaddbuffer'] + let l =<< trim END + precbuffer + postcbuffer + precgetbuffer + postcgetbuffer + precaddbuffer + postcaddbuffer + precbuffer + precgetbuffer + precaddbuffer + END call assert_equal(l, g:acmds) call writefile(['Xtest:1:Line1'], 'Xtest') @@ -2579,24 +2707,26 @@ func Test_Autocmd() silent! cfile do_not_exist silent! caddfile do_not_exist silent! cgetfile do_not_exist - let l = ['precfile', - \ 'postcfile', - \ 'precaddfile', - \ 'postcaddfile', - \ 'precgetfile', - \ 'postcgetfile', - \ 'precfile', - \ 'postcfile', - \ 'precaddfile', - \ 'postcaddfile', - \ 'precgetfile', - \ 'postcgetfile', - \ 'precfile', - \ 'postcfile', - \ 'precaddfile', - \ 'postcaddfile', - \ 'precgetfile', - \ 'postcgetfile'] + let l =<< trim END + precfile + postcfile + precaddfile + postcaddfile + precgetfile + postcgetfile + precfile + postcfile + precaddfile + postcaddfile + precgetfile + postcgetfile + precfile + postcfile + precaddfile + postcaddfile + precgetfile + postcgetfile + END call assert_equal(l, g:acmds) let g:acmds = [] @@ -2609,20 +2739,22 @@ func Test_Autocmd() set makeprg= silent! make set makeprg& - let l = ['prehelpgrep', - \ 'posthelpgrep', - \ 'prehelpgrep', - \ 'posthelpgrep', - \ 'previmgrep', - \ 'postvimgrep', - \ 'previmgrepadd', - \ 'postvimgrepadd', - \ 'previmgrep', - \ 'postvimgrep', - \ 'previmgrepadd', - \ 'postvimgrepadd', - \ 'premake', - \ 'postmake'] + let l =<< trim END + prehelpgrep + posthelpgrep + prehelpgrep + posthelpgrep + previmgrep + postvimgrep + previmgrepadd + postvimgrepadd + previmgrep + postvimgrep + previmgrepadd + postvimgrepadd + premake + postmake + END call assert_equal(l, g:acmds) if has('unix') @@ -2642,22 +2774,24 @@ func Test_Autocmd() silent lgrep Grep_Autocmd_Text test_quickfix.vim silent lgrepadd GrepAdd_Autocmd_Text test_quickfix.vim set grepprg&vim - let l = ['pregrep', - \ 'postgrep', - \ 'pregrepadd', - \ 'postgrepadd', - \ 'pregrep', - \ 'postgrep', - \ 'pregrepadd', - \ 'postgrepadd', - \ 'pregrep', - \ 'postgrep', - \ 'pregrepadd', - \ 'postgrepadd', - \ 'prelgrep', - \ 'postlgrep', - \ 'prelgrepadd', - \ 'postlgrepadd'] + let l =<< trim END + pregrep + postgrep + pregrepadd + postgrepadd + pregrep + postgrep + pregrepadd + postgrepadd + pregrep + postgrep + pregrepadd + postgrepadd + prelgrep + postlgrep + prelgrepadd + postlgrepadd + END call assert_equal(l, g:acmds) endif @@ -2816,15 +2950,16 @@ func Test_cwindow_highlight() CheckScreendump let lines =<< trim END - call setline(1, ['some', 'text', 'with', 'matches']) - write XCwindow - vimgrep e XCwindow - redraw - cwindow 4 + call setline(1, ['some', 'text', 'with', 'matches']) + write XCwindow + vimgrep e XCwindow + redraw + cwindow 4 END call writefile(lines, 'XtestCwindow') let buf = RunVimInTerminal('-S XtestCwindow', #{rows: 12}) call VerifyScreenDump(buf, 'Test_quickfix_cwindow_1', {}) + call term_sendkeys(buf, ":cnext\<CR>") call VerifyScreenDump(buf, 'Test_quickfix_cwindow_2', {}) @@ -2837,10 +2972,13 @@ endfunc func XvimgrepTests(cchar) call s:setup_commands(a:cchar) - call writefile(['Editor:VIM vim', - \ 'Editor:Emacs EmAcS', - \ 'Editor:Notepad NOTEPAD'], 'Xtestfile1') - call writefile(['Linux', 'MacOS', 'MS-Windows'], 'Xtestfile2') + let lines =<< trim END + Editor:VIM vim + Editor:Emacs EmAcS + Editor:Notepad NOTEPAD + END + call writefile(lines, 'Xtestfile1') + call writefile(['Linux', 'macOS', 'MS-Windows'], 'Xtestfile2') " Error cases call assert_fails('Xvimgrep /abc *', 'E682:') @@ -2854,7 +2992,7 @@ func XvimgrepTests(cchar) Xexpr "" Xvimgrepadd Notepad Xtestfile1 - Xvimgrepadd MacOS Xtestfile2 + Xvimgrepadd macOS Xtestfile2 let l = g:Xgetlist() call assert_equal(2, len(l)) call assert_equal('Editor:Notepad NOTEPAD', l[0].text) @@ -2884,6 +3022,19 @@ func XvimgrepTests(cchar) call assert_equal(0, getbufinfo('Xtestfile1')[0].loaded) call assert_equal([], getbufinfo('Xtestfile2')) + " Test for opening the dummy buffer used by vimgrep in a window. The new + " window should be closed + %bw! + augroup QF_Test + au! + autocmd BufReadPre * exe "sb " .. expand("<abuf>") + augroup END + call assert_fails("Xvimgrep /sublime/ Xtestfile1", 'E480:') + call assert_equal(1, winnr('$')) + augroup QF_Test + au! + augroup END + call delete('Xtestfile1') call delete('Xtestfile2') endfunc @@ -3087,7 +3238,7 @@ func Test_cclose_from_copen() endfunc func Test_cclose_in_autocmd() - " Problem is only triggered if "starting" is zero, so that the OptionsSet + " Problem is only triggered if "starting" is zero, so that the OptionSet " event will be triggered. " call test_override('starting', 1) augroup QF_Test @@ -3102,7 +3253,7 @@ func Test_cclose_in_autocmd() " call test_override('starting', 0) endfunc -" Check that ":file" without an argument is possible even when curbuf is locked +" Check that ":file" without an argument is possible even when "curbuf->b_ro_locked" " is set. func Test_file_from_copen() " Works without argument. @@ -3148,6 +3299,21 @@ func Test_resize_from_copen() endtry endfunc +func Test_filetype_autocmd() + " this changes the location list while it is in use to fill a buffer + lexpr '' + lopen + augroup FT_loclist + au FileType * call setloclist(0, [], 'f') + augroup END + silent! lolder + lexpr '' + + augroup FT_loclist + au! FileType + augroup END +endfunc + func Test_vimgrep_with_textlock() new @@ -3291,9 +3457,9 @@ func Xmultidirstack_tests(cchar) let l1 = g:Xgetlist({'nr':1, 'items':1}) let l2 = g:Xgetlist({'nr':2, 'items':1}) - call assert_equal(expand('Xone/a/one.txt'), bufname(l1.items[1].bufnr)) + call assert_equal('Xone/a/one.txt', bufname(l1.items[1].bufnr)) call assert_equal(3, l1.items[1].lnum) - call assert_equal(expand('Xtwo/a/two.txt'), bufname(l2.items[1].bufnr)) + call assert_equal('Xtwo/a/two.txt', bufname(l2.items[1].bufnr)) call assert_equal(5, l2.items[1].lnum) endfunc @@ -3337,14 +3503,15 @@ func Xmultifilestack_tests(cchar) " error line ends with a file stack. let efm_val = 'Error\ l%l\ in\ %f,' let efm_val .= '%-P%>(%f%r,Error\ l%l\ in\ %m,%-Q)%r' - let l = g:Xgetlist({'lines' : [ - \ '(one.txt', - \ 'Error l4 in one.txt', - \ ') (two.txt', - \ 'Error l6 in two.txt', - \ ')', - \ 'Error l8 in one.txt' - \ ], 'efm' : efm_val}) + let lines =<< trim END + (one.txt + Error l4 in one.txt + ) (two.txt + Error l6 in two.txt + ) + Error l8 in one.txt + END + let l = g:Xgetlist({'lines': lines, 'efm' : efm_val}) call assert_equal(3, len(l.items)) call assert_equal('one.txt', bufname(l.items[0].bufnr)) call assert_equal(4, l.items[0].lnum) @@ -3622,7 +3789,15 @@ func Xqfjump_tests(cchar) call g:Xsetlist([], 'f') setlocal buftype=nofile new - call g:Xsetlist([], ' ', {'lines' : ['F1:1:1:Line1', 'F1:2:2:Line2', 'F2:1:1:Line1', 'F2:2:2:Line2', 'F3:1:1:Line1', 'F3:2:2:Line2']}) + let lines =<< trim END + F1:1:1:Line1 + F1:2:2:Line2 + F2:1:1:Line1 + F2:2:2:Line2 + F3:1:1:Line1 + F3:2:2:Line2 + END + call g:Xsetlist([], ' ', {'lines': lines}) Xopen let winid = win_getid() wincmd p @@ -3758,6 +3933,22 @@ func Xgetlist_empty_tests(cchar) endif endfunc +func Test_empty_list_quickfixtextfunc() + " This was crashing. Can only reproduce by running it in a separate Vim + " instance. + let lines =<< trim END + func s:Func(o) + cgetexpr '0' + endfunc + cope + let &quickfixtextfunc = 's:Func' + cgetfile [ex + END + call writefile(lines, 'Xquickfixtextfunc') + call RunVim([], [], '-e -s -S Xquickfixtextfunc -c qa') + call delete('Xquickfixtextfunc') +endfunc + func Test_getqflist() call Xgetlist_empty_tests('c') call Xgetlist_empty_tests('l') @@ -3943,8 +4134,8 @@ endfunc func Test_lvimgrep_crash2() au BufNewFile x sfind - call assert_fails('lvimgrep x x', 'E480:') - call assert_fails('lvimgrep x x x', 'E480:') + call assert_fails('lvimgrep x x', 'E471:') + call assert_fails('lvimgrep x x x', 'E471:') au! BufNewFile endfunc @@ -4055,14 +4246,19 @@ endfunc " The following test used to crash Vim func Test_lhelpgrep_autocmd() lhelpgrep quickfix - autocmd QuickFixCmdPost * call setloclist(0, [], 'f') + augroup QF_Test + au! + autocmd QuickFixCmdPost * call setloclist(0, [], 'f') + augroup END lhelpgrep buffer call assert_equal('help', &filetype) call assert_equal(0, getloclist(0, {'nr' : '$'}).nr) lhelpgrep tabpage call assert_equal('help', &filetype) call assert_equal(1, getloclist(0, {'nr' : '$'}).nr) - au! QuickFixCmdPost + augroup QF_Test + au! + augroup END new | only augroup QF_Test @@ -4075,7 +4271,7 @@ func Test_lhelpgrep_autocmd() wincmd w call assert_fails('helpgrep quickfix', 'E925:') augroup QF_Test - au! BufEnter + au! augroup END new | only @@ -4085,7 +4281,7 @@ func Test_lhelpgrep_autocmd() augroup END call assert_fails('helpgrep quickfix', 'E925:') augroup QF_Test - au! BufEnter + au! augroup END new | only @@ -4095,10 +4291,43 @@ func Test_lhelpgrep_autocmd() augroup END call assert_fails('lhelpgrep quickfix', 'E926:') augroup QF_Test - au! BufEnter + au! augroup END + " Replace the contents of a help window location list when it is still in + " use. new | only + lhelpgrep quickfix + wincmd w + augroup QF_Test + au! + autocmd WinEnter * call setloclist(0, [], 'r') + augroup END + call assert_fails('lhelpgrep win_getid', 'E926:') + augroup QF_Test + au! + augroup END + + %bw! +endfunc + +" The following test used to crash Vim +func Test_lhelpgrep_autocmd_free_loclist() + %bw! + lhelpgrep quickfix + wincmd w + augroup QF_Test + au! + autocmd WinEnter * call setloclist(0, [], 'f') + augroup END + lhelpgrep win_getid + wincmd w + wincmd w + wincmd w + augroup QF_Test + au! + augroup END + %bw! endfunc " Test for shortening/simplifying the file name when opening the @@ -4816,9 +5045,20 @@ func Xtest_below(cchar) endif " Test for lines with multiple quickfix entries - Xexpr ["X1:5:L5", "X2:5:1:L5_1", "X2:5:2:L5_2", "X2:5:3:L5_3", - \ "X2:10:1:L10_1", "X2:10:2:L10_2", "X2:10:3:L10_3", - \ "X2:15:1:L15_1", "X2:15:2:L15_2", "X2:15:3:L15_3", "X3:3:L3"] + let lines =<< trim END + X1:5:L5 + X2:5:1:L5_1 + X2:5:2:L5_2 + X2:5:3:L5_3 + X2:10:1:L10_1 + X2:10:2:L10_2 + X2:10:3:L10_3 + X2:15:1:L15_1 + X2:15:2:L15_2 + X2:15:3:L15_3 + X3:3:L3 + END + Xexpr lines edit +1 X2 Xbelow 2 call assert_equal(['X2', 10, 1], [@%, line('.'), col('.')]) @@ -4882,33 +5122,32 @@ func Test_cbelow() endfunc func Test_quickfix_count() - let commands = [ - \ 'cNext', - \ 'cNfile', - \ 'cabove', - \ 'cbelow', - \ 'cfirst', - \ 'clast', - \ 'cnewer', - \ 'cnext', - \ 'cnfile', - \ 'colder', - \ 'cprevious', - \ 'crewind', - \ - \ 'lNext', - \ 'lNfile', - \ 'labove', - \ 'lbelow', - \ 'lfirst', - \ 'llast', - \ 'lnewer', - \ 'lnext', - \ 'lnfile', - \ 'lolder', - \ 'lprevious', - \ 'lrewind', - \ ] + let commands =<< trim END + cNext + cNfile + cabove + cbelow + cfirst + clast + cnewer + cnext + cnfile + colder + cprevious + crewind + lNext + lNfile + labove + lbelow + lfirst + llast + lnewer + lnext + lnfile + lolder + lprevious + lrewind + END for cmd in commands call assert_fails('-1' .. cmd, 'E16:') call assert_fails('.' .. cmd, 'E16:') @@ -5090,6 +5329,29 @@ func Test_lhelpgrep_from_help_window() new | only! endfunc +" Test for the crash fixed by 7.3.715 +func Test_setloclist_crash() + %bw! + let g:BufNum = bufnr() + augroup QF_Test + au! + au BufUnload * call setloclist(0, [{'bufnr':g:BufNum, 'lnum':1, 'col':1, 'text': 'tango down'}]) + augroup END + + try + lvimgrep /.*/ *.mak + catch /E926:/ + endtry + call assert_equal('tango down', getloclist(0, {'items' : 0}).items[0].text) + call assert_equal(1, getloclist(0, {'size' : 0}).size) + + augroup QF_Test + au! + augroup END + unlet g:BufNum + %bw! +endfunc + " Test for adding an invalid entry with the quickfix window open and making " sure that the window contents are not changed func Test_add_invalid_entry_with_qf_window() @@ -5289,6 +5551,7 @@ func Xtest_getqflist_by_idx(cchar) call assert_equal('L20', l[0].text) call assert_equal([], g:Xgetlist({'idx' : -1, 'items' : 0}).items) call assert_equal([], g:Xgetlist({'idx' : 3, 'items' : 0}).items) + call assert_equal({}, g:Xgetlist(#{idx: "abc"})) %bwipe! endfunc @@ -5347,6 +5610,19 @@ func Xtest_qftextfunc(cchar) call assert_equal('F1|10 col 2-7| green', getline(1)) call assert_equal('F1|20-25 col 4-8| blue', getline(2)) Xclose + + set efm=%f:%l:%c:%m + set quickfixtextfunc=Tqfexpr + " Update the list with only the cwindow + Xwindow + only + call g:Xsetlist([ + \ { 'filename': 'F2', 'lnum': 20, 'col': 2, + \ 'end_col': 7, 'text': 'red'} + \ ]) + call assert_equal(['F2-L20C2-red'], getline(1, '$')) + new + Xclose set efm& set quickfixtextfunc& @@ -5470,6 +5746,155 @@ func Test_qftextfunc() call Xtest_qftextfunc('l') endfunc +func Test_qftextfunc_callback() + let lines =<< trim END + set efm=%f:%l:%c:%m + + #" Test for using a function name + LET &qftf = 'g:Tqfexpr' + cexpr "F0:0:0:L0" + copen + call assert_equal('F0-L0C0-L0', getline(1)) + cclose + + #" Test for using a function() + set qftf=function('g:Tqfexpr') + cexpr "F1:1:1:L1" + copen + call assert_equal('F1-L1C1-L1', getline(1)) + cclose + + #" Using a funcref variable to set 'quickfixtextfunc' + VAR Fn = function('g:Tqfexpr') + LET &qftf = Fn + cexpr "F2:2:2:L2" + copen + call assert_equal('F2-L2C2-L2', getline(1)) + cclose + + #" Using string(funcref_variable) to set 'quickfixtextfunc' + LET Fn = function('g:Tqfexpr') + LET &qftf = string(Fn) + cexpr "F3:3:3:L3" + copen + call assert_equal('F3-L3C3-L3', getline(1)) + cclose + + #" Test for using a funcref() + set qftf=funcref('g:Tqfexpr') + cexpr "F4:4:4:L4" + copen + call assert_equal('F4-L4C4-L4', getline(1)) + cclose + + #" Using a funcref variable to set 'quickfixtextfunc' + LET Fn = funcref('g:Tqfexpr') + LET &qftf = Fn + cexpr "F5:5:5:L5" + copen + call assert_equal('F5-L5C5-L5', getline(1)) + cclose + + #" Using a string(funcref_variable) to set 'quickfixtextfunc' + LET Fn = funcref('g:Tqfexpr') + LET &qftf = string(Fn) + cexpr "F5:5:5:L5" + copen + call assert_equal('F5-L5C5-L5', getline(1)) + cclose + + #" Test for using a lambda function with set + VAR optval = "LSTART a LMIDDLE Tqfexpr(a) LEND" + LET optval = substitute(optval, ' ', '\\ ', 'g') + exe "set qftf=" .. optval + cexpr "F6:6:6:L6" + copen + call assert_equal('F6-L6C6-L6', getline(1)) + cclose + + #" Set 'quickfixtextfunc' to a lambda expression + LET &qftf = LSTART a LMIDDLE Tqfexpr(a) LEND + cexpr "F7:7:7:L7" + copen + call assert_equal('F7-L7C7-L7', getline(1)) + cclose + + #" Set 'quickfixtextfunc' to string(lambda_expression) + LET &qftf = "LSTART a LMIDDLE Tqfexpr(a) LEND" + cexpr "F8:8:8:L8" + copen + call assert_equal('F8-L8C8-L8', getline(1)) + cclose + + #" Set 'quickfixtextfunc' to a variable with a lambda expression + VAR Lambda = LSTART a LMIDDLE Tqfexpr(a) LEND + LET &qftf = Lambda + cexpr "F9:9:9:L9" + copen + call assert_equal('F9-L9C9-L9', getline(1)) + cclose + + #" Set 'quickfixtextfunc' to a string(variable with a lambda expression) + LET Lambda = LSTART a LMIDDLE Tqfexpr(a) LEND + LET &qftf = string(Lambda) + cexpr "F9:9:9:L9" + copen + call assert_equal('F9-L9C9-L9', getline(1)) + cclose + END + call CheckLegacyAndVim9Success(lines) + + " Test for using a script-local function name + func s:TqfFunc2(info) + let g:TqfFunc2Args = [a:info.start_idx, a:info.end_idx] + return '' + endfunc + let g:TqfFunc2Args = [] + set quickfixtextfunc=s:TqfFunc2 + cexpr "F10:10:10:L10" + cclose + call assert_equal([1, 1], g:TqfFunc2Args) + + let &quickfixtextfunc = 's:TqfFunc2' + cexpr "F11:11:11:L11" + cclose + call assert_equal([1, 1], g:TqfFunc2Args) + delfunc s:TqfFunc2 + + " set 'quickfixtextfunc' to a partial with dict. This used to cause a crash. + func SetQftfFunc() + let params = {'qftf': function('g:DictQftfFunc')} + let &quickfixtextfunc = params.qftf + endfunc + func g:DictQftfFunc(_) dict + endfunc + call SetQftfFunc() + new + call SetQftfFunc() + bw + call test_garbagecollect_now() + new + set qftf= + wincmd w + set qftf= + :%bw! + + " set per-quickfix list 'quickfixtextfunc' to a partial with dict. This used + " to cause a crash. + let &qftf = '' + func SetLocalQftfFunc() + let params = {'qftf': function('g:DictQftfFunc')} + call setqflist([], 'a', {'quickfixtextfunc' : params.qftf}) + endfunc + call SetLocalQftfFunc() + call test_garbagecollect_now() + call setqflist([], 'a', {'quickfixtextfunc' : ''}) + delfunc g:DictQftfFunc + delfunc SetQftfFunc + delfunc SetLocalQftfFunc + set efm& +endfunc + " Test for updating a location list for some other window and check that " 'qftextfunc' uses the correct location list. func Test_qftextfunc_other_loclist() @@ -5535,9 +5960,12 @@ func Test_locationlist_open_in_newtab() %bwipe! - lgetexpr ['Xqftestfile1:5:Line5', - \ 'Xqftestfile2:10:Line10', - \ 'Xqftestfile3:16:Line16'] + let lines =<< trim END + Xqftestfile1:5:Line5 + Xqftestfile2:10:Line10 + Xqftestfile3:16:Line16 + END + lgetexpr lines silent! llast call assert_equal(1, tabpagenr('$')) @@ -5578,6 +6006,21 @@ func Test_win_gettype() lclose endfunc +fun Test_vimgrep_nomatch() + call XexprTests('c') + call g:Xsetlist([{'lnum':10,'text':'Line1'}]) + copen + if has("win32") + call assert_fails('vimgrep foo *.zzz', 'E479:') + let expected = [{'lnum': 10, 'bufnr': 0, 'end_lnum': 0, 'pattern': '', 'valid': 0, 'vcol': 0, 'nr': 0, 'module': '', 'type': '', 'end_col': 0, 'col': 0, 'text': 'Line1'}] + else + call assert_fails('vimgrep foo *.zzz', 'E480:') + let expected = [] + endif + call assert_equal(expected, getqflist()) + cclose +endfunc + " Test for opening the quickfix window in two tab pages and then closing one " of the quickfix windows. This should not make the quickfix buffer unlisted. " (github issue #9300). @@ -5646,7 +6089,7 @@ func Test_lopen_bwipe_all() qall! END call writefile(lines, 'Xscript') - if RunVim([], [], '--clean -n -S Xscript') + if RunVim([], [], '-u NONE -n -X -Z -e -m -s -S Xscript') call assert_equal(['done'], readfile('Xresult')) endif @@ -5654,5 +6097,140 @@ func Test_lopen_bwipe_all() call delete('Xresult') endfunc +" Test for calling setqflist() function recursively +func Test_recursive_setqflist() + augroup QF_Test + au! + autocmd BufWinEnter quickfix call setqflist([], 'r') + augroup END + + copen + call assert_fails("call setqflist([], 'a')", 'E952:') + + augroup QF_Test + au! + augroup END + %bw! +endfunc + +" Test for failure to create a new window when selecting a file from the +" quickfix window +func Test_cwindow_newwin_fails() + cgetexpr ["Xfile1:10:L10", "Xfile1:20:L20"] + cwindow + only + let qf_wid = win_getid() + " create the maximum number of scratch windows + let hor_win_count = (&lines - 1)/2 + let hor_split_count = hor_win_count - 1 + for s in range(1, hor_split_count) | new | set buftype=nofile | endfor + call win_gotoid(qf_wid) + call assert_fails('exe "normal \<CR>"', 'E36:') + %bw! +endfunc + +" Test for updating the location list when only the location list window is +" present and the corresponding file window is closed. +func Test_loclist_update_with_llwin_only() + %bw! + new + wincmd w + lexpr ["Xfile1:1:Line1"] + lopen + wincmd p + close + call setloclist(2, [], 'r', {'lines': ["Xtest2:2:Line2"]}) + call assert_equal(['Xtest2|2| Line2'], getbufline(winbufnr(2), 1, '$')) + %bw! +endfunc + +" Test for getting the quickfix list after a buffer with an error is wiped out +func Test_getqflist_wiped_out_buffer() + %bw! + cexpr ["Xtest1:34:Wiped out"] + let bnum = bufnr('Xtest1') + call assert_equal(bnum, getqflist()[0].bufnr) + bw Xtest1 + call assert_equal(0, getqflist()[0].bufnr) + %bw! +endfunc + +" Test for the status message that is displayed when opening a new quickfix +" list +func Test_qflist_statusmsg() + cexpr "1\n2" + cexpr "1\n2\n3\ntest_quickfix.vim:1:msg" + call assert_equal('(4 of 4): msg', v:statusmsg) + call setqflist([], 'f') + %bw! + + " When creating a new quickfix list, if an autocmd changes the quickfix list + " in the stack, then an error message should be displayed. + augroup QF_Test + au! + au BufEnter test_quickfix.vim colder + augroup END + cexpr "1\n2" + call assert_fails('cexpr "1\n2\n3\ntest_quickfix.vim:1:msg"', 'E925:') + call setqflist([], 'f') + augroup QF_Test + au! + augroup END + %bw! + + augroup QF_Test + au! + au BufEnter test_quickfix.vim caddexpr "4" + augroup END + call assert_fails('cexpr "1\n2\n3\ntest_quickfix.vim:1:msg"', 'E925:') + call setqflist([], 'f') + augroup QF_Test + au! + augroup END + %bw! +endfunc + +func Test_quickfixtextfunc_recursive() + func s:QFTfunc(o) + cgete '0' + endfunc + copen + let &quickfixtextfunc = 's:QFTfunc' + cex "" + + let &quickfixtextfunc = '' + cclose +endfunc + +" Test for replacing the location list from an autocmd. This used to cause a +" read from freed memory. +func Test_loclist_replace_autocmd() + %bw! + call setloclist(0, [], 'f') + let s:bufnr = bufnr() + cal setloclist(0, [{'0': 0, '': ''}]) + au BufEnter * cal setloclist(1, [{'t': ''}, {'bufnr': s:bufnr}], 'r') + lopen + try + exe "norm j\<CR>" + catch + endtry + lnext + %bw! + call setloclist(0, [], 'f') +endfunc + +func s:QfTf(_) +endfunc + +func Test_setqflist_cb_arg() + " This was changing the callback name in the dictionary. + let d = #{quickfixtextfunc: 's:QfTf'} + call setqflist([], 'a', d) + call assert_equal('s:QfTf', d.quickfixtextfunc) + + call setqflist([], 'f') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_quotestar.vim b/src/nvim/testdir/test_quotestar.vim index 93865869fa..6a6719da8b 100644 --- a/src/nvim/testdir/test_quotestar.vim +++ b/src/nvim/testdir/test_quotestar.vim @@ -98,16 +98,17 @@ func Do_test_quotestar_for_x11() " Running in a terminal and the GUI is available: Tell the server to open " the GUI and check that the remote command still works. - " Need to wait for the GUI to start up, otherwise the send hangs in trying - " to send to the terminal window. - if has('gui_athena') || has('gui_motif') + if has('gui_motif') " For those GUIs, ignore the 'failed to create input context' error. call remote_send(name, ":call test_ignore_error('E285') | gui -f\<CR>") else call remote_send(name, ":gui -f\<CR>") endif " Wait for the server in the GUI to be up and answering requests. + " First need to wait for the GUI to start up, otherwise the send hangs in + " trying to send to the terminal window. " On some systems and with valgrind this can be very slow. + sleep 1 call WaitForAssert({-> assert_match("1", remote_expr(name, "has('gui_running')", "", 1))}, 10000) call remote_send(name, ":let @* = 'maybe'\<CR>") diff --git a/src/nvim/testdir/test_random.vim b/src/nvim/testdir/test_random.vim index 6d3f7dcfd9..5fdbfe9cd8 100644 --- a/src/nvim/testdir/test_random.vim +++ b/src/nvim/testdir/test_random.vim @@ -1,5 +1,8 @@ " Tests for srand() and rand() +source check.vim +source shared.vim + func Test_Rand() let r = srand(123456789) call assert_equal([1573771921, 319883699, 2742014374, 1324369493], r) @@ -9,18 +12,9 @@ func Test_Rand() call assert_equal(2658065534, rand(r)) call assert_equal(3104308804, rand(r)) - " Nvim does not support test_settime - " call test_settime(12341234) let s = srand() - if !has('win32') && filereadable('/dev/urandom') - " using /dev/urandom - call assert_notequal(s, srand()) - " else - " " using time() - " call assert_equal(s, srand()) - " call test_settime(12341235) - " call assert_notequal(s, srand()) - endif + " using /dev/urandom or used time, result is different each time + call assert_notequal(s, srand()) " Nvim does not support test_srand_seed " call test_srand_seed(123456789) @@ -33,13 +27,11 @@ func Test_Rand() endif call assert_fails('echo srand([1])', 'E745:') call assert_fails('echo rand("burp")', 'E475:') - call assert_fails('echo rand([1, 2, 3])', 'E475:') - call assert_fails('echo rand([[1], 2, 3, 4])', 'E475:') - call assert_fails('echo rand([1, [2], 3, 4])', 'E475:') - call assert_fails('echo rand([1, 2, [3], 4])', 'E475:') - call assert_fails('echo rand([1, 2, 3, [4]])', 'E475:') - - " call test_settime(0) + call assert_fails('echo rand([1, 2, 3])', 'E730:') + call assert_fails('echo rand([[1], 2, 3, 4])', 'E730:') + call assert_fails('echo rand([1, [2], 3, 4])', 'E730:') + call assert_fails('echo rand([1, 2, [3], 4])', 'E730:') + call assert_fails('echo rand([1, 2, 3, [4]])', 'E730:') endfunc func Test_issue_5587() @@ -48,4 +40,20 @@ func Test_issue_5587() call rand() endfunc +func Test_srand() + CheckNotGui + + let cmd = GetVimCommand() .. ' -V -es -c "echo rand()" -c qa!' + let bad = 0 + for _ in range(10) + echo cmd + let result1 = system(cmd) + let result2 = system(cmd) + if result1 ==# result2 + let bad += 1 + endif + endfor + call assert_inrange(0, 4, bad) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim index fc073cacd2..92e22687af 100644 --- a/src/nvim/testdir/test_recover.vim +++ b/src/nvim/testdir/test_recover.vim @@ -1,5 +1,7 @@ " Test :recover +source check.vim + func Test_recover_root_dir() " This used to access invalid memory. split Xtest @@ -23,6 +25,21 @@ func Test_recover_root_dir() set dir& endfunc +" Make a copy of the current swap file to "Xswap". +" Return the name of the swap file. +func CopySwapfile() + preserve + " get the name of the swap file + let swname = split(execute("swapname"))[0] + let swname = substitute(swname, '[[:blank:][:cntrl:]]*\(.\{-}\)[[:blank:][:cntrl:]]*$', '\1', '') + " make a copy of the swap file in Xswap + set binary + exe 'sp ' . swname + w! Xswap + set nobinary + return swname +endfunc + " Inserts 10000 lines with text to fill the swap file with two levels of pointer " blocks. Then recovers from the swap file and checks all text is restored. " @@ -40,15 +57,9 @@ func Test_swap_file() let i += 1 endwhile $delete - preserve - " get the name of the swap file - let swname = split(execute("swapname"))[0] - let swname = substitute(swname, '[[:blank:][:cntrl:]]*\(.\{-}\)[[:blank:][:cntrl:]]*$', '\1', '') - " make a copy of the swap file in Xswap - set binary - exe 'sp ' . swname - w! Xswap - set nobinary + + let swname = CopySwapfile() + new only! bwipe! Xtest @@ -69,3 +80,388 @@ func Test_swap_file() set undolevels& enew! | only endfunc + +func Test_nocatch_process_still_running() + let g:skipped_reason = 'test_override() is N/A' + return + " sysinfo.uptime probably only works on Linux + if !has('linux') + let g:skipped_reason = 'only works on Linux' + return + endif + " the GUI dialog can't be handled + if has('gui_running') + let g:skipped_reason = 'only works in the terminal' + return + endif + + " don't intercept existing swap file here + au! SwapExists + + " Edit a file and grab its swapfile. + edit Xswaptest + call setline(1, ['a', 'b', 'c']) + let swname = CopySwapfile() + + " Forget we edited this file + new + only! + bwipe! Xswaptest + + call rename('Xswap', swname) + call feedkeys('e', 'tL') + redir => editOutput + edit Xswaptest + redir END + call assert_match('E325: ATTENTION', editOutput) + call assert_match('file name: .*Xswaptest', editOutput) + call assert_match('process ID: \d* (STILL RUNNING)', editOutput) + + " Forget we edited this file + new + only! + bwipe! Xswaptest + + " pretend we rebooted + call test_override("uptime", 0) + sleep 1 + + call feedkeys('e', 'tL') + redir => editOutput + edit Xswaptest + redir END + call assert_match('E325: ATTENTION', editOutput) + call assert_notmatch('(STILL RUNNING)', editOutput) + + call test_override("ALL", 0) + call delete(swname) +endfunc + +" Test for :recover with multiple swap files +func Test_recover_multiple_swap_files() + CheckUnix + new Xfile1 + call setline(1, ['a', 'b', 'c']) + preserve + let b = readblob(swapname('')) + call writefile(b, '.Xfile1.swm') + call writefile(b, '.Xfile1.swn') + call writefile(b, '.Xfile1.swo') + %bw! + call feedkeys(":recover Xfile1\<CR>3\<CR>q", 'xt') + call assert_equal(['a', 'b', 'c'], getline(1, '$')) + " try using out-of-range number to select a swap file + bw! + call feedkeys(":recover Xfile1\<CR>4\<CR>q", 'xt') + call assert_equal('Xfile1', @%) + call assert_equal([''], getline(1, '$')) + bw! + call feedkeys(":recover Xfile1\<CR>0\<CR>q", 'xt') + call assert_equal('Xfile1', @%) + call assert_equal([''], getline(1, '$')) + bw! + + call delete('.Xfile1.swm') + call delete('.Xfile1.swn') + call delete('.Xfile1.swo') +endfunc + +" Test for :recover using an empty swap file +func Test_recover_empty_swap_file() + CheckUnix + call writefile([], '.Xfile1.swp') + let msg = execute('recover Xfile1') + call assert_match('Unable to read block 0 from .Xfile1.swp', msg) + call assert_equal('Xfile1', @%) + bw! + + " make sure there are no old swap files laying around + for f in glob('.sw?', 0, 1) + call delete(f) + endfor + + " :recover from an empty buffer + call assert_fails('recover', 'E305:') + call delete('.Xfile1.swp') +endfunc + +" Test for :recover using a corrupted swap file +" Refer to the comments in the memline.c file for the swap file headers +" definition. +func Test_recover_corrupted_swap_file() + CheckUnix + + " recover using a partial swap file + call writefile(0z1234, '.Xfile1.swp') + call assert_fails('recover Xfile1', 'E295:') + bw! + + " recover using invalid content in the swap file + call writefile([repeat('1', 2*1024)], '.Xfile1.swp') + call assert_fails('recover Xfile1', 'E307:') + call delete('.Xfile1.swp') + + " :recover using a swap file with a corrupted header + edit Xfile1 + preserve + let sn = swapname('') + let b = readblob(sn) + let save_b = copy(b) + bw! + + " Not all fields are written in a system-independent manner. Detect whether + " the test is running on a little or big-endian system, so the correct + " corruption values can be set. + " The B0_MAGIC_LONG field may be 32-bit or 64-bit, depending on the system, + " even though the value stored is only 32-bits. Therefore, need to check + " both the high and low 32-bits to compute these values. + let little_endian = (b[1008:1011] == 0z33323130) || (b[1012:1015] == 0z33323130) + let system_64bit = little_endian ? (b[1012:1015] == 0z00000000) : (b[1008:1011] == 0z00000000) + + " clear the B0_MAGIC_LONG field + if system_64bit + let b[1008:1015] = 0z00000000.00000000 + else + let b[1008:1011] = 0z00000000 + endif + call writefile(b, sn) + let msg = execute('recover Xfile1') + call assert_match('the file has been damaged', msg) + call assert_equal('Xfile1', @%) + call assert_equal([''], getline(1, '$')) + bw! + + " reduce the page size + let b = copy(save_b) + let b[12:15] = 0z00010000 + call writefile(b, sn) + let msg = execute('recover Xfile1') + call assert_match('page size is smaller than minimum value', msg) + call assert_equal('Xfile1', @%) + call assert_equal([''], getline(1, '$')) + bw! + + " clear the pointer ID + let b = copy(save_b) + let b[4096:4097] = 0z0000 + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E310:') + call assert_equal('Xfile1', @%) + call assert_equal([''], getline(1, '$')) + bw! + + " set the number of pointers in a pointer block to zero + let b = copy(save_b) + let b[4098:4099] = 0z0000 + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['???EMPTY BLOCK'], getline(1, '$')) + bw! + + " set the block number in a pointer entry to a negative number + let b = copy(save_b) + if system_64bit + let b[4104:4111] = little_endian ? 0z00000000.00000080 : 0z80000000.00000000 + else + let b[4104:4107] = little_endian ? 0z00000080 : 0z80000000 + endif + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['???LINES MISSING'], getline(1, '$')) + bw! + + " clear the data block ID + let b = copy(save_b) + let b[8192:8193] = 0z0000 + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['???BLOCK MISSING'], getline(1, '$')) + bw! + + " set the number of lines in the data block to zero + let b = copy(save_b) + if system_64bit + let b[8208:8215] = 0z00000000.00000000 + else + let b[8208:8211] = 0z00000000 + endif + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['??? from here until ???END lines may have been inserted/deleted', + \ '???END'], getline(1, '$')) + bw! + + " use an invalid text start for the lines in a data block + let b = copy(save_b) + if system_64bit + let b[8216:8219] = 0z00000000 + else + let b[8212:8215] = 0z00000000 + endif + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['???'], getline(1, '$')) + bw! + + " use an incorrect text end (db_txt_end) for the data block + let b = copy(save_b) + let b[8204:8207] = little_endian ? 0z80000000 : 0z00000080 + call writefile(b, sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['??? from here until ???END lines may be messed up', '', + \ '???END'], getline(1, '$')) + bw! + + " remove the data block + let b = copy(save_b) + call writefile(b[:8191], sn) + call assert_fails('recover Xfile1', 'E312:') + call assert_equal('Xfile1', @%) + call assert_equal(['???MANY LINES MISSING'], getline(1, '$')) + + bw! + call delete(sn) +endfunc + +" Test for :recover using an encrypted swap file +func Test_recover_encrypted_swap_file() + CheckFeature cryptv + CheckUnix + + " Recover an encrypted file from the swap file without the original file + new Xfile1 + call feedkeys(":X\<CR>vim\<CR>vim\<CR>", 'xt') + call setline(1, ['aaa', 'bbb', 'ccc']) + preserve + let b = readblob('.Xfile1.swp') + call writefile(b, '.Xfile1.swm') + bw! + call feedkeys(":recover Xfile1\<CR>vim\<CR>\<CR>", 'xt') + call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$')) + bw! + call delete('.Xfile1.swm') + + " Recover an encrypted file from the swap file with the original file + new Xfile1 + call feedkeys(":X\<CR>vim\<CR>vim\<CR>", 'xt') + call setline(1, ['aaa', 'bbb', 'ccc']) + update + call setline(1, ['111', '222', '333']) + preserve + let b = readblob('.Xfile1.swp') + call writefile(b, '.Xfile1.swm') + bw! + call feedkeys(":recover Xfile1\<CR>vim\<CR>\<CR>", 'xt') + call assert_equal(['111', '222', '333'], getline(1, '$')) + call assert_true(&modified) + bw! + call delete('.Xfile1.swm') + call delete('Xfile1') +endfunc + +" Test for :recover using a unreadable swap file +func Test_recover_unreadble_swap_file() + CheckUnix + CheckNotRoot + new Xfile1 + let b = readblob('.Xfile1.swp') + call writefile(b, '.Xfile1.swm') + bw! + call setfperm('.Xfile1.swm', '-w-------') + call assert_fails('recover Xfile1', 'E306:') + call delete('.Xfile1.swm') +endfunc + +" Test for using :recover when the original file and the swap file have the +" same contents. +func Test_recover_unmodified_file() + CheckUnix + call writefile(['aaa', 'bbb', 'ccc'], 'Xfile1') + edit Xfile1 + preserve + let b = readblob('.Xfile1.swp') + %bw! + call writefile(b, '.Xfile1.swz') + let msg = execute('recover Xfile1') + call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$')) + call assert_false(&modified) + call assert_match('Buffer contents equals file contents', msg) + bw! + call delete('Xfile1') + call delete('.Xfile1.swz') +endfunc + +" Test for recovering a file when editing a symbolically linked file +func Test_recover_symbolic_link() + CheckUnix + call writefile(['aaa', 'bbb', 'ccc'], 'Xfile1') + silent !ln -s Xfile1 Xfile2 + edit Xfile2 + call assert_equal('.Xfile1.swp', fnamemodify(swapname(''), ':t')) + preserve + let b = readblob('.Xfile1.swp') + %bw! + call writefile([], 'Xfile1') + call writefile(b, '.Xfile1.swp') + silent! recover Xfile2 + call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$')) + call assert_true(&modified) + update + %bw! + call assert_equal(['aaa', 'bbb', 'ccc'], readfile('Xfile1')) + call delete('Xfile1') + call delete('Xfile2') + call delete('.Xfile1.swp') +endfunc + +" Test for recovering a file when an autocmd moves the cursor to an invalid +" line. This used to result in an internal error (E315) which is fixed +" by 8.2.2966. +func Test_recover_invalid_cursor_pos() + call writefile([], 'Xfile1') + edit Xfile1 + preserve + let b = readblob('.Xfile1.swp') + bw! + augroup Test + au! + au BufReadPost Xfile1 normal! 3G + augroup END + call writefile(range(1, 3), 'Xfile1') + call writefile(b, '.Xfile1.swp') + try + recover Xfile1 + catch /E308:/ + " this test is for the :E315 internal error. + " ignore the 'E308: Original file may have been changed' error + endtry + redraw! + augroup Test + au! + augroup END + augroup! Test + call delete('Xfile1') + call delete('.Xfile1.swp') +endfunc + +" Test for recovering a buffer without a name +func Test_noname_buffer() + new + call setline(1, ['one', 'two']) + preserve + let sn = swapname('') + let b = readblob(sn) + bw! + call writefile(b, sn) + exe "recover " .. sn + call assert_equal(['one', 'two'], getline(1, '$')) + call delete(sn) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index d08a980787..ece6ae518e 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -101,8 +101,33 @@ func Test_multi_failure() set re=2 call assert_fails('/a**', 'E871:') call assert_fails('/a*\+', 'E871:') - call assert_fails('/a\{a}', 'E870:') + call assert_fails('/a\{a}', 'E554:') + set re=0 +endfunc + +func Test_column_success_failure() + new + call setline(1, 'xbar') + + set re=1 + %s/\%>0v./A/ + call assert_equal('Abar', getline(1)) + call assert_fails('/\%v', 'E71:') + call assert_fails('/\%>v', 'E71:') + call assert_fails('/\%c', 'E71:') + call assert_fails('/\%<c', 'E71:') + call assert_fails('/\%l', 'E71:') + set re=2 + %s/\%>0v./B/ + call assert_equal('Bbar', getline(1)) + call assert_fails('/\%v', 'E1273:') + call assert_fails('/\%>v', 'E1273:') + call assert_fails('/\%c', 'E1273:') + call assert_fails('/\%<c', 'E1273:') + call assert_fails('/\%l', 'E1273:') + set re=0 + bwipe! endfunc func Test_recursive_addstate() @@ -146,6 +171,10 @@ func Test_regexp_single_line_pat() call add(tl, [2, 'c*', 'abdef', '']) call add(tl, [2, 'bc\+', 'abccccdef', 'bcccc']) call add(tl, [2, 'bc\+', 'abdef']) " no match + " match escape character in a string + call add(tl, [2, '.\e.', "one\<Esc>two", "e\<Esc>t"]) + " match backspace character in a string + call add(tl, [2, '.\b.', "one\<C-H>two", "e\<C-H>t"]) " match newline character in a string call add(tl, [2, 'o\nb', "foo\nbar", "o\nb"]) @@ -895,6 +924,8 @@ func Test_regexp_error() call assert_fails("call matchlist('x x', '\\%#=2 \\zs*')", 'E888:') call assert_fails("call matchlist('x x', '\\%#=2 \\ze*')", 'E888:') call assert_fails('exe "normal /\\%#=1\\%[x\\%[x]]\<CR>"', 'E369:') + call assert_fails("call matchstr('abcd', '\\%o841\\%o142')", 'E678:') + call assert_equal('', matchstr('abcd', '\%o181\%o142')) endfunc " Test for using the last substitute string pattern (~) @@ -1027,6 +1058,28 @@ func Test_using_invalid_visual_position() bwipe! endfunc +func Test_using_two_engines_pattern() + new + call setline(1, ['foobar=0', 'foobar=1', 'foobar=2']) + " \%#= at the end of the pattern + for i in range(0, 2) + for j in range(0, 2) + exe "set re=" .. i + call cursor(j + 1, 7) + call assert_fails("%s/foobar\\%#=" .. j, 'E1281:') + endfor + endfor + set re=0 + + " \%#= at the start of the pattern + for i in range(0, 2) + call cursor(i + 1, 7) + exe ":%s/\\%#=" .. i .. "foobar=" .. i .. "/xx" + endfor + call assert_equal(['xx', 'xx', 'xx'], getline(1, '$')) + bwipe! +endfunc + func Test_recursive_substitute_expr() new func Repl() diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index 14b9724d67..2253242a7c 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -258,8 +258,7 @@ func Test_multibyte_chars() " When there is no match use only the first two items. let tl = [] - " Multi-byte character tests. These will fail unless vim is compiled - " with Multibyte (FEAT_MBYTE) or BIG/HUGE features. + " Multi-byte character tests. call add(tl, [2, '[[:alpha:][=a=]]\+', '879 aiaãâaiuvna ', 'aiaãâaiuvna']) call add(tl, [2, '[[=a=]]\+', 'ddaãâbcd', 'aãâ']) " equivalence classes call add(tl, [2, '[^ม ]\+', 'มม oijasoifjos ifjoisj f osij j มมมมม abcd', 'oijasoifjos']) diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 11dd3badb6..bbf1aa53b5 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -263,8 +263,16 @@ func Test_get_register() call assert_equal('', getreg("\<C-F>")) call assert_equal('', getreg("\<C-W>")) call assert_equal('', getreg("\<C-L>")) + " Change the last used register to '"' for the next test + normal! ""yy + let @" = 'happy' + call assert_equal('happy', getreg()) + call assert_equal('happy', getreg('')) call assert_equal('', getregtype('!')) + call assert_fails('echo getregtype([])', 'E730:') + call assert_equal('v', getregtype()) + call assert_equal('v', getregtype('')) " Test for inserting an invalid register content call assert_beeps('exe "normal i\<C-R>!"') @@ -277,7 +285,9 @@ func Test_get_register() " Test for inserting a multi-line register in the command line call feedkeys(":\<C-R>r\<Esc>", 'xt') - call assert_equal("a\rb", histget(':', -1)) " Modified because of #6137 + " Nvim: no trailing CR because of #6137 + " call assert_equal("a\rb\r", histget(':', -1)) + call assert_equal("a\rb", histget(':', -1)) call assert_fails('let r = getreg("=", [])', 'E745:') call assert_fails('let r = getreg("=", 1, [])', 'E745:') @@ -289,6 +299,7 @@ endfunc func Test_set_register() call assert_fails("call setreg('#', 200)", 'E86:') + " call assert_fails("call setreg('a', test_unknown())", 'E908:') edit Xfile_alt_1 let b1 = bufnr('') @@ -349,6 +360,12 @@ func Test_set_register() normal 0".gP call assert_equal('abcabcabc', getline(1)) + let @"='' + call setreg('', '1') + call assert_equal('1', @") + call setreg('@', '2') + call assert_equal('2', @") + enew! endfunc @@ -474,6 +491,21 @@ func Test_get_reginfo() let info = getreginfo('"') call assert_equal('z', info.points_to) + let @a="a1b2" + nnoremap <F2> <Cmd>let g:RegInfo = getreginfo()<CR> + exe "normal \"a\<F2>" + call assert_equal({'regcontents': ['a1b2'], 'isunnamed': v:false, + \ 'regtype': 'v'}, g:RegInfo) + nunmap <F2> + unlet g:RegInfo + + " The type of "isunnamed" was VAR_SPECIAL but should be VAR_BOOL. Can only + " be noticed when using json_encod(). + call setreg('a', 'foo') + let reginfo = getreginfo('a') + let expected = #{regcontents: ['foo'], isunnamed: v:false, regtype: 'v'} + call assert_equal(json_encode(expected), json_encode(reginfo)) + bwipe! endfunc diff --git a/src/nvim/testdir/test_reltime.vim b/src/nvim/testdir/test_reltime.vim index b381f1ddbb..f4ce5de118 100644 --- a/src/nvim/testdir/test_reltime.vim +++ b/src/nvim/testdir/test_reltime.vim @@ -24,4 +24,8 @@ func Test_reltime() call assert_true(reltimefloat(differs) < 0.1) call assert_true(reltimefloat(differs) > 0.0) + call assert_equal(0, reltime({})) + call assert_equal(0, reltime({}, {})) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_retab.vim b/src/nvim/testdir/test_retab.vim index 1650a03876..a4f95053c0 100644 --- a/src/nvim/testdir/test_retab.vim +++ b/src/nvim/testdir/test_retab.vim @@ -1,4 +1,7 @@ " Test :retab + +source check.vim + func SetUp() new call setline(1, "\ta \t b c ") @@ -81,19 +84,32 @@ func Test_retab_error() call assert_fails('ret 80000000000000000000', 'E475:') endfunc +func RetabLoop() + while 1 + set ts=4000 + retab 4 + endwhile +endfunc + func Test_retab_endless() - new + " inside try/catch we can catch the error message call setline(1, "\t0\t") let caught = 'no' try - while 1 - set ts=4000 - retab 4 - endwhile - catch /E1240/ - let caught = 'yes' + call RetabLoop() + catch /E1240:/ + let caught = v:exception endtry - bwipe! + call assert_match('E1240:', caught) + + set tabstop& +endfunc + +func Test_nocatch_retab_endless() + " when not inside try/catch an interrupt is generated to get out of loops + call setline(1, "\t0\t") + call assert_fails('call RetabLoop()', ['E1240:', 'Interrupted']) + set tabstop& endfunc diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 3d1bbfb726..885043accf 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -294,6 +294,9 @@ func Test_searchpair() new call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]']) + " should not give an error for using "42" + call assert_equal(0, searchpair('a', 'b', 'c', '', 42)) + 4 call assert_equal(3, searchpair('\[', '', ']', 'bW')) call assert_equal([0, 3, 2, 0], getpos('.')) @@ -897,9 +900,7 @@ func Test_incsearch_cmdline_modifier() endfunc func Test_incsearch_scrolling() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal call assert_equal(0, &scrolloff) call writefile([ \ 'let dots = repeat(".", 120)', @@ -956,24 +957,24 @@ func Test_incsearch_search_dump() call delete('Xis_search_script') endfunc -func Test_hlsearch_block_visual_match() +func Test_hlsearch_dump() + CheckOption hlsearch CheckScreendump - let lines =<< trim END - set hlsearch - call setline(1, ['aa', 'bbbb', 'cccccc']) - END - call writefile(lines, 'Xhlsearch_block') - let buf = RunVimInTerminal('-S Xhlsearch_block', {'rows': 9, 'cols': 60}) + call writefile([ + \ 'set hlsearch cursorline', + \ 'call setline(1, ["xxx", "xxx", "xxx"])', + \ '/.*', + \ '2', + \ ], 'Xhlsearch_script') + let buf = RunVimInTerminal('-S Xhlsearch_script', {'rows': 6, 'cols': 50}) + call VerifyScreenDump(buf, 'Test_hlsearch_1', {}) - call term_sendkeys(buf, "G\<C-V>$kk\<Esc>") - sleep 100m - call term_sendkeys(buf, "/\\%V\<CR>") - sleep 100m - call VerifyScreenDump(buf, 'Test_hlsearch_block_visual_match', {}) + call term_sendkeys(buf, "/\\_.*\<CR>") + call VerifyScreenDump(buf, 'Test_hlsearch_2', {}) call StopVimInTerminal(buf) - call delete('Xhlsearch_block') + call delete('Xhlsearch_script') endfunc func Test_hlsearch_and_visual() @@ -996,6 +997,26 @@ func Test_hlsearch_and_visual() call delete('Xhlvisual_script') endfunc +func Test_hlsearch_block_visual_match() + CheckScreendump + + let lines =<< trim END + set hlsearch + call setline(1, ['aa', 'bbbb', 'cccccc']) + END + call writefile(lines, 'Xhlsearch_block') + let buf = RunVimInTerminal('-S Xhlsearch_block', {'rows': 9, 'cols': 60}) + + call term_sendkeys(buf, "G\<C-V>$kk\<Esc>") + sleep 100m + call term_sendkeys(buf, "/\\%V\<CR>") + sleep 100m + call VerifyScreenDump(buf, 'Test_hlsearch_block_visual_match', {}) + + call StopVimInTerminal(buf) + call delete('Xhlsearch_block') +endfunc + func Test_incsearch_substitute() CheckFunction test_override CheckOption incsearch @@ -1017,6 +1038,21 @@ func Test_incsearch_substitute() call Incsearch_cleanup() endfunc +func Test_incsearch_substitute_long_line() + CheckFunction test_override + new + call test_override("char_avail", 1) + set incsearch + + call repeat('x', 100000)->setline(1) + call feedkeys(':s/\%c', 'xt') + redraw + call feedkeys("\<Esc>", 'xt') + + call Incsearch_cleanup() + bwipe! +endfunc + func Test_hlsearch_cursearch() CheckScreendump @@ -1341,18 +1377,19 @@ func Test_subst_word_under_cursor() set noincsearch endfunc -func Test_incsearch_substitute_long_line() - CheckFunction test_override - new - call test_override("char_avail", 1) - set incsearch - - call repeat('x', 100000)->setline(1) - call feedkeys(':s/\%c', 'xt') - redraw - call feedkeys("\<Esc>", 'xt') +func Test_search_skip_all_matches() + enew + call setline(1, ['no match here', + \ 'match this line', + \ 'nope', + \ 'match in this line', + \ 'last line', + \ ]) + call cursor(1, 1) + let lnum = search('this', '', 0, 0, 'getline(".") =~ "this line"') + " Only check that no match is found. Previously it searched forever. + call assert_equal(0, lnum) - call Incsearch_cleanup() bwipe! endfunc @@ -1629,8 +1666,8 @@ func Test_search_with_no_last_pat() call assert_fails(";//p", 'E35:') call assert_fails("??p", 'E35:') call assert_fails(";??p", 'E35:') - call assert_fails('g//p', 'E476:') - call assert_fails('v//p', 'E476:') + call assert_fails('g//p', ['E35:', 'E476:']) + call assert_fails('v//p', ['E35:', 'E476:']) call writefile(v:errors, 'Xresult') qall! [SCRIPT] @@ -1651,8 +1688,8 @@ func Test_search_tilde_pat() call assert_fails('exe "normal /~\<CR>"', 'E33:') call assert_fails('exe "normal ?~\<CR>"', 'E33:') set regexpengine=2 - call assert_fails('exe "normal /~\<CR>"', 'E383:') - call assert_fails('exe "normal ?~\<CR>"', 'E383:') + call assert_fails('exe "normal /~\<CR>"', ['E33:', 'E383:']) + call assert_fails('exe "normal ?~\<CR>"', ['E33:', 'E383:']) set regexpengine& call writefile(v:errors, 'Xresult') qall! @@ -1732,6 +1769,25 @@ func Test_invalid_regexp() call assert_fails("call search('\\%#=3ab')", 'E864:') endfunc +" Test for searching a very complex pattern in a string. Should switch the +" regexp engine from NFA to the old engine. +func Test_regexp_switch_engine() + let l = readfile('samples/re.freeze.txt') + let v = substitute(l[4], '..\@<!', '', '') + call assert_equal(l[4], v) +endfunc + +" Test for the \%V atom to search within visually selected text +func Test_search_in_visual_area() + new + call setline(1, ['foo bar1', 'foo bar2', 'foo bar3', 'foo bar4']) + exe "normal 2GVjo/\\%Vbar\<CR>\<Esc>" + call assert_equal([2, 5], [line('.'), col('.')]) + exe "normal 2GVj$?\\%Vbar\<CR>\<Esc>" + call assert_equal([3, 5], [line('.'), col('.')]) + close! +endfunc + " Test for searching with 'smartcase' and 'ignorecase' func Test_search_smartcase() new @@ -1776,7 +1832,7 @@ func Test_search_smartcase_utf8() set ignorecase& smartcase& let &encoding = save_enc - close! + bwipe! endfunc " Test searching past the end of a file @@ -1785,7 +1841,29 @@ func Test_search_past_eof() call setline(1, ['Line']) exe "normal /\\n\\zs\<CR>" call assert_equal([1, 4], [line('.'), col('.')]) - close! + bwipe! +endfunc + +" Test setting the start of the match and still finding a next match in the +" same line. +func Test_search_set_start_same_line() + new + set cpo-=c + + call setline(1, ['1', '2', '3 .', '4', '5']) + exe "normal /\\_s\\zs\\S\<CR>" + call assert_equal([2, 1], [line('.'), col('.')]) + exe 'normal n' + call assert_equal([3, 1], [line('.'), col('.')]) + exe 'normal n' + call assert_equal([3, 3], [line('.'), col('.')]) + exe 'normal n' + call assert_equal([4, 1], [line('.'), col('.')]) + exe 'normal n' + call assert_equal([5, 1], [line('.'), col('.')]) + + set cpo+=c + bwipe! endfunc " Test for various search offsets @@ -1907,6 +1985,100 @@ func Test_incsearch_highlighting_newline() bw endfunc +func Test_incsearch_substitute_dump2() + CheckOption incsearch + CheckScreendump + + call writefile([ + \ 'set incsearch hlsearch scrolloff=0', + \ 'for n in range(1, 4)', + \ ' call setline(n, "foo " . n)', + \ 'endfor', + \ 'call setline(5, "abc|def")', + \ '3', + \ ], 'Xis_subst_script2') + let buf = RunVimInTerminal('-S Xis_subst_script2', {'rows': 9, 'cols': 70}) + + call term_sendkeys(buf, ':%s/\vabc|') + sleep 100m + call VerifyScreenDump(buf, 'Test_incsearch_sub_01', {}) + call term_sendkeys(buf, "\<Esc>") + + " The following should not be highlighted + call term_sendkeys(buf, ':1,5s/\v|') + sleep 100m + call VerifyScreenDump(buf, 'Test_incsearch_sub_02', {}) + + + call StopVimInTerminal(buf) + call delete('Xis_subst_script2') +endfunc + +func Test_pattern_is_uppercase_smartcase() + new + let input=['abc', 'ABC', 'Abc', 'abC'] + call setline(1, input) + call cursor(1,1) + " default, matches firstline + %s/abc//g + call assert_equal(['', 'ABC', 'Abc', 'abC'], + \ getline(1, '$')) + + set smartcase ignorecase + sil %d + call setline(1, input) + call cursor(1,1) + " with smartcase and incsearch set, matches everything + %s/abc//g + call assert_equal(['', '', '', ''], getline(1, '$')) + + sil %d + call setline(1, input) + call cursor(1,1) + " with smartcase and incsearch set and found an uppercase letter, + " match only that. + %s/abC//g + call assert_equal(['abc', 'ABC', 'Abc', ''], + \ getline(1, '$')) + + sil %d + call setline(1, input) + call cursor(1,1) + exe "norm! vG$\<esc>" + " \%V should not be detected as uppercase letter + %s/\%Vabc//g + call assert_equal(['', '', '', ''], getline(1, '$')) + + call setline(1, input) + call cursor(1,1) + exe "norm! vG$\<esc>" + " \v%V should not be detected as uppercase letter + %s/\v%Vabc//g + call assert_equal(['', '', '', ''], getline(1, '$')) + + call setline(1, input) + call cursor(1,1) + exe "norm! vG$\<esc>" + " \v%VabC should be detected as uppercase letter + %s/\v%VabC//g + call assert_equal(['abc', 'ABC', 'Abc', ''], + \ getline(1, '$')) + + call setline(1, input) + call cursor(1,1) + " \Vabc should match everything + %s/\Vabc//g + call assert_equal(['', '', '', ''], getline(1, '$')) + + call setline(1, input + ['_abc']) + " _ matches normally + %s/\v_.*//g + call assert_equal(['abc', 'ABC', 'Abc', 'abC', ''], getline(1, '$')) + + set smartcase& ignorecase& + bw! +endfunc + func Test_no_last_search_pattern() CheckOption incsearch diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim index 89e09cf85b..77bd50ada2 100644 --- a/src/nvim/testdir/test_search_stat.vim +++ b/src/nvim/testdir/test_search_stat.vim @@ -260,6 +260,14 @@ endfunc func Test_searchcount_fails() call assert_fails('echo searchcount("boo!")', 'E715:') + call assert_fails('echo searchcount({"timeout" : []})', 'E745:') + call assert_fails('echo searchcount({"maxcount" : []})', 'E745:') + call assert_fails('echo searchcount({"pattern" : []})', 'E730:') + call assert_fails('echo searchcount({"pos" : 1})', 'E475:') + call assert_fails('echo searchcount({"pos" : [1]})', 'E475:') + call assert_fails('echo searchcount({"pos" : [[], 2, 3]})', 'E745:') + call assert_fails('echo searchcount({"pos" : [1, [], 3]})', 'E745:') + call assert_fails('echo searchcount({"pos" : [1, 2, []]})', 'E745:') endfunc func Test_searchcount_in_statusline() diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim index f2cab45450..041f0592f1 100644 --- a/src/nvim/testdir/test_selectmode.vim +++ b/src/nvim/testdir/test_selectmode.vim @@ -34,6 +34,9 @@ func Test_selectmode_start() set selectmode=cmd call feedkeys('gvabc', 'xt') call assert_equal('abctdef', getline(1)) + " arrow keys without shift should not start selection + call feedkeys("A\<Home>\<Right>\<Left>ro", 'xt') + call assert_equal('roabctdef', getline(1)) set selectmode= keymodel= bw! endfunc diff --git a/src/nvim/testdir/test_set.vim b/src/nvim/testdir/test_set.vim index 2b1e9eeee0..7215772a00 100644 --- a/src/nvim/testdir/test_set.vim +++ b/src/nvim/testdir/test_set.vim @@ -26,4 +26,23 @@ function Test_set_add() let &wig = wig_save endfunction + +" :set, :setlocal, :setglobal without arguments show values of options. +func Test_set_no_arg() + set textwidth=79 + let a = execute('set') + call assert_match("^\n--- Options ---\n.*textwidth=79\\>", a) + set textwidth& + + setlocal textwidth=78 + let a = execute('setlocal') + call assert_match("^\n--- Local option values ---\n.*textwidth=78\\>", a) + setlocal textwidth& + + setglobal textwidth=77 + let a = execute('setglobal') + call assert_match("^\n--- Global option values ---\n.*textwidth=77\\>", a) + setglobal textwidth& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_shell.vim b/src/nvim/testdir/test_shell.vim new file mode 100644 index 0000000000..8b9c7a5b12 --- /dev/null +++ b/src/nvim/testdir/test_shell.vim @@ -0,0 +1,209 @@ +" Test for the shell related options ('shell', 'shellcmdflag', 'shellpipe', +" 'shellquote', 'shellredir', 'shellxescape', and 'shellxquote') + +source check.vim +source shared.vim + +func Test_shell_options() + " The expected value of 'shellcmdflag', 'shellpipe', 'shellquote', + " 'shellredir', 'shellxescape', 'shellxquote' for the supported shells. + let shells = [] + if has('unix') + let shells += [['sh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['ksh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['mksh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['zsh', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['zsh-beta', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['bash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['fish', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['ash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['dash', '-c', '2>&1| tee', '', '>%s 2>&1', '', ''], + \ ['csh', '-c', '|& tee', '', '>&', '', ''], + \ ['tcsh', '-c', '|& tee', '', '>&', '', '']] + endif + if has('win32') + let shells += [['cmd', '/s /c', '>%s 2>&1', '', '>%s 2>&1', '', '"']] + endif + + " start a new Vim instance with 'shell' set to each of the supported shells + " and check the default shell option settings + let after =<< trim END + let l = [&shell, &shellcmdflag, &shellpipe, &shellquote] + let l += [&shellredir, &shellxescape, &shellxquote] + call writefile([json_encode(l)], 'Xtestout') + qall! + END + for e in shells + if RunVim([], after, '--cmd "set shell=' .. e[0] .. '"') + call assert_equal(e, json_decode(readfile('Xtestout')[0])) + endif + endfor + + " Test shellescape() for each of the shells. + for e in shells + exe 'set shell=' .. e[0] + if e[0] =~# '.*csh$' || e[0] =~# '.*csh.exe$' + let str1 = "'cmd \"arg1\" '\\''arg2'\\'' \\!%#'" + let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\\\!\\%\\#'" + elseif e[0] =~# '.*powershell$' || e[0] =~# '.*powershell.exe$' + let str1 = "'cmd \"arg1\" ''arg2'' !%#'" + let str2 = "'cmd \"arg1\" ''arg2'' \\!\\%\\#'" + else + let str1 = "'cmd \"arg1\" '\\''arg2'\\'' !%#'" + let str2 = "'cmd \"arg1\" '\\''arg2'\\'' \\!\\%\\#'" + endif + call assert_equal(str1, shellescape("cmd \"arg1\" 'arg2' !%#"), e[0]) + call assert_equal(str2, shellescape("cmd \"arg1\" 'arg2' !%#", 1), e[0]) + + " Try running an external command with the shell. + if executable(e[0]) + " set the shell options for the current 'shell' + let [&shellcmdflag, &shellpipe, &shellquote, &shellredir, + \ &shellxescape, &shellxquote] = e[1:6] + new + r !echo hello + call assert_equal('hello', substitute(getline(2), '\W', '', 'g'), e[0]) + bwipe! + endif + endfor + set shell& shellcmdflag& shellpipe& shellquote& + set shellredir& shellxescape& shellxquote& + call delete('Xtestout') +endfunc + +" Test for the 'shell' option +func Test_shell() + throw 'Skipped: Nvim missing :shell currently' + CheckUnix + let save_shell = &shell + set shell= + let caught_e91 = 0 + try + shell + catch /E91:/ + let caught_e91 = 1 + endtry + call assert_equal(1, caught_e91) + let &shell = save_shell +endfunc + +" Test for the 'shellquote' option +func Test_shellquote() + CheckUnix + set shellquote=# + set verbose=20 + redir => v + silent! !echo Hello + redir END + set verbose& + set shellquote& + call assert_match(': "#echo Hello#"', v) +endfunc + +" Test for the 'shellescape' option +func Test_shellescape() + let save_shell = &shell + set shell=bash + call assert_equal("'text'", shellescape('text')) + call assert_equal("'te\"xt'", 'te"xt'->shellescape()) + call assert_equal("'te'\\''xt'", shellescape("te'xt")) + + call assert_equal("'te%xt'", shellescape("te%xt")) + call assert_equal("'te\\%xt'", shellescape("te%xt", 1)) + call assert_equal("'te#xt'", shellescape("te#xt")) + call assert_equal("'te\\#xt'", shellescape("te#xt", 1)) + call assert_equal("'te!xt'", shellescape("te!xt")) + call assert_equal("'te\\!xt'", shellescape("te!xt", 1)) + + call assert_equal("'te\nxt'", shellescape("te\nxt")) + call assert_equal("'te\\\nxt'", shellescape("te\nxt", 1)) + set shell=tcsh + call assert_equal("'te\\!xt'", shellescape("te!xt")) + call assert_equal("'te\\\\!xt'", shellescape("te!xt", 1)) + call assert_equal("'te\\\nxt'", shellescape("te\nxt")) + call assert_equal("'te\\\\\nxt'", shellescape("te\nxt", 1)) + + let &shell = save_shell +endfunc + +" Test for 'shellslash' +func Test_shellslash() + CheckOption shellslash + let save_shellslash = &shellslash + " The shell and cmdflag, and expected slash in tempname with shellslash set or + " unset. The assert checks the file separator before the leafname. + " ".*\\\\[^\\\\]*$" + let shells = [['cmd', '/c', '/', '/'], + \ ['powershell', '-Command', '/', '/'], + \ ['sh', '-c', '/', '/']] + for e in shells + exe 'set shell=' .. e[0] .. ' | set shellcmdflag=' .. e[1] + set noshellslash + let file = tempname() + call assert_match('^.\+' .. e[2] .. '[^' .. e[2] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' nossl') + set shellslash + let file = tempname() + call assert_match('^.\+' .. e[3] .. '[^' .. e[3] .. ']\+$', file, e[0] .. ' ' .. e[1] .. ' ssl') + endfor + let &shellslash = save_shellslash +endfunc + +" Test for 'shellxquote' +func Test_shellxquote() + CheckUnix + + let save_shell = &shell + let save_sxq = &shellxquote + let save_sxe = &shellxescape + + call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell') + call setfperm('Xtestshell', "r-x------") + set shell=./Xtestshell + + set shellxquote=\\" + call feedkeys(":!pwd\<CR>\<CR>", 'xt') + call assert_equal(['Cmd: [-c "pwd"]'], readfile('Xlog')) + + set shellxquote=( + call feedkeys(":!pwd\<CR>\<CR>", 'xt') + call assert_equal(['Cmd: [-c (pwd)]'], readfile('Xlog')) + + set shellxquote=\\"( + call feedkeys(":!pwd\<CR>\<CR>", 'xt') + call assert_equal(['Cmd: [-c "(pwd)"]'], readfile('Xlog')) + + set shellxescape=\"&<<()@^ + set shellxquote=( + call feedkeys(":!pwd\"&<<{}@^\<CR>\<CR>", 'xt') + call assert_equal(['Cmd: [-c (pwd^"^&^<^<{}^@^^)]'], readfile('Xlog')) + + let &shell = save_shell + let &shellxquote = save_sxq + let &shellxescape = save_sxe + call delete('Xtestshell') + call delete('Xlog') +endfunc + +" Test for using the shell set in the $SHELL environment variable +func Test_set_shell() + let after =<< trim [CODE] + call writefile([&shell], "Xtestout") + quit! + [CODE] + + if has('win32') + let $SHELL = 'C:\with space\cmd.exe' + let expected = '"C:\with space\cmd.exe"' + else + let $SHELL = '/bin/with space/sh' + let expected = '"/bin/with space/sh"' + endif + + if RunVimPiped([], after, '', '') + let lines = readfile('Xtestout') + call assert_equal(expected, lines[0]) + endif + call delete('Xtestout') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_signals.vim b/src/nvim/testdir/test_signals.vim index 338c0d79ff..c291c68e0d 100644 --- a/src/nvim/testdir/test_signals.vim +++ b/src/nvim/testdir/test_signals.vim @@ -16,8 +16,9 @@ endfunc " Test signal WINCH (window resize signal) func Test_signal_WINCH() throw 'skipped: Nvim cannot avoid terminal resize' - if has('gui_running') || !HasSignal('WINCH') - return + CheckNotGui + if !HasSignal('WINCH') + throw 'Skipped: WINCH signal not supported' endif " We do not actually want to change the size of the terminal. @@ -52,7 +53,7 @@ endfunc " Test signal PWR, which should update the swap file. func Test_signal_PWR() if !HasSignal('PWR') - return + throw 'Skipped: PWR signal not supported' endif " Set a very large 'updatetime' and 'updatecount', so that we can be sure @@ -78,6 +79,33 @@ func Test_signal_PWR() set updatetime& updatecount& endfunc +" Test signal INT. Handler sets got_int. It should be like typing CTRL-C. +func Test_signal_INT() + CheckRunVimInTerminal + if !HasSignal('INT') + throw 'Skipped: INT signal not supported' + endif + + " Skip the rest of the test when running with valgrind as signal INT is not + " received somehow by Vim when running with valgrind. + let cmd = GetVimCommand() + if cmd =~ 'valgrind' + throw 'Skipped: cannot test signal INT with valgrind' + endif + + let buf = RunVimInTerminal('', {'rows': 6}) + let pid_vim = term_getjob(buf)->job_info().process + + " Check that an endless loop in Vim is interrupted by signal INT. + call term_sendkeys(buf, ":while 1 | endwhile\n") + call WaitForAssert({-> assert_equal(':while 1 | endwhile', term_getline(buf, 6))}) + exe 'silent !kill -s INT ' .. pid_vim + call term_sendkeys(buf, ":call setline(1, 'INTERUPTED')\n") + call WaitForAssert({-> assert_equal('INTERUPTED', term_getline(buf, 1))}) + + call StopVimInTerminal(buf) +endfunc + " Test a deadly signal. " " There are several deadly signals: SISEGV, SIBUS, SIGTERM... @@ -91,9 +119,7 @@ func Test_deadly_signal_TERM() if !HasSignal('TERM') throw 'Skipped: TERM signal not supported' endif - if !CanRunVimInTerminal() - throw 'Skipped: cannot run vim in terminal' - endif + CheckRunVimInTerminal let cmd = GetVimCommand() if cmd =~ 'valgrind' throw 'Skipped: cannot test signal TERM with valgrind' @@ -128,8 +154,7 @@ func Test_deadly_signal_TERM() call assert_equal(['foo'], getline(1, '$')) let result = readfile('XautoOut') - call assert_match('VimLeavePre triggered', result[0]) - call assert_match('VimLeave triggered', result[1]) + call assert_equal(["VimLeavePre triggered", "VimLeave triggered"], result) %bwipe! call delete('.Xsig_TERM.swp') diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index ff9ba3d8ed..8311955a15 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -15,13 +15,13 @@ func Test_sign() " the icon name when listing signs. sign define Sign1 text=x - call Sign_command_ignore_error('sign define Sign2 text=xy texthl=Title linehl=Error culhl=Search icon=../../pixmaps/stock_vim_find_help.png') + call Sign_command_ignore_error('sign define Sign2 text=xy texthl=Title linehl=Error culhl=Search numhl=Number icon=../../pixmaps/stock_vim_find_help.png') " Test listing signs. let a=execute('sign list') call assert_match('^\nsign Sign1 text=x \nsign Sign2 ' . \ 'icon=../../pixmaps/stock_vim_find_help.png .*text=xy ' . - \ 'linehl=Error texthl=Title culhl=Search$', a) + \ 'linehl=Error texthl=Title culhl=Search numhl=Number$', a) let a=execute('sign list Sign1') call assert_equal("\nsign Sign1 text=x ", a) @@ -127,26 +127,34 @@ func Test_sign() call assert_fails("sign define Sign4 text=\\ ab linehl=Comment", 'E239:') " an empty highlight argument for an existing sign clears it - sign define SignY texthl=TextHl culhl=CulHl linehl=LineHl + sign define SignY texthl=TextHl culhl=CulHl linehl=LineHl numhl=NumHl let sl = sign_getdefined('SignY')[0] call assert_equal('TextHl', sl.texthl) call assert_equal('CulHl', sl.culhl) call assert_equal('LineHl', sl.linehl) + call assert_equal('NumHl', sl.numhl) - sign define SignY texthl= culhl=CulHl linehl=LineHl + sign define SignY texthl= culhl=CulHl linehl=LineHl numhl=NumHl let sl = sign_getdefined('SignY')[0] call assert_false(has_key(sl, 'texthl')) call assert_equal('CulHl', sl.culhl) call assert_equal('LineHl', sl.linehl) + call assert_equal('NumHl', sl.numhl) sign define SignY linehl= let sl = sign_getdefined('SignY')[0] call assert_false(has_key(sl, 'linehl')) call assert_equal('CulHl', sl.culhl) + call assert_equal('NumHl', sl.numhl) sign define SignY culhl= let sl = sign_getdefined('SignY')[0] call assert_false(has_key(sl, 'culhl')) + call assert_equal('NumHl', sl.numhl) + + sign define SignY numhl= + let sl = sign_getdefined('SignY')[0] + call assert_false(has_key(sl, 'numhl')) sign undefine SignY @@ -158,7 +166,7 @@ func Test_sign() sign define Sign5 text=X\ linehl=Comment sign undefine Sign5 - sign define Sign5 linehl=Comment text=X\ + sign define Sign5 linehl=Comment text=X\ sign undefine Sign5 " define sign with backslash @@ -417,8 +425,8 @@ func Test_sign_funcs() let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error', \ 'culhl': 'Visual', 'numhl': 'Number'} call assert_equal(0, "sign1"->sign_define(attr)) - call assert_equal([{'name' : 'sign1', 'texthl' : 'Error', 'linehl': 'Search', - \ 'culhl': 'Visual', 'numhl': 'Number', 'text' : '=>'}], + call assert_equal([{'name' : 'sign1', 'texthl' : 'Error', 'linehl' : 'Search', + \ 'culhl' : 'Visual', 'numhl': 'Number', 'text' : '=>'}], \ sign_getdefined()) " Define a new sign without attributes and then update it @@ -483,13 +491,13 @@ func Test_sign_funcs() call assert_fails('call sign_place(5, "", "sign1", "@", {"lnum" : 10})', \ 'E158:') call assert_fails('call sign_place(5, "", "sign1", [], {"lnum" : 10})', - \ 'E158:') + \ 'E730:') call assert_fails('call sign_place(21, "", "sign1", "Xsign", \ {"lnum" : -1})', 'E474:') call assert_fails('call sign_place(22, "", "sign1", "Xsign", \ {"lnum" : 0})', 'E474:') call assert_fails('call sign_place(22, "", "sign1", "Xsign", - \ {"lnum" : []})', 'E474:') + \ {"lnum" : []})', 'E745:') call assert_equal(-1, sign_place(1, "*", "sign1", "Xsign", {"lnum" : 10})) " Tests for sign_getplaced() @@ -535,9 +543,9 @@ func Test_sign_funcs() call assert_equal(15, sign_place(15, '', 'sign1', 'Xsign', {'lnum' : 20})) call assert_equal(15, sign_place(15, '', 'sign2', 'Xsign')) call assert_equal([{'bufnr' : bufnr(''), 'signs' : - \ [{'id' : 15, 'group' : '', 'lnum' : 20, 'name' : 'sign2', - \ 'priority' : 10}]}], - \ sign_getplaced()) + \ [{'id' : 15, 'group' : '', 'lnum' : 20, 'name' : 'sign2', + \ 'priority' : 10}]}], + \ sign_getplaced()) " Tests for sign_undefine() call assert_equal(0, sign_undefine("sign1")) @@ -1165,7 +1173,7 @@ func Test_sign_unplace() call delete("Xsign2") endfunc -" Tests for auto-generating the sign identifier +" Tests for auto-generating the sign identifier. func Test_aaa_sign_id_autogen() enew | only call sign_unplace('*') @@ -1650,10 +1658,34 @@ func Test_sign_lnum_adjust() " changes made by this function. let &undolevels=&undolevels + " Nvim: make sign adjustment when deleting lines match Vim + set signcolumn=yes:1 + " Delete the line with the sign call deletebufline('', 4) let l = sign_getplaced(bufnr('')) - call assert_equal(0, len(l[0].signs)) + call assert_equal(4, l[0].signs[0].lnum) + + " Undo the delete operation + undo + let l = sign_getplaced(bufnr('')) + call assert_equal(5, l[0].signs[0].lnum) + + " Break the undo + let &undolevels=&undolevels + + " Delete few lines at the end of the buffer including the line with the sign + " Sign line number should not change (as it is placed outside of the buffer) + call deletebufline('', 3, 6) + let l = sign_getplaced(bufnr('')) + call assert_equal(5, l[0].signs[0].lnum) + + " Undo the delete operation. Sign should be restored to the previous line + undo + let l = sign_getplaced(bufnr('')) + call assert_equal(5, l[0].signs[0].lnum) + + set signcolumn& sign unplace * group=* sign undefine sign1 @@ -1731,7 +1763,7 @@ func Test_sign_jump_func() call assert_fails("call sign_jump(5, 'g5', 'foo')", 'E157:') call assert_fails('call sign_jump([], "", "foo")', 'E745:') call assert_fails('call sign_jump(2, [], "foo")', 'E730:') - call assert_fails('call sign_jump(2, "", {})', 'E158:') + call assert_fails('call sign_jump(2, "", {})', 'E731:') call assert_fails('call sign_jump(2, "", "baz")', 'E158:') sign unplace * group=* @@ -1741,9 +1773,7 @@ endfunc " Test for correct cursor position after the sign column appears or disappears. func Test_sign_cursor_position() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal let lines =<< trim END call setline(1, [repeat('x', 75), 'mmmm', 'yyyy']) @@ -1790,8 +1820,8 @@ func Test_sign_numcol() set number set signcolumn=number sign define sign1 text==> - sign place 10 line=1 name=sign1 sign define sign2 text=V + sign place 10 line=1 name=sign1 redraw! call assert_equal("=> 01234", s:ScreenLine(1, 1, 8)) diff --git a/src/nvim/testdir/test_sleep.vim b/src/nvim/testdir/test_sleep.vim index f71855fd4b..a428f380b0 100644 --- a/src/nvim/testdir/test_sleep.vim +++ b/src/nvim/testdir/test_sleep.vim @@ -21,6 +21,7 @@ func! Test_sleep_bang() call s:assert_takes_longer('sl 50m', 50) call s:assert_takes_longer('sl! 50m', 50) call s:assert_takes_longer('1sleep', 1000) + call s:assert_takes_longer('normal 1gs', 1000) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim index 9895ad754c..534393b724 100644 --- a/src/nvim/testdir/test_sort.vim +++ b/src/nvim/testdir/test_sort.vim @@ -80,7 +80,7 @@ func Test_sort_default() call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], '')) call assert_equal(['2', 'A', 'AA', 'a', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], 0)) call assert_equal(['2', 'A', 'a', 'AA', 1, 3.3], sort([3.3, 1, "2", "A", "a", "AA"], 1)) - call assert_fails('call sort([3.3, 1, "2"], 3)', "E474") + call assert_fails('call sort([3.3, 1, "2"], 3)', "E474:") endfunc " Tests for the ":sort" command. @@ -1360,7 +1360,8 @@ func Test_sort_cmd() call setline(1, ['line1', 'line2']) call assert_fails('sort no', 'E474:') call assert_fails('sort c', 'E475:') - call assert_fails('sort #pat%', 'E682:') + call assert_fails('sort #pat%', 'E654:') + call assert_fails('sort /\%(/', 'E53:') enew! endfunc diff --git a/src/nvim/testdir/test_source.vim b/src/nvim/testdir/test_source.vim index ba6fd5ad95..d4d96e36bf 100644 --- a/src/nvim/testdir/test_source.vim +++ b/src/nvim/testdir/test_source.vim @@ -1,5 +1,8 @@ " Tests for the :source command. +source check.vim +source view_util.vim + func Test_source_autocmd() call writefile([ \ 'let did_source = 1', @@ -87,4 +90,24 @@ func Test_source_autocmd_sfile() call delete('Xscript.vim') endfunc +func Test_source_error() + call assert_fails('scriptencoding utf-8', 'E167:') + call assert_fails('finish', 'E168:') + " call assert_fails('scriptversion 2', 'E984:') +endfunc + +" Test for sourcing a script recursively +func Test_nested_script() + CheckRunVimInTerminal + call writefile([':source! Xscript.vim', ''], 'Xscript.vim') + let buf = RunVimInTerminal('', {'rows': 6}) + call term_wait(buf) + call term_sendkeys(buf, ":set noruler\n") + call term_sendkeys(buf, ":source! Xscript.vim\n") + call term_wait(buf) + call WaitForAssert({-> assert_match('E22: Scripts nested too deep\s*', term_getline(buf, 6))}) + call delete('Xscript.vim') + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 8ab8204b10..c840e834b9 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -147,7 +147,7 @@ func Test_spell_file_missing() augroup TestSpellFileMissing autocmd! SpellFileMissing * bwipe augroup END - call assert_fails('set spell spelllang=ab_cd', 'E797:') + call assert_fails('set spell spelllang=ab_cd', 'E937:') " clean up augroup TestSpellFileMissing @@ -159,6 +159,19 @@ func Test_spell_file_missing() %bwipe! endfunc +func Test_spell_file_missing_bwipe() + " this was using a window that was wiped out in a SpellFileMissing autocmd + set spelllang=xy + au SpellFileMissing * n0 + set spell + au SpellFileMissing * bw + snext somefile + + au! SpellFileMissing + bwipe! + set nospell spelllang=en +endfunc + func Test_spelldump() " In case the spell file is not found avoid getting the download dialog, we " would get stuck at the prompt. @@ -329,6 +342,11 @@ func Test_spellsuggest() call assert_equal(['Third'], spellsuggest('THird', 1)) call assert_equal(['All'], spellsuggest('ALl', 1)) + " Special suggestion for repeated 'the the'. + call assert_inrange(0, 2, index(spellsuggest('the the', 3), 'the')) + call assert_inrange(0, 2, index(spellsuggest('the the', 3), 'the')) + call assert_inrange(0, 2, index(spellsuggest('The the', 3), 'The')) + call assert_fails("call spellsuggest('maxch', [])", 'E745:') call assert_fails("call spellsuggest('maxch', 2, [])", 'E745:') @@ -474,6 +492,35 @@ func Test_spellsuggest_option_expr() bwipe! endfunc +" Test for 'spellsuggest' expr errrors +func Test_spellsuggest_expr_errors() + " 'spellsuggest' + func MySuggest() + return range(3) + endfunc + set spell spellsuggest=expr:MySuggest() + call assert_equal([], spellsuggest('baord', 3)) + + " Test for 'spellsuggest' expression returning a non-list value + func! MySuggest2() + return 'good' + endfunc + set spellsuggest=expr:MySuggest2() + call assert_equal([], spellsuggest('baord')) + + " Test for 'spellsuggest' expression returning a list with dict values + func! MySuggest3() + return [[{}, {}]] + endfunc + set spellsuggest=expr:MySuggest3() + call assert_fails("call spellsuggest('baord')", 'E731:') + + set nospell spellsuggest& + delfunc MySuggest + delfunc MySuggest2 + delfunc MySuggest3 +endfunc + func Test_spellsuggest_timeout() set spellsuggest=timeout:30 set spellsuggest=timeout:-123 @@ -484,8 +531,23 @@ func Test_spellsuggest_timeout() call assert_fails('set spellsuggest=timeout:--9', 'E474:') endfunc +func Test_spellsuggest_visual_end_of_line() + let enc_save = &encoding + " set encoding=iso8859 + + " This was reading beyond the end of the line. + norm R00000000000 + sil norm 0 + sil! norm i00000) + sil! norm i00000) + call feedkeys("\<CR>") + norm z= + + let &encoding = enc_save +endfunc + func Test_spellinfo() - throw 'skipped: Nvim does not support enc=latin1' + throw 'Skipped: Nvim does not support enc=latin1' new let runtime = substitute($VIMRUNTIME, '\\', '/', 'g') @@ -1401,3 +1463,5 @@ let g:test_data_aff_sal = [ \"SAL ZZ- _", \"SAL Z S", \ ] + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index 39fafbf7b4..1ee1d0dfe3 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -267,10 +267,9 @@ endfunc " Test the -V[N] argument to set the 'verbose' option to [N] func Test_V_arg() - if has('gui_running') - " Can't catch the output of gvim. - return - endif + " Can't catch the output of gvim. + CheckNotGui + let out = system(GetVimCommand() . ' --clean -es -X -V0 -c "set verbose?" -cq') call assert_equal(" verbose=0\n", out) @@ -431,7 +430,7 @@ endfunction " Test the -reverse and +reverse arguments (for GUI only). func Test_reverse() CheckCanRunGui - CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + CheckAnyOf Feature:gui_gtk Feature:gui_motif let after =<< trim [CODE] call writefile([&background], "Xtest_reverse") @@ -452,7 +451,7 @@ endfunc " Test the -background and -foreground arguments (for GUI only). func Test_background_foreground() CheckCanRunGui - CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + CheckAnyOf Feature:gui_gtk Feature:gui_motif " Is there a better way to check the effect of -background & -foreground " other than merely looking at &background (dark or light)? @@ -479,7 +478,7 @@ func Test_font() if has('gui_gtk') let font = 'Courier 14' - elseif has('gui_motif') || has('gui_athena') + elseif has('gui_motif') let font = '-misc-fixed-bold-*' else throw 'Skipped: test does not set a valid font for this GUI' @@ -501,10 +500,10 @@ endfunc " Test the -geometry argument (for GUI only). func Test_geometry() CheckCanRunGui - CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + CheckAnyOf Feature:gui_gtk Feature:gui_motif - if has('gui_motif') || has('gui_athena') - " FIXME: With GUI Athena or Motif, the value of getwinposx(), + if has('gui_motif') + " FIXME: With GUI Motif the value of getwinposx(), " getwinposy() and getwinpos() do not match exactly the " value given in -geometry. Why? " So only check &columns and &lines for those GUIs. @@ -521,9 +520,18 @@ func Test_geometry() call writefile([&columns, &lines, getwinposx(), getwinposy(), string(getwinpos())], "Xtest_geometry") qall [CODE] - if RunVim([], after, '-f -g -geometry 31x13+41+43') + " Some window managers have a bar at the top that pushes windows down, + " need to use at least 130, let's do 150 + if RunVim([], after, '-f -g -geometry 31x13+41+150') let lines = readfile('Xtest_geometry') - call assert_equal(['31', '13', '41', '43', '[41, 43]'], lines) + " Depending on the GUI library and the windowing system the final size + " might be a bit different, allow for some tolerance. Tuned based on + " actual failures. + call assert_inrange(31, 35, str2nr(lines[0])) + call assert_equal('13', lines[1]) + call assert_equal('41', lines[2]) + call assert_equal('150', lines[3]) + call assert_equal('[41, 150]', lines[4]) endif endif @@ -533,7 +541,7 @@ endfunc " Test the -iconic argument (for GUI only). func Test_iconic() CheckCanRunGui - CheckAnyOf Feature:gui_gtk Feature:gui_motif Feature:gui_athena + CheckAnyOf Feature:gui_gtk Feature:gui_motif call RunVim([], [], '-f -g -iconic -cq') @@ -543,10 +551,9 @@ endfunc func Test_invalid_args() - if !has('unix') || has('gui_running') - " can't get output of Vim. - return - endif + " must be able to get the output of Vim. + CheckUnix + CheckNotGui for opt in ['-Y', '--does-not-exist'] let out = split(system(GetVimCommand() .. ' ' .. opt), "\n") @@ -603,7 +610,7 @@ func Test_invalid_args() call assert_equal(0, v:shell_error) if has('quickfix') - " Detect invalid repeated arguments '-t foo -t foo", '-q foo -q foo'. + " Detect invalid repeated arguments '-t foo -t foo', '-q foo -q foo'. for opt in ['-t', '-q'] let out = split(system(GetVimCommand() .. repeat(' ' .. opt .. ' foo', 2)), "\n") call assert_equal(1, v:shell_error) @@ -622,6 +629,12 @@ func Test_invalid_args() endfor if has('gui_gtk') + let out = split(system(GetVimCommand() .. ' --socketid'), "\n") + call assert_equal(1, v:shell_error) + call assert_match('^VIM - Vi IMproved .* (.*)$', out[0]) + call assert_equal('Argument missing after: "--socketid"', out[1]) + call assert_equal('More info with: "vim -h"', out[2]) + for opt in ['--socketid x', '--socketid 0xg'] let out = split(system(GetVimCommand() .. ' ' .. opt), "\n") call assert_equal(1, v:shell_error) @@ -629,6 +642,7 @@ func Test_invalid_args() call assert_equal('Invalid argument for: "--socketid"', out[1]) call assert_equal('More info with: "vim -h"', out[2]) endfor + endif endfunc @@ -711,27 +725,6 @@ func Test_read_stdin() call delete('Xtestout') endfunc -func Test_set_shell() - let after =<< trim [CODE] - call writefile([&shell], "Xtestout") - quit! - [CODE] - - if has('win32') - let $SHELL = 'C:\with space\cmd.exe' - let expected = '"C:\with space\cmd.exe"' - else - let $SHELL = '/bin/with space/sh' - let expected = '"/bin/with space/sh"' - endif - - if RunVimPiped([], after, '', '') - let lines = readfile('Xtestout') - call assert_equal(expected, lines[0]) - endif - call delete('Xtestout') -endfunc - func Test_progpath() " Tests normally run with "./vim" or "../vim", these must have been expanded " to a full path. @@ -747,10 +740,9 @@ func Test_progpath() endfunc func Test_silent_ex_mode() - if !has('unix') || has('gui_running') - " can't get output of Vim. - return - endif + " must be able to get the output of Vim. + CheckUnix + CheckNotGui " This caused an ml_get error. let out = system(GetVimCommand() . ' -u NONE -es -c''set verbose=1|h|exe "%norm\<c-y>\<c-d>"'' -c cq') @@ -758,10 +750,9 @@ func Test_silent_ex_mode() endfunc func Test_default_term() - if !has('unix') || has('gui_running') - " can't get output of Vim. - return - endif + " must be able to get the output of Vim. + CheckUnix + CheckNotGui let save_term = $TERM let $TERM = 'unknownxxx' @@ -796,10 +787,17 @@ func Test_zzz_startinsert() call delete('Xtestout') endfunc +func Test_issue_3969() + " Can't catch the output of gvim. + CheckNotGui + + " Check that message is not truncated. + let out = system(GetVimCommand() . ' -es -X -V1 -c "echon ''hello''" -cq') + call assert_equal('hello', out) +endfunc + func Test_start_with_tabs() - if !CanRunVimInTerminal() - return - endif + CheckRunVimInTerminal let buf = RunVimInTerminal('-p a b c', {}) call VerifyScreenDump(buf, 'Test_start_with_tabs', {}) @@ -855,7 +853,7 @@ func Test_t_arg() call writefile([' first', ' second', ' third'], 'Xfile1') for t_arg in ['-t second', '-tsecond'] - if RunVim(before, after, '-t second') + if RunVim(before, after, t_arg) call assert_equal(['Xfile1:L2C5'], readfile('Xtestout'), t_arg) call delete('Xtestout') endif @@ -943,6 +941,7 @@ endfunc " Test for enabling the lisp mode on startup func Test_l_arg() + throw 'Skipped: Nvim -l arg differs from Vim' let after =<< trim [CODE] let s = 'lisp=' .. &lisp .. ', showmatch=' .. &showmatch call writefile([s], 'Xtestout') @@ -956,9 +955,7 @@ endfunc " Test for specifying a non-existing vimrc file using "-u" func Test_missing_vimrc() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run vim in terminal' - endif + CheckRunVimInTerminal let after =<< trim [CODE] call assert_match('^E282:', v:errmsg) call writefile(v:errors, 'Xtestout') @@ -1016,6 +1013,7 @@ endfunc " Test for using the 'exrc' option func Test_exrc() + throw 'Skipped: Nvim requires user input for the exrc option' let after =<< trim [CODE] call assert_equal(1, &exrc) call assert_equal(1, &secure) @@ -1045,7 +1043,7 @@ func Test_io_not_a_terminal() \ 'Vim: Warning: Input is not from a terminal'], l) endfunc -" Test for --not-a-term avoiding escape codes. +" Test for not being a term avoiding escape codes. func Test_not_a_term() CheckUnix CheckNotGui @@ -1056,18 +1054,14 @@ func Test_not_a_term() let redir = &shellredir .. ' Xvimout' endif - " Without --not-a-term there are a few escape sequences. - " This will take 2 seconds because of the missing --not-a-term + " As nvim checks the environment by itself there will be no escape sequences + " This will also happen to take two (2) seconds. let cmd = GetVimProg() .. ' --cmd quit ' .. redir exe "silent !" . cmd - call assert_match("\<Esc>", readfile('Xvimout')->join()) + call assert_notmatch("\e", readfile('Xvimout')->join()) call delete('Xvimout') - " With --not-a-term there are no escape sequences. - let cmd = GetVimProg() .. ' --not-a-term --cmd quit ' .. redir - exe "silent !" . cmd - call assert_notmatch("\<Esc>", readfile('Xvimout')->join()) - call delete('Xvimout') + " --not-a-term flag has thus been deleted endfunc @@ -1271,4 +1265,19 @@ func Test_progname() call delete('Xprogname', 'd') endfunc +" Test for doing a write from .vimrc +func Test_write_in_vimrc() + call writefile(['silent! write'], 'Xvimrc') + let after =<< trim [CODE] + call assert_match('E32: ', v:errmsg) + call writefile(v:errors, 'Xtestout') + qall + [CODE] + if RunVim([], after, '-u Xvimrc') + call assert_equal([], readfile('Xtestout')) + call delete('Xtestout') + endif + call delete('Xvimrc') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_startup_utf8.vim b/src/nvim/testdir/test_startup_utf8.vim index bb4304396e..2ee6ecc41d 100644 --- a/src/nvim/testdir/test_startup_utf8.vim +++ b/src/nvim/testdir/test_startup_utf8.vim @@ -63,9 +63,7 @@ func Test_read_fifo_utf8() endfunc func Test_detect_ambiwidth() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run Vim in a terminal window' - endif + CheckRunVimInTerminal " Use the title termcap entries to output the escape sequence. call writefile([ diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index 6bde052442..990c852ccd 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -565,4 +565,45 @@ func Test_statusline_highlight_truncate() call delete('XTest_statusline') endfunc +func Test_statusline_showcmd() + CheckScreendump + + let lines =<< trim END + func MyStatusLine() + return '%S' + endfunc + + set laststatus=2 + set statusline=%!MyStatusLine() + set showcmdloc=statusline + call setline(1, ['a', 'b', 'c']) + set foldopen+=jump + 1,2fold + 3 + END + call writefile(lines, 'XTest_statusline', 'D') + + let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 6}) + + call term_sendkeys(buf, "g") + call VerifyScreenDump(buf, 'Test_statusline_showcmd_1', {}) + + " typing "gg" should open the fold + call term_sendkeys(buf, "g") + call VerifyScreenDump(buf, 'Test_statusline_showcmd_2', {}) + + call term_sendkeys(buf, "\<C-V>Gl") + call VerifyScreenDump(buf, 'Test_statusline_showcmd_3', {}) + + call term_sendkeys(buf, "\<Esc>1234") + call VerifyScreenDump(buf, 'Test_statusline_showcmd_4', {}) + + call term_sendkeys(buf, "\<Esc>:set statusline=\<CR>") + call term_sendkeys(buf, ":\<CR>") + call term_sendkeys(buf, "1234") + call VerifyScreenDump(buf, 'Test_statusline_showcmd_5', {}) + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index b3a80072d9..c99a0d456d 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -1,6 +1,8 @@ " Tests for the substitute (:s) command source shared.vim +source check.vim +source screendump.vim func Test_multiline_subst() enew! @@ -444,13 +446,19 @@ func Test_substitute_errors() call assert_fails('s/FOO/bar/', 'E486:') call assert_fails('s/foo/bar/@', 'E488:') - call assert_fails('s/\(/bar/', 'E476:') + call assert_fails('s/\(/bar/', 'E54:') call assert_fails('s afooabara', 'E146:') call assert_fails('s\\a', 'E10:') setl nomodifiable call assert_fails('s/foo/bar/', 'E21:') + call assert_fails("let s=substitute([], 'a', 'A', 'g')", 'E730:') + call assert_fails("let s=substitute('abcda', [], 'A', 'g')", 'E730:') + call assert_fails("let s=substitute('abcda', 'a', [], 'g')", 'E730:') + call assert_fails("let s=substitute('abcda', 'a', 'A', [])", 'E730:') + call assert_fails("let s=substitute('abc', '\\%(', 'A', 'g')", 'E53:') + bwipe! endfunc @@ -486,6 +494,9 @@ func Test_sub_replace_1() call assert_equal("x\<C-M>x", substitute('xXx', 'X', "\r", '')) call assert_equal("YyyY", substitute('Y', 'Y', '\L\uyYy\l\EY', '')) call assert_equal("zZZz", substitute('Z', 'Z', '\U\lZzZ\u\Ez', '')) + " \v or \V after $ + call assert_equal('abxx', substitute('abcd', 'xy$\v|cd$', 'xx', '')) + call assert_equal('abxx', substitute('abcd', 'xy$\V\|cd\$', 'xx', '')) endfunc func Test_sub_replace_2() @@ -637,12 +648,16 @@ endfunc func SubReplacer(text, submatches) return a:text .. a:submatches[0] .. a:text endfunc +func SubReplacerVar(text, ...) + return a:text .. a:1[0] .. a:text +endfunc func SubReplacer20(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, submatches) return a:t3 .. a:submatches[0] .. a:t11 endfunc func Test_substitute_partial() call assert_equal('1foo2foo3', substitute('123', '2', function('SubReplacer', ['foo']), 'g')) + call assert_equal('1foo2foo3', substitute('123', '2', function('SubReplacerVar', ['foo']), 'g')) " 19 arguments plus one is just OK let Replacer = function('SubReplacer20', repeat(['foo'], 19)) @@ -668,6 +683,21 @@ func Test_sub_cmd_9() bw! endfunc +func Test_sub_highlight_zero_match() + CheckRunVimInTerminal + + let lines =<< trim END + call setline(1, ['one', 'two', 'three']) + END + call writefile(lines, 'XscriptSubHighlight', 'D') + let buf = RunVimInTerminal('-S XscriptSubHighlight', #{rows: 8, cols: 60}) + call term_sendkeys(buf, ":%s/^/ /c\<CR>") + call VerifyScreenDump(buf, 'Test_sub_highlight_zer_match_1', {}) + + call term_sendkeys(buf, "\<Esc>") + call StopVimInTerminal(buf) +endfunc + func Test_nocatch_sub_failure_handling() " normal error results in all replacements func Foo() @@ -812,9 +842,9 @@ endfunc func Test_sub_with_no_last_pat() let lines =<< trim [SCRIPT] call assert_fails('~', 'E33:') - call assert_fails('s//abc/g', 'E476:') - call assert_fails('s\/bar', 'E476:') - call assert_fails('s\&bar&', 'E476:') + call assert_fails('s//abc/g', 'E35:') + call assert_fails('s\/bar', 'E35:') + call assert_fails('s\&bar&', 'E33:') call writefile(v:errors, 'Xresult') qall! [SCRIPT] @@ -841,6 +871,40 @@ endfunc func Test_substitute() call assert_equal('a1a2a3a', substitute('123', '\zs', 'a', 'g')) + " Substitute with special keys + call assert_equal("a\<End>c", substitute('abc', "a.c", "a\<End>c", '')) +endfunc + +func Test_substitute_expr() + let g:val = 'XXX' + call assert_equal('XXX', substitute('yyy', 'y*', '\=g:val', '')) + call assert_equal('XXX', substitute('yyy', 'y*', {-> g:val}, '')) + call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', + \ '\=nr2char("0x" . submatch(1))', 'g')) + call assert_equal("-\u1b \uf2-", substitute("-%1b %f2-", '%\(\x\x\)', + \ {-> nr2char("0x" . submatch(1))}, 'g')) + + call assert_equal('231', substitute('123', '\(.\)\(.\)\(.\)', + \ {-> submatch(2) . submatch(3) . submatch(1)}, '')) + + func Recurse() + return substitute('yyy', 'y\(.\)y', {-> submatch(1)}, '') + endfunc + " recursive call works + call assert_equal('-y-x-', substitute('xxx', 'x\(.\)x', {-> '-' . Recurse() . '-' . submatch(1) . '-'}, '')) + + call assert_fails("let s=submatch([])", 'E745:') + call assert_fails("let s=submatch(2, [])", 'E745:') +endfunc + +func Test_invalid_submatch() + " This was causing invalid memory access in Vim-7.4.2232 and older + call assert_fails("call substitute('x', '.', {-> submatch(10)}, '')", 'E935:') + call assert_fails('eval submatch(-1)', 'E935:') + call assert_equal('', submatch(0)) + call assert_equal('', submatch(1)) + call assert_equal([], submatch(0, 1)) + call assert_equal([], submatch(1, 1)) endfunc func Test_submatch_list_concatenate() @@ -849,6 +913,44 @@ func Test_submatch_list_concatenate() call substitute('A1', pat, Rep, '')->assert_equal("[['A1'], ['1']]") endfunc +func Test_substitute_expr_arg() + call assert_equal('123456789-123456789=', substitute('123456789', + \ '\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_equal('123456-123456=789', substitute('123456789', + \ '\(.\)\(.\)\(.\)\(a*\)\(n*\)\(.\)\(.\)\(.\)\(x*\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_equal('123456789-123456789x=', substitute('123456789', + \ '\(.\)\(.\)\(.*\)', + \ {m -> m[0] . '-' . m[1] . m[2] . m[3] . 'x' . m[4] . m[5] . m[6] . m[7] . m[8] . m[9] . '='}, '')) + + call assert_fails("call substitute('xxx', '.', {m -> string(add(m, 'x'))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(insert(m, 'x'))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(extend(m, ['x']))}, '')", 'E742:') + call assert_fails("call substitute('xxx', '.', {m -> string(remove(m, 1))}, '')", 'E742:') +endfunc + +" Test for using a function to supply the substitute string +func Test_substitute_using_func() + func Xfunc() + return '1234' + endfunc + call assert_equal('a1234f', substitute('abcdef', 'b..e', + \ function("Xfunc"), '')) + delfunc Xfunc +endfunc + +" Test for using submatch() with a multiline match +func Test_substitute_multiline_submatch() + new + call setline(1, ['line1', 'line2', 'line3', 'line4']) + %s/^line1\(\_.\+\)line4$/\=submatch(1)/ + call assert_equal(['', 'line2', 'line3', ''], getline(1, '$')) + close! +endfunc + func Test_substitute_skipped_range() new if 0 @@ -974,6 +1076,19 @@ func Test_sub_open_cmdline_win() call delete('Xresult') endfunc +" This was editing a script file from the expression +func Test_sub_edit_scriptfile() + new + norm o0000000000000000000000000000000000000000000000000000 + func EditScript() + silent! scr! Xfile + endfunc + s/\%')/\=EditScript() + + delfunc EditScript + bwipe! +endfunc + " Test for the 2-letter and 3-letter :substitute commands func Test_substitute_short_cmd() new diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim index 923e1cbf50..cf46b4c5bd 100644 --- a/src/nvim/testdir/test_swap.vim +++ b/src/nvim/testdir/test_swap.vim @@ -2,6 +2,7 @@ source check.vim source shared.vim +source term_util.vim func s:swapname() return trim(execute('swapname')) @@ -202,8 +203,8 @@ func Test_swapfile_delete() " This test won't work as root because root can successfully run kill(1, 0) if !IsRoot() " Write the swapfile with a modified PID, now it will be automatically - " deleted. Process one should never be Vim. - let swapfile_bytes[24:27] = 0z01000000 + " deleted. Process 0x3fffffff most likely does not exist. + let swapfile_bytes[24:27] = 0zffffff3f call writefile(swapfile_bytes, swapfile_name) let s:swapname = '' split XswapfileText @@ -374,24 +375,26 @@ func Test_swap_prompt_splitwin() call WaitForAssert({-> assert_match('^1$', term_getline(buf, 20))}) call StopVimInTerminal(buf) - " This caused Vim to crash when typing "q". - " TODO: it does not actually reproduce the crash. - call writefile(['au BufAdd * set virtualedit=all'], 'Xvimrc') - - let buf = RunVimInTerminal('-u Xvimrc Xfile1', {'rows': 20, 'wait_for_ruler': 0}) - call TermWait(buf) - call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort:', term_getline(buf, 20))}) + " This caused Vim to crash when typing "q" at the swap file prompt. + let buf = RunVimInTerminal('-c "au bufadd * let foo_w = wincol()"', {'rows': 18}) + call term_sendkeys(buf, ":e Xfile1\<CR>") + call WaitForAssert({-> assert_match('More', term_getline(buf, 18))}) + call term_sendkeys(buf, " ") + call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort:', term_getline(buf, 18))}) call term_sendkeys(buf, "q") + call TermWait(buf) + " check that Vim is still running + call term_sendkeys(buf, ":echo 'hello'\<CR>") + call WaitForAssert({-> assert_match('^hello', term_getline(buf, 18))}) + call term_sendkeys(buf, ":%bwipe!\<CR>") + call StopVimInTerminal(buf) %bwipe! call delete('Xfile1') - call delete('Xvimrc') endfunc func Test_swap_symlink() - if !has("unix") - return - endif + CheckUnix call writefile(['text'], 'Xtestfile') silent !ln -s -f Xtestfile Xtestlink @@ -418,6 +421,161 @@ func Test_swap_symlink() call delete('Xswapdir', 'rf') endfunc +func s:get_unused_pid(base) + if has('job') + " Execute 'echo' as a temporary job, and return its pid as an unused pid. + if has('win32') + let cmd = 'cmd /c echo' + else + let cmd = 'echo' + endif + let j = job_start(cmd) + while job_status(j) ==# 'run' + sleep 10m + endwhile + if job_status(j) ==# 'dead' + return job_info(j).process + endif + endif + " Must add four for MS-Windows to see it as a different one. + return a:base + 4 +endfunc + +func s:blob_to_pid(b) + return a:b[3] * 16777216 + a:b[2] * 65536 + a:b[1] * 256 + a:b[0] +endfunc + +func s:pid_to_blob(i) + let b = 0z + let b[0] = and(a:i, 0xff) + let b[1] = and(a:i / 256, 0xff) + let b[2] = and(a:i / 65536, 0xff) + let b[3] = and(a:i / 16777216, 0xff) + return b +endfunc + +func Test_swap_auto_delete() + " Create a valid swapfile by editing a file with a special extension. + split Xtest.scr + call setline(1, ['one', 'two', 'three']) + write " file is written, not modified + write " write again to make sure the swapfile is created + " read the swapfile as a Blob + let swapfile_name = swapname('%') + let swapfile_bytes = readfile(swapfile_name, 'B') + + " Forget about the file, recreate the swap file, then edit it again. The + " swap file should be automatically deleted. + bwipe! + " Change the process ID to avoid the "still running" warning. + let swapfile_bytes[24:27] = s:pid_to_blob(s:get_unused_pid( + \ s:blob_to_pid(swapfile_bytes[24:27]))) + call writefile(swapfile_bytes, swapfile_name) + edit Xtest.scr + " will end up using the same swap file after deleting the existing one + call assert_equal(swapfile_name, swapname('%')) + bwipe! + + " create the swap file again, but change the host name so that it won't be + " deleted + autocmd! SwapExists + augroup test_swap_recover_ext + autocmd! + autocmd SwapExists * let v:swapchoice = 'e' + augroup END + + " change the host name + let swapfile_bytes[28 + 40] = swapfile_bytes[28 + 40] + 2 + call writefile(swapfile_bytes, swapfile_name) + edit Xtest.scr + call assert_equal(1, filereadable(swapfile_name)) + " will use another same swap file name + call assert_notequal(swapfile_name, swapname('%')) + bwipe! + + call delete('Xtest.scr') + call delete(swapfile_name) + augroup test_swap_recover_ext + autocmd! + augroup END + augroup! test_swap_recover_ext +endfunc + +" Test for renaming a buffer when the swap file is deleted out-of-band +func Test_missing_swap_file() + CheckUnix + new Xfile2 + call delete(swapname('')) + call assert_fails('file Xfile3', 'E301:') + call assert_equal('Xfile3', bufname()) + call assert_true(bufexists('Xfile2')) + call assert_true(bufexists('Xfile3')) + %bw! +endfunc + +" Test for :preserve command +func Test_preserve() + new Xfile4 + setlocal noswapfile + call assert_fails('preserve', 'E313:') + bw! +endfunc + +" Test for the v:swapchoice variable +func Test_swapchoice() + call writefile(['aaa', 'bbb'], 'Xfile5') + edit Xfile5 + preserve + let swapfname = swapname('') + let b = readblob(swapfname) + bw! + call writefile(b, swapfname) + + autocmd! SwapExists + + " Test for v:swapchoice = 'o' (readonly) + augroup test_swapchoice + autocmd! + autocmd SwapExists * let v:swapchoice = 'o' + augroup END + edit Xfile5 + call assert_true(&readonly) + call assert_equal(['aaa', 'bbb'], getline(1, '$')) + %bw! + call assert_true(filereadable(swapfname)) + + " Test for v:swapchoice = 'a' (abort) + augroup test_swapchoice + autocmd! + autocmd SwapExists * let v:swapchoice = 'a' + augroup END + try + edit Xfile5 + catch /^Vim:Interrupt$/ + endtry + call assert_equal('', @%) + call assert_true(bufexists('Xfile5')) + %bw! + call assert_true(filereadable(swapfname)) + + " Test for v:swapchoice = 'd' (delete) + augroup test_swapchoice + autocmd! + autocmd SwapExists * let v:swapchoice = 'd' + augroup END + edit Xfile5 + call assert_equal('Xfile5', @%) + %bw! + call assert_false(filereadable(swapfname)) + + call delete('Xfile5') + call delete(swapfname) + augroup test_swapchoice + autocmd! + augroup END + augroup! test_swapchoice +endfunc + func Test_no_swap_file() call assert_equal("\nNo swap file", execute('swapname')) endfunc diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 7ba0149971..45230c4208 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -113,6 +113,9 @@ func Test_syntime() let a = execute('syntime report') call assert_equal("\nNo Syntax items defined for this buffer", a) + let a = execute('syntime clear') + call assert_equal("\nNo Syntax items defined for this buffer", a) + view samples/memfile_test.c setfiletype cpp redraw @@ -171,6 +174,10 @@ func Test_syntax_list() let a = execute('syntax list') call assert_equal("\nNo Syntax items defined for this buffer", a) + syntax keyword Type int containedin=g1 skipwhite skipempty skipnl nextgroup=Abc + let exp = "Type xxx containedin=g1 nextgroup=Abc skipnl skipwhite skipempty int" + call assert_equal(exp, split(execute("syntax list"), "\n")[1]) + bd endfunc @@ -188,22 +195,26 @@ func Test_syntax_completion() call assert_equal('"syn sync ccomment clear fromstart linebreaks= linecont lines= match maxlines= minlines= region', @:) " Check that clearing "Aap" avoids it showing up before Boolean. - hi Aap ctermfg=blue + hi @Aap ctermfg=blue call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('^"syn list Aap Boolean Character ', @:) - hi clear Aap + call assert_match('^"syn list @Aap @boolean @character ', @:) + hi clear @Aap call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('^"syn list Boolean Character ', @:) + call assert_match('^"syn list @boolean @character ', @:) call feedkeys(":syn match \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('^"syn match Boolean Character ', @:) + call assert_match('^"syn match @boolean @character ', @:) + + syn cluster Aax contains=Aap + call feedkeys(":syn list @A\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('^"syn list @Aax', @:) endfunc func Test_echohl_completion() call feedkeys(":echohl no\<C-A>\<C-B>\"\<CR>", 'tx') " call assert_equal('"echohl NonText Normal none', @:) - call assert_equal('"echohl NonText Normal NormalFloat NormalNC none', @:) + call assert_equal('"echohl NonText Normal NormalFloat none', @:) endfunc func Test_syntax_arg_skipped() @@ -343,6 +354,18 @@ func Test_syntax_arg_skipped() syn clear endfunc +" Check for an error. Used when multiple errors are thrown and we are checking +" for an earliest error. +func AssertFails(cmd, errcode) + let save_exception = '' + try + exe a:cmd + catch + let save_exception = v:exception + endtry + call assert_match(a:errcode, save_exception) +endfunc + func Test_syntax_invalid_arg() call assert_fails('syntax case asdf', 'E390:') if has('conceal') @@ -350,11 +373,51 @@ func Test_syntax_invalid_arg() endif call assert_fails('syntax spell asdf', 'E390:') call assert_fails('syntax clear @ABCD', 'E391:') - call assert_fails('syntax include @Xxx', 'E397:') - call assert_fails('syntax region X start="{"', 'E399:') + call assert_fails('syntax include random_file', 'E484:') + call assert_fails('syntax include <afile>', 'E495:') call assert_fails('syntax sync x', 'E404:') call assert_fails('syntax keyword Abc a[', 'E789:') call assert_fails('syntax keyword Abc a[bc]d', 'E890:') + call assert_fails('syntax cluster Abc add=A add=', 'E406:') + + " Test for too many \z\( and unmatched \z\( + " Not able to use assert_fails() here because both E50:/E879: and E475: + " messages are emitted. + set regexpengine=1 + call AssertFails("syntax region MyRegion start='\\z\\(' end='\\*/'", 'E52:') + + let cmd = "syntax region MyRegion start='" + let cmd ..= repeat("\\z\\(.\\)", 10) .. "' end='\*/'" + call AssertFails(cmd, 'E50:') + + set regexpengine=2 + call AssertFails("syntax region MyRegion start='\\z\\(' end='\\*/'", 'E54:') + + let cmd = "syntax region MyRegion start='" + let cmd ..= repeat("\\z\\(.\\)", 10) .. "' end='\*/'" + call AssertFails(cmd, 'E879:') + set regexpengine& + + call AssertFails('syntax keyword cMyItem grouphere G1', 'E393:') + call AssertFails('syntax sync match Abc grouphere MyItem "abc"', 'E394:') + call AssertFails('syn keyword Type contains int', 'E395:') + call assert_fails('syntax include @Xxx', 'E397:') + call AssertFails('syntax region X start', 'E398:') + call assert_fails('syntax region X start="{"', 'E399:') + call AssertFails('syntax cluster contains=Abc', 'E400:') + call AssertFails("syntax match Character /'.'", 'E401:') + call AssertFails("syntax match Character /'.'/a", 'E402:') + call assert_fails('syntax sync linecont /\%(/', 'E53:') + call assert_fails('syntax sync linecont /pat', 'E404:') + call assert_fails('syntax sync linecont', 'E404:') + call assert_fails('syntax sync linecont /pat1/ linecont /pat2/', 'E403:') + call assert_fails('syntax sync minlines=a', 'E404:') + call AssertFails('syntax match ABC /x/ contains=', 'E406:') + call AssertFails("syntax match Character contains /'.'/", 'E405:') + call AssertFails('syntax match ccFoo "Foo" nextgroup=ALLBUT,F', 'E407:') + call AssertFails('syntax region Block start="{" contains=F,ALLBUT', 'E408:') + call AssertFails("syntax match Characters contains=a.*x /'.'/", 'E409:') + call assert_fails('syntax match Search /abc/ contains=ALLBUT,/\%(/', 'E53:') endfunc func Test_syn_sync() @@ -382,6 +445,7 @@ func Test_syn_clear() hi clear Foo call assert_equal('Foo', synIDattr(hlID("Foo"), "name")) hi clear Bar + call assert_fails('syntax clear invalid_syngroup', 'E28:') endfunc func Test_invalid_name() @@ -389,7 +453,7 @@ func Test_invalid_name() syn keyword Nop yes call assert_fails("syntax keyword Wr\x17ong bar", 'E669:') syntax keyword @Wrong bar - call assert_match('W18:', execute('1messages')) + call assert_fails("syntax keyword @#Wrong bar", 'E5248:') syn clear hi clear Nop hi clear @Wrong @@ -475,15 +539,16 @@ func Test_conceal() call assert_match('16 ', ScreenLines(2, 7)[0]) call assert_equal([[0, '', 0], [1, '', 1], [1, '', 1], [1, '', 2], [1, '', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + call AssertFails("syntax match Entity '&' conceal cchar=\<Tab>", 'E844:') + syn clear set conceallevel& bw! endfunc func Test_bg_detection() - if has('gui_running') - return - endif + CheckNotGui + " auto-detection of &bg, make sure sure it isn't set anywhere before " this test hi Normal ctermbg=0 @@ -560,15 +625,15 @@ func Test_synstack_synIDtrans() call assert_equal(['cComment', 'cTodo'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")')) call assert_equal(['Comment', 'Todo'], map(synstack(line("."), col(".")), 'synIDattr(synIDtrans(v:val), "name")')) + call assert_fails("let n=synIDtrans([])", 'E745:') + syn clear bw! endfunc " Check highlighting for a small piece of C code with a screen dump. func Test_syntax_c() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal call writefile([ \ '/* comment line at the top */', \ 'int main(int argc, char **argv) { // another comment', @@ -603,6 +668,24 @@ func Test_syntax_c() call delete('Xtest.c') endfun +" Test \z(...) along with \z1 +func Test_syn_zsub() + new + syntax on + call setline(1, 'xxx start foo xxx not end foo xxx end foo xxx') + let l:expected = ' ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ' + + for l:re in [0, 1, 2] + " Example taken from :help :syn-ext-match + syntax region Z start="start \z(\I\i*\)" skip="not end \z1" end="end \z1" + eval AssertHighlightGroups(1, 1, l:expected, 1, 'regexp=' .. l:re) + syntax clear Z + endfor + + set re& + bw! +endfunc + " Using \z() in a region with NFA failing should not crash. func Test_syn_wrong_z_one() new diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index 18692f42c9..6c8373b335 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -141,3 +141,5 @@ func Test_system_with_shell_quote() call delete('Xdir with spaces', 'rf') endtry endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim index e58a412c5a..d9bef09067 100644 --- a/src/nvim/testdir/test_tabline.vim +++ b/src/nvim/testdir/test_tabline.vim @@ -1,6 +1,9 @@ " Test for tabline source shared.vim +source view_util.vim +source check.vim +source screendump.vim func TablineWithCaughtError() let s:func_in_tabline_called = 1 @@ -147,4 +150,58 @@ func Test_tabline_20_format_items_no_overrun() set showtabline& tabline& endfunc +func Test_mouse_click_in_tab() + " This used to crash because TabPageIdxs[] was not initialized + let lines =<< trim END + tabnew + set mouse=a + exe "norm \<LeftMouse>" + END + call writefile(lines, 'Xclickscript') + call RunVim([], [], "-e -s -S Xclickscript -c qa") + + call delete('Xclickscript') +endfunc + +func Test_tabline_showcmd() + CheckScreendump + + let lines =<< trim END + func MyTabLine() + return '%S' + endfunc + + set showtabline=2 + set tabline=%!MyTabLine() + set showcmdloc=tabline + call setline(1, ['a', 'b', 'c']) + set foldopen+=jump + 1,2fold + 3 + END + call writefile(lines, 'XTest_tabline', 'D') + + let buf = RunVimInTerminal('-S XTest_tabline', {'rows': 6}) + + call term_sendkeys(buf, "g") + call VerifyScreenDump(buf, 'Test_tabline_showcmd_1', {}) + + " typing "gg" should open the fold + call term_sendkeys(buf, "g") + call VerifyScreenDump(buf, 'Test_tabline_showcmd_2', {}) + + call term_sendkeys(buf, "\<C-V>Gl") + call VerifyScreenDump(buf, 'Test_tabline_showcmd_3', {}) + + call term_sendkeys(buf, "\<Esc>1234") + call VerifyScreenDump(buf, 'Test_tabline_showcmd_4', {}) + + call term_sendkeys(buf, "\<Esc>:set tabline=\<CR>") + call term_sendkeys(buf, ":\<CR>") + call term_sendkeys(buf, "1234") + call VerifyScreenDump(buf, 'Test_tabline_showcmd_5', {}) + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index 6d468ec9de..b97aa409d8 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -591,9 +591,7 @@ func Test_tabs() endfunc func Test_tabpage_cmdheight() - if !CanRunVimInTerminal() - throw 'Skipped: cannot make screendumps' - endif + CheckRunVimInTerminal call writefile([ \ 'set laststatus=2', \ 'set cmdheight=2', @@ -623,6 +621,17 @@ func Test_tabpage_close_cmdwin() tabonly endfunc +" Pressing <C-PageUp> in insert mode should go to the previous tab page +" and <C-PageDown> should go to the next tab page +func Test_tabpage_Ctrl_Pageup() + tabnew + call feedkeys("i\<C-PageUp>", 'xt') + call assert_equal(1, tabpagenr()) + call feedkeys("i\<C-PageDown>", 'xt') + call assert_equal(2, tabpagenr()) + %bw! +endfunc + " Return the terminal key code for selecting a tab page from the tabline. This " sequence contains the following codes: a CSI (0x9b), KS_TABLINE (0xf0), " KS_FILLER (0x58) and then the tab page number. diff --git a/src/nvim/testdir/test_tagfunc.vim b/src/nvim/testdir/test_tagfunc.vim index ffc1d63b90..cba96d3504 100644 --- a/src/nvim/testdir/test_tagfunc.vim +++ b/src/nvim/testdir/test_tagfunc.vim @@ -1,5 +1,9 @@ " Test 'tagfunc' +source vim9.vim +source check.vim +source screendump.vim + func TagFunc(pat, flag, info) let g:tagfunc_args = [a:pat, a:flag, a:info] let tags = [] @@ -85,7 +89,7 @@ func Test_tagfunc() return v:null endfunc set tags= tfu=NullTagFunc - call assert_fails('tag nothing', 'E426') + call assert_fails('tag nothing', 'E433') delf NullTagFunc bwipe! @@ -117,4 +121,297 @@ func Test_tagfunc_settagstack() delfunc Mytagfunc2 endfunc +" Script local tagfunc callback function +func s:ScriptLocalTagFunc(pat, flags, info) + let g:ScriptLocalFuncArgs = [a:pat, a:flags, a:info] + return v:null +endfunc + +" Test for different ways of setting the 'tagfunc' option +func Test_tagfunc_callback() + func TagFunc1(callnr, pat, flags, info) + let g:TagFunc1Args = [a:callnr, a:pat, a:flags, a:info] + return v:null + endfunc + func TagFunc2(pat, flags, info) + let g:TagFunc2Args = [a:pat, a:flags, a:info] + return v:null + endfunc + + let lines =<< trim END + #" Test for using a function name + LET &tagfunc = 'g:TagFunc2' + new + LET g:TagFunc2Args = [] + call assert_fails('tag a10', 'E433:') + call assert_equal(['a10', '', {}], g:TagFunc2Args) + bw! + + #" Test for using a function() + set tagfunc=function('g:TagFunc1',\ [10]) + new + LET g:TagFunc1Args = [] + call assert_fails('tag a11', 'E433:') + call assert_equal([10, 'a11', '', {}], g:TagFunc1Args) + bw! + + #" Using a funcref variable to set 'tagfunc' + VAR Fn = function('g:TagFunc1', [11]) + LET &tagfunc = Fn + new + LET g:TagFunc1Args = [] + call assert_fails('tag a12', 'E433:') + call assert_equal([11, 'a12', '', {}], g:TagFunc1Args) + bw! + + #" Using a string(funcref_variable) to set 'tagfunc' + LET Fn = function('g:TagFunc1', [12]) + LET &tagfunc = string(Fn) + new + LET g:TagFunc1Args = [] + call assert_fails('tag a12', 'E433:') + call assert_equal([12, 'a12', '', {}], g:TagFunc1Args) + bw! + + #" Test for using a funcref() + set tagfunc=funcref('g:TagFunc1',\ [13]) + new + LET g:TagFunc1Args = [] + call assert_fails('tag a13', 'E433:') + call assert_equal([13, 'a13', '', {}], g:TagFunc1Args) + bw! + + #" Using a funcref variable to set 'tagfunc' + LET Fn = funcref('g:TagFunc1', [14]) + LET &tagfunc = Fn + new + LET g:TagFunc1Args = [] + call assert_fails('tag a14', 'E433:') + call assert_equal([14, 'a14', '', {}], g:TagFunc1Args) + bw! + + #" Using a string(funcref_variable) to set 'tagfunc' + LET Fn = funcref('g:TagFunc1', [15]) + LET &tagfunc = string(Fn) + new + LET g:TagFunc1Args = [] + call assert_fails('tag a14', 'E433:') + call assert_equal([15, 'a14', '', {}], g:TagFunc1Args) + bw! + + #" Test for using a lambda function + VAR optval = "LSTART a, b, c LMIDDLE TagFunc1(16, a, b, c) LEND" + LET optval = substitute(optval, ' ', '\\ ', 'g') + exe "set tagfunc=" .. optval + new + LET g:TagFunc1Args = [] + call assert_fails('tag a17', 'E433:') + call assert_equal([16, 'a17', '', {}], g:TagFunc1Args) + bw! + + #" Set 'tagfunc' to a lambda expression + LET &tagfunc = LSTART a, b, c LMIDDLE TagFunc1(17, a, b, c) LEND + new + LET g:TagFunc1Args = [] + call assert_fails('tag a18', 'E433:') + call assert_equal([17, 'a18', '', {}], g:TagFunc1Args) + bw! + + #" Set 'tagfunc' to a string(lambda expression) + LET &tagfunc = 'LSTART a, b, c LMIDDLE TagFunc1(18, a, b, c) LEND' + new + LET g:TagFunc1Args = [] + call assert_fails('tag a18', 'E433:') + call assert_equal([18, 'a18', '', {}], g:TagFunc1Args) + bw! + + #" Set 'tagfunc' to a variable with a lambda expression + VAR Lambda = LSTART a, b, c LMIDDLE TagFunc1(19, a, b, c) LEND + LET &tagfunc = Lambda + new + LET g:TagFunc1Args = [] + call assert_fails("tag a19", "E433:") + call assert_equal([19, 'a19', '', {}], g:TagFunc1Args) + bw! + + #" Set 'tagfunc' to a string(variable with a lambda expression) + LET Lambda = LSTART a, b, c LMIDDLE TagFunc1(20, a, b, c) LEND + LET &tagfunc = string(Lambda) + new + LET g:TagFunc1Args = [] + call assert_fails("tag a19", "E433:") + call assert_equal([20, 'a19', '', {}], g:TagFunc1Args) + bw! + + #" Test for using a lambda function with incorrect return value + LET Lambda = LSTART a, b, c LMIDDLE strlen(a) LEND + LET &tagfunc = string(Lambda) + new + call assert_fails("tag a20", "E987:") + bw! + + #" Test for clearing the 'tagfunc' option + set tagfunc='' + set tagfunc& + call assert_fails("set tagfunc=function('abc')", "E700:") + call assert_fails("set tagfunc=funcref('abc')", "E700:") + + #" set 'tagfunc' to a non-existing function + LET &tagfunc = function('g:TagFunc2', [21]) + LET g:TagFunc2Args = [] + call assert_fails("set tagfunc=function('NonExistingFunc')", 'E700:') + call assert_fails("LET &tagfunc = function('NonExistingFunc')", 'E700:') + call assert_fails("tag axb123", 'E426:') + call assert_equal([], g:TagFunc2Args) + bw! + END + call CheckLegacyAndVim9Success(lines) + + " Test for using a script-local function name + func s:TagFunc3(pat, flags, info) + let g:TagFunc3Args = [a:pat, a:flags, a:info] + return v:null + endfunc + set tagfunc=s:TagFunc3 + new + let g:TagFunc3Args = [] + call assert_fails('tag a21', 'E433:') + call assert_equal(['a21', '', {}], g:TagFunc3Args) + bw! + let &tagfunc = 's:TagFunc3' + new + let g:TagFunc3Args = [] + call assert_fails('tag a22', 'E433:') + call assert_equal(['a22', '', {}], g:TagFunc3Args) + bw! + delfunc s:TagFunc3 + + " invalid return value + let &tagfunc = "{a -> 'abc'}" + call assert_fails("echo taglist('a')", "E987:") + + " Using Vim9 lambda expression in legacy context should fail + " set tagfunc=(a,\ b,\ c)\ =>\ g:TagFunc1(21,\ a,\ b,\ c) + new + let g:TagFunc1Args = [] + " call assert_fails("tag a17", "E117:") + call assert_equal([], g:TagFunc1Args) + bw! + + " Test for using a script local function + set tagfunc=<SID>ScriptLocalTagFunc + new + let g:ScriptLocalFuncArgs = [] + call assert_fails('tag a15', 'E433:') + call assert_equal(['a15', '', {}], g:ScriptLocalFuncArgs) + bw! + + " Test for using a script local funcref variable + let Fn = function("s:ScriptLocalTagFunc") + let &tagfunc= Fn + new + let g:ScriptLocalFuncArgs = [] + call assert_fails('tag a16', 'E433:') + call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs) + bw! + + " Test for using a string(script local funcref variable) + let Fn = function("s:ScriptLocalTagFunc") + let &tagfunc= string(Fn) + new + let g:ScriptLocalFuncArgs = [] + call assert_fails('tag a16', 'E433:') + call assert_equal(['a16', '', {}], g:ScriptLocalFuncArgs) + bw! + + " set 'tagfunc' to a partial with dict. This used to cause a crash. + func SetTagFunc() + let params = {'tagfn': function('g:DictTagFunc')} + let &tagfunc = params.tagfn + endfunc + func g:DictTagFunc(_) dict + endfunc + call SetTagFunc() + new + call SetTagFunc() + bw + call test_garbagecollect_now() + new + set tagfunc= + wincmd w + set tagfunc= + :%bw! + delfunc g:DictTagFunc + delfunc SetTagFunc + + " Vim9 tests + let lines =<< trim END + vim9script + + def Vim9tagFunc(callnr: number, pat: string, flags: string, info: dict<any>): any + g:Vim9tagFuncArgs = [callnr, pat, flags, info] + return null + enddef + + # Test for using a def function with completefunc + set tagfunc=function('Vim9tagFunc',\ [60]) + new + g:Vim9tagFuncArgs = [] + assert_fails('tag a10', 'E433:') + assert_equal([60, 'a10', '', {}], g:Vim9tagFuncArgs) + + # Test for using a global function name + &tagfunc = g:TagFunc2 + new + g:TagFunc2Args = [] + assert_fails('tag a11', 'E433:') + assert_equal(['a11', '', {}], g:TagFunc2Args) + bw! + + # Test for using a script-local function name + def s:LocalTagFunc(pat: string, flags: string, info: dict<any> ): any + g:LocalTagFuncArgs = [pat, flags, info] + return null + enddef + &tagfunc = s:LocalTagFunc + new + g:LocalTagFuncArgs = [] + assert_fails('tag a12', 'E433:') + assert_equal(['a12', '', {}], g:LocalTagFuncArgs) + bw! + END + call CheckScriptSuccess(lines) + + " cleanup + delfunc TagFunc1 + delfunc TagFunc2 + set tagfunc& + %bw! +endfunc + +func Test_tagfunc_wipes_buffer() + func g:Tag0unc0(t,f,o) + bwipe + endfunc + set tagfunc=g:Tag0unc0 + new + cal assert_fails('tag 0', 'E987:') + + delfunc g:Tag0unc0 + set tagfunc= +endfunc + +func Test_tagfunc_closes_window() + split any + func MytagfuncClose(pat, flags, info) + close + return [{'name' : 'mytag', 'filename' : 'Xtest', 'cmd' : '1'}] + endfunc + set tagfunc=MytagfuncClose + call assert_fails('tag xyz', 'E1299:') + + set tagfunc= +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 04c0218f74..be60a3535c 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -8,7 +8,7 @@ func Test_ptag_with_notagstack() CheckFeature quickfix set notagstack - call assert_fails('ptag does_not_exist_tag_name', 'E426') + call assert_fails('ptag does_not_exist_tag_name', 'E433') set tagstack&vim endfunc @@ -184,6 +184,10 @@ function Test_keyword_jump() call search("start") exe "normal! 5\<C-W>\<C-I>" call assert_equal(" start OK if found this line", getline('.')) + + " invalid tag search pattern + call assert_fails('tag /\%(/', 'E426:') + enew! | only call delete('Xtestfile') call delete('Xinclude') @@ -227,15 +231,13 @@ func Test_tag_symbolic() endfunc " Tests for tag search with !_TAG_FILE_ENCODING. -" Depends on the test83-tags2 and test83-tags3 files. func Test_tag_file_encoding() - throw 'skipped: Nvim removed test83-tags2, test83-tags3' if has('vms') - return + throw 'Skipped: does not work on VMS' endif if !has('iconv') || iconv("\x82\x60", "cp932", "utf-8") != "\uff21" - return + throw 'Skipped: iconv does not work' endif let save_enc = &encoding @@ -260,18 +262,31 @@ func Test_tag_file_encoding() " case2: new - set tags=test83-tags2 + let content = ['!_TAG_FILE_ENCODING cp932 //', + \ "\x82`\x82a\x82b Xtags2.txt /\x82`\x82a\x82b"] + call writefile(content, 'Xtags') + set tags=Xtags tag /.BC call assert_equal('Xtags2.txt', expand('%:t')) call assert_equal('ABC', getline('.')) + call delete('Xtags') close " case3: new - set tags=test83-tags3 + let contents = [ + \ "!_TAG_FILE_SORTED 1 //", + \ "!_TAG_FILE_ENCODING cp932 //"] + for i in range(1, 100) + call add(contents, 'abc' .. i + \ .. " Xtags3.txt /\x82`\x82a\x82b") + endfor + call writefile(contents, 'Xtags') + set tags=Xtags tag abc50 call assert_equal('Xtags3.txt', expand('%:t')) call assert_equal('ABC', getline('.')) + call delete('Xtags') close set tags& @@ -282,6 +297,7 @@ func Test_tag_file_encoding() call delete('Xtags1') endfunc +" Test for emacs-style tags file (TAGS) func Test_tagjump_etags() if !has('emacs_tags') return @@ -322,6 +338,7 @@ func Test_tagjump_etags() \ ], 'Xtags2') tag main call assert_equal(2, line('.')) + call assert_fails('tag bar', 'E426:') " corrupted tag line call writefile([ @@ -345,7 +362,28 @@ func Test_tagjump_etags() \ "Xmain.c,64", \ ";;;;\x7f1,0", \ ], 'Xtags') - call assert_fails('tag foo', 'E426:') + call assert_fails('tag foo', 'E431:') + + " end of file after a CTRL-L line + call writefile([ + \ "\x0c", + \ "Xmain.c,64", + \ "void foo() {}\x7ffoo\x011,0", + \ "\x0c", + \ ], 'Xtags') + call assert_fails('tag main', 'E426:') + + " error in an included tags file + call writefile([ + \ "\x0c", + \ "Xtags2,include" + \ ], 'Xtags') + call writefile([ + \ "\x0c", + \ "Xmain.c,64", + \ "void foo() {}", + \ ], 'Xtags2') + call assert_fails('tag foo', 'E431:') call delete('Xtags') call delete('Xtags2') @@ -371,6 +409,7 @@ func Test_getsettagstack() call assert_fails("call settagstack(1, {'items' : 10})", 'E714') call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E928') call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962') + call assert_equal(-1, settagstack(0, v:_null_dict)) set tags=Xtags call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", @@ -766,11 +805,11 @@ endfunc " Test for an unsorted tags file func Test_tag_sort() - call writefile([ + let l = [ \ "first\tXfoo\t1", \ "ten\tXfoo\t3", - \ "six\tXfoo\t2"], - \ 'Xtags') + \ "six\tXfoo\t2"] + call writefile(l, 'Xtags') set tags=Xtags let code =<< trim [CODE] int first() {} @@ -781,7 +820,14 @@ func Test_tag_sort() call assert_fails('tag first', 'E432:') + " When multiple tag files are not sorted, then message should be displayed + " multiple times + call writefile(l, 'Xtags2') + set tags=Xtags,Xtags2 + call assert_fails('tag first', ['E432:', 'E432:']) + call delete('Xtags') + call delete('Xtags2') call delete('Xfoo') set tags& %bwipe @@ -1099,7 +1145,7 @@ func Test_tselect_listing() call writefile([ \ "!_TAG_FILE_ENCODING\tutf-8\t//", \ "first\tXfoo\t1" .. ';"' .. "\tv\ttyperef:typename:int\tfile:", - \ "first\tXfoo\t2" .. ';"' .. "\tv\ttyperef:typename:char\tfile:"], + \ "first\tXfoo\t2" .. ';"' .. "\tkind:v\ttyperef:typename:char\tfile:"], \ 'Xtags') set tags=Xtags @@ -1422,4 +1468,148 @@ func Test_tag_length() set tags& taglength& endfunc +" Tests for errors in a tags file +func Test_tagfile_errors() + set tags=Xtags + + " missing search pattern or line number for a tag + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo\tXfile\t"], 'Xtags', 'b') + call writefile(['foo'], 'Xfile') + + enew + tag foo + call assert_equal('', @%) + let caught_431 = v:false + try + eval taglist('.*') + catch /:E431:/ + let caught_431 = v:true + endtry + call assert_equal(v:true, caught_431) + + " tag name and file name are not separated by a tab + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo Xfile 1"], 'Xtags') + call assert_fails('tag foo', 'E431:') + + " file name and search pattern are not separated by a tab + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo\tXfile 1;"], 'Xtags') + call assert_fails('tag foo', 'E431:') + + call delete('Xtags') + call delete('Xfile') + set tags& +endfunc + +" When :stag fails to open the file, should close the new window +func Test_stag_close_window_on_error() + new | only + set tags=Xtags + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo\tXfile\t1"], 'Xtags') + call writefile(['foo'], 'Xfile') + call writefile([], '.Xfile.swp') + " Remove the catch-all that runtest.vim adds + au! SwapExists + augroup StagTest + au! + autocmd SwapExists Xfile let v:swapchoice='q' + augroup END + + stag foo + call assert_equal(1, winnr('$')) + call assert_equal('', @%) + + augroup StagTest + au! + augroup END + call delete('Xfile') + call delete('.Xfile.swp') + set tags& +endfunc + +" Test for 'tagbsearch' (binary search) +func Test_tagbsearch() + " If a tags file header says the tags are sorted, but the tags are actually + " unsorted, then binary search should fail and linear search should work. + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/", + \ "third\tXfoo\t3", + \ "second\tXfoo\t2", + \ "first\tXfoo\t1"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int second() {} + int third() {} + [CODE] + call writefile(code, 'Xfoo') + + enew + set tagbsearch + call assert_fails('tag first', 'E426:') + call assert_equal('', bufname()) + call assert_fails('tag second', 'E426:') + call assert_equal('', bufname()) + tag third + call assert_equal('Xfoo', bufname()) + call assert_equal(3, line('.')) + %bw! + + set notagbsearch + tag first + call assert_equal('Xfoo', bufname()) + call assert_equal(1, line('.')) + enew + tag second + call assert_equal('Xfoo', bufname()) + call assert_equal(2, line('.')) + enew + tag third + call assert_equal('Xfoo', bufname()) + call assert_equal(3, line('.')) + %bw! + + " If a tags file header says the tags are unsorted, but the tags are + " actually sorted, then binary search should work. + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "!_TAG_FILE_SORTED\t0\t/0=unsorted, 1=sorted, 2=foldcase/", + \ "first\tXfoo\t1", + \ "second\tXfoo\t2", + \ "third\tXfoo\t3"], + \ 'Xtags') + + set tagbsearch + tag first + call assert_equal('Xfoo', bufname()) + call assert_equal(1, line('.')) + enew + tag second + call assert_equal('Xfoo', bufname()) + call assert_equal(2, line('.')) + enew + tag third + call assert_equal('Xfoo', bufname()) + call assert_equal(3, line('.')) + %bw! + + " Binary search fails on EOF + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "!_TAG_FILE_SORTED\t1\t/0=unsorted, 1=sorted, 2=foldcase/", + \ "bar\tXfoo\t1", + \ "foo\tXfoo\t2"], + \ 'Xtags') + call assert_fails('tag bbb', 'E426:') + + call delete('Xtags') + call delete('Xfoo') + set tags& tagbsearch& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim index be46773256..0387ef2bd8 100644 --- a/src/nvim/testdir/test_taglist.vim +++ b/src/nvim/testdir/test_taglist.vim @@ -36,6 +36,14 @@ func Test_taglist() call assert_equal('d', cmd[0]['kind']) call assert_equal('call cursor(3, 4)', cmd[0]['cmd']) + " Use characters with value > 127 in the tag extra field. + call writefile([ + \ "vFoo\tXfoo\t4" .. ';"' .. "\ttypename:int\ta£££\tv", + \ ], 'Xtags') + call assert_equal('v', taglist('vFoo')[0].kind) + + call assert_fails("let l=taglist([])", 'E730:') + call delete('Xtags') set tags& bwipe @@ -82,13 +90,11 @@ func Test_taglist_ctags_etags() endfunc func Test_tags_too_long() - call assert_fails('tag ' . repeat('x', 1020), 'E426') + call assert_fails('tag ' . repeat('x', 1020), ['E433', 'E426']) tags endfunc func Test_tagfiles() - " Nvim: different default for 'tags'. - set tags=./tags,tags call assert_equal([], tagfiles()) call writefile(["FFoo\tXfoo\t1"], 'Xtags1') @@ -221,6 +227,11 @@ func Test_format_error() endtry call assert_true(caught_exception) + " no field after the filename for a tag + call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", + \ "foo\tXfile"], 'Xtags') + call assert_fails("echo taglist('foo')", 'E431:') + set tags& call delete('Xtags') endfunc @@ -241,4 +252,30 @@ func Test_tag_complete_wildoptions() set tags& endfunc +func Test_tag_complete_with_overlong_line() + let tagslines =<< trim END + !_TAG_FILE_FORMAT 2 // + !_TAG_FILE_SORTED 1 // + !_TAG_FILE_ENCODING utf-8 // + inboundGSV a 1;" r + inboundGovernor a 2;" kind:⊢ type:forall (muxMode :: MuxMode) socket peerAddr versionNumber m a b. (MonadAsync m, MonadCatch m, MonadEvaluate m, MonadThrow m, MonadThrow (STM m), MonadTime m, MonadTimer m, MonadMask m, Ord peerAddr, HasResponder muxMode ~ True) => Tracer m (RemoteTransitionTrace peerAddr) -> Tracer m (InboundGovernorTrace peerAddr) -> ServerControlChannel muxMode peerAddr ByteString m a b -> DiffTime -> MuxConnectionManager muxMode socket peerAddr versionNumber ByteString m a b -> StrictTVar m InboundGovernorObservableState -> m Void + inboundGovernorCounters a 3;" kind:⊢ type:InboundGovernorState muxMode peerAddr m a b -> InboundGovernorCounters + END + call writefile(tagslines, 'Xtags') + set tags=Xtags + + " try with binary search + set tagbsearch + call feedkeys(":tag inbou\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"tag inboundGSV inboundGovernor inboundGovernorCounters', @:) + " try with linear search + set notagbsearch + call feedkeys(":tag inbou\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"tag inboundGSV inboundGovernor inboundGovernorCounters', @:) + set tagbsearch& + + call delete('Xtags') + set tags& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_termcodes.vim b/src/nvim/testdir/test_termcodes.vim index eda485c512..99bc2d1d37 100644 --- a/src/nvim/testdir/test_termcodes.vim +++ b/src/nvim/testdir/test_termcodes.vim @@ -1,4 +1,30 @@ +" Test for translation of special key codes (<xF1>, <xF2>, etc.) +func Test_Keycode_Translation() + let keycodes = [ + \ ["<xUp>", "<Up>"], + \ ["<xDown>", "<Down>"], + \ ["<xLeft>", "<Left>"], + \ ["<xRight>", "<Right>"], + \ ["<xHome>", "<Home>"], + \ ["<xEnd>", "<End>"], + \ ["<zHome>", "<Home>"], + \ ["<zEnd>", "<End>"], + \ ["<xF1>", "<F1>"], + \ ["<xF2>", "<F2>"], + \ ["<xF3>", "<F3>"], + \ ["<xF4>", "<F4>"], + \ ["<S-xF1>", "<S-F1>"], + \ ["<S-xF2>", "<S-F2>"], + \ ["<S-xF3>", "<S-F3>"], + \ ["<S-xF4>", "<S-F4>"]] + for [k1, k2] in keycodes + exe "nnoremap " .. k1 .. " 2wx" + call assert_true(maparg(k1, 'n', 0, 1).lhs == k2) + exe "nunmap " .. k1 + endfor +endfunc + " Test for terminal keycodes that doesn't have termcap entries func Test_special_term_keycodes() new diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index 0fc56083aa..7a13de12f4 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -962,78 +962,6 @@ func Test_tw_2_fo_tm_noai() bwipe! endfunc -func Test_tw_2_fo_cqm_com() - new - let t =<< trim END - { - X - Xa - XaY - XY - XYZ - X Y - X YZ - XX - XXa - XXY - } - END - call setline(1, t) - call cursor(2, 1) - - set tw=2 fo=cqm comments=n:X - exe "normal gqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgqjgqgq" - let t =<< trim END - X - Xa - XaY - XY - XYZ - X Y - X YZ - XX - XXa - XXY - END - exe "normal o\n" . join(t, "\n") - - let expected =<< trim END - { - X - Xa - Xa - XY - XY - XY - XZ - X Y - X Y - X Z - XX - XXa - XXY - - X - Xa - Xa - XY - XY - XY - XZ - X Y - X Y - X Z - XX - XXa - XXY - } - END - call assert_equal(expected, getline(1, '$')) - - set tw& fo& comments& - bwipe! -endfunc - func Test_tw_2_fo_tm_replace() new let t =<< trim END @@ -1116,6 +1044,22 @@ func Test_empty_matchpairs() bwipe! endfunc +func Test_mps_error() + let encoding_save = &encoding + + " for e in ['utf-8', 'latin1'] + for e in ['utf-8'] + exe 'set encoding=' .. e + + call assert_fails('set mps=<:', 'E474:', e) + call assert_fails('set mps=:>', 'E474:', e) + call assert_fails('set mps=<>', 'E474:', e) + call assert_fails('set mps=<:>_', 'E474:', e) + endfor + + let &encoding = encoding_save +endfunc + " Test for ra on multi-byte characters func Test_ra_multibyte() new @@ -1161,140 +1105,6 @@ func Test_whichwrap_multi_byte() bwipe! endfunc -" Test for automatically adding comment leaders in insert mode -func Test_threepiece_comment() - new - setlocal expandtab - call setline(1, ["\t/*"]) - setlocal formatoptions=croql - call cursor(1, 3) - call feedkeys("A\<cr>\<cr>/", 'tnix') - call assert_equal(["\t/*", " *", " */"], getline(1, '$')) - - " If a comment ends in a single line, then don't add it in the next line - %d - call setline(1, '/* line1 */') - call feedkeys("A\<CR>next line", 'xt') - call assert_equal(['/* line1 */', 'next line'], getline(1, '$')) - - %d - " Copy the trailing indentation from the leader comment to a new line - setlocal autoindent noexpandtab - call feedkeys("a\t/*\tone\ntwo\n/", 'xt') - call assert_equal(["\t/*\tone", "\t *\ttwo", "\t */"], getline(1, '$')) - close! -endfunc - -" Test for the 'f' flag in 'comments' (only the first line has the comment -" string) -func Test_firstline_comment() - new - setlocal comments=f:- fo+=ro - exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" - call assert_equal(['A', '- B', ' C', ' D'], getline(1, '$')) - %d - setlocal comments=:- - exe "normal i- B\nD\<C-C>ggoC\<C-C>ggOA\<C-C>" - call assert_equal(['- A', '- B', '- C', '- D'], getline(1, '$')) - %bw! -endfunc - -" Test for the 'r' flag in 'comments' (right align comment) -func Test_comment_rightalign() - new - setlocal comments=sr:/***,m:**,ex-2:******/ fo+=ro - exe "normal i=\<C-C>o\t /***\nD\n/" - exe "normal 2GOA\<C-C>joB\<C-C>jOC\<C-C>joE\<C-C>GOF\<C-C>joG" - let expected =<< trim END - = - A - /*** - ** B - ** C - ** D - ** E - ** F - ******/ - G - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - -" Test for the 'b' flag in 'comments' -func Test_comment_blank() - new - setlocal comments=b:* fo+=ro - exe "normal i* E\nF\n\<BS>G\nH\<C-C>ggOC\<C-C>O\<BS>B\<C-C>OA\<C-C>2joD" - let expected =<< trim END - A - *B - * C - * D - * E - * F - *G - H - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - -" Test for the 'n' flag in comments -func Test_comment_nested() - new - setlocal comments=n:> fo+=ro - exe "normal i> B\nD\<C-C>ggOA\<C-C>joC\<C-C>Go\<BS>>>> F\nH" - exe "normal 5GOE\<C-C>6GoG" - let expected =<< trim END - > A - > B - > C - > D - >>>> E - >>>> F - >>>> G - >>>> H - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - -" Test for a space character in 'comments' setting -func Test_comment_space() - new - setlocal comments=b:\ > fo+=ro - exe "normal i> B\nD\<C-C>ggOA\<C-C>joC" - exe "normal Go > F\nH\<C-C>kOE\<C-C>joG" - let expected =<< trim END - A - > B - C - D - > E - > F - > G - > H - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - -" Test for the 'O' flag in 'comments' -func Test_comment_O() - new - setlocal comments=Ob:* fo+=ro - exe "normal i* B\nD\<C-C>kOA\<C-C>joC" - let expected =<< trim END - A - * B - * C - * D - END - call assert_equal(expected, getline(1, '$')) - %bw! -endfunc - " Test for 'a' and 'w' flags in 'formatoptions' func Test_fo_a_w() new @@ -1302,6 +1112,20 @@ func Test_fo_a_w() call feedkeys("iabc abc a abc\<Esc>k0weade", 'xt') call assert_equal(['abc abcde ', 'a abc'], getline(1, '$')) + " when a line ends with space, it is not broken up. + %d + call feedkeys("ione two to ", 'xt') + call assert_equal('one two to ', getline(1)) + + " when a line ends with spaces and backspace is used in the next line, the + " last space in the previous line should be removed. + %d + set backspace=indent,eol,start + call setline(1, ['one ', 'two']) + exe "normal 2Gi\<BS>" + call assert_equal(['one two'], getline(1, '$')) + set backspace& + " Test for 'a', 'w' and '1' options. setlocal textwidth=0 setlocal fo=1aw @@ -1334,25 +1158,6 @@ func Test_fo_a_w() %bw! endfunc -" Test for 'j' flag in 'formatoptions' -func Test_fo_j() - new - setlocal fo+=j comments=:// - call setline(1, ['i++; // comment1', ' // comment2']) - normal J - call assert_equal('i++; // comment1 comment2', getline(1)) - setlocal fo-=j - call setline(1, ['i++; // comment1', ' // comment2']) - normal J - call assert_equal('i++; // comment1 // comment2', getline(1)) - " Test with nested comments - setlocal fo+=j comments=n:>,n:) - call setline(1, ['i++; > ) > ) comment1', ' > ) comment2']) - normal J - call assert_equal('i++; > ) > ) comment1 comment2', getline(1)) - %bw! -endfunc - " Test for formatting lines using gq in visual mode func Test_visual_gq_format() new @@ -1487,53 +1292,6 @@ func Test_fo_2() close! endfunc -" Test for formatting lines where only the first line has a comment. -func Test_fo_gq_with_firstline_comment() - new - setlocal formatoptions=tcq - call setline(1, ['- one two', 'three']) - normal gggqG - call assert_equal(['- one two three'], getline(1, '$')) - - %d - call setline(1, ['- one', '- two']) - normal gggqG - call assert_equal(['- one', '- two'], getline(1, '$')) - close! -endfunc - -" Test for trying to join a comment line with a non-comment line -func Test_join_comments() - new - call setline(1, ['one', '/* two */', 'three']) - normal gggqG - call assert_equal(['one', '/* two */', 'three'], getline(1, '$')) - close! -endfunc - -" Test for using 'a' in 'formatoptions' with comments -func Test_autoformat_comments() - new - setlocal formatoptions+=a - call feedkeys("a- one\n- two\n", 'xt') - call assert_equal(['- one', '- two', ''], getline(1, '$')) - - %d - call feedkeys("a\none\n", 'xt') - call assert_equal(['', 'one', ''], getline(1, '$')) - - setlocal formatoptions+=aw - %d - call feedkeys("aone \ntwo\n", 'xt') - call assert_equal(['one two', ''], getline(1, '$')) - - %d - call feedkeys("aone\ntwo\n", 'xt') - call assert_equal(['one', 'two', ''], getline(1, '$')) - - close! -endfunc - " This was leaving the cursor after the end of a line. Complicated way to " have the problem show up with valgrind. func Test_correct_cursor_position() diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 6adf503f14..f94ee6c9f3 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -3,6 +3,7 @@ source check.vim CheckFeature timers +source screendump.vim source shared.vim source term_util.vim source load.vim @@ -46,9 +47,6 @@ endfunc func Test_timer_repeat_many() let g:val = 0 let timer = timer_start(50, 'MyHandler', {'repeat': -1}) - if has('mac') - sleep 200m - endif sleep 200m call timer_stop(timer) call assert_inrange((has('mac') ? 1 : 2), LoadAdjust(5), g:val) @@ -266,8 +264,9 @@ endfunc func Test_timer_peek_and_get_char() if !has('unix') && !has('gui_running') - return + throw 'Skipped: cannot feed low-level input' endif + call timer_start(0, 'FeedAndPeek') let intr = timer_start(100, 'Interrupt') let c = getchar() @@ -277,9 +276,9 @@ endfunc func Test_timer_getchar_zero() if has('win32') && !has('gui_running') - " Console: no low-level input - return + throw 'Skipped: cannot feed low-level input' endif + CheckFunction reltimefloat " Measure the elapsed time to avoid a hang when it fails. let start = reltime() @@ -305,9 +304,7 @@ func Test_timer_ex_mode() endfunc func Test_timer_restore_count() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run Vim in a terminal window' - endif + CheckRunVimInTerminal " Check that v:count is saved and restored, not changed by a timer. call writefile([ \ 'nnoremap <expr><silent> L v:count ? v:count . "l" : "l"', @@ -349,11 +346,13 @@ func Test_nocatch_timer_garbage_collect() let a = {'foo', 'bar'} endfunc func FeedChar(id) - call feedkeys('x', 't') + call feedkeys(":\<CR>", 't') endfunc call timer_start(300, 'FeedChar') call timer_start(100, 'CauseAnError') - let x = getchar() + let x = getchar() " wait for error in timer + let x = getchar(0) " read any remaining chars + let x = getchar(0) set ut& call test_override('no_wait_return', 1) @@ -409,6 +408,30 @@ func Test_timer_invalid_callback() call assert_fails('call timer_start(0, "0")', 'E921') endfunc +func Test_timer_changing_function_list() + CheckRunVimInTerminal + + " Create a large number of functions. Should get the "more" prompt. + " The typing "G" triggers the timer, which changes the function table. + let lines =<< trim END + for func in map(range(1,99), "'Func' .. v:val") + exe "func " .. func .. "()" + endfunc + endfor + au CmdlineLeave : call timer_start(0, {-> 0}) + END + call writefile(lines, 'XTest_timerchange') + let buf = RunVimInTerminal('-S XTest_timerchange', #{rows: 10}) + call term_sendkeys(buf, ":fu\<CR>") + call WaitForAssert({-> assert_match('-- More --', term_getline(buf, 10))}) + call term_sendkeys(buf, "G") + call WaitForAssert({-> assert_match('E454', term_getline(buf, 9))}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('XTest_timerchange') +endfunc + func Test_timer_using_win_execute_undo_sync() let bufnr1 = bufnr() new diff --git a/src/nvim/testdir/test_trycatch.vim b/src/nvim/testdir/test_trycatch.vim index 646594e482..ef20e03126 100644 --- a/src/nvim/testdir/test_trycatch.vim +++ b/src/nvim/testdir/test_trycatch.vim @@ -3,6 +3,7 @@ source check.vim source shared.vim +source vim9.vim "------------------------------------------------------------------------------- " Test environment {{{1 @@ -2000,15 +2001,34 @@ endfunc func Test_try_catch_errors() call assert_fails('throw |', 'E471:') call assert_fails("throw \n ", 'E471:') - call assert_fails('catch abc', 'E603:') + call assert_fails('catch abc', 'E654:') call assert_fails('try | let i = 1| finally | catch | endtry', 'E604:') call assert_fails('finally', 'E606:') call assert_fails('try | finally | finally | endtry', 'E607:') - " v8.2.3486 has been ported, but v8.2.1183 hasn't, so E170 appears here. - " call assert_fails('try | for i in range(5) | endif | endtry', 'E580:') - call assert_fails('try | for i in range(5) | endif | endtry', 'E170:') + call assert_fails('try | for i in range(5) | endif | endtry', 'E580:') call assert_fails('try | while v:true | endtry', 'E170:') call assert_fails('try | if v:true | endtry', 'E171:') + + " this was using a negative index in cstack[] + let lines =<< trim END + try + for + if + endwhile + if + finally + END + call CheckScriptFailure(lines, 'E690:') + + let lines =<< trim END + try + for + if + endwhile + if + endtry + END + call CheckScriptFailure(lines, 'E690:') endfunc " Test for verbose messages with :try :catch, and :finally {{{1 @@ -2027,16 +2047,179 @@ func Test_try_catch_verbose() endtry redir END let expected = [ - \ 'Exception thrown: Vim(echo):E121: Undefined variable: i', - \ '', - \ 'Exception caught: Vim(echo):E121: Undefined variable: i', - \ '', - \ 'Exception finished: Vim(echo):E121: Undefined variable: i' - \ ] + \ 'Exception thrown: Vim(echo):E121: Undefined variable: i', '', + \ 'Exception caught: Vim(echo):E121: Undefined variable: i', '', + \ 'Exception finished: Vim(echo):E121: Undefined variable: i'] + call assert_equal(expected, split(msg, "\n")) + + " Test for verbose messages displayed when an exception is discarded + redir => msg + try + try + throw 'abc' + finally + throw 'xyz' + endtry + catch + endtry + redir END + let expected = [ + \ 'Exception thrown: abc', '', + \ 'Exception made pending: abc', '', + \ 'Exception thrown: xyz', '', + \ 'Exception discarded: abc', '', + \ 'Exception caught: xyz', '', + \ 'Exception finished: xyz'] + call assert_equal(expected, split(msg, "\n")) + + " Test for messages displayed when :throw is resumed after :finally + redir => msg + try + try + throw 'abc' + finally + endtry + catch + endtry + redir END + let expected = [ + \ 'Exception thrown: abc', '', + \ 'Exception made pending: abc', '', + \ 'Exception resumed: abc', '', + \ 'Exception caught: abc', '', + \ 'Exception finished: abc'] + call assert_equal(expected, split(msg, "\n")) + + " Test for messages displayed when :break is resumed after :finally + redir => msg + for i in range(1) + try + break + finally + endtry + endfor + redir END + let expected = [':break made pending', '', ':break resumed'] call assert_equal(expected, split(msg, "\n")) + + " Test for messages displayed when :continue is resumed after :finally + redir => msg + for i in range(1) + try + continue + finally + endtry + endfor + redir END + let expected = [':continue made pending', '', ':continue resumed'] + call assert_equal(expected, split(msg, "\n")) + + " Test for messages displayed when :return is resumed after :finally + func Xtest() + try + return 'vim' + finally + endtry + endfunc + redir => msg + call Xtest() + redir END + let expected = [ + \ 'calling Xtest()', '', + \ ':return vim made pending', '', + \ ':return vim resumed', '', + \ 'Xtest returning ''vim''', '', + \ 'continuing in Test_try_catch_verbose'] + call assert_equal(expected, split(msg, "\n")) + delfunc Xtest + + " Test for messages displayed when :finish is resumed after :finally + call writefile(['try', 'finish', 'finally', 'endtry'], 'Xscript') + redir => msg + source Xscript + redir END + let expected = [ + \ ':finish made pending', '', + \ ':finish resumed', '', + \ 'finished sourcing Xscript', + \ 'continuing in Test_try_catch_verbose'] + call assert_equal(expected, split(msg, "\n")[1:]) + call delete('Xscript') + + " Test for messages displayed when a pending :continue is discarded by an + " exception in a finally handler + redir => msg + try + for i in range(1) + try + continue + finally + throw 'abc' + endtry + endfor + catch + endtry + redir END + let expected = [ + \ ':continue made pending', '', + \ 'Exception thrown: abc', '', + \ ':continue discarded', '', + \ 'Exception caught: abc', '', + \ 'Exception finished: abc'] + call assert_equal(expected, split(msg, "\n")) + set verbose& endfunc +" Test for throwing an exception from a BufEnter autocmd {{{1 +func Test_BufEnter_exception() + augroup bufenter_exception + au! + autocmd BufEnter Xfile1 throw 'abc' + augroup END + + let caught_abc = 0 + try + sp Xfile1 + catch /^abc/ + let caught_abc = 1 + endtry + call assert_equal(1, caught_abc) + call assert_equal(1, winnr('$')) + + augroup bufenter_exception + au! + augroup END + augroup! bufenter_exception + %bwipe! + + " Test for recursively throwing exceptions in autocmds + augroup bufenter_exception + au! + autocmd BufEnter Xfile1 throw 'bufenter' + autocmd BufLeave Xfile1 throw 'bufleave' + augroup END + + let ex_count = 0 + try + try + sp Xfile1 + catch /^bufenter/ + let ex_count += 1 + endtry + catch /^bufleave/ + let ex_count += 10 + endtry + call assert_equal(10, ex_count) + call assert_equal(2, winnr('$')) + + augroup bufenter_exception + au! + augroup END + augroup! bufenter_exception + %bwipe! +endfunc + " Test for using throw in a called function with following error {{{1 func Test_user_command_throw_in_function_call() let lines =<< trim END @@ -2060,6 +2243,23 @@ func Test_user_command_throw_in_function_call() unlet g:caught endfunc +" Test that after reporting an uncaught exception there is no error for a +" missing :endif +func Test_after_exception_no_endif_error() + function Throw() + throw "Failure" + endfunction + + function Foo() + if 1 + call Throw() + endif + endfunction + call assert_fails('call Foo()', ['E605:', 'E605:']) + delfunc Throw + delfunc Foo +endfunc + " Test for using throw in a called function with following endtry {{{1 func Test_user_command_function_call_with_endtry() let lines =<< trim END diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index eb47af08d7..ee8b52caaf 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -3,6 +3,9 @@ " undo-able pieces. Do that by setting 'undolevels'. " Also tests :earlier and :later. +source check.vim +source screendump.vim + func Test_undotree() new @@ -773,4 +776,30 @@ func Test_undo_mark() bwipe! endfunc +func Test_undo_after_write() + " use a terminal to make undo work like when text is typed + CheckRunVimInTerminal + + let lines =<< trim END + edit Xtestfile.txt + set undolevels=100 undofile + imap . <Cmd>write<CR> + write + END + call writefile(lines, 'Xtest_undo_after_write', 'D') + let buf = RunVimInTerminal('-S Xtest_undo_after_write', #{rows: 6}) + + call term_sendkeys(buf, "Otest.\<CR>boo!!!\<Esc>") + sleep 100m + call term_sendkeys(buf, "u") + call VerifyScreenDump(buf, 'Test_undo_after_write_1', {}) + + call term_sendkeys(buf, "u") + call VerifyScreenDump(buf, 'Test_undo_after_write_2', {}) + + call StopVimInTerminal(buf) + call delete('Xtestfile.txt') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_unlet.vim b/src/nvim/testdir/test_unlet.vim index b02bdaab3b..4779d17906 100644 --- a/src/nvim/testdir/test_unlet.vim +++ b/src/nvim/testdir/test_unlet.vim @@ -1,12 +1,9 @@ " Tests for :unlet func Test_read_only() - try - " this caused a crash - unlet v:count - catch - call assert_true(v:exception =~ ':E795:') - endtry + " these caused a crash + call assert_fails('unlet v:count', 'E795:') + call assert_fails('unlet v:errmsg', 'E795:') endfunc func Test_existing() @@ -18,15 +15,19 @@ endfunc func Test_not_existing() unlet! does_not_exist - try - unlet does_not_exist - catch - call assert_true(v:exception =~ ':E108:') - endtry + call assert_fails('unlet does_not_exist', 'E108:') endfunc func Test_unlet_fails() call assert_fails('unlet v:["count"]', 'E46:') + call assert_fails('unlet $', 'E475:') + let v = {} + call assert_fails('unlet v[:]', 'E719:') + let l = [] + call assert_fails("unlet l['k'", 'E111:') + let d = {'k' : 1} + call assert_fails("unlet d.k2", 'E716:') + call assert_fails("unlet {a};", 'E488:') endfunc func Test_unlet_env() @@ -62,3 +63,5 @@ func Test_unlet_complete() call feedkeys(":unlet $FOO\t\n", 'tx') call assert_true(!exists('$FOOBAR') || empty($FOOBAR)) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_user_func.vim b/src/nvim/testdir/test_user_func.vim index 5231ef7b4f..4742293ed5 100644 --- a/src/nvim/testdir/test_user_func.vim +++ b/src/nvim/testdir/test_user_func.vim @@ -3,6 +3,9 @@ " Also test that a builtin function cannot be replaced. " Also test for regression when calling arbitrary expression. +source check.vim +source shared.vim + func Table(title, ...) let ret = a:title let idx = 1 @@ -83,10 +86,14 @@ func Test_user_func() normal o[(one again call assert_equal('1. one again', getline('.')) + " Try to overwrite a function in the global (g:) scope call assert_equal(3, max([1, 2, 3])) call assert_fails("call extend(g:, {'max': function('min')})", 'E704') call assert_equal(3, max([1, 2, 3])) + " Try to overwrite an user defined function with a function reference + call assert_fails("let Expr1 = function('min')", 'E705:') + " Regression: the first line below used to throw ?E110: Missing ')'? " Second is here just to prove that this line is correct when not skipping " rhs of &&. @@ -142,8 +149,8 @@ func Test_default_arg() call assert_equal(res.optional, 2) call assert_equal(res['0'], 1) - call assert_fails("call MakeBadFunc()", 'E989') - call assert_fails("fu F(a=1 ,) | endf", 'E475') + call assert_fails("call MakeBadFunc()", 'E989:') + call assert_fails("fu F(a=1 ,) | endf", 'E1068:') " Since neovim does not have v:none, the ability to use the default " argument with the intermediate argument set to v:none has been omitted. @@ -156,6 +163,16 @@ func Test_default_arg() \ .. "1 return deepcopy(a:)\n" \ .. " endfunction", \ execute('func Args2')) + + " Error in default argument expression + let l =<< trim END + func F1(x = y) + return a:x * 2 + endfunc + echo F1() + END + let @a = l->join("\n") + call assert_fails("exe @a", 'E121:') endfunc func s:addFoo(lead) @@ -169,3 +186,319 @@ endfunc func Test_failed_call_in_try() try | call UnknownFunc() | catch | endtry endfunc + +" Test for listing user-defined functions +func Test_function_list() + call assert_fails("function Xabc", 'E123:') +endfunc + +" Test for <sfile>, <slnum> in a function +func Test_sfile_in_function() + func Xfunc() + call assert_match('..Test_sfile_in_function\[5]..Xfunc', expand('<sfile>')) + call assert_equal('2', expand('<slnum>')) + endfunc + call Xfunc() + delfunc Xfunc +endfunc + +" Test trailing text after :endfunction {{{1 +func Test_endfunction_trailing() + call assert_false(exists('*Xtest')) + + exe "func Xtest()\necho 'hello'\nendfunc\nlet done = 'yes'" + call assert_true(exists('*Xtest')) + call assert_equal('yes', done) + delfunc Xtest + unlet done + + exe "func Xtest()\necho 'hello'\nendfunc|let done = 'yes'" + call assert_true(exists('*Xtest')) + call assert_equal('yes', done) + delfunc Xtest + unlet done + + " trailing line break + exe "func Xtest()\necho 'hello'\nendfunc\n" + call assert_true(exists('*Xtest')) + delfunc Xtest + + set verbose=1 + exe "func Xtest()\necho 'hello'\nendfunc \" garbage" + call assert_notmatch('W22:', split(execute('1messages'), "\n")[0]) + call assert_true(exists('*Xtest')) + delfunc Xtest + + exe "func Xtest()\necho 'hello'\nendfunc garbage" + call assert_match('W22:', split(execute('1messages'), "\n")[0]) + call assert_true(exists('*Xtest')) + delfunc Xtest + set verbose=0 + + func Xtest(a1, a2) + echo a:a1 .. a:a2 + endfunc + set verbose=15 + redir @a + call Xtest(123, repeat('x', 100)) + redir END + call assert_match('calling Xtest(123, ''xxxxxxx.*x\.\.\.x.*xxxx'')', getreg('a')) + delfunc Xtest + set verbose=0 + + function Foo() + echo 'hello' + endfunction | echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + delfunc Foo +endfunc + +func Test_delfunction_force() + delfunc! Xtest + delfunc! Xtest + func Xtest() + echo 'nothing' + endfunc + delfunc! Xtest + delfunc! Xtest + + " Try deleting the current function + call assert_fails('delfunc Test_delfunction_force', 'E131:') +endfunc + +func Test_function_defined_line() + CheckNotGui + + let lines =<< trim [CODE] + " F1 + func F1() + " F2 + func F2() + " + " + " + return + endfunc + " F3 + execute "func F3()\n\n\n\nreturn\nendfunc" + " F4 + execute "func F4()\n + \\n + \\n + \\n + \return\n + \endfunc" + endfunc + " F5 + execute "func F5()\n\n\n\nreturn\nendfunc" + " F6 + execute "func F6()\n + \\n + \\n + \\n + \return\n + \endfunc" + call F1() + verbose func F1 + verbose func F2 + verbose func F3 + verbose func F4 + verbose func F5 + verbose func F6 + qall! + [CODE] + + call writefile(lines, 'Xtest.vim') + let res = system(GetVimCommandClean() .. ' -es -X -S Xtest.vim') + call assert_equal(0, v:shell_error) + + let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*') + call assert_match(' line 2$', m) + + let m = matchstr(res, 'function F2()[^[:print:]]*[[:print:]]*') + call assert_match(' line 4$', m) + + let m = matchstr(res, 'function F3()[^[:print:]]*[[:print:]]*') + call assert_match(' line 11$', m) + + let m = matchstr(res, 'function F4()[^[:print:]]*[[:print:]]*') + call assert_match(' line 13$', m) + + let m = matchstr(res, 'function F5()[^[:print:]]*[[:print:]]*') + call assert_match(' line 21$', m) + + let m = matchstr(res, 'function F6()[^[:print:]]*[[:print:]]*') + call assert_match(' line 23$', m) + + call delete('Xtest.vim') +endfunc + +" Test for defining a function reference in the global scope +func Test_add_funcref_to_global_scope() + let x = g: + let caught_E862 = 0 + try + func x.Xfunc() + return 1 + endfunc + catch /E862:/ + let caught_E862 = 1 + endtry + call assert_equal(1, caught_E862) +endfunc + +func Test_funccall_garbage_collect() + func Func(x, ...) + call add(a:x, a:000) + endfunc + call Func([], []) + " Must not crash cause by invalid freeing + call test_garbagecollect_now() + call assert_true(v:true) + delfunc Func +endfunc + +" Test for script-local function +func <SID>DoLast() + call append(line('$'), "last line") +endfunc + +func s:DoNothing() + call append(line('$'), "nothing line") +endfunc + +func Test_script_local_func() + set nocp nomore viminfo+=nviminfo + new + nnoremap <buffer> _x :call <SID>DoNothing()<bar>call <SID>DoLast()<bar>delfunc <SID>DoNothing<bar>delfunc <SID>DoLast<cr> + + normal _x + call assert_equal('nothing line', getline(2)) + call assert_equal('last line', getline(3)) + close! + + " Try to call a script local function in global scope + let lines =<< trim [CODE] + :call assert_fails('call s:Xfunc()', 'E81:') + :call assert_fails('let x = call("<SID>Xfunc", [])', 'E120:') + :call writefile(v:errors, 'Xresult') + :qall + + [CODE] + call writefile(lines, 'Xscript') + if RunVim([], [], '-s Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xresult') + call delete('Xscript') +endfunc + +" Test for errors in defining new functions +func Test_func_def_error() + call assert_fails('func Xfunc abc ()', 'E124:') + call assert_fails('func Xfunc(', 'E125:') + call assert_fails('func xfunc()', 'E128:') + + " Try to redefine a function that is in use + let caught_E127 = 0 + try + func! Test_func_def_error() + endfunc + catch /E127:/ + let caught_E127 = 1 + endtry + call assert_equal(1, caught_E127) + + " Try to define a function in a dict twice + let d = {} + let lines =<< trim END + func d.F1() + return 1 + endfunc + END + let l = join(lines, "\n") . "\n" + exe l + call assert_fails('exe l', 'E717:') + + " Define an autoload function with an incorrect file name + call writefile(['func foo#Bar()', 'return 1', 'endfunc'], 'Xscript') + call assert_fails('source Xscript', 'E746:') + call delete('Xscript') + + " Try to list functions using an invalid search pattern + call assert_fails('function /\%(/', 'E53:') +endfunc + +" Test for deleting a function +func Test_del_func() + call assert_fails('delfunction Xabc', 'E130:') + let d = {'a' : 10} + call assert_fails('delfunc d.a', 'E718:') + func d.fn() + return 1 + endfunc + + " cannot delete the dict function by number + let nr = substitute(execute('echo d'), '.*function(''\(\d\+\)'').*', '\1', '') + call assert_fails('delfunction g:' .. nr, 'E475: Invalid argument: g:') + + delfunc d.fn + call assert_equal({'a' : 10}, d) +endfunc + +" Test for calling return outside of a function +func Test_return_outside_func() + call writefile(['return 10'], 'Xscript') + call assert_fails('source Xscript', 'E133:') + call delete('Xscript') +endfunc + +" Test for errors in calling a function +func Test_func_arg_error() + " Too many arguments + call assert_fails("call call('min', range(1,20))", 'E118:') + call assert_fails("call call('min', range(1,21))", 'E699:') + call assert_fails('echo min(0,1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,0,1)', + \ 'E740:') + + " Missing dict argument + func Xfunc() dict + return 1 + endfunc + call assert_fails('call Xfunc()', 'E725:') + delfunc Xfunc +endfunc + +func Test_func_dict() + let mydict = {'a': 'b'} + function mydict.somefunc() dict + return len(self) + endfunc + + call assert_equal("{'a': 'b', 'somefunc': function('3')}", string(mydict)) + call assert_equal(2, mydict.somefunc()) + call assert_match("^\n function \\d\\\+() dict" + \ .. "\n1 return len(self)" + \ .. "\n endfunction$", execute('func mydict.somefunc')) + call assert_fails('call mydict.nonexist()', 'E716:') +endfunc + +func Test_func_range() + new + call setline(1, range(1, 8)) + func FuncRange() range + echo a:firstline + echo a:lastline + endfunc + 3 + call assert_equal("\n3\n3", execute('call FuncRange()')) + call assert_equal("\n4\n6", execute('4,6 call FuncRange()')) + call assert_equal("\n function FuncRange() range" + \ .. "\n1 echo a:firstline" + \ .. "\n2 echo a:lastline" + \ .. "\n endfunction", + \ execute('function FuncRange')) + + bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim index e37fe43b22..6910361345 100644 --- a/src/nvim/testdir/test_usercommands.vim +++ b/src/nvim/testdir/test_usercommands.vim @@ -79,6 +79,19 @@ function Test_cmdmods() call assert_equal('silent!', g:mods) tab MyCmd call assert_equal('tab', g:mods) + 0tab MyCmd + call assert_equal('0tab', g:mods) + tab split + tab MyCmd + call assert_equal('tab', g:mods) + 1tab MyCmd + call assert_equal('1tab', g:mods) + tabprev + tab MyCmd + call assert_equal('tab', g:mods) + 2tab MyCmd + call assert_equal('2tab', g:mods) + 2tabclose topleft MyCmd call assert_equal('topleft', g:mods) to MyCmd @@ -101,6 +114,10 @@ function Test_cmdmods() call assert_equal('vertical', g:mods) vert MyCmd call assert_equal('vertical', g:mods) + horizontal MyCmd + call assert_equal('horizontal', g:mods) + hor MyCmd + call assert_equal('horizontal', g:mods) aboveleft belowright botright browse confirm hide keepalt keepjumps \ keepmarks keeppatterns lockmarks noautocmd noswapfile silent @@ -306,6 +323,11 @@ func Test_CmdErrors() call assert_fails('com DoCmd :', 'E174:') comclear call assert_fails('delcom DoCmd', 'E184:') + + " These used to leak memory + call assert_fails('com! -complete=custom,CustomComplete _ :', 'E182:') + call assert_fails('com! -complete=custom,CustomComplete docmd :', 'E183:') + call assert_fails('com! -complete=custom,CustomComplete -xxx DoCmd :', 'E181:') endfunc func CustomComplete(A, L, P) @@ -313,7 +335,7 @@ func CustomComplete(A, L, P) endfunc func CustomCompleteList(A, L, P) - return [ "Monday", "Tuesday", "Wednesday", {}] + return [ "Monday", "Tuesday", "Wednesday", {}, v:_null_string] endfunc func Test_CmdCompletion() @@ -332,6 +354,14 @@ func Test_CmdCompletion() call feedkeys(":com -complete=co\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"com -complete=color command compiler', @:) + " try completion for unsupported argument values + call feedkeys(":com -newarg=\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"com -newarg=\t", @:) + + " command completion after the name in a user defined command + call feedkeys(":com MyCmd chist\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"com MyCmd chistory", @:) + command! DoCmd1 : command! DoCmd2 : call feedkeys(":com \<C-A>\<C-B>\"\<CR>", 'tx') @@ -343,6 +373,10 @@ func Test_CmdCompletion() call feedkeys(":delcom DoC\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"delcom DoCmd1 DoCmd2', @:) + " try argument completion for a command without completion + call feedkeys(":DoCmd1 \<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal("\"DoCmd1 \t", @:) + delcom DoCmd1 call feedkeys(":delcom DoC\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"delcom DoCmd2', @:) @@ -361,6 +395,21 @@ func Test_CmdCompletion() call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"DoCmd mswin xterm', @:) + " Test for file name completion + com! -nargs=1 -complete=file DoCmd : + call feedkeys(":DoCmd READM\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"DoCmd README.txt', @:) + + " Test for buffer name completion + com! -nargs=1 -complete=buffer DoCmd : + let bnum = bufadd('BufForUserCmd') + call setbufvar(bnum, '&buflisted', 1) + call feedkeys(":DoCmd BufFor\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"DoCmd BufForUserCmd', @:) + bwipe BufForUserCmd + call feedkeys(":DoCmd BufFor\<Tab>\<C-B>\"\<CR>", 'tx') + call assert_equal('"DoCmd BufFor', @:) + com! -nargs=* -complete=custom,CustomComplete DoCmd : call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"DoCmd January February Mars', @:) @@ -383,6 +432,17 @@ func Test_CmdCompletion() com! -nargs=? -complete=custom,min DoCmd call assert_fails("call feedkeys(':DoCmd \t', 'tx')", 'E118:') + " custom completion for a pattern with a backslash + let g:ArgLead = '' + func! CustCompl(A, L, P) + let g:ArgLead = a:A + return ['one', 'two', 'three'] + endfunc + com! -nargs=? -complete=customlist,CustCompl DoCmd + call feedkeys(":DoCmd a\\\t", 'xt') + call assert_equal('a\', g:ArgLead) + delfunc CustCompl + delcom DoCmd endfunc @@ -600,6 +660,27 @@ func Test_command_list() call assert_equal("\nNo user-defined commands found", execute('command')) endfunc +" Test for a custom user completion returning the wrong value type +func Test_usercmd_custom() + func T1(a, c, p) + return "a\nb\n" + endfunc + command -nargs=* -complete=customlist,T1 TCmd1 + call feedkeys(":TCmd1 \<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"TCmd1 ', @:) + delcommand TCmd1 + delfunc T1 + + func T2(a, c, p) + return {} + endfunc + command -nargs=* -complete=customlist,T2 TCmd2 + call feedkeys(":TCmd2 \<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal('"TCmd2 ', @:) + delcommand TCmd2 + delfunc T2 +endfunc + func Test_delcommand_buffer() command Global echo 'global' command -buffer OneBuffer echo 'one' @@ -640,5 +721,30 @@ func Test_recursive_define() endwhile endfunc +" Test for using buffer-local ambiguous user-defined commands +func Test_buflocal_ambiguous_usercmd() + new + command -buffer -nargs=1 -complete=sign TestCmd1 echo "Hello" + command -buffer -nargs=1 -complete=sign TestCmd2 echo "World" + + call assert_fails("call feedkeys(':TestCmd\<CR>', 'xt')", 'E464:') + call feedkeys(":TestCmd \<Tab>\<C-B>\"\<CR>", 'xt') + call assert_equal('"TestCmd ', @:) + + delcommand TestCmd1 + delcommand TestCmd2 + bw! +endfunc + +" Test for using a multibyte character in a user command +func Test_multibyte_in_usercmd() + command SubJapanesePeriodToDot exe "%s/\u3002/./g" + new + call setline(1, "Hello\u3002") + SubJapanesePeriodToDot + call assert_equal('Hello.', getline(1)) + bw! + delcommand SubJapanesePeriodToDot +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim index ab3503c282..e5f6d68720 100644 --- a/src/nvim/testdir/test_utf8.vim +++ b/src/nvim/testdir/test_utf8.vim @@ -2,6 +2,7 @@ source check.vim source view_util.vim +source screendump.vim " Visual block Insert adjusts for multi-byte char func Test_visual_block_insert() @@ -12,7 +13,7 @@ func Test_visual_block_insert() bwipeout! endfunc -" Test for built-in function strchars() +" Test for built-in functions strchars() and strcharlen() func Test_strchars() let inp = ["a", "あいa", "A\u20dd", "A\u20dd\u20dd", "\u20dd"] let exp = [[1, 1, 1], [3, 3, 3], [2, 2, 1], [3, 3, 1], [1, 1, 1]] @@ -21,6 +22,15 @@ func Test_strchars() call assert_equal(exp[i][1], inp[i]->strchars(0)) call assert_equal(exp[i][2], strchars(inp[i], 1)) endfor + + let exp = [1, 3, 1, 1, 1] + for i in range(len(inp)) + call assert_equal(exp[i], inp[i]->strcharlen()) + call assert_equal(exp[i], strcharlen(inp[i])) + endfor + + call assert_fails("let v=strchars('abc', [])", 'E745:') + call assert_fails("let v=strchars('abc', 2)", 'E1023:') endfunc " Test for customlist completion @@ -131,9 +141,13 @@ func Test_list2str_str2list_latin1() let save_encoding = &encoding " set encoding=latin1 - + let lres = str2list(s, 1) let sres = list2str(l, 1) + call assert_equal([65, 66, 67], str2list("ABC")) + + " Try converting a list to a string in latin-1 encoding + call assert_equal([1, 2, 3], str2list(list2str([1, 2, 3]))) let &encoding = save_encoding call assert_equal(l, lres) @@ -153,6 +167,39 @@ func Test_setcellwidths() call assert_equal(2, strwidth("\u1339")) call assert_equal(1, strwidth("\u133a")) + for aw in ['single', 'double'] + exe 'set ambiwidth=' . aw + " Handle \u0080 to \u009F as control chars even on MS-Windows. + set isprint=@,161-255 + + call setcellwidths([]) + " Control chars + call assert_equal(4, strwidth("\u0081")) + call assert_equal(6, strwidth("\uFEFF")) + " Ambiguous width chars + call assert_equal((aw == 'single') ? 1 : 2, strwidth("\u00A1")) + call assert_equal((aw == 'single') ? 1 : 2, strwidth("\u2010")) + + call setcellwidths([[0x81, 0x81, 1], [0xA1, 0xA1, 1], + \ [0x2010, 0x2010, 1], [0xFEFF, 0xFEFF, 1]]) + " Control chars + call assert_equal(4, strwidth("\u0081")) + call assert_equal(6, strwidth("\uFEFF")) + " Ambiguous width chars + call assert_equal(1, strwidth("\u00A1")) + call assert_equal(1, strwidth("\u2010")) + + call setcellwidths([[0x81, 0x81, 2], [0xA1, 0xA1, 2], + \ [0x2010, 0x2010, 2], [0xFEFF, 0xFEFF, 2]]) + " Control chars + call assert_equal(4, strwidth("\u0081")) + call assert_equal(6, strwidth("\uFEFF")) + " Ambiguous width chars + call assert_equal(2, strwidth("\u00A1")) + call assert_equal(2, strwidth("\u2010")) + endfor + set ambiwidth& isprint& + call setcellwidths([]) call assert_fails('call setcellwidths(1)', 'E714:') @@ -185,6 +232,42 @@ func Test_setcellwidths() call setcellwidths([]) endfunc +func Test_getcellwidths() + call setcellwidths([]) + call assert_equal([], getcellwidths()) + + let widthlist = [ + \ [0x1330, 0x1330, 2], + \ [9999, 10000, 1], + \ [0x1337, 0x1339, 2], + \] + let widthlistsorted = [ + \ [0x1330, 0x1330, 2], + \ [0x1337, 0x1339, 2], + \ [9999, 10000, 1], + \] + call setcellwidths(widthlist) + call assert_equal(widthlistsorted, getcellwidths()) + + call setcellwidths([]) +endfunc + +func Test_setcellwidths_dump() + CheckRunVimInTerminal + + let lines =<< trim END + call setline(1, "\ue5ffDesktop") + END + call writefile(lines, 'XCellwidths', 'D') + let buf = RunVimInTerminal('-S XCellwidths', {'rows': 6}) + call VerifyScreenDump(buf, 'Test_setcellwidths_dump_1', {}) + + call term_sendkeys(buf, ":call setcellwidths([[0xe5ff, 0xe5ff, 2]])\<CR>") + call VerifyScreenDump(buf, 'Test_setcellwidths_dump_2', {}) + + call StopVimInTerminal(buf) +endfunc + func Test_print_overlong() " Text with more composing characters than MB_MAXBYTES. new diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim index 68fe15ff93..e12c71d521 100644 --- a/src/nvim/testdir/test_vartabs.vim +++ b/src/nvim/testdir/test_vartabs.vim @@ -97,7 +97,7 @@ func Test_vartabs() .retab! call assert_equal("\t\t\t\tl", getline(1)) - " Test for 'retab' with same vlaues as vts + " Test for 'retab' with same values as vts set ts=8 sts=0 vts=5,3,6,2 vsts= exe "norm! S l" .retab! 5,3,6,2 @@ -379,6 +379,8 @@ func Test_vartabs_shiftwidth() let lines = ScreenLines([1, 3], winwidth(0)) call s:compare_lines(expect4, lines) + call assert_fails('call shiftwidth([])', 'E745:') + " cleanup bw! bw! @@ -429,4 +431,18 @@ func Test_varsofttabstop() close! endfunc +" Setting 'shiftwidth' to a negative value, should set it to either the value +" of 'tabstop' (if 'vartabstop' is not set) or to the first value in +" 'vartabstop' +func Test_shiftwidth_vartabstop() + throw 'Skipped: Nvim removed this behavior in #6377' + setlocal tabstop=7 vartabstop= + call assert_fails('set shiftwidth=-1', 'E487:') + call assert_equal(7, &shiftwidth) + setlocal tabstop=7 vartabstop=5,7,10 + call assert_fails('set shiftwidth=-1', 'E487:') + call assert_equal(5, &shiftwidth) + setlocal shiftwidth& vartabstop& tabstop& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_viminfo.vim b/src/nvim/testdir/test_viminfo.vim new file mode 100644 index 0000000000..e792db90ab --- /dev/null +++ b/src/nvim/testdir/test_viminfo.vim @@ -0,0 +1,26 @@ + +" Test for errors in setting 'viminfo' +func Test_viminfo_option_error() + " Missing number + call assert_fails('set viminfo=\"', 'E526:') + for c in split("'/:<@s", '\zs') + call assert_fails('set viminfo=' .. c, 'E526:') + endfor + + " Missing comma + call assert_fails('set viminfo=%10!', 'E527:') + call assert_fails('set viminfo=!%10', 'E527:') + call assert_fails('set viminfo=h%10', 'E527:') + call assert_fails('set viminfo=c%10', 'E527:') + call assert_fails('set viminfo=:10%10', 'E527:') + + " Missing ' setting + call assert_fails('set viminfo=%10', 'E528:') +endfunc + +func Test_viminfo_oldfiles_newfile() + let v:oldfiles = v:_null_list + call assert_equal("\nNo old files", execute('oldfiles')) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 0f204cdd0c..b0c4baf7c2 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1,16 +1,15 @@ " Test various aspects of the Vim script language. -" Most of this was formerly in test49. +" Most of this was formerly in test49.vim (developed by Servatius Brandt +" <Servatius.Brandt@fujitsu-siemens.com>) source check.vim source shared.vim +source script_util.vim "------------------------------------------------------------------------------- " Test environment {{{1 "------------------------------------------------------------------------------- -com! XpathINIT let g:Xpath = '' -com! -nargs=1 -bar Xpath let g:Xpath = g:Xpath . <args> - " Append a message to the "messages" file func Xout(text) split messages @@ -20,67 +19,31 @@ endfunc com! -nargs=1 Xout call Xout(<args>) -" MakeScript() - Make a script file from a function. {{{2 -" -" Create a script that consists of the body of the function a:funcname. -" Replace any ":return" by a ":finish", any argument variable by a global -" variable, and every ":call" by a ":source" for the next following argument -" in the variable argument list. This function is useful if similar tests are -" to be made for a ":return" from a function call or a ":finish" in a script -" file. -func MakeScript(funcname, ...) - let script = tempname() - execute "redir! >" . script - execute "function" a:funcname - redir END - execute "edit" script - " Delete the "function" and the "endfunction" lines. Do not include the - " word "function" in the pattern since it might be translated if LANG is - " set. When MakeScript() is being debugged, this deletes also the debugging - " output of its line 3 and 4. - exec '1,/.*' . a:funcname . '(.*)/d' - /^\d*\s*endfunction\>/,$d - %s/^\d*//e - %s/return/finish/e - %s/\<a:\(\h\w*\)/g:\1/ge - normal gg0 - let cnt = 0 - while search('\<call\s*\%(\u\|s:\)\w*\s*(.*)', 'W') > 0 - let cnt = cnt + 1 - s/\<call\s*\%(\u\|s:\)\w*\s*(.*)/\='source ' . a:{cnt}/ - endwhile - g/^\s*$/d - write - bwipeout - return script -endfunc - -" ExecAsScript - Source a temporary script made from a function. {{{2 -" -" Make a temporary script file from the function a:funcname, ":source" it, and -" delete it afterwards. However, if an exception is thrown the file may remain, -" the caller should call DeleteTheScript() afterwards. -let s:script_name = '' -function! ExecAsScript(funcname) - " Make a script from the function passed as argument. - let s:script_name = MakeScript(a:funcname) - - " Source and delete the script. - exec "source" s:script_name - call delete(s:script_name) - let s:script_name = '' -endfunction - -function! DeleteTheScript() - if s:script_name - call delete(s:script_name) - let s:script_name = '' - endif +" Create a new instance of Vim and run the commands in 'test' and then 'verify' +" The commands in 'test' are expected to store the test results in the Xtest.out +" file. If the test passes successfully, then Xtest.out should be empty. +func RunInNewVim(test, verify) + let init =<< trim END + set cpo-=C " support line-continuation in sourced script + source script_util.vim + XpathINIT + XloopINIT + END + let cleanup =<< trim END + call writefile(v:errors, 'Xtest.out') + qall + END + call writefile(init, 'Xtest.vim') + call writefile(a:test, 'Xtest.vim', 'a') + call writefile(a:verify, 'Xverify.vim') + call writefile(cleanup, 'Xverify.vim', 'a') + call RunVim([], [], "-S Xtest.vim -S Xverify.vim") + call assert_equal([], readfile('Xtest.out')) + call delete('Xtest.out') + call delete('Xtest.vim') + call delete('Xverify.vim') endfunc -com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) - - "------------------------------------------------------------------------------- " Test 1: :endwhile in function {{{1 " @@ -90,7 +53,7 @@ com! -nargs=1 -bar ExecAsScript call ExecAsScript(<f-args>) " tests will hang. "------------------------------------------------------------------------------- -function! T1_F() +func T1_F() Xpath 'a' let first = 1 while 1 @@ -104,9 +67,9 @@ function! T1_F() return endif endwhile -endfunction +endfunc -function! T1_G() +func T1_G() Xpath 'h' let first = 1 while 1 @@ -121,7 +84,7 @@ function! T1_G() endif if 1 " unmatched :if endwhile -endfunction +endfunc func Test_endwhile_function() XpathINIT @@ -175,7 +138,7 @@ endfunc " Test 3: :if, :elseif, :while, :continue, :break {{{1 "------------------------------------------------------------------------------- -function Test_if_while() +func Test_if_while() XpathINIT if 1 Xpath 'a' @@ -231,11 +194,21 @@ function Test_if_while() call assert_equal('ab3j3b2c2b1f1h1km', g:Xpath) endfunc +" Check double quote after skipped "elseif" does not give error E15 +func Test_skipped_elseif() + if "foo" ==? "foo" + let result = "first" + elseif "foo" ==? "foo" + let result = "second" + endif + call assert_equal('first', result) +endfunc + "------------------------------------------------------------------------------- " Test 4: :return {{{1 "------------------------------------------------------------------------------- -function! T4_F() +func T4_F() if 1 Xpath 'a' let loops = 3 @@ -253,15 +226,15 @@ function! T4_F() else Xpath 'g' endif -endfunction +endfunc -function Test_return() +func Test_return() XpathINIT call T4_F() Xpath '4' call assert_equal('ab3e3b2c24', g:Xpath) -endfunction +endfunc "------------------------------------------------------------------------------- @@ -271,14 +244,14 @@ endfunction " test as a script file (:return replaced by :finish). "------------------------------------------------------------------------------- -function Test_finish() +func Test_finish() XpathINIT ExecAsScript T4_F Xpath '5' call DeleteTheScript() call assert_equal('ab3e3b2c25', g:Xpath) -endfunction +endfunc @@ -412,7 +385,7 @@ delfunction G31 delfunction G32 delfunction G33 -function Test_defining_functions() +func Test_defining_functions() call assert_equal('ade2ie3ibcg0h1g1h2g2h3fg0h1g1h2g2h3m', g:test6_result) call assert_equal('F1G1F2G21G22G23F3G31G32G33', g:test6_calls) endfunc @@ -476,7 +449,7 @@ endfunc XpathINIT -function! T8_F() +func T8_F() if 1 Xpath 'a' while 1 @@ -508,9 +481,9 @@ function! T8_F() return novar " returns (default return value 0) Xpath 'q' return 1 " not reached -endfunction +endfunc -function! T8_G() abort +func T8_G() abort if 1 Xpath 'r' while 1 @@ -524,9 +497,9 @@ function! T8_G() abort Xpath 'x' return -4 " not reached -endfunction +endfunc -function! T8_H() abort +func T8_H() abort while 1 Xpath 'A' if 1 @@ -540,7 +513,7 @@ function! T8_H() abort Xpath 'F' return -4 " not reached -endfunction +endfunc " Aborted functions (T8_G and T8_H) return -1. let g:test8_sum = (T8_F() + 1) - 4 * T8_G() - 8 * T8_H() @@ -567,7 +540,7 @@ endfunc XpathINIT -function! F() abort +func F() abort Xpath 'a' let result = G() " not aborted Xpath 'b' @@ -575,30 +548,30 @@ function! F() abort Xpath 'c' endif return 1 -endfunction +endfunc -function! G() " no abort attribute +func G() " no abort attribute Xpath 'd' if H() != -1 " aborted Xpath 'e' endif Xpath 'f' return 2 -endfunction +endfunc -function! H() abort +func H() abort Xpath 'g' call I() " aborted Xpath 'h' return 4 -endfunction +endfunc -function! I() abort +func I() abort Xpath 'i' asdf " error Xpath 'j' return 8 -endfunction +endfunc if F() != 1 Xpath 'k' @@ -626,7 +599,7 @@ endfunc XpathINIT -function! MSG(enr, emsg) +func MSG(enr, emsg) let english = v:lang == "C" || v:lang =~ '^[Ee]n' if a:enr == "" Xout "TODO: Add message number for:" a:emsg @@ -710,10 +683,10 @@ XpathINIT let calls = 0 -function! P(num) +func P(num) let g:calls = g:calls + a:num " side effect on call return 0 -endfunction +endfunc if 1 Xpath 'a' @@ -756,23 +729,23 @@ endfunc XpathINIT -function! NULL() +func NULL() Xpath 'a' return 0 -endfunction +endfunc -function! ZERO() +func ZERO() Xpath 'b' return 0 -endfunction +endfunc -function! F0() +func! F0() Xpath 'c' -endfunction +endfunc -function! F1(arg) +func! F1(arg) Xpath 'e' -endfunction +endfunc let V0 = 1 @@ -1092,7 +1065,5387 @@ func Test_unmatched_if_in_while() endfunc "------------------------------------------------------------------------------- +" Test 18: Interrupt (Ctrl-C pressed) {{{1 +" +" On an interrupt, the script processing is terminated immediately. +"------------------------------------------------------------------------------- + +func Test_interrupt_while_if() + let test =<< trim [CODE] + try + if 1 + Xpath 'a' + while 1 + Xpath 'b' + if 1 + Xpath 'c' + call interrupt() + call assert_report('should not get here') + break + finish + endif | call assert_report('should not get here') + call assert_report('should not get here') + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + endif | call assert_report('should not get here') + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'd' + endtry | Xpath 'e' + Xpath 'f' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdef', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_interrupt_try() + let test =<< trim [CODE] + try + try + Xpath 'a' + call interrupt() + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'b' + endtry | Xpath 'c' + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_interrupt_func_while_if() + let test =<< trim [CODE] + func F() + if 1 + Xpath 'a' + while 1 + Xpath 'b' + if 1 + Xpath 'c' + call interrupt() + call assert_report('should not get here') + break + return + endif | call assert_report('should not get here') + call assert_report('should not get here') + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + endif | call assert_report('should not get here') + call assert_report('should not get here') + endfunc + + Xpath 'd' + try + call F() | call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'e' + endtry | Xpath 'f' + Xpath 'g' + [CODE] + let verify =<< trim [CODE] + call assert_equal('dabcefg', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_interrupt_func_try() + let test =<< trim [CODE] + func G() + try + Xpath 'a' + call interrupt() + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + endfunc + + Xpath 'b' + try + call G() | call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'c' + endtry | Xpath 'd' + Xpath 'e' + [CODE] + let verify =<< trim [CODE] + call assert_equal('bacde', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 19: Aborting on errors inside :try/:endtry {{{1 +" +" An error in a command dynamically enclosed in a :try/:endtry region +" aborts script processing immediately. It does not matter whether +" the failing command is outside or inside a function and whether a +" function has an "abort" attribute. +"------------------------------------------------------------------------------- + +func Test_try_error_abort_1() + let test =<< trim [CODE] + func F() abort + Xpath 'a' + asdf + call assert_report('should not get here') + endfunc + + try + Xpath 'b' + call F() + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('ba', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_2() + let test =<< trim [CODE] + func G() + Xpath 'a' + asdf + call assert_report('should not get here') + endfunc + + try + Xpath 'b' + call G() + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('ba', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_3() + let test =<< trim [CODE] + try + Xpath 'a' + asdf + call assert_report('should not get here') + endtry | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_4() + let test =<< trim [CODE] + if 1 + try + Xpath 'a' + asdf + call assert_report('should not get here') + endtry | call assert_report('should not get here') + endif | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_5() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + try + Xpath 'a' + asdf + call assert_report('should not get here') + endtry | call assert_report('should not get here') + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_try_error_abort_6() + let test =<< trim [CODE] + let p = 1 + Xpath 'a' + while p + Xpath 'b' + let p = 0 + try + Xpath 'c' + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 20: Aborting on errors after :try/:endtry {{{1 +" +" When an error occurs after the last active :try/:endtry region has +" been left, termination behavior is as if no :try/:endtry has been +" seen. +"------------------------------------------------------------------------------- + +func Test_error_after_try_1() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + Xpath 'a' + try + Xpath 'b' + endtry + asdf + call assert_report('should not get here') + endwhile | call assert_report('should not get here') + Xpath 'c' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_2() + let test =<< trim [CODE] + while 1 + try + Xpath 'a' + break + call assert_report('should not get here') + endtry + endwhile + Xpath 'b' + asdf + Xpath 'c' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_3() + let test =<< trim [CODE] + while 1 + try + Xpath 'a' + break + call assert_report('should not get here') + finally + Xpath 'b' + endtry + endwhile + Xpath 'c' + asdf + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_4() + let test =<< trim [CODE] + while 1 + try + Xpath 'a' + finally + Xpath 'b' + break + call assert_report('should not get here') + endtry + endwhile + Xpath 'c' + asdf + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_5() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + try + Xpath 'a' + continue + call assert_report('should not get here') + endtry + endwhile + Xpath 'b' + asdf + Xpath 'c' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_6() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + try + Xpath 'a' + continue + call assert_report('should not get here') + finally + Xpath 'b' + endtry + endwhile + Xpath 'c' + asdf + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_error_after_try_7() + let test =<< trim [CODE] + let p = 1 + while p + let p = 0 + try + Xpath 'a' + finally + Xpath 'b' + continue + call assert_report('should not get here') + endtry + endwhile + Xpath 'c' + asdf + Xpath 'd' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcd', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 21: :finally for :try after :continue/:break/:return/:finish {{{1 +" +" If a :try conditional stays inactive due to a preceding :continue, +" :break, :return, or :finish, its :finally clause should not be +" executed. +"------------------------------------------------------------------------------- + +func Test_finally_after_loop_ctrl_statement() + let test =<< trim [CODE] + func F() + let loops = 2 + while loops > 0 + XloopNEXT + let loops = loops - 1 + try + if loops == 1 + Xloop 'a' + continue + call assert_report('should not get here') + elseif loops == 0 + Xloop 'b' + break + call assert_report('should not get here') + endif + + try " inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + finally + Xloop 'c' + endtry + call assert_report('should not get here') + endwhile + + try + Xpath 'd' + return + call assert_report('should not get here') + try " inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + finally + Xpath 'e' + endtry + call assert_report('should not get here') + endfunc + + try + Xpath 'f' + call F() + Xpath 'g' + finish + call assert_report('should not get here') + try " inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + finally + Xpath 'h' + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('fa2c2b3c3degh', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 22: :finally for a :try after an error/interrupt/:throw {{{1 +" +" If a :try conditional stays inactive due to a preceding error or +" interrupt or :throw, its :finally clause should not be executed. +"------------------------------------------------------------------------------- + +func Test_finally_after_error_in_func() + let test =<< trim [CODE] + func Error() + try + Xpath 'b' + asdf " aborting error, triggering error exception + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endfunc + + Xpath 'a' + call Error() + call assert_report('should not get here') + + if 1 " not active due to error + try " not active since :if inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + endif + + try " not active due to error + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_finally_after_interrupt() + let test =<< trim [CODE] + func Interrupt() + try + Xpath 'a' + call interrupt() " triggering interrupt exception + call assert_report('should not get here') + endtry + endfunc + + Xpath 'b' + try + call Interrupt() + catch /^Vim:Interrupt$/ + Xpath 'c' + finish + endtry + call assert_report('should not get here') + + if 1 " not active due to interrupt + try " not active since :if inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + endif + + try " not active due to interrupt + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('bac', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_finally_after_throw() + let test =<< trim [CODE] + func Throw() + Xpath 'a' + throw 'xyz' + endfunc + + Xpath 'b' + call Throw() + call assert_report('should not get here') + + if 1 " not active due to :throw + try " not active since :if inactive + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + endif + + try " not active due to :throw + call assert_report('should not get here') + finally + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('ba', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 23: :catch clauses for a :try after a :throw {{{1 +" +" If a :try conditional stays inactive due to a preceding :throw, +" none of its :catch clauses should be executed. +"------------------------------------------------------------------------------- + +func Test_catch_after_throw() + let test =<< trim [CODE] + try + Xpath 'a' + throw "xyz" + call assert_report('should not get here') + + if 1 " not active due to :throw + try " not active since :if inactive + call assert_report('should not get here') + catch /xyz/ + call assert_report('should not get here') + endtry + endif + catch /xyz/ + Xpath 'b' + endtry + + Xpath 'c' + throw "abc" + call assert_report('should not get here') + + try " not active due to :throw + call assert_report('should not get here') + catch /abc/ + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 24: :endtry for a :try after a :throw {{{1 +" +" If a :try conditional stays inactive due to a preceding :throw, +" its :endtry should not rethrow the exception to the next surrounding +" active :try conditional. +"------------------------------------------------------------------------------- + +func Test_endtry_after_throw() + let test =<< trim [CODE] + try " try 1 + try " try 2 + Xpath 'a' + throw "xyz" " makes try 2 inactive + call assert_report('should not get here') + + try " try 3 + call assert_report('should not get here') + endtry " no rethrow to try 1 + catch /xyz/ " should catch although try 2 inactive + Xpath 'b' + endtry + catch /xyz/ " try 1 active, but exception already caught + call assert_report('should not get here') + endtry + Xpath 'c' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 27: Executing :finally clauses after :return {{{1 +" +" For a :return command dynamically enclosed in a :try/:endtry region, +" :finally clauses are executed and the called function is ended. +"------------------------------------------------------------------------------- + +func T27_F() + try + Xpath 'a' + try + Xpath 'b' + return + call assert_report('should not get here') + finally + Xpath 'c' + endtry + Xpath 'd' + finally + Xpath 'e' + endtry + call assert_report('should not get here') +endfunc + +func T27_G() + try + Xpath 'f' + return + call assert_report('should not get here') + finally + Xpath 'g' + call T27_F() + Xpath 'h' + endtry + call assert_report('should not get here') +endfunc + +func T27_H() + try + Xpath 'i' + call T27_G() + Xpath 'j' + finally + Xpath 'k' + return + call assert_report('should not get here') + endtry + call assert_report('should not get here') +endfunction + +func Test_finally_after_return() + XpathINIT + try + Xpath 'l' + call T27_H() + Xpath 'm' + finally + Xpath 'n' + endtry + call assert_equal('lifgabcehjkmn', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 28: Executing :finally clauses after :finish {{{1 +" +" For a :finish command dynamically enclosed in a :try/:endtry region, +" :finally clauses are executed and the sourced file is finished. +" +" This test executes the bodies of the functions F, G, and H from the +" previous test as script files (:return replaced by :finish). +"------------------------------------------------------------------------------- + +func Test_finally_after_finish() + XpathINIT + + let scriptF = MakeScript("T27_F") + let scriptG = MakeScript("T27_G", scriptF) + let scriptH = MakeScript("T27_H", scriptG) + + try + Xpath 'A' + exec "source" scriptH + Xpath 'B' + finally + Xpath 'C' + endtry + Xpath 'D' + call assert_equal('AifgabcehjkBCD', g:Xpath) + call delete(scriptF) + call delete(scriptG) + call delete(scriptH) +endfunc + +"------------------------------------------------------------------------------- +" Test 29: Executing :finally clauses on errors {{{1 +" +" After an error in a command dynamically enclosed in a :try/:endtry +" region, :finally clauses are executed and the script processing is +" terminated. +"------------------------------------------------------------------------------- + +func Test_finally_after_error_1() + let test =<< trim [CODE] + func F() + while 1 + try + Xpath 'a' + while 1 + try + Xpath 'b' + asdf " error + call assert_report('should not get here') + finally + Xpath 'c' + endtry | call assert_report('should not get here') + call assert_report('should not get here') + break + endwhile + call assert_report('should not get here') + finally + Xpath 'd' + endtry | call assert_report('should not get here') + call assert_report('should not get here') + break + endwhile + call assert_report('should not get here') + endfunc + + while 1 + try + Xpath 'e' + while 1 + call F() + call assert_report('should not get here') + break + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + finally + Xpath 'f' + endtry | call assert_report('should not get here') + endwhile | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('eabcdf', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_finally_after_error_2() + let test =<< trim [CODE] + func G() abort + if 1 + try + Xpath 'a' + asdf " error + call assert_report('should not get here') + finally + Xpath 'b' + endtry | Xpath 'c' + endif | Xpath 'd' + call assert_report('should not get here') + endfunc + + if 1 + try + Xpath 'e' + call G() + call assert_report('should not get here') + finally + Xpath 'f' + endtry | call assert_report('should not get here') + endif | call assert_report('should not get here') + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('eabf', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 30: Executing :finally clauses on interrupt {{{1 +" +" After an interrupt in a command dynamically enclosed in +" a :try/:endtry region, :finally clauses are executed and the +" script processing is terminated. +"------------------------------------------------------------------------------- + +func Test_finally_on_interrupt() + let test =<< trim [CODE] + func F() + try + Xloop 'a' + call interrupt() + call assert_report('should not get here') + finally + Xloop 'b' + endtry + call assert_report('should not get here') + endfunc + + try + try + Xpath 'c' + try + Xpath 'd' + call interrupt() + call assert_report('should not get here') + finally + Xpath 'e' + try + Xpath 'f' + try + Xpath 'g' + finally + Xpath 'h' + try + Xpath 'i' + call interrupt() + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + finally + Xpath 'j' + try + Xpath 'k' + call F() + call assert_report('should not get here') + finally + Xpath 'l' + try + Xpath 'm' + XloopNEXT + ExecAsScript F + call assert_report('should not get here') + finally + Xpath 'n' + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'o' + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('cdefghijka1b1lma2b2no', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 31: Executing :finally clauses after :throw {{{1 +" +" After a :throw dynamically enclosed in a :try/:endtry region, +" :finally clauses are executed and the script processing is +" terminated. +"------------------------------------------------------------------------------- + +func Test_finally_after_throw_2() + let test =<< trim [CODE] + func F() + try + Xloop 'a' + throw "exception" + call assert_report('should not get here') + finally + Xloop 'b' + endtry + call assert_report('should not get here') + endfunc + + try + Xpath 'c' + try + Xpath 'd' + throw "exception" + call assert_report('should not get here') + finally + Xpath 'e' + try + Xpath 'f' + try + Xpath 'g' + finally + Xpath 'h' + try + Xpath 'i' + throw "exception" + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + finally + Xpath 'j' + try + Xpath 'k' + call F() + call assert_report('should not get here') + finally + Xpath 'l' + try + Xpath 'm' + XloopNEXT + ExecAsScript F + call assert_report('should not get here') + finally + Xpath 'n' + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('cdefghijka1b1lma2b2n', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 34: :finally reason discarded by :continue {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :continue in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_after_continue() + let test =<< trim [CODE] + func C(jump) + XloopNEXT + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + continue " discards jump that caused the :finally + call assert_report('should not get here') + endtry + call assert_report('should not get here') + elseif loop == 2 + Xloop 'a' + endif + endwhile + endfunc + + call C("continue") + Xpath 'b' + call C("break") + Xpath 'c' + call C("return") + Xpath 'd' + let g:jump = "finish" + ExecAsScript C + unlet g:jump + Xpath 'e' + try + call C("error") + Xpath 'f' + finally + Xpath 'g' + try + call C("interrupt") + Xpath 'h' + finally + Xpath 'i' + call C("throw") + Xpath 'j' + endtry + endtry + Xpath 'k' + [CODE] + let verify =<< trim [CODE] + call assert_equal('a2ba3ca4da5ea6fga7hia8jk', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 35: :finally reason discarded by :break {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :break in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_break() + let test =<< trim [CODE] + func B(jump) + XloopNEXT + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + break " discards jump that caused the :finally + call assert_report('should not get here') + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + Xloop 'a' + endfunc + + call B("continue") + Xpath 'b' + call B("break") + Xpath 'c' + call B("return") + Xpath 'd' + let g:jump = "finish" + ExecAsScript B + unlet g:jump + Xpath 'e' + try + call B("error") + Xpath 'f' + finally + Xpath 'g' + try + call B("interrupt") + Xpath 'h' + finally + Xpath 'i' + call B("throw") + Xpath 'j' + endtry + endtry + Xpath 'k' + [CODE] + let verify =<< trim [CODE] + call assert_equal('a2ba3ca4da5ea6fga7hia8jk', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 36: :finally reason discarded by :return {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :return in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_return() + let test =<< trim [CODE] + func R(jump, retval) abort + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + return a:retval " discards jump that caused the :finally + call assert_report('should not get here') + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + let sum = -R("continue", -8) + Xpath 'a' + let sum = sum - R("break", -16) + Xpath 'b' + let sum = sum - R("return", -32) + Xpath 'c' + try + let sum = sum - R("error", -64) + Xpath 'd' + finally + Xpath 'e' + try + let sum = sum - R("interrupt", -128) + Xpath 'f' + finally + Xpath 'g' + let sum = sum - R("throw", -256) + Xpath 'h' + endtry + endtry + Xpath 'i' + + let expected = 8 + 16 + 32 + 64 + 128 + 256 + call assert_equal(sum, expected) + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghi', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 37: :finally reason discarded by :finish {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :finish in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_finish() + let test =<< trim [CODE] + func F(jump) " not executed as function, transformed to a script + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "finish" + finish + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + finish " discards jump that caused the :finally + call assert_report('should not get here') + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + let scriptF = MakeScript("F") + delfunction F + + let g:jump = "continue" + exec "source" scriptF + Xpath 'a' + let g:jump = "break" + exec "source" scriptF + Xpath 'b' + let g:jump = "finish" + exec "source" scriptF + Xpath 'c' + try + let g:jump = "error" + exec "source" scriptF + Xpath 'd' + finally + Xpath 'e' + try + let g:jump = "interrupt" + exec "source" scriptF + Xpath 'f' + finally + Xpath 'g' + try + let g:jump = "throw" + exec "source" scriptF + Xpath 'h' + finally + Xpath 'i' + endtry + endtry + endtry + unlet g:jump + call delete(scriptF) + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghi', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 38: :finally reason discarded by an error {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by an error in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_error() + let test =<< trim [CODE] + func E(jump) + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + asdf " error; discards jump that caused the :finally + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + try + Xpath 'a' + call E("continue") + call assert_report('should not get here') + finally + try + Xpath 'b' + call E("break") + call assert_report('should not get here') + finally + try + Xpath 'c' + call E("return") + call assert_report('should not get here') + finally + try + Xpath 'd' + let g:jump = "finish" + ExecAsScript E + call assert_report('should not get here') + finally + unlet g:jump + try + Xpath 'e' + call E("error") + call assert_report('should not get here') + finally + try + Xpath 'f' + call E("interrupt") + call assert_report('should not get here') + finally + try + Xpath 'g' + call E("throw") + call assert_report('should not get here') + finally + Xpath 'h' + delfunction E + endtry + endtry + endtry + endtry + endtry + endtry + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefgh', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 39: :finally reason discarded by an interrupt {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by an interrupt in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discarded_by_interrupt() + let test =<< trim [CODE] + func I(jump) + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + call interrupt() + let dummy = 0 + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + try + try + Xpath 'a' + call I("continue") + call assert_report('should not get here') + finally + try + Xpath 'b' + call I("break") + call assert_report('should not get here') + finally + try + Xpath 'c' + call I("return") + call assert_report('should not get here') + finally + try + Xpath 'd' + let g:jump = "finish" + ExecAsScript I + call assert_report('should not get here') + finally + unlet g:jump + try + Xpath 'e' + call I("error") + call assert_report('should not get here') + finally + try + Xpath 'f' + call I("interrupt") + call assert_report('should not get here') + finally + try + Xpath 'g' + call I("throw") + call assert_report('should not get here') + finally + Xpath 'h' + delfunction I + endtry + endtry + endtry + endtry + endtry + endtry + endtry + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'A' + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghA', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 40: :finally reason discarded by :throw {{{1 +" +" When a :finally clause is executed due to a :continue, :break, +" :return, :finish, error, interrupt or :throw, the jump reason is +" discarded by a :throw in the finally clause. +"------------------------------------------------------------------------------- + +func Test_finally_discard_by_throw() + let test =<< trim [CODE] + func T(jump) + let loop = 0 + while loop < 2 + let loop = loop + 1 + if loop == 1 + try + if a:jump == "continue" + continue + elseif a:jump == "break" + break + elseif a:jump == "return" || a:jump == "finish" + return + elseif a:jump == "error" + asdf + elseif a:jump == "interrupt" + call interrupt() + let dummy = 0 + elseif a:jump == "throw" + throw "abc" + endif + finally + throw "xyz" " discards jump that caused the :finally + endtry + elseif loop == 2 + call assert_report('should not get here') + endif + endwhile + call assert_report('should not get here') + endfunc + + try + Xpath 'a' + call T("continue") + call assert_report('should not get here') + finally + try + Xpath 'b' + call T("break") + call assert_report('should not get here') + finally + try + Xpath 'c' + call T("return") + call assert_report('should not get here') + finally + try + Xpath 'd' + let g:jump = "finish" + ExecAsScript T + call assert_report('should not get here') + finally + unlet g:jump + try + Xpath 'e' + call T("error") + call assert_report('should not get here') + finally + try + Xpath 'f' + call T("interrupt") + call assert_report('should not get here') + finally + try + Xpath 'g' + call T("throw") + call assert_report('should not get here') + finally + Xpath 'h' + delfunction T + endtry + endtry + endtry + endtry + endtry + endtry + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefgh', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 49: Throwing exceptions across functions {{{1 +" +" When an exception is thrown but not caught inside a function, the +" caller is checked for a matching :catch clause. +"------------------------------------------------------------------------------- + +func T49_C() + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here') + catch /arrgh/ + Xpath 'b' + endtry + Xpath 'c' +endfunc + +func T49_T1() + XloopNEXT + try + Xloop 'd' + throw "arrgh" + call assert_report('should not get here') + finally + Xloop 'e' + endtry + Xloop 'f' +endfunc + +func T49_T2() + try + Xpath 'g' + call T49_T1() + call assert_report('should not get here') + finally + Xpath 'h' + endtry + call assert_report('should not get here') +endfunc + +func Test_throw_exception_across_funcs() + XpathINIT + XloopINIT + try + Xpath 'i' + call T49_C() " throw and catch + Xpath 'j' + catch /.*/ + call assert_report('should not get here') + endtry + + try + Xpath 'k' + call T49_T1() " throw, one level + call assert_report('should not get here') + catch /arrgh/ + Xpath 'l' + catch /.*/ + call assert_report('should not get here') + endtry + + try + Xpath 'm' + call T49_T2() " throw, two levels + call assert_report('should not get here') + catch /arrgh/ + Xpath 'n' + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'o' + + call assert_equal('iabcjkd2e2lmgd3e3hno', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 50: Throwing exceptions across script files {{{1 +" +" When an exception is thrown but not caught inside a script file, +" the sourcing script or function is checked for a matching :catch +" clause. +" +" This test executes the bodies of the functions C, T1, and T2 from +" the previous test as script files (:return replaced by :finish). +"------------------------------------------------------------------------------- + +func T50_F() + try + Xpath 'A' + exec "source" g:scriptC + Xpath 'B' + catch /.*/ + call assert_report('should not get here') + endtry + + try + Xpath 'C' + exec "source" g:scriptT1 + call assert_report('should not get here') + catch /arrgh/ + Xpath 'D' + catch /.*/ + call assert_report('should not get here') + endtry +endfunc + +func Test_throw_across_script() + XpathINIT + XloopINIT + let g:scriptC = MakeScript("T49_C") + let g:scriptT1 = MakeScript("T49_T1") + let scriptT2 = MakeScript("T49_T2", g:scriptT1) + + try + Xpath 'E' + call T50_F() + Xpath 'F' + exec "source" scriptT2 + call assert_report('should not get here') + catch /arrgh/ + Xpath 'G' + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'H' + call assert_equal('EAabcBCd2e2DFgd3e3hGH', g:Xpath) + + call delete(g:scriptC) + call delete(g:scriptT1) + call delete(scriptT2) + unlet g:scriptC g:scriptT1 scriptT2 +endfunc + +"------------------------------------------------------------------------------- +" Test 52: Uncaught exceptions {{{1 +" +" When an exception is thrown but not caught, an error message is +" displayed when the script is terminated. In case of an interrupt +" or error exception, the normal interrupt or error message(s) are +" displayed. +"------------------------------------------------------------------------------- + +func Test_uncaught_exception_1() + CheckEnglish + + let test =<< trim [CODE] + Xpath 'a' + throw "arrgh" + call assert_report('should not get here')` + [CODE] + let verify =<< trim [CODE] + call assert_equal('E605: Exception not caught: arrgh', v:errmsg) + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_2() + CheckEnglish + + let test =<< trim [CODE] + try + Xpath 'a' + throw "oops" + call assert_report('should not get here')` + catch /arrgh/ + call assert_report('should not get here')` + endtry + call assert_report('should not get here')` + [CODE] + let verify =<< trim [CODE] + call assert_equal('E605: Exception not caught: oops', v:errmsg) + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_3() + CheckEnglish + + let test =<< trim [CODE] + func T() + Xpath 'c' + throw "brrr" + call assert_report('should not get here')` + endfunc + + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here')` + catch /.*/ + Xpath 'b' + call T() + call assert_report('should not get here')` + endtry + call assert_report('should not get here')` + [CODE] + let verify =<< trim [CODE] + call assert_equal('E605: Exception not caught: brrr', v:errmsg) + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_4() + CheckEnglish + + let test =<< trim [CODE] + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here')` + finally + Xpath 'b' + throw "brrr" + call assert_report('should not get here')` + endtry + call assert_report('should not get here')` + [CODE] + let verify =<< trim [CODE] + call assert_equal('E605: Exception not caught: brrr', v:errmsg) + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_5() + CheckEnglish + + " Need to catch and handle interrupt, otherwise the test will wait for the + " user to press <Enter> to continue + let test =<< trim [CODE] + try + try + Xpath 'a' + call interrupt() + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'b' + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_6() + CheckEnglish + + let test =<< trim [CODE] + try + Xpath 'a' + let x = novar " error E121; exception: E121 + catch /E15:/ " should not catch + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + call assert_equal('E121: Undefined variable: novar', v:errmsg) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_uncaught_exception_7() + CheckEnglish + + let test =<< trim [CODE] + try + Xpath 'a' + " error E108/E488; exception: E488 + unlet novar # + catch /E108:/ " should not catch + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + call assert_equal('E488: Trailing characters: #', v:errmsg) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 53: Nesting errors: :endif/:else/:elseif {{{1 +" +" For nesting errors of :if conditionals the correct error messages +" should be given. +"------------------------------------------------------------------------------- + +func Test_nested_if_else_errors() + CheckEnglish + + " :endif without :if + let code =<< trim END + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :endif without :if + let code =<< trim END + while 1 + endif + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :endif without :if + let code =<< trim END + try + finally + endif + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :endif without :if + let code =<< trim END + try + endif + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :endif without :if + let code =<< trim END + try + throw "a" + catch /a/ + endif + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endif):E580: :endif without :if') + + " :else without :if + let code =<< trim END + else + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :else without :if + let code =<< trim END + while 1 + else + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :else without :if + let code =<< trim END + try + finally + else + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :else without :if + let code =<< trim END + try + else + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :else without :if + let code =<< trim END + try + throw "a" + catch /a/ + else + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E581: :else without :if') + + " :elseif without :if + let code =<< trim END + elseif 1 + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " :elseif without :if + let code =<< trim END + while 1 + elseif 1 + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " :elseif without :if + let code =<< trim END + try + finally + elseif 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " :elseif without :if + let code =<< trim END + try + elseif 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " :elseif without :if + let code =<< trim END + try + throw "a" + catch /a/ + elseif 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E582: :elseif without :if') + + " multiple :else + let code =<< trim END + if 1 + else + else + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(else):E583: multiple :else') + + " :elseif after :else + let code =<< trim END + if 1 + else + elseif 1 + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(elseif):E584: :elseif after :else') + + call delete('Xtest') +endfunc + +"------------------------------------------------------------------------------- +" Test 54: Nesting errors: :while/:endwhile {{{1 +" +" For nesting errors of :while conditionals the correct error messages +" should be given. +" +" This test reuses the function MESSAGES() from the previous test. +" This functions checks the messages in g:msgfile. "------------------------------------------------------------------------------- + +func Test_nested_while_error() + CheckEnglish + + " :endwhile without :while + let code =<< trim END + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " :endwhile without :while + let code =<< trim END + if 1 + endwhile + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " Missing :endif + let code =<< trim END + while 1 + if 1 + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E171: Missing :endif') + + " :endwhile without :while + let code =<< trim END + try + finally + endwhile + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " Missing :endtry + let code =<< trim END + while 1 + try + finally + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E600: Missing :endtry') + + " Missing :endtry + let code =<< trim END + while 1 + if 1 + try + finally + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E600: Missing :endtry') + + " Missing :endif + let code =<< trim END + while 1 + try + finally + if 1 + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E171: Missing :endif') + + " :endwhile without :while + let code =<< trim END + try + endwhile + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " :endwhile without :while + let code =<< trim END + while 1 + try + endwhile + endtry + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " :endwhile without :while + let code =<< trim END + try + throw "a" + catch /a/ + endwhile + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + " :endwhile without :while + let code =<< trim END + while 1 + try + throw "a" + catch /a/ + endwhile + endtry + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endwhile):E588: :endwhile without :while') + + call delete('Xtest') +endfunc + +"------------------------------------------------------------------------------- +" Test 55: Nesting errors: :continue/:break {{{1 +" +" For nesting errors of :continue and :break commands the correct +" error messages should be given. +" +" This test reuses the function MESSAGES() from the previous test. +" This functions checks the messages in g:msgfile. +"------------------------------------------------------------------------------- + +func Test_nested_cont_break_error() + CheckEnglish + + " :continue without :while + let code =<< trim END + continue + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :continue without :while + let code =<< trim END + if 1 + continue + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :continue without :while + let code =<< trim END + try + finally + continue + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :continue without :while + let code =<< trim END + try + continue + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :continue without :while + let code =<< trim END + try + throw "a" + catch /a/ + continue + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(continue):E586: :continue without :while or :for') + + " :break without :while + let code =<< trim END + break + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + " :break without :while + let code =<< trim END + if 1 + break + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + " :break without :while + let code =<< trim END + try + finally + break + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + " :break without :while + let code =<< trim END + try + break + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + " :break without :while + let code =<< trim END + try + throw "a" + catch /a/ + break + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(break):E587: :break without :while or :for') + + call delete('Xtest') +endfunc + +"------------------------------------------------------------------------------- +" Test 56: Nesting errors: :endtry {{{1 +" +" For nesting errors of :try conditionals the correct error messages +" should be given. +" +" This test reuses the function MESSAGES() from the previous test. +" This functions checks the messages in g:msgfile. +"------------------------------------------------------------------------------- + +func Test_nested_endtry_error() + CheckEnglish + + " :endtry without :try + let code =<< trim END + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try') + + " :endtry without :try + let code =<< trim END + if 1 + endtry + endif + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try') + + " :endtry without :try + let code =<< trim END + while 1 + endtry + endwhile + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E602: :endtry without :try') + + " Missing :endif + let code =<< trim END + try + if 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif') + + " Missing :endwhile + let code =<< trim END + try + while 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile') + + " Missing :endif + let code =<< trim END + try + finally + if 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif') + + " Missing :endwhile + let code =<< trim END + try + finally + while 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile') + + " Missing :endif + let code =<< trim END + try + throw "a" + catch /a/ + if 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E171: Missing :endif') + + " Missing :endwhile + let code =<< trim END + try + throw "a" + catch /a/ + while 1 + endtry + END + call writefile(code, 'Xtest') + call AssertException(['source Xtest'], 'Vim(endtry):E170: Missing :endwhile') + + call delete('Xtest') +endfunc + +"------------------------------------------------------------------------------- +" Test 57: v:exception and v:throwpoint for user exceptions {{{1 +" +" v:exception evaluates to the value of the exception that was caught +" most recently and is not finished. (A caught exception is finished +" when the next ":catch", ":finally", or ":endtry" is reached.) +" v:throwpoint evaluates to the script/function name and line number +" where that exception has been thrown. +"------------------------------------------------------------------------------- + +func Test_user_exception_info() + CheckEnglish + + XpathINIT + XloopINIT + + func FuncException() + let g:exception = v:exception + endfunc + + func FuncThrowpoint() + let g:throwpoint = v:throwpoint + endfunc + + let scriptException = MakeScript("FuncException") + let scriptThrowPoint = MakeScript("FuncThrowpoint") + + command! CmdException let g:exception = v:exception + command! CmdThrowpoint let g:throwpoint = v:throwpoint + + func T(arg, line) + if a:line == 2 + throw a:arg " in line 2 + elseif a:line == 4 + throw a:arg " in line 4 + elseif a:line == 6 + throw a:arg " in line 6 + elseif a:line == 8 + throw a:arg " in line 8 + endif + endfunc + + func G(arg, line) + call T(a:arg, a:line) + endfunc + + func F(arg, line) + call G(a:arg, a:line) + endfunc + + let scriptT = MakeScript("T") + let scriptG = MakeScript("G", scriptT) + let scriptF = MakeScript("F", scriptG) + + try + Xpath 'a' + call F("oops", 2) + catch /.*/ + Xpath 'b' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + exec "let exception = v:exception" + exec "let throwpoint = v:throwpoint" + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + CmdException + CmdThrowpoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + call FuncException() + call FuncThrowpoint() + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + exec "source" scriptException + exec "source" scriptThrowPoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + + try + Xpath 'c' + call G("arrgh", 4) + catch /.*/ + Xpath 'd' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + + try + Xpath 'e' + let g:arg = "autsch" + let g:line = 6 + exec "source" scriptF + catch /.*/ + Xpath 'f' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("autsch", v:exception) + call assert_match(fnamemodify(scriptT, ':t'), v:throwpoint) + call assert_match('\<6\>', v:throwpoint) + finally + Xpath 'g' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + try + Xpath 'h' + let g:arg = "brrrr" + let g:line = 8 + exec "source" scriptG + catch /.*/ + Xpath 'i' + let exception = v:exception + let throwpoint = v:throwpoint + " Resolve scriptT for matching it against v:throwpoint. + call assert_equal("brrrr", v:exception) + call assert_match(fnamemodify(scriptT, ':t'), v:throwpoint) + call assert_match('\<8\>', v:throwpoint) + finally + Xpath 'j' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + endtry + Xpath 'k' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + endtry + Xpath 'l' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("arrgh", v:exception) + call assert_match('\<G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<4\>', v:throwpoint) + finally + Xpath 'm' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + endtry + Xpath 'n' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("oops", v:exception) + call assert_match('\<F\[1]\.\.G\[1]\.\.T\>', v:throwpoint) + call assert_match('\<2\>', v:throwpoint) + finally + Xpath 'o' + let exception = v:exception + let throwpoint = v:throwpoint + call assert_equal("", v:exception) + call assert_match('^$', v:throwpoint) + call assert_match('^$', v:throwpoint) + endtry + + call assert_equal('abcdefghijklmno', g:Xpath) + + unlet exception throwpoint + delfunction FuncException + delfunction FuncThrowpoint + call delete(scriptException) + call delete(scriptThrowPoint) + unlet scriptException scriptThrowPoint + delcommand CmdException + delcommand CmdThrowpoint + delfunction T + delfunction G + delfunction F + call delete(scriptT) + call delete(scriptG) + call delete(scriptF) + unlet scriptT scriptG scriptF +endfunc + +"------------------------------------------------------------------------------- +" +" Test 58: v:exception and v:throwpoint for error/interrupt exceptions {{{1 +" +" v:exception and v:throwpoint work also for error and interrupt +" exceptions. +"------------------------------------------------------------------------------- + +func Test_execption_info_for_error() + CheckEnglish + + let test =<< trim [CODE] + func T(line) + if a:line == 2 + delfunction T " error (function in use) in line 2 + elseif a:line == 4 + call interrupt() + endif + endfunc + + while 1 + try + Xpath 'a' + call T(2) + call assert_report('should not get here') + catch /.*/ + Xpath 'b' + if v:exception !~ 'Vim(delfunction):' + call assert_report('should not get here') + endif + if v:throwpoint !~ '\<T\>' + call assert_report('should not get here') + endif + if v:throwpoint !~ '\<2\>' + call assert_report('should not get here') + endif + finally + Xpath 'c' + if v:exception != "" + call assert_report('should not get here') + endif + if v:throwpoint != "" + call assert_report('should not get here') + endif + break + endtry + endwhile + + Xpath 'd' + if v:exception != "" + call assert_report('should not get here') + endif + if v:throwpoint != "" + call assert_report('should not get here') + endif + + while 1 + try + Xpath 'e' + call T(4) + call assert_report('should not get here') + catch /.*/ + Xpath 'f' + if v:exception != 'Vim:Interrupt' + call assert_report('should not get here') + endif + if v:throwpoint !~ 'function T' + call assert_report('should not get here') + endif + if v:throwpoint !~ '\<4\>' + call assert_report('should not get here') + endif + finally + Xpath 'g' + if v:exception != "" + call assert_report('should not get here') + endif + if v:throwpoint != "" + call assert_report('should not get here') + endif + break + endtry + endwhile + + Xpath 'h' + if v:exception != "" + call assert_report('should not get here') + endif + if v:throwpoint != "" + call assert_report('should not get here') + endif + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefgh', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" +" Test 59: v:exception and v:throwpoint when discarding exceptions {{{1 +" +" When a :catch clause is left by a ":break" etc or an error or +" interrupt exception, v:exception and v:throwpoint are reset. They +" are not affected by an exception that is discarded before being +" caught. +"------------------------------------------------------------------------------- +func Test_exception_info_on_discard() + CheckEnglish + + let test =<< trim [CODE] + let sfile = expand("<sfile>") + + while 1 + try + throw "x1" + catch /.*/ + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + while 1 + try + throw "x2" + catch /.*/ + break + finally + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + endtry + break + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + while 1 + try + let errcaught = 0 + try + try + throw "x3" + catch /.*/ + let lnum = expand("<sflnum>") + asdf + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim:E492: Not an editor command:', v:exception) + call assert_match('line ' .. (lnum + 1), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'a' + + while 1 + try + let intcaught = 0 + try + try + throw "x4" + catch /.*/ + let lnum = expand("<sflnum>") + call interrupt() + endtry + catch /.*/ + let intcaught = 1 + call assert_match('Vim:Interrupt', v:exception) + call assert_match('line ' .. (lnum + 1), v:throwpoint) + endtry + finally + call assert_equal(1, intcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'b' + + while 1 + try + let errcaught = 0 + try + try + if 1 + let lnum = expand("<sflnum>") + throw "x5" + " missing endif + catch /.*/ + call assert_report('should not get here') + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim(catch):E171: Missing :endif:', v:exception) + call assert_match('line ' .. (lnum + 3), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'c' + + try + while 1 + try + throw "x6" + finally + break + endtry + break + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + try + while 1 + try + throw "x7" + finally + break + endtry + break + endwhile + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + endtry + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + while 1 + try + let errcaught = 0 + try + try + throw "x8" + finally + let lnum = expand("<sflnum>") + asdf + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim:E492: Not an editor command:', v:exception) + call assert_match('line ' .. (lnum + 1), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'd' + + while 1 + try + let intcaught = 0 + try + try + throw "x9" + finally + let lnum = expand("<sflnum>") + call interrupt() + endtry + catch /.*/ + let intcaught = 1 + call assert_match('Vim:Interrupt', v:exception) + call assert_match('line ' .. (lnum + 1), v:throwpoint) + endtry + finally + call assert_equal(1, intcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'e' + + while 1 + try + let errcaught = 0 + try + try + if 1 + let lnum = expand("<sflnum>") + throw "x10" + " missing endif + finally + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim(finally):E171: Missing :endif:', v:exception) + call assert_match('line ' .. (lnum + 3), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'f' + + while 1 + try + let errcaught = 0 + try + try + if 1 + let lnum = expand("<sflnum>") + throw "x11" + " missing endif + endtry + catch /.*/ + let errcaught = 1 + call assert_match('Vim(endtry):E171: Missing :endif:', v:exception) + call assert_match('line ' .. (lnum + 3), v:throwpoint) + endtry + finally + call assert_equal(1, errcaught) + break + endtry + endwhile + call assert_equal('', v:exception) + call assert_equal('', v:throwpoint) + + Xpath 'g' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefg', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" +" Test 60: (Re)throwing v:exception; :echoerr. {{{1 +" +" A user exception can be rethrown after catching by throwing +" v:exception. An error or interrupt exception cannot be rethrown +" because Vim exceptions cannot be faked. A Vim exception using the +" value of v:exception can, however, be triggered by the :echoerr +" command. +"------------------------------------------------------------------------------- + +func Test_rethrow_exception_1() + XpathINIT + try + try + Xpath 'a' + throw "oops" + catch /oops/ + Xpath 'b' + throw v:exception " rethrow user exception + catch /.*/ + call assert_report('should not get here') + endtry + catch /^oops$/ " catches rethrown user exception + Xpath 'c' + catch /.*/ + call assert_report('should not get here') + endtry + call assert_equal('abc', g:Xpath) +endfunc + +func Test_rethrow_exception_2() + XpathINIT + try + let caught = 0 + try + Xpath 'a' + write /n/o/n/w/r/i/t/a/b/l/e/_/f/i/l/e + call assert_report('should not get here') + catch /^Vim(write):/ + let caught = 1 + throw v:exception " throw error: cannot fake Vim exception + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'b' + call assert_equal(1, caught) + endtry + catch /^Vim(throw):/ " catches throw error + let caught = caught + 1 + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + call assert_equal(2, caught) + endtry + call assert_equal('abc', g:Xpath) +endfunc + +func Test_rethrow_exception_3() + XpathINIT + try + let caught = 0 + try + Xpath 'a' + asdf + catch /^Vim/ " catch error exception + let caught = 1 + " Trigger Vim error exception with value specified after :echoerr + let value = substitute(v:exception, '^Vim\((.*)\)\=:', '', "") + echoerr value + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'b' + call assert_equal(1, caught) + endtry + catch /^Vim(echoerr):/ + let caught = caught + 1 + call assert_match(value, v:exception) + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + call assert_equal(2, caught) + endtry + call assert_equal('abc', g:Xpath) +endfunc + +func Test_rethrow_exception_3() + XpathINIT + try + let errcaught = 0 + try + Xpath 'a' + let intcaught = 0 + call interrupt() + catch /^Vim:/ " catch interrupt exception + let intcaught = 1 + " Trigger Vim error exception with value specified after :echoerr + echoerr substitute(v:exception, '^Vim\((.*)\)\=:', '', "") + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'b' + call assert_equal(1, intcaught) + endtry + catch /^Vim(echoerr):/ + let errcaught = 1 + call assert_match('Interrupt', v:exception) + finally + Xpath 'c' + call assert_equal(1, errcaught) + endtry + call assert_equal('abc', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 61: Catching interrupt exceptions {{{1 +" +" When an interrupt occurs inside a :try/:endtry region, an +" interrupt exception is thrown and can be caught. Its value is +" "Vim:Interrupt". If the interrupt occurs after an error or a :throw +" but before a matching :catch is reached, all following :catches of +" that try block are ignored, but the interrupt exception can be +" caught by the next surrounding try conditional. An interrupt is +" ignored when there is a previous interrupt that has not been caught +" or causes a :finally clause to be executed. +"------------------------------------------------------------------------------- + +func Test_catch_intr_exception() + let test =<< trim [CODE] + while 1 + try + try + Xpath 'a' + call interrupt() + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'b' + finally + Xpath 'c' + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + endwhile + + while 1 + try + try + try + Xpath 'e' + asdf + call assert_report('should not get here') + catch /do_not_catch/ + call assert_report('should not get here') + catch /.*/ + Xpath 'f' + call interrupt() + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'g' + call interrupt() + call assert_report('should not get here') + endtry + catch /^Vim:Interrupt$/ + Xpath 'h' + finally + Xpath 'i' + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'j' + break + endtry + endwhile + + while 1 + try + try + try + Xpath 'k' + throw "x" + call assert_report('should not get here') + catch /do_not_catch/ + call assert_report('should not get here') + catch /x/ + Xpath 'l' + call interrupt() + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + catch /^Vim:Interrupt$/ + Xpath 'm' + finally + Xpath 'n' + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'o' + break + endtry + endwhile + + while 1 + try + try + Xpath 'p' + call interrupt() + call assert_report('should not get here') + catch /do_not_catch/ + call interrupt() + call assert_report('should not get here') + catch /^Vim:Interrupt$/ + Xpath 'q' + finally + Xpath 'r' + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 's' + break + endtry + endwhile + + Xpath 't' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghijklmnopqrst', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 62: Catching error exceptions {{{1 +" +" An error inside a :try/:endtry region is converted to an exception +" and can be caught. The error exception has a "Vim(cmdname):" prefix +" where cmdname is the name of the failing command, or a "Vim:" prefix +" if no command name is known. The "Vim" prefixes cannot be faked. +"------------------------------------------------------------------------------- + +func Test_catch_err_exception_1() + XpathINIT + while 1 + try + try + let caught = 0 + unlet novar + catch /^Vim(unlet):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(unlet):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match('E108: No such variable: "novar"', v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) +endfunc + +func Test_catch_err_exception_2() + XpathINIT + while 1 + try + try + let caught = 0 + throw novar " error in :throw + catch /^Vim(throw):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match('E121: Undefined variable: novar', v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) +endfunc + +func Test_catch_err_exception_3() + XpathINIT + while 1 + try + try + let caught = 0 + throw "Vim:faked" " error: cannot fake Vim exception + catch /^Vim(throw):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(throw):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E608: Cannot :throw exceptions with 'Vim' prefix", + \ v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) +endfunc + +func Test_catch_err_exception_4() + XpathINIT + func F() + while 1 + " Missing :endwhile + endfunc + + while 1 + try + try + let caught = 0 + call F() + catch /^Vim(endfunction):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(endfunction):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E170: Missing :endwhile", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) + delfunc F +endfunc + +func Test_catch_err_exception_5() + XpathINIT + func F() + while 1 + " Missing :endwhile + endfunc + + while 1 + try + try + let caught = 0 + ExecAsScript F + catch /^Vim:/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim:', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E170: Missing :endwhile", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) + delfunc F +endfunc + +func Test_catch_err_exception_6() + XpathINIT + func G() + call G() + endfunc + + while 1 + try + let mfd_save = &mfd + set mfd=3 + try + let caught = 0 + call G() + catch /^Vim(call):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(call):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E132: Function call depth is higher than 'maxfuncdepth'", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + let &mfd = mfd_save + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abc', g:Xpath) + delfunc G +endfunc + +func Test_catch_err_exception_7() + XpathINIT + func H() + return H() + endfunc + + while 1 + try + let mfd_save = &mfd + set mfd=3 + try + let caught = 0 + call H() + catch /^Vim(return):/ + Xpath 'a' + let caught = 1 + let v:errmsg = substitute(v:exception, '^Vim(return):', '', "") + finally + Xpath 'b' + call assert_equal(1, caught) + call assert_match("E132: Function call depth is higher than 'maxfuncdepth'", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + let &mfd = mfd_save + break " discard error for $VIMNOERRTHROW + endtry + call assert_report('should not get here') + endwhile + + call assert_equal('abc', g:Xpath) + delfunc H +endfunc + +"------------------------------------------------------------------------------- +" Test 63: Suppressing error exceptions by :silent!. {{{1 +" +" A :silent! command inside a :try/:endtry region suppresses the +" conversion of errors to an exception and the immediate abortion on +" error. When the commands executed by the :silent! themselves open +" a new :try/:endtry region, conversion of errors to exception and +" immediate abortion is switched on again - until the next :silent! +" etc. The :silent! has the effect of setting v:errmsg to the error +" message text (without displaying it) and continuing with the next +" script line. +" +" When a command triggering autocommands is executed by :silent! +" inside a :try/:endtry, the autocommand execution is not suppressed +" on error. +" +" This test reuses the function MSG() from the previous test. +"------------------------------------------------------------------------------- + +func Test_silent_exception() + XpathINIT + XloopINIT + let g:taken = "" + + func S(n) abort + XloopNEXT + let g:taken = g:taken . "E" . a:n + let v:errmsg = "" + exec "asdf" . a:n + + " Check that ":silent!" continues: + Xloop 'a' + + " Check that ":silent!" sets "v:errmsg": + call assert_match("E492: Not an editor command", v:errmsg) + endfunc + + func Foo() + while 1 + try + try + let caught = 0 + " This is not silent: + call S(3) + catch /^Vim:/ + Xpath 'b' + let caught = 1 + let errmsg3 = substitute(v:exception, '^Vim:', '', "") + silent! call S(4) + finally + call assert_equal(1, caught) + Xpath 'c' + call assert_match("E492: Not an editor command", errmsg3) + silent! call S(5) + " Break out of try conditionals that cover ":silent!". This also + " discards the aborting error when $VIMNOERRTHROW is non-zero. + break + endtry + catch /.*/ + call assert_report('should not get here') + endtry + endwhile + " This is a double ":silent!" (see caller). + silent! call S(6) + endfunc + + func Bar() + try + silent! call S(2) + silent! execute "call Foo() | call S(7)" + silent! call S(8) + endtry " normal end of try cond that covers ":silent!" + " This has a ":silent!" from the caller: + call S(9) + endfunc + + silent! call S(1) + silent! call Bar() + silent! call S(10) + + call assert_equal("E1E2E3E4E5E6E7E8E9E10", g:taken) + + augroup TMP + au! + autocmd BufWritePost * Xpath 'd' + augroup END + + Xpath 'e' + silent! write /i/m/p/o/s/s/i/b/l/e + Xpath 'f' + + call assert_equal('a2a3ba5ca6a7a8a9a10a11edf', g:Xpath) + + augroup TMP + au! + augroup END + augroup! TMP + delfunction S + delfunction Foo + delfunction Bar +endfunc + +"------------------------------------------------------------------------------- +" Test 64: Error exceptions after error, interrupt or :throw {{{1 +" +" When an error occurs after an interrupt or a :throw but before +" a matching :catch is reached, all following :catches of that try +" block are ignored, but the error exception can be caught by the next +" surrounding try conditional. Any previous error exception is +" discarded. An error is ignored when there is a previous error that +" has not been caught. +"------------------------------------------------------------------------------- + +func Test_exception_after_error_1() + XpathINIT + while 1 + try + try + Xpath 'a' + let caught = 0 + while 1 + if 1 + " Missing :endif + endwhile " throw error exception + catch /^Vim(/ + Xpath 'b' + let caught = 1 + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +func Test_exception_after_error_2() + XpathINIT + while 1 + try + try + Xpath 'a' + let caught = 0 + try + if 1 + " Missing :endif + catch /.*/ " throw error exception + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + catch /^Vim(/ + Xpath 'b' + let caught = 1 + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +func Test_exception_after_error_3() + XpathINIT + while 1 + try + try + let caught = 0 + try + Xpath 'a' + call interrupt() + catch /do_not_catch/ + call assert_report('should not get here') + if 1 + " Missing :endif + catch /.*/ " throw error exception + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + catch /^Vim(/ + Xpath 'b' + let caught = 1 + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +func Test_exception_after_error_4() + XpathINIT + while 1 + try + try + let caught = 0 + try + Xpath 'a' + throw "x" + catch /do_not_catch/ + call assert_report('should not get here') + if 1 + " Missing :endif + catch /x/ " throw error exception + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + catch /^Vim(/ + Xpath 'b' + let caught = 1 + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +func Test_exception_after_error_5() + XpathINIT + while 1 + try + try + let caught = 0 + Xpath 'a' + endif " :endif without :if; throw error exception + if 1 + " Missing :endif + catch /do_not_catch/ " ignore new error + call assert_report('should not get here') + catch /^Vim(endif):/ + Xpath 'b' + let caught = 1 + catch /^Vim(/ + call assert_report('should not get here') + finally + Xpath 'c' + call assert_equal(1, caught) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'd' + break + endtry + call assert_report('should not get here') + endwhile + call assert_equal('abcd', g:Xpath) +endfunc + +"------------------------------------------------------------------------------- +" Test 65: Errors in the /pattern/ argument of a :catch {{{1 +" +" On an error in the /pattern/ argument of a :catch, the :catch does +" not match. Any following :catches of the same :try/:endtry don't +" match either. Finally clauses are executed. +"------------------------------------------------------------------------------- + +func Test_catch_pattern_error() + CheckEnglish + XpathINIT + + try + try + Xpath 'a' + throw "oops" + catch /^oops$/ + Xpath 'b' + catch /\)/ " not checked; exception has already been caught + call assert_report('should not get here') + endtry + Xpath 'c' + catch /.*/ + call assert_report('should not get here') + endtry + call assert_equal('abc', g:Xpath) + + XpathINIT + func F() + try + try + try + Xpath 'a' + throw "ab" + catch /abc/ " does not catch + call assert_report('should not get here') + catch /\)/ " error; discards exception + call assert_report('should not get here') + catch /.*/ " not checked + call assert_report('should not get here') + finally + Xpath 'b' + endtry + call assert_report('should not get here') + catch /^ab$/ " checked, but original exception is discarded + call assert_report('should not get here') + catch /^Vim(catch):/ + Xpath 'c' + call assert_match('Vim(catch):E475: Invalid argument:', v:exception) + finally + Xpath 'd' + endtry + Xpath 'e' + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'f' + endfunc + + call F() + call assert_equal('abcdef', g:Xpath) + + delfunc F +endfunc + +"------------------------------------------------------------------------------- +" Test 66: Stop range :call on error, interrupt, or :throw {{{1 +" +" When a function which is multiply called for a range since it +" doesn't handle the range itself has an error in a command +" dynamically enclosed by :try/:endtry or gets an interrupt or +" executes a :throw, no more calls for the remaining lines in the +" range are made. On an error in a command not dynamically enclosed +" by :try/:endtry, the function is executed again for the remaining +" lines in the range. +"------------------------------------------------------------------------------- + +func Test_stop_range_on_error() + let test =<< trim [CODE] + let file = tempname() + exec "edit" file + call setline(1, ['line 1', 'line 2', 'line 3']) + let taken = "" + let expected = "G1EF1E(1)F1E(2)F1E(3)G2EF2E(1)G3IF3I(1)G4TF4T(1)G5AF5A(1)" + + func F(reason, n) abort + let g:taken = g:taken .. "F" .. a:n .. + \ substitute(a:reason, '\(\l\).*', '\u\1', "") .. + \ "(" .. line(".") .. ")" + + if a:reason == "error" + asdf + elseif a:reason == "interrupt" + call interrupt() + elseif a:reason == "throw" + throw "xyz" + elseif a:reason == "aborting error" + XloopNEXT + call assert_equal(g:taken, g:expected) + try + bwipeout! + call delete(g:file) + asdf + endtry + endif + endfunc + + func G(reason, n) + let g:taken = g:taken .. "G" .. a:n .. + \ substitute(a:reason, '\(\l\).*', '\u\1', "") + 1,3call F(a:reason, a:n) + endfunc + + Xpath 'a' + call G("error", 1) + try + Xpath 'b' + try + call G("error", 2) + call assert_report('should not get here') + finally + Xpath 'c' + try + call G("interrupt", 3) + call assert_report('should not get here') + finally + Xpath 'd' + try + call G("throw", 4) + call assert_report('should not get here') + endtry + endtry + endtry + catch /xyz/ + Xpath 'e' + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'f' + call G("aborting error", 5) + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdef', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 67: :throw across :call command {{{1 +" +" On a call command, an exception might be thrown when evaluating the +" function name, during evaluation of the arguments, or when the +" function is being executed. The exception can be caught by the +" caller. +"------------------------------------------------------------------------------- + +func THROW(x, n) + if a:n == 1 + Xpath 'A' + elseif a:n == 2 + Xpath 'B' + elseif a:n == 3 + Xpath 'C' + endif + throw a:x +endfunc + +func NAME(x, n) + if a:n == 1 + call assert_report('should not get here') + elseif a:n == 2 + Xpath 'D' + elseif a:n == 3 + Xpath 'E' + elseif a:n == 4 + Xpath 'F' + endif + return a:x +endfunc + +func ARG(x, n) + if a:n == 1 + call assert_report('should not get here') + elseif a:n == 2 + call assert_report('should not get here') + elseif a:n == 3 + Xpath 'G' + elseif a:n == 4 + Xpath 'I' + endif + return a:x +endfunc + +func Test_throw_across_call_cmd() + XpathINIT + + func F(x, n) + if a:n == 2 + call assert_report('should not get here') + elseif a:n == 4 + Xpath 'a' + endif + endfunc + + while 1 + try + let v:errmsg = "" + + while 1 + try + Xpath 'b' + call {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1) + call assert_report('should not get here') + catch /^name$/ + Xpath 'c' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + while 1 + try + Xpath 'd' + call {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2) + call assert_report('should not get here') + catch /^arg$/ + Xpath 'e' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + while 1 + try + Xpath 'f' + call {NAME("THROW", 3)}(ARG("call", 3), 3) + call assert_report('should not get here') + catch /^call$/ + Xpath 'g' + catch /^0$/ " default return value + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + while 1 + try + Xpath 'h' + call {NAME("F", 4)}(ARG(4711, 4), 4) + Xpath 'i' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + catch /^0$/ " default return value + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + + call assert_equal('bAcdDBefEGCghFIai', g:Xpath) + delfunction F +endfunc + +"------------------------------------------------------------------------------- +" Test 68: :throw across function calls in expressions {{{1 +" +" On a function call within an expression, an exception might be +" thrown when evaluating the function name, during evaluation of the +" arguments, or when the function is being executed. The exception +" can be caught by the caller. +" +" This test reuses the functions THROW(), NAME(), and ARG() from the +" previous test. +"------------------------------------------------------------------------------- + +func Test_throw_across_call_expr() + XpathINIT + + func F(x, n) + if a:n == 2 + call assert_report('should not get here') + elseif a:n == 4 + Xpath 'a' + endif + return a:x + endfunction + + while 1 + try + let error = 0 + let v:errmsg = "" + + while 1 + try + Xpath 'b' + let var1 = {NAME(THROW("name", 1), 1)}(ARG(4711, 1), 1) + call assert_report('should not get here') + catch /^name$/ + Xpath 'c' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + call assert_true(!exists('var1')) + + while 1 + try + Xpath 'd' + let var2 = {NAME("F", 2)}(ARG(THROW("arg", 2), 2), 2) + call assert_report('should not get here') + catch /^arg$/ + Xpath 'e' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + call assert_true(!exists('var2')) + + while 1 + try + Xpath 'f' + let var3 = {NAME("THROW", 3)}(ARG("call", 3), 3) + call assert_report('should not get here') + catch /^call$/ + Xpath 'g' + catch /^0$/ " default return value + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + call assert_true(!exists('var3')) + + while 1 + try + Xpath 'h' + let var4 = {NAME("F", 4)}(ARG(4711, 4), 4) + Xpath 'i' + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + let v:errmsg = "" + break + endtry + endwhile + call assert_true(exists('var4') && var4 == 4711) + + catch /^0$/ " default return value + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + finally + call assert_equal("", v:errmsg) + break + endtry + endwhile + + call assert_equal('bAcdDBefEGCghFIai', g:Xpath) + delfunc F +endfunc + +"------------------------------------------------------------------------------- +" Test 76: Errors, interrupts, :throw during expression evaluation {{{1 +" +" When a function call made during expression evaluation is aborted +" due to an error inside a :try/:endtry region or due to an interrupt +" or a :throw, the expression evaluation is aborted as well. No +" message is displayed for the cancelled expression evaluation. On an +" error not inside :try/:endtry, the expression evaluation continues. +"------------------------------------------------------------------------------- + +func Test_expr_eval_error() + let test =<< trim [CODE] + let taken = "" + + func ERR(n) + let g:taken = g:taken .. "E" .. a:n + asdf + endfunc + + func ERRabort(n) abort + let g:taken = g:taken .. "A" .. a:n + asdf + endfunc " returns -1; may cause follow-up msg for illegal var/func name + + func WRAP(n, arg) + let g:taken = g:taken .. "W" .. a:n + let g:saved_errmsg = v:errmsg + return arg + endfunc + + func INT(n) + let g:taken = g:taken .. "I" .. a:n + call interrupt() + endfunc + + func THR(n) + let g:taken = g:taken .. "T" .. a:n + throw "should not be caught" + endfunc + + func CONT(n) + let g:taken = g:taken .. "C" .. a:n + endfunc + + func MSG(n) + let g:taken = g:taken .. "M" .. a:n + let errmsg = (a:n >= 37 && a:n <= 44) ? g:saved_errmsg : v:errmsg + let msgptn = (a:n >= 10 && a:n <= 27) ? "^$" : "asdf" + call assert_match(msgptn, errmsg) + let v:errmsg = "" + let g:saved_errmsg = "" + endfunc + + let v:errmsg = "" + + try + let t = 1 + while t <= 9 + Xloop 'a' + try + if t == 1 + let v{ERR(t) + CONT(t)} = 0 + elseif t == 2 + let v{ERR(t) + CONT(t)} + elseif t == 3 + let var = exists('v{ERR(t) + CONT(t)}') + elseif t == 4 + unlet v{ERR(t) + CONT(t)} + elseif t == 5 + function F{ERR(t) + CONT(t)}() + endfunction + elseif t == 6 + function F{ERR(t) + CONT(t)} + elseif t == 7 + let var = exists('*F{ERR(t) + CONT(t)}') + elseif t == 8 + delfunction F{ERR(t) + CONT(t)} + elseif t == 9 + let var = ERR(t) + CONT(t) + endif + catch /asdf/ + " v:errmsg is not set when the error message is converted to an + " exception. Set it to the original error message. + let v:errmsg = substitute(v:exception, '^Vim:', '', "") + catch /^Vim\((\a\+)\)\=:/ + " An error exception has been thrown after the original error. + let v:errmsg = "" + finally + call MSG(t) + let t = t + 1 + XloopNEXT + continue " discard an aborting error + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + + try + let t = 10 + while t <= 18 + Xloop 'b' + try + if t == 10 + let v{INT(t) + CONT(t)} = 0 + elseif t == 11 + let v{INT(t) + CONT(t)} + elseif t == 12 + let var = exists('v{INT(t) + CONT(t)}') + elseif t == 13 + unlet v{INT(t) + CONT(t)} + elseif t == 14 + function F{INT(t) + CONT(t)}() + endfunction + elseif t == 15 + function F{INT(t) + CONT(t)} + elseif t == 16 + let var = exists('*F{INT(t) + CONT(t)}') + elseif t == 17 + delfunction F{INT(t) + CONT(t)} + elseif t == 18 + let var = INT(t) + CONT(t) + endif + catch /^Vim\((\a\+)\)\=:\(Interrupt\)\@!/ + " An error exception has been triggered after the interrupt. + let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") + finally + call MSG(t) + let t = t + 1 + XloopNEXT + continue " discard interrupt + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + + try + let t = 19 + while t <= 27 + Xloop 'c' + try + if t == 19 + let v{THR(t) + CONT(t)} = 0 + elseif t == 20 + let v{THR(t) + CONT(t)} + elseif t == 21 + let var = exists('v{THR(t) + CONT(t)}') + elseif t == 22 + unlet v{THR(t) + CONT(t)} + elseif t == 23 + function F{THR(t) + CONT(t)}() + endfunction + elseif t == 24 + function F{THR(t) + CONT(t)} + elseif t == 25 + let var = exists('*F{THR(t) + CONT(t)}') + elseif t == 26 + delfunction F{THR(t) + CONT(t)} + elseif t == 27 + let var = THR(t) + CONT(t) + endif + catch /^Vim\((\a\+)\)\=:/ + " An error exception has been triggered after the :throw. + let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") + finally + call MSG(t) + let t = t + 1 + XloopNEXT + continue " discard exception + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + + let v{ERR(28) + CONT(28)} = 0 + call MSG(28) + let v{ERR(29) + CONT(29)} + call MSG(29) + let var = exists('v{ERR(30) + CONT(30)}') + call MSG(30) + unlet v{ERR(31) + CONT(31)} + call MSG(31) + function F{ERR(32) + CONT(32)}() + endfunction + call MSG(32) + function F{ERR(33) + CONT(33)} + call MSG(33) + let var = exists('*F{ERR(34) + CONT(34)}') + call MSG(34) + delfunction F{ERR(35) + CONT(35)} + call MSG(35) + let var = ERR(36) + CONT(36) + call MSG(36) + + let saved_errmsg = "" + + let v{WRAP(37, ERRabort(37)) + CONT(37)} = 0 + call MSG(37) + let v{WRAP(38, ERRabort(38)) + CONT(38)} + call MSG(38) + let var = exists('v{WRAP(39, ERRabort(39)) + CONT(39)}') + call MSG(39) + unlet v{WRAP(40, ERRabort(40)) + CONT(40)} + call MSG(40) + function F{WRAP(41, ERRabort(41)) + CONT(41)}() + endfunction + call MSG(41) + function F{WRAP(42, ERRabort(42)) + CONT(42)} + call MSG(42) + let var = exists('*F{WRAP(43, ERRabort(43)) + CONT(43)}') + call MSG(43) + delfunction F{WRAP(44, ERRabort(44)) + CONT(44)} + call MSG(44) + let var = ERRabort(45) + CONT(45) + call MSG(45) + Xpath 'd' + + let expected = "" + \ .. "E1M1E2M2E3M3E4M4E5M5E6M6E7M7E8M8E9M9" + \ .. "I10M10I11M11I12M12I13M13I14M14I15M15I16M16I17M17I18M18" + \ .. "T19M19T20M20T21M21T22M22T23M23T24M24T25M25T26M26T27M27" + \ .. "E28C28M28E29C29M29E30C30M30E31C31M31E32C32M32E33C33M33" + \ .. "E34C34M34E35C35M35E36C36M36" + \ .. "A37W37C37M37A38W38C38M38A39W39C39M39A40W40C40M40A41W41C41M41" + \ .. "A42W42C42M42A43W43C43M43A44W44C44M44A45C45M45" + call assert_equal(expected, taken) + [CODE] + let verify =<< trim [CODE] + let expected = "a1a2a3a4a5a6a7a8a9" + \ .. "b10b11b12b13b14b15b16b17b18" + \ .. "c19c20c21c22c23c24c25c26c27d" + call assert_equal(expected, g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 77: Errors, interrupts, :throw in name{brace-expression} {{{1 +" +" When a function call made during evaluation of an expression in +" braces as part of a function name after ":function" is aborted due +" to an error inside a :try/:endtry region or due to an interrupt or +" a :throw, the expression evaluation is aborted as well, and the +" function definition is ignored, skipping all commands to the +" ":endfunction". On an error not inside :try/:endtry, the expression +" evaluation continues and the function gets defined, and can be +" called and deleted. +"------------------------------------------------------------------------------- +func Test_brace_expr_error() + let test =<< trim [CODE] + func ERR() abort + Xloop 'a' + asdf + endfunc " returns -1 + + func OK() + Xloop 'b' + let v:errmsg = "" + return 0 + endfunc + + let v:errmsg = "" + + Xpath 'c' + func F{1 + ERR() + OK()}(arg) + " F0 should be defined. + if exists("a:arg") && a:arg == "calling" + Xpath 'd' + else + call assert_report('should not get here') + endif + endfunction + call assert_equal("", v:errmsg) + XloopNEXT + + Xpath 'e' + call F{1 + ERR() + OK()}("calling") + call assert_equal("", v:errmsg) + XloopNEXT + + Xpath 'f' + delfunction F{1 + ERR() + OK()} + call assert_equal("", v:errmsg) + XloopNEXT + + try + while 1 + try + Xpath 'g' + func G{1 + ERR() + OK()}(arg) + " G0 should not be defined, and the function body should be + " skipped. + call assert_report('should not get here') + " Use an unmatched ":finally" to check whether the body is + " skipped when an error occurs in ERR(). This works whether or + " not the exception is converted to an exception. + finally + call assert_report('should not get here') + endtry + try + call assert_report('should not get here') + endfunction + + call assert_report('should not get here') + catch /asdf/ + " Jumped to when the function is not defined and the body is + " skipped. + Xpath 'h' + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'i' + break + endtry " jumped to when the body is not skipped + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + [CODE] + let verify =<< trim [CODE] + call assert_equal('ca1b1ea2b2dfa3b3ga4hi', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 78: Messages on parsing errors in expression evaluation {{{1 +" +" When an expression evaluation detects a parsing error, an error +" message is given and converted to an exception, and the expression +" evaluation is aborted. +"------------------------------------------------------------------------------- +func Test_expr_eval_error_msg() + CheckEnglish + + let test =<< trim [CODE] + let taken = "" + + func F(n) + let g:taken = g:taken . "F" . a:n + endfunc + + func MSG(n, enr, emsg) + let g:taken = g:taken . "M" . a:n + call assert_match('^' .. a:enr .. ':', v:errmsg) + call assert_match(a:emsg, v:errmsg) + endfunc + + func CONT(n) + let g:taken = g:taken . "C" . a:n + endfunc + + let v:errmsg = "" + try + let t = 1 + while t <= 14 + let g:taken = g:taken . "T" . t + let v:errmsg = "" + try + if t == 1 + let v{novar + CONT(t)} = 0 + elseif t == 2 + let v{novar + CONT(t)} + elseif t == 3 + let var = exists('v{novar + CONT(t)}') + elseif t == 4 + unlet v{novar + CONT(t)} + elseif t == 5 + function F{novar + CONT(t)}() + endfunction + elseif t == 6 + function F{novar + CONT(t)} + elseif t == 7 + let var = exists('*F{novar + CONT(t)}') + elseif t == 8 + delfunction F{novar + CONT(t)} + elseif t == 9 + echo novar + CONT(t) + elseif t == 10 + echo v{novar + CONT(t)} + elseif t == 11 + echo F{novar + CONT(t)} + elseif t == 12 + let var = novar + CONT(t) + elseif t == 13 + let var = v{novar + CONT(t)} + elseif t == 14 + let var = F{novar + CONT(t)}() + endif + catch /^Vim\((\a\+)\)\=:/ + Xloop 'a' + " v:errmsg is not set when the error message is converted to an + " exception. Set it to the original error message. + let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") + finally + Xloop 'b' + if t <= 8 && t != 3 && t != 7 + call MSG(t, 'E475', 'Invalid argument\>') + else + call MSG(t, 'E121', "Undefined variable") + endif + let t = t + 1 + XloopNEXT + continue " discard an aborting error + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + + func T(n, expr, enr, emsg) + try + let g:taken = g:taken . "T" . a:n + let v:errmsg = "" + try + execute "let var = " . a:expr + catch /^Vim\((\a\+)\)\=:/ + Xloop 'c' + " v:errmsg is not set when the error message is converted to an + " exception. Set it to the original error message. + let v:errmsg = substitute(v:exception, '^Vim\((\a\+)\)\=:', '', "") + finally + Xloop 'd' + call MSG(a:n, a:enr, a:emsg) + XloopNEXT + " Discard an aborting error: + return + endtry + catch /.*/ + call assert_report('should not get here') + endtry + endfunc + + call T(15, 'Nofunc() + CONT(15)', 'E117', "Unknown function") + call T(16, 'F(1 2 + CONT(16))', 'E116', "Invalid arguments") + call T(17, 'F(1, 2) + CONT(17)', 'E118', "Too many arguments") + call T(18, 'F() + CONT(18)', 'E119', "Not enough arguments") + call T(19, '{(1} + CONT(19)', 'E110', "Missing ')'") + call T(20, '("abc"[1) + CONT(20)', 'E111', "Missing ']'") + call T(21, '(1 +) + CONT(21)', 'E15', "Invalid expression") + call T(22, '1 2 + CONT(22)', 'E488', "Trailing characters: 2 +") + call T(23, '(1 ? 2) + CONT(23)', 'E109', "Missing ':' after '?'") + call T(24, '("abc) + CONT(24)', 'E114', "Missing quote") + call T(25, "('abc) + CONT(25)", 'E115', "Missing quote") + call T(26, '& + CONT(26)', 'E112', "Option name missing") + call T(27, '&asdf + CONT(27)', 'E113', "Unknown option") + + let expected = "" + \ .. "T1M1T2M2T3M3T4M4T5M5T6M6T7M7T8M8T9M9T10M10T11M11T12M12T13M13T14M14" + \ .. "T15M15T16M16T17M17T18M18T19M19T20M20T21M21T22M22T23M23T24M24T25M25" + \ .. "T26M26T27M27" + + call assert_equal(expected, taken) + [CODE] + let verify =<< trim [CODE] + let expected = "a1b1a2b2a3b3a4b4a5b5a6b6a7b7a8b8a9b9a10b10a11b11a12b12" + \ .. "a13b13a14b14c15d15c16d16c17d17c18d18c19d19c20d20" + \ .. "c21d21c22d22c23d23c24d24c25d25c26d26c27d27" + call assert_equal(expected, g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 79: Throwing one of several errors for the same command {{{1 +" +" When several errors appear in a row (for instance during expression +" evaluation), the first as the most specific one is used when +" throwing an error exception. If, however, a syntax error is +" detected afterwards, this one is used for the error exception. +" On a syntax error, the next command is not executed, on a normal +" error, however, it is (relevant only in a function without the +" "abort" flag). v:errmsg is not set. +" +" If throwing error exceptions is configured off, v:errmsg is always +" set to the latest error message, that is, to the more general +" message or the syntax error, respectively. +"------------------------------------------------------------------------------- +func Test_throw_multi_error() + CheckEnglish + + let test =<< trim [CODE] + func NEXT(cmd) + exec a:cmd . " | Xloop 'a'" + endfun + + call NEXT('echo novar') " (checks nextcmd) + XloopNEXT + call NEXT('let novar #') " (skips nextcmd) + XloopNEXT + call NEXT('unlet novar #') " (skips nextcmd) + XloopNEXT + call NEXT('let {novar}') " (skips nextcmd) + XloopNEXT + call NEXT('unlet{ novar}') " (skips nextcmd) + + call assert_equal('a1', g:Xpath) + XpathINIT + XloopINIT + + func EXEC(cmd) + exec a:cmd + endfunc + + try + while 1 " dummy loop + try + let v:errmsg = "" + call EXEC('echo novar') " normal error + catch /^Vim\((\a\+)\)\=:/ + Xpath 'b' + call assert_match('E121: Undefined variable: novar', v:exception) + finally + Xpath 'c' + call assert_equal("", v:errmsg) + break + endtry + endwhile + + Xpath 'd' + let cmd = "let" + while cmd != "" + try + let v:errmsg = "" + call EXEC(cmd . ' novar #') " normal plus syntax error + catch /^Vim\((\a\+)\)\=:/ + Xloop 'e' + call assert_match('E488: Trailing characters', v:exception) + finally + Xloop 'f' + call assert_equal("", v:errmsg) + if cmd == "let" + let cmd = "unlet" + else + let cmd = "" + endif + XloopNEXT + continue + endtry + endwhile + + Xpath 'g' + let cmd = "let" + while cmd != "" + try + let v:errmsg = "" + call EXEC(cmd . ' {novar}') " normal plus syntax error + catch /^Vim\((\a\+)\)\=:/ + Xloop 'h' + call assert_match('E475: Invalid argument: {novar}', v:exception) + finally + Xloop 'i' + call assert_equal("", v:errmsg) + if cmd == "let" + let cmd = "unlet" + else + let cmd = "" + endif + XloopNEXT + continue + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'j' + [CODE] + let verify =<< trim [CODE] + call assert_equal('bcde1f1e2f2gh3i3h4i4j', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 80: Syntax error in expression for illegal :elseif {{{1 +" +" If there is a syntax error in the expression after an illegal +" :elseif, an error message is given (or an error exception thrown) +" for the illegal :elseif rather than the expression error. +"------------------------------------------------------------------------------- +func Test_if_syntax_error() + CheckEnglish + + let test =<< trim [CODE] + let v:errmsg = "" + if 0 + else + elseif 1 ||| 2 + endif + Xpath 'a' + call assert_match('E584: :elseif after :else', v:errmsg) + + let v:errmsg = "" + if 1 + else + elseif 1 ||| 2 + endif + Xpath 'b' + call assert_match('E584: :elseif after :else', v:errmsg) + + let v:errmsg = "" + elseif 1 ||| 2 + Xpath 'c' + call assert_match('E582: :elseif without :if', v:errmsg) + + let v:errmsg = "" + while 1 + elseif 1 ||| 2 + endwhile + Xpath 'd' + call assert_match('E582: :elseif without :if', v:errmsg) + + while 1 + try + try + let v:errmsg = "" + if 0 + else + elseif 1 ||| 2 + endif + catch /^Vim\((\a\+)\)\=:/ + Xpath 'e' + call assert_match('E584: :elseif after :else', v:exception) + finally + Xpath 'f' + call assert_equal("", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'g' + break + endtry + endwhile + + while 1 + try + try + let v:errmsg = "" + if 1 + else + elseif 1 ||| 2 + endif + catch /^Vim\((\a\+)\)\=:/ + Xpath 'h' + call assert_match('E584: :elseif after :else', v:exception) + finally + Xpath 'i' + call assert_equal("", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'j' + break + endtry + endwhile + + while 1 + try + try + let v:errmsg = "" + elseif 1 ||| 2 + catch /^Vim\((\a\+)\)\=:/ + Xpath 'k' + call assert_match('E582: :elseif without :if', v:exception) + finally + Xpath 'l' + call assert_equal("", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'm' + break + endtry + endwhile + + while 1 + try + try + let v:errmsg = "" + while 1 + elseif 1 ||| 2 + endwhile + catch /^Vim\((\a\+)\)\=:/ + Xpath 'n' + call assert_match('E582: :elseif without :if', v:exception) + finally + Xpath 'o' + call assert_equal("", v:errmsg) + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'p' + break + endtry + endwhile + Xpath 'q' + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghijklmnopq', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 81: Discarding exceptions after an error or interrupt {{{1 +" +" When an exception is thrown from inside a :try conditional without +" :catch and :finally clauses and an error or interrupt occurs before +" the :endtry is reached, the exception is discarded. +"------------------------------------------------------------------------------- + +func Test_discard_exception_after_error_1() + let test =<< trim [CODE] + try + Xpath 'a' + try + Xpath 'b' + throw "arrgh" + call assert_report('should not get here') + if 1 + call assert_report('should not get here') + " error after :throw: missing :endif + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('ab', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +" interrupt the code before the endtry is invoked +func Test_discard_exception_after_error_2() + XpathINIT + let lines =<< trim [CODE] + try + Xpath 'a' + try + Xpath 'b' + throw "arrgh" + call assert_report('should not get here') + endtry " interrupt here + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + call writefile(lines, 'Xscript') + + breakadd file 7 Xscript + try + let caught_intr = 0 + debuggreedy + call feedkeys(":source Xscript\<CR>quit\<CR>", "xt") + catch /^Vim:Interrupt$/ + call assert_match('Xscript, line 7', v:throwpoint) + let caught_intr = 1 + endtry + 0debuggreedy + call assert_equal(1, caught_intr) + call assert_equal('ab', g:Xpath) + breakdel * + call delete('Xscript') +endfunc + +"------------------------------------------------------------------------------- +" Test 82: Ignoring :catch clauses after an error or interrupt {{{1 +" +" When an exception is thrown and an error or interrupt occurs before +" the matching :catch clause is reached, the exception is discarded +" and the :catch clause is ignored (also for the error or interrupt +" exception being thrown then). +"------------------------------------------------------------------------------- + +func Test_ignore_catch_after_error_1() + let test =<< trim [CODE] + try + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here') + if 1 + call assert_report('should not get here') + " error after :throw: missing :endif + catch /.*/ + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +func Test_ignore_catch_after_error_2() + let test =<< trim [CODE] + func E() + try + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here') + if 1 + call assert_report('should not get here') + " error after :throw: missing :endif + catch /.*/ + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + endfunc + + call E() + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('a', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +" interrupt right before a catch is invoked in a script +func Test_ignore_catch_after_intr_1() + XpathINIT + let lines =<< trim [CODE] + try + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here') + catch /.*/ " interrupt here + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + call writefile(lines, 'Xscript') + + breakadd file 6 Xscript + try + let caught_intr = 0 + debuggreedy + call feedkeys(":source Xscript\<CR>quit\<CR>", "xt") + catch /^Vim:Interrupt$/ + call assert_match('Xscript, line 6', v:throwpoint) + let caught_intr = 1 + endtry + 0debuggreedy + call assert_equal(1, caught_intr) + call assert_equal('a', g:Xpath) + breakdel * + call delete('Xscript') +endfunc + +" interrupt right before a catch is invoked inside a function. +func Test_ignore_catch_after_intr_2() + XpathINIT + func F() + try + try + Xpath 'a' + throw "arrgh" + call assert_report('should not get here') + catch /.*/ " interrupt here + call assert_report('should not get here') + catch /.*/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + endfunc + + breakadd func 6 F + try + let caught_intr = 0 + debuggreedy + call feedkeys(":call F()\<CR>quit\<CR>", "xt") + catch /^Vim:Interrupt$/ + call assert_match('\.F, line 6', v:throwpoint) + let caught_intr = 1 + endtry + 0debuggreedy + call assert_equal(1, caught_intr) + call assert_equal('a', g:Xpath) + breakdel * + delfunc F +endfunc + +"------------------------------------------------------------------------------- +" Test 83: Executing :finally clauses after an error or interrupt {{{1 +" +" When an exception is thrown and an error or interrupt occurs before +" the :finally of the innermost :try is reached, the exception is +" discarded and the :finally clause is executed. +"------------------------------------------------------------------------------- + +func Test_finally_after_error() + let test =<< trim [CODE] + try + Xpath 'a' + try + Xpath 'b' + throw "arrgh" + call assert_report('should not get here') + if 1 + call assert_report('should not get here') + " error after :throw: missing :endif + finally + Xpath 'c' + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + let verify =<< trim [CODE] + call assert_equal('abc', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +" interrupt the code right before the finally is invoked +func Test_finally_after_intr() + XpathINIT + let lines =<< trim [CODE] + try + Xpath 'a' + try + Xpath 'b' + throw "arrgh" + call assert_report('should not get here') + finally " interrupt here + Xpath 'c' + endtry + call assert_report('should not get here') + catch /arrgh/ + call assert_report('should not get here') + endtry + call assert_report('should not get here') + [CODE] + call writefile(lines, 'Xscript') + + breakadd file 7 Xscript + try + let caught_intr = 0 + debuggreedy + call feedkeys(":source Xscript\<CR>quit\<CR>", "xt") + catch /^Vim:Interrupt$/ + call assert_match('Xscript, line 7', v:throwpoint) + let caught_intr = 1 + endtry + 0debuggreedy + call assert_equal(1, caught_intr) + call assert_equal('abc', g:Xpath) + breakdel * + call delete('Xscript') +endfunc + +"------------------------------------------------------------------------------- +" Test 84: Exceptions in autocommand sequences. {{{1 +" +" When an exception occurs in a sequence of autocommands for +" a specific event, the rest of the sequence is not executed. The +" command that triggered the autocommand execution aborts, and the +" exception is propagated to the caller. +" +" For the FuncUndefined event under a function call expression or +" :call command, the function is not executed, even when it has +" been defined by the autocommands before the exception occurred. +"------------------------------------------------------------------------------- + +func Test_autocmd_exception() + let test =<< trim [CODE] + func INT() + call interrupt() + endfunc + + aug TMP + autocmd! + + autocmd User x1 Xpath 'a' + autocmd User x1 throw "x1" + autocmd User x1 call assert_report('should not get here') + + autocmd User x2 Xpath 'b' + autocmd User x2 asdf + autocmd User x2 call assert_report('should not get here') + + autocmd User x3 Xpath 'c' + autocmd User x3 call INT() + autocmd User x3 call assert_report('should not get here') + + autocmd FuncUndefined U1 func U1() + autocmd FuncUndefined U1 call assert_report('should not get here') + autocmd FuncUndefined U1 endfunc + autocmd FuncUndefined U1 Xpath 'd' + autocmd FuncUndefined U1 throw "U1" + autocmd FuncUndefined U1 call assert_report('should not get here') + + autocmd FuncUndefined U2 func U2() + autocmd FuncUndefined U2 call assert_report('should not get here') + autocmd FuncUndefined U2 endfunc + autocmd FuncUndefined U2 Xpath 'e' + autocmd FuncUndefined U2 ASDF + autocmd FuncUndefined U2 call assert_report('should not get here') + + autocmd FuncUndefined U3 func U3() + autocmd FuncUndefined U3 call assert_report('should not get here') + autocmd FuncUndefined U3 endfunc + autocmd FuncUndefined U3 Xpath 'f' + autocmd FuncUndefined U3 call INT() + autocmd FuncUndefined U3 call assert_report('should not get here') + aug END + + try + try + Xpath 'g' + doautocmd User x1 + catch /x1/ + Xpath 'h' + endtry + + while 1 + try + Xpath 'i' + doautocmd User x2 + catch /asdf/ + Xpath 'j' + finally + Xpath 'k' + break + endtry + endwhile + + while 1 + try + Xpath 'l' + doautocmd User x3 + catch /Vim:Interrupt/ + Xpath 'm' + finally + Xpath 'n' + " ... but break loop for caught interrupt exception, + " or discard interrupt and break loop if $VIMNOINTTHROW + break + endtry + endwhile + + if exists("*U1") | delfunction U1 | endif + if exists("*U2") | delfunction U2 | endif + if exists("*U3") | delfunction U3 | endif + + try + Xpath 'o' + call U1() + catch /U1/ + Xpath 'p' + endtry + + while 1 + try + Xpath 'q' + call U2() + catch /ASDF/ + Xpath 'r' + finally + Xpath 's' + " ... but break loop for caught error exception, + " or discard error and break loop if $VIMNOERRTHROW + break + endtry + endwhile + + while 1 + try + Xpath 't' + call U3() + catch /Vim:Interrupt/ + Xpath 'u' + finally + Xpath 'v' + " ... but break loop for caught interrupt exception, + " or discard interrupt and break loop if $VIMNOINTTHROW + break + endtry + endwhile + catch /.*/ + call assert_report('should not get here') + endtry + Xpath 'w' + [CODE] + let verify =<< trim [CODE] + call assert_equal('gahibjklcmnodpqerstfuvw', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + +"------------------------------------------------------------------------------- +" Test 85: Error exceptions in autocommands for I/O command events {{{1 +" +" When an I/O command is inside :try/:endtry, autocommands to be +" executed after it should be skipped on an error (exception) in the +" command itself or in autocommands to be executed before the command. +" In the latter case, the I/O command should not be executed either. +" Example 1: BufWritePre, :write, BufWritePost +" Example 2: FileReadPre, :read, FileReadPost. +"------------------------------------------------------------------------------- + +func Test_autocmd_error_io_exception() + let test =<< trim [CODE] + " Remove the autocommands for the events specified as arguments in all used + " autogroups. + func Delete_autocommands(...) + let augfile = tempname() + while 1 + try + exec "redir >" . augfile + aug + redir END + exec "edit" augfile + g/^$/d + norm G$ + let wrap = "w" + while search('\%( \|^\)\@<=.\{-}\%( \)\@=', wrap) > 0 + let wrap = "W" + exec "norm y/ \n" + let argno = 1 + while argno <= a:0 + exec "au!" escape(@", " ") a:{argno} + let argno = argno + 1 + endwhile + endwhile + catch /.*/ + finally + bwipeout! + call delete(augfile) + break + endtry + endwhile + endfunc + + call Delete_autocommands("BufWritePre", "BufWritePost") + + while 1 + try + try + let post = 0 + aug TMP + au! BufWritePost * let post = 1 + aug END + write /n/o/n/e/x/i/s/t/e/n/t + catch /^Vim(write):/ + Xpath 'a' + call assert_match("E212: Can't open file for writing", v:exception) + finally + Xpath 'b' + call assert_equal(0, post) + au! TMP + aug! TMP + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'c' + break + endtry + endwhile + + while 1 + try + try + let post = 0 + aug TMP + au! BufWritePre * asdf + au! BufWritePost * let post = 1 + aug END + let tmpfile = tempname() + exec "write" tmpfile + catch /^Vim\((write)\)\=:/ + Xpath 'd' + call assert_match('E492: Not an editor command', v:exception) + finally + Xpath 'e' + if filereadable(tmpfile) + call assert_report('should not get here') + endif + call assert_equal(0, post) + au! TMP + aug! TMP + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'f' + break + endtry + endwhile + + call delete(tmpfile) + + call Delete_autocommands("BufWritePre", "BufWritePost", + \ "BufReadPre", "BufReadPost", "FileReadPre", "FileReadPost") + + while 1 + try + try + let post = 0 + aug TMP + au! FileReadPost * let post = 1 + aug END + let caught = 0 + read /n/o/n/e/x/i/s/t/e/n/t + catch /^Vim(read):/ + Xpath 'g' + call assert_match("E484: Can't open file", v:exception) + finally + Xpath 'h' + call assert_equal(0, post) + au! TMP + aug! TMP + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'i' + break + endtry + endwhile + + while 1 + try + let infile = tempname() + let tmpfile = tempname() + call writefile(["XYZ"], infile) + exec "edit" tmpfile + try + Xpath 'j' + try + let post = 0 + aug TMP + au! FileReadPre * asdf + au! FileReadPost * let post = 1 + aug END + exec "0read" infile + catch /^Vim\((read)\)\=:/ + Xpath 'k' + call assert_match('E492: Not an editor command', v:exception) + finally + Xpath 'l' + if getline("1") == "XYZ" + call assert_report('should not get here') + endif + call assert_equal(0, post) + au! TMP + aug! TMP + endtry + finally + Xpath 'm' + bwipeout! + endtry + catch /.*/ + call assert_report('should not get here') + finally + Xpath 'n' + break + endtry + endwhile + + call delete(infile) + call delete(tmpfile) + [CODE] + let verify =<< trim [CODE] + call assert_equal('abcdefghijklmn', g:Xpath) + [CODE] + call RunInNewVim(test, verify) +endfunc + "------------------------------------------------------------------------------- " Test 87 using (expr) ? funcref : funcref {{{1 " @@ -1370,10 +6723,10 @@ endfunc " Test 95: lines of :append, :change, :insert {{{1 "------------------------------------------------------------------------------- -function! DefineFunction(name, body) +func DefineFunction(name, body) let func = join(['function! ' . a:name . '()'] + a:body + ['endfunction'], "\n") exec func -endfunction +endfunc func Test_script_lines() " :append @@ -1443,7 +6796,7 @@ endfunc " Undefined behavior was detected by ubsan with line continuation " after an empty line. "------------------------------------------------------------------------------- -func Test_script_emty_line_continuation() +func Test_script_empty_line_continuation() \ endfunc @@ -1495,55 +6848,6 @@ func Test_bitwise_functions() call assert_fails("call invert({})", 'E728:') endfunc -" Test trailing text after :endfunction {{{1 -func Test_endfunction_trailing() - call assert_false(exists('*Xtest')) - - exe "func Xtest()\necho 'hello'\nendfunc\nlet done = 'yes'" - call assert_true(exists('*Xtest')) - call assert_equal('yes', done) - delfunc Xtest - unlet done - - exe "func Xtest()\necho 'hello'\nendfunc|let done = 'yes'" - call assert_true(exists('*Xtest')) - call assert_equal('yes', done) - delfunc Xtest - unlet done - - " trailing line break - exe "func Xtest()\necho 'hello'\nendfunc\n" - call assert_true(exists('*Xtest')) - delfunc Xtest - - set verbose=1 - exe "func Xtest()\necho 'hello'\nendfunc \" garbage" - call assert_notmatch('W22:', split(execute('1messages'), "\n")[0]) - call assert_true(exists('*Xtest')) - delfunc Xtest - - exe "func Xtest()\necho 'hello'\nendfunc garbage" - call assert_match('W22:', split(execute('1messages'), "\n")[0]) - call assert_true(exists('*Xtest')) - delfunc Xtest - set verbose=0 - - function Foo() - echo 'hello' - endfunction | echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' - delfunc Foo -endfunc - -func Test_delfunction_force() - delfunc! Xtest - delfunc! Xtest - func Xtest() - echo 'nothing' - endfunc - delfunc! Xtest - delfunc! Xtest -endfunc - " Test using bang after user command {{{1 func Test_user_command_with_bang() command -bang Nieuw let nieuw = 1 @@ -1553,26 +6857,6 @@ func Test_user_command_with_bang() delcommand Nieuw endfunc -" Test for script-local function -func <SID>DoLast() - call append(line('$'), "last line") -endfunc - -func s:DoNothing() - call append(line('$'), "nothing line") -endfunc - -func Test_script_local_func() - set nocp nomore viminfo+=nviminfo - new - nnoremap <buffer> _x :call <SID>DoNothing()<bar>call <SID>DoLast()<bar>delfunc <SID>DoNothing<bar>delfunc <SID>DoLast<cr> - - normal _x - call assert_equal('nothing line', getline(2)) - call assert_equal('last line', getline(3)) - enew! | close -endfunc - func Test_script_expand_sfile() let lines =<< trim END func s:snr() @@ -1653,6 +6937,22 @@ func Test_compound_assignment_operators() call assert_equal(4.2, x) call assert_fails('let x %= 0.5', 'E734') call assert_fails('let x .= "f"', 'E734') + let x = !3.14 + call assert_equal(0.0, x) + + " integer and float operations + let x = 1 + let x *= 2.1 + call assert_equal(2.1, x) + let x = 1 + let x /= 0.25 + call assert_equal(4.0, x) + let x = 1 + call assert_fails('let x %= 0.25', 'E734:') + let x = 1 + call assert_fails('let x .= 0.25', 'E734:') + let x = 1.0 + call assert_fails('let x += [1.1]', 'E734:') endif " Test for environment variable @@ -1716,87 +7016,6 @@ func Test_unlet_env() call assert_equal('', $TESTVAR) endfunc -func Test_funccall_garbage_collect() - func Func(x, ...) - call add(a:x, a:000) - endfunc - call Func([], []) - " Must not crash cause by invalid freeing - call test_garbagecollect_now() - call assert_true(v:true) - delfunc Func -endfunc - -func Test_function_defined_line() - if has('gui_running') - " Can't catch the output of gvim. - return - endif - - let lines =<< trim [CODE] - " F1 - func F1() - " F2 - func F2() - " - " - " - return - endfunc - " F3 - execute "func F3()\n\n\n\nreturn\nendfunc" - " F4 - execute "func F4()\n - \\n - \\n - \\n - \return\n - \endfunc" - endfunc - " F5 - execute "func F5()\n\n\n\nreturn\nendfunc" - " F6 - execute "func F6()\n - \\n - \\n - \\n - \return\n - \endfunc" - call F1() - verbose func F1 - verbose func F2 - verbose func F3 - verbose func F4 - verbose func F5 - verbose func F6 - qall! - [CODE] - - call writefile(lines, 'Xtest.vim') - let res = system(GetVimCommandClean() .. ' -es -X -S Xtest.vim') - call assert_equal(0, v:shell_error) - - let m = matchstr(res, 'function F1()[^[:print:]]*[[:print:]]*') - call assert_match(' line 2$', m) - - let m = matchstr(res, 'function F2()[^[:print:]]*[[:print:]]*') - call assert_match(' line 4$', m) - - let m = matchstr(res, 'function F3()[^[:print:]]*[[:print:]]*') - call assert_match(' line 11$', m) - - let m = matchstr(res, 'function F4()[^[:print:]]*[[:print:]]*') - call assert_match(' line 13$', m) - - let m = matchstr(res, 'function F5()[^[:print:]]*[[:print:]]*') - call assert_match(' line 21$', m) - - let m = matchstr(res, 'function F6()[^[:print:]]*[[:print:]]*') - call assert_match(' line 23$', m) - - call delete('Xtest.vim') -endfunc - " Test for missing :endif, :endfor, :endwhile and :endtry {{{1 func Test_missing_end() call writefile(['if 2 > 1', 'echo ">"'], 'Xscript') @@ -1829,15 +7048,19 @@ func Test_missing_end() endtry call assert_equal(1, caught_e733) + " Using endfunc with :if + call assert_fails('exe "if 1 | endfunc | endif"', 'E193:') + " Missing 'in' in a :for statement call assert_fails('for i range(1) | endfor', 'E690:') + + " Incorrect number of variables in for + call assert_fails('for [i,] in range(3) | endfor', 'E475:') endfunc " Test for deep nesting of if/for/while/try statements {{{1 func Test_deep_nest() - if !CanRunVimInTerminal() - throw 'Skipped: cannot run vim in terminal' - endif + CheckRunVimInTerminal let lines =<< trim [SCRIPT] " Deep nesting of if ... endif @@ -1875,6 +7098,15 @@ func Test_deep_nest() @a let @a = '' endfunc + + " Deep nesting of function ... endfunction + func Test5() + let @a = join(repeat(['function X()'], 51), "\n") + let @a ..= "\necho v:true\n" + let @a ..= join(repeat(['endfunction'], 51), "\n") + @a + let @a = '' + endfunc [SCRIPT] call writefile(lines, 'Xscript') @@ -1882,20 +7114,31 @@ func Test_deep_nest() " Deep nesting of if ... endif call term_sendkeys(buf, ":call Test1()\n") + call term_wait(buf) call WaitForAssert({-> assert_match('^E579:', term_getline(buf, 5))}) " Deep nesting of for ... endfor call term_sendkeys(buf, ":call Test2()\n") + call term_wait(buf) call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))}) " Deep nesting of while ... endwhile call term_sendkeys(buf, ":call Test3()\n") + call term_wait(buf) call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))}) " Deep nesting of try ... endtry call term_sendkeys(buf, ":call Test4()\n") + call term_wait(buf) call WaitForAssert({-> assert_match('^E601:', term_getline(buf, 5))}) + " Deep nesting of function ... endfunction + call term_sendkeys(buf, ":call Test5()\n") + call term_wait(buf) + call WaitForAssert({-> assert_match('^E1058:', term_getline(buf, 4))}) + call term_sendkeys(buf, "\<C-C>\n") + call term_wait(buf) + "let l = '' "for i in range(1, 6) " let l ..= term_getline(buf, i) . "\n" @@ -1906,14 +7149,113 @@ func Test_deep_nest() call delete('Xscript') endfunc -" Test for <sfile>, <slnum> in a function {{{1 -func Test_sfile_in_function() - func Xfunc() - call assert_match('..Test_sfile_in_function\[5]..Xfunc', expand('<sfile>')) - call assert_equal('2', expand('<slnum>')) - endfunc - call Xfunc() - delfunc Xfunc +" Test for errors in converting to float from various types {{{1 +func Test_float_conversion_errors() + if has('float') + call assert_fails('let x = 4.0 % 2.0', 'E804') + call assert_fails('echo 1.1[0]', 'E806') + call assert_fails('echo sort([function("min"), 1], "f")', 'E891:') + call assert_fails('echo 3.2 == "vim"', 'E892:') + call assert_fails('echo sort([[], 1], "f")', 'E893:') + call assert_fails('echo sort([{}, 1], "f")', 'E894:') + call assert_fails('echo 3.2 == v:true', 'E362:') + " call assert_fails('echo 3.2 == v:none', 'E907:') + endif +endfunc + +" invalid function names {{{1 +func Test_invalid_function_names() + " function name not starting with capital + let caught_e128 = 0 + try + func! g:test() + echo "test" + endfunc + catch /E128:/ + let caught_e128 = 1 + endtry + call assert_equal(1, caught_e128) + + " function name includes a colon + let caught_e884 = 0 + try + func! b:test() + echo "test" + endfunc + catch /E884:/ + let caught_e884 = 1 + endtry + call assert_equal(1, caught_e884) + + " function name followed by # + let caught_e128 = 0 + try + func! test2() "# + echo "test2" + endfunc + catch /E128:/ + let caught_e128 = 1 + endtry + call assert_equal(1, caught_e128) + + " function name starting with/without "g:", buffer-local funcref. + function! g:Foo(n) + return 'called Foo(' . a:n . ')' + endfunction + let b:my_func = function('Foo') + call assert_equal('called Foo(1)', b:my_func(1)) + call assert_equal('called Foo(2)', g:Foo(2)) + call assert_equal('called Foo(3)', Foo(3)) + delfunc g:Foo + + " script-local function used in Funcref must exist. + let lines =<< trim END + func s:Testje() + return "foo" + endfunc + let Bar = function('s:Testje') + call assert_equal(0, exists('s:Testje')) + call assert_equal(1, exists('*s:Testje')) + call assert_equal(1, exists('Bar')) + call assert_equal(1, exists('*Bar')) + END + call writefile(lines, 'Xscript') + source Xscript + call delete('Xscript') +endfunc + +" substring and variable name {{{1 +func Test_substring_var() + let str = 'abcdef' + let n = 3 + call assert_equal('def', str[n:]) + call assert_equal('abcd', str[:n]) + call assert_equal('d', str[n:n]) + unlet n + let nn = 3 + call assert_equal('def', str[nn:]) + call assert_equal('abcd', str[:nn]) + call assert_equal('d', str[nn:nn]) + unlet nn + let b:nn = 4 + call assert_equal('ef', str[b:nn:]) + call assert_equal('abcde', str[:b:nn]) + call assert_equal('e', str[b:nn:b:nn]) + unlet b:nn +endfunc + +" Test using s: with a typed command {{{1 +func Test_typed_script_var() + CheckRunVimInTerminal + + let buf = RunVimInTerminal('', {'rows': 6}) + + " Deep nesting of if ... endif + call term_sendkeys(buf, ":echo get(s:, 'foo', 'x')\n") + call TermWait(buf) + call WaitForAssert({-> assert_match('^E116:', term_getline(buf, 5))}) + + call StopVimInTerminal(buf) endfunc func Test_for_over_string() diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index 522ca17675..2bf8c3fc77 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -80,6 +80,10 @@ func Test_edit_change() call setline(1, "\t⒌") normal Cx call assert_equal('x', getline(1)) + " Do a visual block change + call setline(1, ['a', 'b', 'c']) + exe "normal gg3l\<C-V>2jcx" + call assert_equal(['a x', 'b x', 'c x'], getline(1, '$')) bwipe! set virtualedit= endfunc @@ -289,6 +293,16 @@ func Test_replace_after_eol() call append(0, '"r"') normal gg$5lrxa call assert_equal('"r" x', getline(1)) + " visual block replace + %d _ + call setline(1, ['a', '', 'b']) + exe "normal 2l\<C-V>2jrx" + call assert_equal(['a x', ' x', 'b x'], getline(1, '$')) + " visual characterwise selection replace after eol + %d _ + call setline(1, 'a') + normal 4lv2lrx + call assert_equal('a xxx', getline(1)) bwipe! set virtualedit= endfunc @@ -346,6 +360,48 @@ func Test_yank_paste_small_del_reg() set virtualedit= endfunc +" Test for delete that breaks a tab into spaces +func Test_delete_break_tab() + new + call setline(1, "one\ttwo") + set virtualedit=all + normal v3ld + call assert_equal(' two', getline(1)) + set virtualedit& + close! +endfunc + +" Test for using <BS>, <C-W> and <C-U> in virtual edit mode +" to erase character, word and line. +func Test_ve_backspace() + new + call setline(1, 'sample') + set virtualedit=all + set backspace=indent,eol,start + exe "normal 15|i\<BS>\<BS>" + call assert_equal([0, 1, 7, 5], getpos('.')) + exe "normal 15|i\<C-W>" + call assert_equal([0, 1, 6, 0], getpos('.')) + exe "normal 15|i\<C-U>" + call assert_equal([0, 1, 1, 0], getpos('.')) + set backspace& + set virtualedit& + close! +endfunc + +" Test for delete (x) on EOL character and after EOL +func Test_delete_past_eol() + new + call setline(1, "ab") + set virtualedit=all + exe "normal 2lx" + call assert_equal('ab', getline(1)) + exe "normal 10lx" + call assert_equal('ab', getline(1)) + set virtualedit& + bw! +endfunc + " After calling s:TryVirtualeditReplace(), line 1 will contain one of these " two strings, depending on whether virtual editing is on or off. let s:result_ve_on = 'a x' @@ -481,4 +537,53 @@ func Test_global_local_virtualedit() set virtualedit& endfunc +func Test_virtualedit_mouse() + let save_mouse = &mouse + set mouse=a + set virtualedit=all + new + + call setline(1, ["text\tword"]) + redraw + call Ntest_setmouse(1, 4) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 4, 0, 4], getcurpos()) + call Ntest_setmouse(1, 5) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 5, 0, 5], getcurpos()) + call Ntest_setmouse(1, 6) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 5, 1, 6], getcurpos()) + call Ntest_setmouse(1, 7) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 5, 2, 7], getcurpos()) + call Ntest_setmouse(1, 8) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 5, 3, 8], getcurpos()) + call Ntest_setmouse(1, 9) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 6, 0, 9], getcurpos()) + call Ntest_setmouse(1, 15) + call feedkeys("\<LeftMouse>", "xt") + call assert_equal([0, 1, 10, 2, 15], getcurpos()) + + bwipe! + let &mouse = save_mouse + set virtualedit& +endfunc + +" this was replacing the NUL at the end of the line +func Test_virtualedit_replace_after_tab() + new + s/\v/ 0 + set ve=all + let @" = '' + sil! norm vPvr0 + + call assert_equal("\t0", getline(1)) + set ve& + bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 9c1ad0c099..14d62089cf 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -225,6 +225,15 @@ func Test_virtual_replace() exe "normal iabcdefghijklmnopqrst\<Esc>0gRAB\tIJKLMNO\tQR" call assert_equal(['AB......CDEFGHI.Jkl', \ 'AB IJKLMNO QRst'], getline(12, 13)) + + " Test inserting Tab with 'noexpandtab' and 'softabstop' set to 4 + %d + call setline(1, 'aaaaaaaaaaaaa') + set softtabstop=4 + exe "normal gggR\<Tab>\<Tab>x" + call assert_equal("\txaaaa", getline(1)) + set softtabstop& + enew! set noai bs&vim if exists('save_t_kD') @@ -474,6 +483,18 @@ func Test_visual_block_put() bw! endfunc +func Test_visual_block_put_invalid() + enew! + behave mswin + norm yy + norm v)Ps/^/ + " this was causing the column to become negative + silent norm ggv)P + + bwipe! + behave xterm +endfunc + " Visual modes (v V CTRL-V) followed by an operator; count; repeating func Test_visual_mode_op() new @@ -1269,7 +1290,7 @@ func Test_visual_block_with_virtualedit() endfunc func Test_visual_block_ctrl_w_f() - " Emtpy block selected in new buffer should not result in an error. + " Empty block selected in new buffer should not result in an error. au! BufNew foo sil norm f edit foo @@ -1317,6 +1338,18 @@ func Test_visual_reselect_with_count() call delete('XvisualReselect') endfunc +func Test_visual_reselect_exclusive() + new + call setline(1, ['abcde', 'abcde']) + set selection=exclusive + normal 1G0viwd + normal 2G01vd + call assert_equal(['', ''], getline(1, 2)) + + set selection& + bwipe! +endfunc + func Test_visual_block_insert_round_off() new " The number of characters are tuned to fill a 4096 byte allocated block, @@ -1492,5 +1525,4 @@ func Test_switch_buffer_ends_visual_mode() exe 'bwipe!' buf2 endfunc - " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim index f4878c2397..26b4ba8778 100644 --- a/src/nvim/testdir/test_winbuf_close.vim +++ b/src/nvim/testdir/test_winbuf_close.vim @@ -115,7 +115,7 @@ func Test_winbuf_close() call assert_equal('Xtest2', bufname('%')) quit! call assert_equal('Xtest3', bufname('%')) - call assert_fails('silent! quit!', 'E162') + call assert_fails('silent! quit!', 'E37') call assert_equal('Xtest1', bufname('%')) call delete('Xtest1') @@ -192,7 +192,23 @@ func Test_tabwin_close() call win_execute(l:wid, 'close') " Should not crash. call assert_true(v:true) - %bwipe! + + " This tests closing a window in another tab, while leaving the tab open + " i.e. two windows in another tab. + tabedit + let w:this_win = 42 + new + let othertab_wid = win_getid() + tabprevious + call win_execute(othertab_wid, 'q') + " drawing the tabline helps check that the other tab's windows and buffers + " are still valid + redrawtabline + " but to be certain, ensure we can focus the other tab too + tabnext + call assert_equal(42, w:this_win) + + bwipe! endfunc " Test when closing a split window (above/below) restores space to the window diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 83a3216534..c25b1f1157 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -1,5 +1,8 @@ " Tests for window cmd (:wincmd, :split, :vsplit, :resize and etc...) +source check.vim +source screendump.vim + func Test_window_cmd_ls0_with_split() set ls=0 set splitbelow @@ -343,6 +346,46 @@ func Test_window_height() bw Xa Xb Xc endfunc +func Test_wincmd_equal() + edit Xone + below split Xtwo + rightbelow vsplit Xthree + call assert_equal('Xone', bufname(winbufnr(1))) + call assert_equal('Xtwo', bufname(winbufnr(2))) + call assert_equal('Xthree', bufname(winbufnr(3))) + + " Xone and Xtwo should be about the same height + let [wh1, wh2] = [winheight(1), winheight(2)] + call assert_inrange(wh1 - 1, wh1 + 1, wh2) + " Xtwo and Xthree should be about the same width + let [ww2, ww3] = [winwidth(2), winwidth(3)] + call assert_inrange(ww2 - 1, ww2 + 1, ww3) + + 1wincmd w + 10wincmd _ + 2wincmd w + 20wincmd | + call assert_equal(10, winheight(1)) + call assert_equal(20, winwidth(2)) + + " equalizing horizontally doesn't change the heights + hor wincmd = + call assert_equal(10, winheight(1)) + let [ww2, ww3] = [winwidth(2), winwidth(3)] + call assert_inrange(ww2 - 1, ww2 + 1, ww3) + + 2wincmd w + 20wincmd | + call assert_equal(20, winwidth(2)) + " equalizing vertically doesn't change the widths + vert wincmd = + call assert_equal(20, winwidth(2)) + let [wh1, wh2] = [winheight(1), winheight(2)] + call assert_inrange(wh1 - 1, wh1 + 1, wh2) + + bwipe Xone Xtwo Xthree +endfunc + func Test_window_width() e Xa vsplit Xb @@ -393,7 +436,15 @@ func Test_window_width() call assert_inrange(ww1, ww1 + 1, ww2) call assert_inrange(ww3, ww3 + 1, ww2) - bw Xa Xb Xc + " when the current window width is less than the new 'winwidth', the current + " window width should be increased. + enew | only + split + 10vnew + set winwidth=15 + call assert_equal(15, winwidth(0)) + + %bw! endfunc func Test_equalalways_on_close() @@ -450,10 +501,13 @@ func Test_win_screenpos() call assert_equal([1, 32], win_screenpos(2)) call assert_equal([12, 1], win_screenpos(3)) call assert_equal([0, 0], win_screenpos(4)) + call assert_fails('let l = win_screenpos([])', 'E745:') only endfunc func Test_window_jump_tag() + CheckFeature quickfix + help /iccf call assert_match('^|iccf|', getline('.')) @@ -491,6 +545,7 @@ func Test_window_newtab() call assert_equal(2, tabpagenr('$')) call assert_equal(['Xb', 'Xa'], map(tabpagebuflist(1), 'bufname(v:val)')) call assert_equal(['Xc' ], map(2->tabpagebuflist(), 'bufname(v:val)')) + call assert_equal(['Xc' ], map(tabpagebuflist(), 'bufname(v:val)')) %bw! endfunc @@ -541,6 +596,11 @@ func Test_window_contents() call assert_equal(59, line("w0")) call assert_equal('59 ', s3) + %d + call setline(1, ['one', 'two', 'three']) + call assert_equal(1, line('w0')) + call assert_equal(3, line('w$')) + bwipeout! call test_garbagecollect_now() endfunc @@ -677,6 +737,7 @@ func Test_relative_cursor_position_in_one_line_window() only! bwipe! + call assert_fails('call winrestview(v:_null_dict)', 'E474:') endfunc func Test_relative_cursor_position_after_move_and_resize() @@ -853,6 +914,10 @@ func Test_winnr() call assert_fails("echo winnr('ll')", 'E15:') call assert_fails("echo winnr('5')", 'E15:') call assert_equal(4, winnr('0h')) + call assert_fails("let w = winnr([])", 'E730:') + call assert_equal('unknown', win_gettype(-1)) + call assert_equal(-1, winheight(-1)) + call assert_equal(-1, winwidth(-1)) tabnew call assert_equal(8, tabpagewinnr(1, 'j')) @@ -873,9 +938,12 @@ func Test_winrestview() call assert_equal(view, winsaveview()) bwipe! + call assert_fails('call winrestview(v:_null_dict)', 'E474:') endfunc func Test_win_splitmove() + CheckFeature quickfix + edit a leftabove split b leftabove vsplit c @@ -901,6 +969,7 @@ func Test_win_splitmove() call assert_equal(bufname(winbufnr(2)), 'b') call assert_equal(bufname(winbufnr(3)), 'a') call assert_equal(bufname(winbufnr(4)), 'd') + call assert_fails('call win_splitmove(winnr(), winnr("k"), v:_null_dict)', 'E474:') only | bd call assert_fails('call win_splitmove(winnr(), 123)', 'E957:') @@ -1073,6 +1142,18 @@ func Test_split_cmds_with_no_room() call Run_noroom_for_newwindow_test('v') endfunc +" Test for various wincmd failures +func Test_wincmd_fails() + only! + call assert_beeps("normal \<C-W>w") + call assert_beeps("normal \<C-W>p") + call assert_beeps("normal \<C-W>gk") + call assert_beeps("normal \<C-W>r") + call assert_beeps("normal \<C-W>K") + call assert_beeps("normal \<C-W>H") + call assert_beeps("normal \<C-W>2gt") +endfunc + func Test_window_resize() " Vertical :resize (absolute, relative, min and max size). vsplit @@ -1158,7 +1239,7 @@ endfunc " Test for jumping to a vertical/horizontal neighbor window based on the " current cursor position -func Test_window_goto_neightbor() +func Test_window_goto_neighbor() %bw! " Vertical window movement @@ -1337,17 +1418,20 @@ func Test_win_move_separator() call assert_equal(w0, winwidth(0)) call assert_true(win_move_separator(0, -1)) call assert_equal(w0, winwidth(0)) + " check that win_move_separator doesn't error with offsets beyond moving " possibility call assert_true(win_move_separator(id, 5000)) call assert_true(winwidth(id) > w) call assert_true(win_move_separator(id, -5000)) call assert_true(winwidth(id) < w) + " check that win_move_separator returns false for an invalid window wincmd = let w = winwidth(0) call assert_false(win_move_separator(-1, 1)) call assert_equal(w, winwidth(0)) + " check that win_move_separator returns false for a floating window let id = nvim_open_win( \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3}) @@ -1355,6 +1439,13 @@ func Test_win_move_separator() call assert_false(win_move_separator(id, 1)) call assert_equal(w, winwidth(id)) call nvim_win_close(id, 1) + + " check that using another tabpage fails without crash + let id = win_getid() + tabnew + call assert_fails('call win_move_separator(id, -1)', 'E1308:') + tabclose + %bwipe! endfunc @@ -1403,17 +1494,20 @@ func Test_win_move_statusline() call assert_true(id->win_move_statusline(-offset)) call assert_equal(h, winheight(id)) endfor + " check that win_move_statusline doesn't error with offsets beyond moving " possibility call assert_true(win_move_statusline(id, 5000)) call assert_true(winheight(id) > h) call assert_true(win_move_statusline(id, -5000)) call assert_true(winheight(id) < h) + " check that win_move_statusline returns false for an invalid window wincmd = let h = winheight(0) call assert_false(win_move_statusline(-1, 1)) call assert_equal(h, winheight(0)) + " check that win_move_statusline returns false for a floating window let id = nvim_open_win( \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3}) @@ -1421,7 +1515,319 @@ func Test_win_move_statusline() call assert_false(win_move_statusline(id, 1)) call assert_equal(h, winheight(id)) call nvim_win_close(id, 1) + + " check that using another tabpage fails without crash + let id = win_getid() + tabnew + call assert_fails('call win_move_statusline(id, -1)', 'E1308:') + tabclose + %bwipe! endfunc +func Test_win_equal_last_status() + let save_lines = &lines + set lines=20 + set splitbelow + set laststatus=0 + + split | split | quit + call assert_equal(winheight(1), winheight(2)) + + let &lines = save_lines + set splitbelow& + set laststatus& +endfunc + +" Test "screen" and "cursor" values for 'splitkeep' with a sequence of +" split operations for various options: with and without a winbar, +" tabline, for each possible value of 'laststatus', 'scrolloff', +" 'equalalways', and with the cursor at the top, middle and bottom. +func Test_splitkeep_options() + " disallow window resizing + " let save_WS = &t_WS + " set t_WS= + + let gui = has("gui_running") + inoremap <expr> c "<cmd>copen<bar>wincmd k<CR>" + for run in range(0, 20) + let &splitkeep = run > 10 ? 'topline' : 'screen' + let &scrolloff = (!(run % 4) ? 0 : run) + let &laststatus = (run % 3) + let &splitbelow = (run % 3) + let &equalalways = (run % 2) + " Nvim: both windows have a winbar after splitting + " let wsb = (run % 2) && &splitbelow + let wsb = 0 + let tl = (gui ? 0 : ((run % 5) ? 1 : 0)) + let pos = !(run % 3) ? 'H' : ((run % 2) ? 'M' : 'L') + tabnew | tabonly! | redraw + execute (run % 5) ? 'tabnew' : '' + " execute (run % 2) ? 'nnoremenu 1.10 WinBar.Test :echo' : '' + let &winbar = (run % 2) ? '%f' : '' + call setline(1, range(1, 256)) + " No scroll for restore_snapshot + norm G + try + copen | close | colder + catch /E380/ + endtry + call assert_equal(257 - winheight(0), line("w0")) + + " No scroll for firstwin horizontal split + execute 'norm gg' . pos + split | redraw | wincmd k + call assert_equal(1, line("w0")) + call assert_equal(&scroll, winheight(0) / 2) + wincmd j + call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0")) + + " No scroll when resizing windows + wincmd k | resize +2 | redraw + call assert_equal(1, line("w0")) + wincmd j + call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0")) + + " No scroll when dragging statusline + call win_move_statusline(1, -3) + call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0")) + wincmd k + call assert_equal(1, line("w0")) + + " No scroll when changing shellsize + set lines+=2 + call assert_equal(1, line("w0")) + wincmd j + call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0")) + set lines-=2 + call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0")) + wincmd k + call assert_equal(1, line("w0")) + + " No scroll when equalizing windows + wincmd = + call assert_equal(1, line("w0")) + wincmd j + call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0")) + wincmd k + call assert_equal(1, line("w0")) + + " No scroll in windows split multiple times + vsplit | split | 4wincmd w + call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0")) + 1wincmd w | quit | wincmd l | split + call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0")) + wincmd j + call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0")) + + " No scroll in small window + 2wincmd w | only | 5split | wincmd k + call assert_equal(1, line("w0")) + wincmd j + call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0")) + + " No scroll for vertical split + quit | vsplit | wincmd l + call assert_equal(1, line("w0")) + wincmd h + call assert_equal(1, line("w0")) + + " No scroll in windows split and quit multiple times + quit | redraw | split | split | quit | redraw + call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl - wsb, line("w0")) + + " No scroll for new buffer + 1wincmd w | only | copen | wincmd k + call assert_equal(1, line("w0")) + only + call assert_equal(1, line("w0")) + above copen | wincmd j + call assert_equal(&spk == 'topline' ? 1 : win_screenpos(0)[0] - tl, line("w0")) + + " No scroll when opening cmdwin, and no cursor move when closing cmdwin. + only | norm ggL + let curpos = getcurpos() + norm q: + call assert_equal(1, line("w0")) + call assert_equal(curpos, getcurpos()) + + " Scroll when cursor becomes invalid in insert mode + norm Lic + call assert_equal(curpos, getcurpos()) + + " No scroll when topline not equal to 1 + only | execute "norm gg5\<C-e>" | split | wincmd k + call assert_equal(6, line("w0")) + wincmd j + call assert_equal(&spk == 'topline' ? 6 : 5 + win_screenpos(0)[0] - tl - wsb, line("w0")) + endfor + + tabnew | tabonly! | %bwipeout! + iunmap c + set scrolloff& + set splitbelow& + set laststatus& + set equalalways& + set splitkeep& + " let &t_WS = save_WS +endfunc + +function Test_splitkeep_cmdwin_cursor_position() + set splitkeep=screen + call setline(1, range(&lines)) + + " No scroll when cursor is at near bottom of window and cusor position + " recompution (done by line('w0') in this test) happens while in cmdwin. + normal! G + let firstline = line('w0') + autocmd CmdwinEnter * ++once autocmd WinEnter * ++once call line('w0') + execute "normal! q:\<C-w>q" + redraw! + call assert_equal(firstline, line('w0')) + + " User script can change cursor position successfully while in cmdwin and it + " shouldn't be changed when closing cmdwin. + execute "normal! Gq:\<Cmd>call win_execute(winnr('#')->win_getid(), 'call cursor(1, 1)')\<CR>\<C-w>q" + call assert_equal(1, line('.')) + call assert_equal(1, col('.')) + + execute "normal! Gq:\<Cmd>autocmd WinEnter * ++once call cursor(1, 1)\<CR>\<C-w>q" + call assert_equal(1, line('.')) + call assert_equal(1, col('.')) + + %bwipeout! + set splitkeep& +endfunction + +function Test_splitkeep_misc() + set splitkeep=screen + set splitbelow + + call setline(1, range(1, &lines)) + norm Gzz + let top = line('w0') + " No scroll when aucmd_win is opened + call setbufvar(bufnr("test", 1) , '&buftype', 'nofile') + call assert_equal(top, line('w0')) + " No scroll when tab is changed/closed + tab help | close + call assert_equal(top, line('w0')) + " No scroll when help is closed and buffer line count < window height + norm ggdG + call setline(1, range(1, &lines - 10)) + norm G + let top = line('w0') + help | quit + call assert_equal(top, line('w0')) + " No error when resizing window in autocmd and buffer length changed + autocmd FileType qf exe "resize" line('$') + cexpr getline(1, '$') + copen + wincmd p + norm dd + cexpr getline(1, '$') + + %bwipeout! + set splitbelow& + set splitkeep& +endfunc + +function Test_splitkeep_callback() + CheckScreendump + let lines =<< trim END + set splitkeep=screen + call setline(1, range(&lines)) + function C1(a, b) + split | wincmd p + endfunction + function C2(a, b) + close | split + endfunction + nn j <cmd>call job_start([&sh, &shcf, "true"], { 'exit_cb': 'C1' })<CR> + nn t <cmd>call popup_create(term_start([&sh, &shcf, "true"], + \ { 'hidden': 1, 'exit_cb': 'C2' }), {})<CR> + END + call writefile(lines, 'XTestSplitkeepCallback', 'D') + let buf = RunVimInTerminal('-S XTestSplitkeepCallback', #{rows: 8}) + + call term_sendkeys(buf, "j") + call VerifyScreenDump(buf, 'Test_splitkeep_callback_1', {}) + + call term_sendkeys(buf, ":quit\<CR>Ht") + call VerifyScreenDump(buf, 'Test_splitkeep_callback_2', {}) + + call term_sendkeys(buf, ":set sb\<CR>:quit\<CR>Gj") + call VerifyScreenDump(buf, 'Test_splitkeep_callback_3', {}) + + call term_sendkeys(buf, ":quit\<CR>Gt") + call VerifyScreenDump(buf, 'Test_splitkeep_callback_4', {}) + + call StopVimInTerminal(buf) +endfunc + +function Test_splitkeep_fold() + CheckScreendump + + let lines =<< trim END + set splitkeep=screen + set foldmethod=marker + set number + let line = 1 + for n in range(1, &lines) + call setline(line, ['int FuncName() {/*{{{*/', 1, 2, 3, 4, 5, '}/*}}}*/', + \ 'after fold']) + let line += 8 + endfor + END + call writefile(lines, 'XTestSplitkeepFold', 'D') + let buf = RunVimInTerminal('-S XTestSplitkeepFold', #{rows: 10}) + + call term_sendkeys(buf, "L:wincmd s\<CR>") + call VerifyScreenDump(buf, 'Test_splitkeep_fold_1', {}) + + call term_sendkeys(buf, ":quit\<CR>") + call VerifyScreenDump(buf, 'Test_splitkeep_fold_2', {}) + + call term_sendkeys(buf, "H:below split\<CR>") + call VerifyScreenDump(buf, 'Test_splitkeep_fold_3', {}) + + call term_sendkeys(buf, ":wincmd k\<CR>:quit\<CR>") + call VerifyScreenDump(buf, 'Test_splitkeep_fold_4', {}) + + call StopVimInTerminal(buf) +endfunction + +function Test_splitkeep_status() + CheckScreendump + + let lines =<< trim END + call setline(1, ['a', 'b', 'c']) + set nomodified + set splitkeep=screen + let win = winnr() + wincmd s + wincmd j + END + call writefile(lines, 'XTestSplitkeepStatus', 'D') + let buf = RunVimInTerminal('-S XTestSplitkeepStatus', #{rows: 10}) + + call term_sendkeys(buf, ":call win_move_statusline(win, 1)\<CR>") + call VerifyScreenDump(buf, 'Test_splitkeep_status_1', {}) + + call StopVimInTerminal(buf) +endfunction + +function Test_new_help_window_on_error() + help change.txt + execute "normal! /CTRL-@\<CR>" + silent! execute "normal! \<C-W>]" + + let wincount = winnr('$') + help 'mod' + + call assert_equal(wincount, winnr('$')) + call assert_equal(expand("<cword>"), "'mod'") +endfunction + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_window_id.vim b/src/nvim/testdir/test_window_id.vim index 8bf4ede350..396a49b55f 100644 --- a/src/nvim/testdir/test_window_id.vim +++ b/src/nvim/testdir/test_window_id.vim @@ -1,5 +1,7 @@ " Test using the window ID. +source check.vim + func Test_win_getid() edit one let id1 = win_getid() @@ -90,10 +92,16 @@ func Test_win_getid() split call assert_equal(sort([id5, win_getid()]), sort(win_findbuf(bufnr5))) + call assert_fails('let w = win_getid([])', 'E745:') + call assert_equal(0, win_getid(-1)) + call assert_equal(-1, win_getid(1, -1)) + only! endfunc func Test_win_getid_curtab() + CheckFeature quickfix + tabedit X tabfirst copen @@ -127,4 +135,8 @@ func Test_winlayout() let w2 = win_getid() call assert_equal(['leaf', w2], 2->winlayout()) tabclose + + call assert_equal([], winlayout(-1)) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index a8735bcaf1..6019cee193 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -57,7 +57,29 @@ func Test_writefile_fails_conversion() call assert_fails('write ++enc=cp932', 'E513:') call assert_equal(contents, readfile('Xfile')) + " With 'backupcopy' set, if there is a conversion error, the backup file is + " still created. + set backupcopy=yes writebackup& backup& + call delete('Xfile' .. &backupext) + call assert_fails('write ++enc=cp932', 'E513:') + call assert_equal(contents, readfile('Xfile')) + call assert_equal(contents, readfile('Xfile' .. &backupext)) + set backupcopy& + %bw! + + " Conversion error during write + new + call setline(1, ["\U10000000"]) + let output = execute('write! ++enc=utf-16 Xfile') + call assert_match('CONVERSION ERROR', output) + let output = execute('write! ++enc=ucs-2 Xfile') + call assert_match('CONVERSION ERROR', output) + call delete('Xfilz~') + call delete('Xfily~') + %bw! + call delete('Xfile') + call delete('Xfile' .. &backupext) bwipe! set backup& writebackup& backupdir&vim backupskip&vim endfunc @@ -136,9 +158,7 @@ func Test_writefile_sync_arg() endfunc func Test_writefile_sync_dev_stdout() - if !has('unix') - return - endif + CheckUnix if filewritable('/dev/stdout') " Just check that this doesn't cause an error. call writefile(['one'], '/dev/stdout', 's') @@ -216,6 +236,12 @@ func Test_saveas() syntax off %bw! call delete('Xsaveas.pl') + + " :saveas fails for "nofile" buffer + set buftype=nofile + call assert_fails('saveas Xsafile', 'E676: No matching autocommands for buftype=nofile buffer') + + bwipe! endfunc func Test_write_errors() @@ -254,6 +280,225 @@ func Test_write_errors() close call delete('Xfile') + + " Nvim treats NULL list/blob more like empty list/blob + " call writefile(v:_null_list, 'Xfile') + " call assert_false(filereadable('Xfile')) + " call writefile(v:_null_blob, 'Xfile') + " call assert_false(filereadable('Xfile')) + call assert_fails('call writefile([], "")', 'E482:') + + " very long file name + let long_fname = repeat('n', 5000) + call assert_fails('exe "w " .. long_fname', 'E75:') + call assert_fails('call writefile([], long_fname)', 'E482:') + + " Test for writing to a block device on Unix-like systems + if has('unix') && getfperm('/dev/loop0') != '' + \ && getftype('/dev/loop0') == 'bdev' && !IsRoot() + new + edit /dev/loop0 + call assert_fails('write', 'E503: ') + call assert_fails('write!', 'E503: ') + close! + endif +endfunc + +" Test for writing to a file which is modified after Vim read it +func Test_write_file_mtime() + CheckEnglish + CheckRunVimInTerminal + + " First read the file into a buffer + call writefile(["Line1", "Line2"], 'Xfile') + let old_ftime = getftime('Xfile') + let buf = RunVimInTerminal('Xfile', #{rows : 10}) + call term_wait(buf) + call term_sendkeys(buf, ":set noswapfile\<CR>") + call term_wait(buf) + + " Modify the file directly. Make sure the file modification time is + " different. Note that on Linux/Unix, the file is considered modified + " outside, only if the difference is 2 seconds or more + sleep 1 + call writefile(["Line3", "Line4"], 'Xfile') + let new_ftime = getftime('Xfile') + while new_ftime - old_ftime < 2 + sleep 100m + call writefile(["Line3", "Line4"], 'Xfile') + let new_ftime = getftime('Xfile') + endwhile + + " Try to overwrite the file and check for the prompt + call term_sendkeys(buf, ":w\<CR>") + call term_wait(buf) + call WaitForAssert({-> assert_equal("WARNING: The file has been changed since reading it!!!", term_getline(buf, 9))}) + call assert_equal("Do you really want to write to it (y/n)?", + \ term_getline(buf, 10)) + call term_sendkeys(buf, "n\<CR>") + call term_wait(buf) + call assert_equal(new_ftime, getftime('Xfile')) + call term_sendkeys(buf, ":w\<CR>") + call term_wait(buf) + call term_sendkeys(buf, "y\<CR>") + call term_wait(buf) + call WaitForAssert({-> assert_equal('Line2', readfile('Xfile')[1])}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xfile') +endfunc + +" Test for an autocmd unloading a buffer during a write command +func Test_write_autocmd_unloadbuf_lockmark() + augroup WriteTest + autocmd BufWritePre Xfile enew | write + augroup END + e Xfile + call assert_fails('lockmarks write', ['E32', 'E203:']) + augroup WriteTest + au! + augroup END + augroup! WriteTest +endfunc + +" Test for writing a buffer with 'acwrite' but without autocmds +func Test_write_acwrite_error() + new Xfile + call setline(1, ['line1', 'line2', 'line3']) + set buftype=acwrite + call assert_fails('write', 'E676:') + call assert_fails('1,2write!', 'E676:') + call assert_fails('w >>', 'E676:') + close! +endfunc + +" Test for adding and removing lines from an autocmd when writing a buffer +func Test_write_autocmd_add_remove_lines() + new Xfile + call setline(1, ['aaa', 'bbb', 'ccc', 'ddd']) + + " Autocmd deleting lines from the file when writing a partial file + augroup WriteTest2 + au! + autocmd FileWritePre Xfile 1,2d + augroup END + call assert_fails('2,3w!', 'E204:') + + " Autocmd adding lines to a file when writing a partial file + augroup WriteTest2 + au! + autocmd FileWritePre Xfile call append(0, ['xxx', 'yyy']) + augroup END + %d + call setline(1, ['aaa', 'bbb', 'ccc', 'ddd']) + 1,2w! + call assert_equal(['xxx', 'yyy', 'aaa', 'bbb'], readfile('Xfile')) + + " Autocmd deleting lines from the file when writing the whole file + augroup WriteTest2 + au! + autocmd BufWritePre Xfile 1,2d + augroup END + %d + call setline(1, ['aaa', 'bbb', 'ccc', 'ddd']) + w + call assert_equal(['ccc', 'ddd'], readfile('Xfile')) + + augroup WriteTest2 + au! + augroup END + augroup! WriteTest2 + + close! + call delete('Xfile') +endfunc + +" Test for writing to a readonly file +func Test_write_readonly() + call writefile([], 'Xfile') + call setfperm('Xfile', "r--------") + edit Xfile + set noreadonly backupskip= + call assert_fails('write', 'E505:') + let save_cpo = &cpo + set cpo+=W + call assert_fails('write!', 'E504:') + let &cpo = save_cpo + call setline(1, ['line1']) + write! + call assert_equal(['line1'], readfile('Xfile')) + + " Auto-saving a readonly file should fail with 'autowriteall' + %bw! + e Xfile + set noreadonly autowriteall + call setline(1, ['aaaa']) + call assert_fails('n', 'E505:') + set cpo+=W + call assert_fails('n', 'E504:') + set cpo-=W + set autowriteall& + + set backupskip& + call delete('Xfile') + %bw! +endfunc + +" Test for 'patchmode' +func Test_patchmode() + call writefile(['one'], 'Xfile') + set patchmode=.orig nobackup backupskip= writebackup + new Xfile + call setline(1, 'two') + " first write should create the .orig file + write + call assert_equal(['one'], readfile('Xfile.orig')) + call setline(1, 'three') + " subsequent writes should not create/modify the .orig file + write + call assert_equal(['one'], readfile('Xfile.orig')) + + " use 'patchmode' with 'nobackup' and 'nowritebackup' to create an empty + " original file + call delete('Xfile') + call delete('Xfile.orig') + %bw! + set patchmode=.orig nobackup nowritebackup + edit Xfile + call setline(1, ['xxx']) + write + call assert_equal(['xxx'], readfile('Xfile')) + call assert_equal([], readfile('Xfile.orig')) + + set patchmode& backup& backupskip& writebackup& + call delete('Xfile') + call delete('Xfile.orig') +endfunc + +" Test for writing to a file in a readonly directory +" NOTE: if you run tests as root this will fail. Don't run tests as root! +func Test_write_readonly_dir() + " On MS-Windows, modifying files in a read-only directory is allowed. + CheckUnix + " Root can do it too. + CheckNotRoot + + call mkdir('Xdir/') + call writefile(['one'], 'Xdir/Xfile1') + call setfperm('Xdir', 'r-xr--r--') + " try to create a new file in the directory + new Xdir/Xfile2 + call setline(1, 'two') + call assert_fails('write', 'E212:') + " try to create a backup file in the directory + edit! Xdir/Xfile1 + set backupdir=./Xdir backupskip= + set patchmode=.orig + call assert_fails('write', 'E509:') + call setfperm('Xdir', 'rwxr--r--') + call delete('Xdir', 'rf') + set backupdir& backupskip& patchmode& endfunc " Test for writing a file using invalid file encoding @@ -266,15 +511,15 @@ endfunc " Tests for reading and writing files with conversion for Win32. func Test_write_file_encoding() - throw 'skipped: Nvim does not support :w ++enc=cp1251' + throw 'Skipped: Nvim does not support encoding=latin1' CheckMSWindows let save_encoding = &encoding let save_fileencodings = &fileencodings - set encoding& fileencodings& + set encoding=latin1 fileencodings& let text =<< trim END - 1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 - 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 - 3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 1 utf-8 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 + 2 cp1251 text: Vim version 6.2. : 1970 Jan 01 + 3 cp866 text: Vim version 6.2. : 1970 Jan 01 END call writefile(text, 'Xfile') edit Xfile @@ -289,9 +534,9 @@ func Test_write_file_encoding() .w ++enc=cp866 >> Xtest .w! ++enc=utf-8 Xutf8 let expected =<< trim END - 1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 - 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 - 1 utf-8 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 1 utf-8 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 + 1 utf-8 text: Vim version 6.2. : 1970 Jan 01 + 1 utf-8 text: Vim version 6.2. : 1970 Jan 01 END call assert_equal(expected, readfile('Xtest')) @@ -302,9 +547,9 @@ func Test_write_file_encoding() .w ++enc=cp866 >> Xtest .w! ++enc=cp1251 Xcp1251 let expected =<< trim END - 2 cp1251 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 - 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 - 2 cp1251 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 2 cp1251 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 + 2 cp1251 text: Vim version 6.2. : 1970 Jan 01 + 2 cp1251 text: Vim version 6.2. : 1970 Jan 01 END call assert_equal(expected, readfile('Xtest')) @@ -315,9 +560,9 @@ func Test_write_file_encoding() .w ++enc=cp866 >> Xtest .w! ++enc=cp866 Xcp866 let expected =<< trim END - 3 cp866 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 - 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 - 3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 3 cp866 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 + 3 cp866 text: Vim version 6.2. : 1970 Jan 01 + 3 cp866 text: Vim version 6.2. : 1970 Jan 01 END call assert_equal(expected, readfile('Xtest')) @@ -331,9 +576,9 @@ func Test_write_file_encoding() e Xcp866 .w ++enc=utf-8 >> Xtest let expected =<< trim END - 1 utf-8 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 - 2 cp1251 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 - 3 cp866 text: ÐÐ»Ñ Vim version 6.2. ÐоÑледнее изменение: 1970 Jan 01 + 1 utf-8 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 + 2 cp1251 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 + 3 cp866 text: Для Vim version 6.2. Последнее изменение: 1970 Jan 01 END call assert_equal(expected, readfile('Xtest')) @@ -347,9 +592,9 @@ func Test_write_file_encoding() e Xcp866 .w ++enc=cp1251 >> Xtest let expected =<< trim END - 1 utf-8 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 - 2 cp1251 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 - 3 cp866 text: Äëÿ Vim version 6.2. Ïîñëåäíåå èçìåíåíèå: 1970 Jan 01 + 1 utf-8 text: Vim version 6.2. : 1970 Jan 01 + 2 cp1251 text: Vim version 6.2. : 1970 Jan 01 + 3 cp866 text: Vim version 6.2. : 1970 Jan 01 END call assert_equal(expected, readfile('Xtest')) @@ -363,9 +608,9 @@ func Test_write_file_encoding() e Xcp866 .w ++enc=cp866 >> Xtest let expected =<< trim END - 1 utf-8 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 - 2 cp1251 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 - 3 cp866 text: «ï Vim version 6.2. ®á«¥¤¥¥ ¨§¬¥¥¨¥: 1970 Jan 01 + 1 utf-8 text: Vim version 6.2. : 1970 Jan 01 + 2 cp1251 text: Vim version 6.2. : 1970 Jan 01 + 3 cp866 text: Vim version 6.2. : 1970 Jan 01 END call assert_equal(expected, readfile('Xtest')) @@ -523,10 +768,177 @@ func Test_read_write_bin() call assert_equal(0z6E6F656F6C0A, readfile('XNoEolSetEol', 'B')) call delete('XNoEolSetEol') - set ff& + set ff& fixeol& bwipe! XNoEolSetEol endfunc +" Test for the 'backupcopy' option when writing files +func Test_backupcopy() + CheckUnix + set backupskip= + " With the default 'backupcopy' setting, saving a symbolic link file + " should not break the link. + set backupcopy& + call writefile(['1111'], 'Xfile1') + silent !ln -s Xfile1 Xfile2 + new Xfile2 + call setline(1, ['2222']) + write + close + call assert_equal(['2222'], readfile('Xfile1')) + call assert_equal('Xfile1', resolve('Xfile2')) + call assert_equal('link', getftype('Xfile2')) + call delete('Xfile1') + call delete('Xfile2') + + " With the 'backupcopy' set to 'breaksymlink', saving a symbolic link file + " should break the link. + set backupcopy=yes,breaksymlink + call writefile(['1111'], 'Xfile1') + silent !ln -s Xfile1 Xfile2 + new Xfile2 + call setline(1, ['2222']) + write + close + call assert_equal(['1111'], readfile('Xfile1')) + call assert_equal(['2222'], readfile('Xfile2')) + call assert_equal('Xfile2', resolve('Xfile2')) + call assert_equal('file', getftype('Xfile2')) + call delete('Xfile1') + call delete('Xfile2') + set backupcopy& + + " With the default 'backupcopy' setting, saving a hard link file + " should not break the link. + set backupcopy& + call writefile(['1111'], 'Xfile1') + silent !ln Xfile1 Xfile2 + new Xfile2 + call setline(1, ['2222']) + write + close + call assert_equal(['2222'], readfile('Xfile1')) + call delete('Xfile1') + call delete('Xfile2') + + " With the 'backupcopy' set to 'breaksymlink', saving a hard link file + " should break the link. + set backupcopy=yes,breakhardlink + call writefile(['1111'], 'Xfile1') + silent !ln Xfile1 Xfile2 + new Xfile2 + call setline(1, ['2222']) + write + call assert_equal(['1111'], readfile('Xfile1')) + call assert_equal(['2222'], readfile('Xfile2')) + call delete('Xfile1') + call delete('Xfile2') + + " If a backup file is already present, then a slightly modified filename + " should be used as the backup file. Try with 'backupcopy' set to 'yes' and + " 'no'. + %bw + call writefile(['aaaa'], 'Xfile') + call writefile(['bbbb'], 'Xfile.bak') + set backupcopy=yes backupext=.bak + new Xfile + call setline(1, ['cccc']) + write + close + call assert_equal(['cccc'], readfile('Xfile')) + call assert_equal(['bbbb'], readfile('Xfile.bak')) + set backupcopy=no backupext=.bak + new Xfile + call setline(1, ['dddd']) + write + close + call assert_equal(['dddd'], readfile('Xfile')) + call assert_equal(['bbbb'], readfile('Xfile.bak')) + call delete('Xfile') + call delete('Xfile.bak') + + " Write to a device file (in Unix-like systems) which cannot be backed up. + if has('unix') + set writebackup backupcopy=yes nobackup + new + call setline(1, ['aaaa']) + let output = execute('write! /dev/null') + call assert_match('"/dev/null" \[Device]', output) + close + set writebackup backupcopy=no nobackup + new + call setline(1, ['aaaa']) + let output = execute('write! /dev/null') + call assert_match('"/dev/null" \[Device]', output) + close + set backup writebackup& backupcopy& + new + call setline(1, ['aaaa']) + let output = execute('write! /dev/null') + call assert_match('"/dev/null" \[Device]', output) + close + endif + + set backupcopy& backupskip& backupext& backup& +endfunc + +" Test for writing a file with 'encoding' set to 'utf-16' +func Test_write_utf16() + new + call setline(1, ["\U00010001"]) + write ++enc=utf-16 Xfile + bw! + call assert_equal(0zD800DC01, readfile('Xfile', 'B')[0:3]) + call delete('Xfile') +endfunc + +" Test for trying to save a backup file when the backup file is a symbolic +" link to the original file. The backup file should not be modified. +func Test_write_backup_symlink() + CheckUnix + call mkdir('Xbackup') + let save_backupdir = &backupdir + set backupdir=.,./Xbackup + call writefile(['1111'], 'Xfile') + silent !ln -s Xfile Xfile.bak + + new Xfile + set backup backupcopy=yes backupext=.bak + write + call assert_equal('link', getftype('Xfile.bak')) + call assert_equal('Xfile', resolve('Xfile.bak')) + " backup file should be created in the 'backup' directory + if !has('bsd') + " This check fails on FreeBSD + call assert_true(filereadable('./Xbackup/Xfile.bak')) + endif + set backup& backupcopy& backupext& + %bw + + call delete('Xfile') + call delete('Xfile.bak') + call delete('Xbackup', 'rf') + let &backupdir = save_backupdir +endfunc + +" Test for ':write ++bin' and ':write ++nobin' +func Test_write_binary_file() + " create a file without an eol/eof character + call writefile(0z616161, 'Xfile1', 'b') + new Xfile1 + write ++bin Xfile2 + write ++nobin Xfile3 + call assert_equal(0z616161, readblob('Xfile2')) + if has('win32') + call assert_equal(0z6161610D.0A, readblob('Xfile3')) + else + call assert_equal(0z6161610A, readblob('Xfile3')) + endif + call delete('Xfile1') + call delete('Xfile2') + call delete('Xfile3') +endfunc + " Check that buffer is written before triggering QuitPre func Test_wq_quitpre_autocommand() edit Xsomefile diff --git a/src/nvim/testdir/view_util.vim b/src/nvim/testdir/view_util.vim index 1cdce21602..a4d0e56af9 100644 --- a/src/nvim/testdir/view_util.vim +++ b/src/nvim/testdir/view_util.vim @@ -19,7 +19,7 @@ endfunc " Get text on the screen, including composing characters. " ScreenLines(lnum, width) or " ScreenLines([start, end], width) -function! ScreenLines(lnum, width) abort +func ScreenLines(lnum, width) abort redraw! if type(a:lnum) == v:t_list let start = a:lnum[0] @@ -33,9 +33,9 @@ function! ScreenLines(lnum, width) abort let lines += [join(map(range(1, a:width), 'screenstring(l, v:val)'), '')] endfor return lines -endfunction +endfunc -function! ScreenAttrs(lnum, width) abort +func ScreenAttrs(lnum, width) abort redraw! if type(a:lnum) == v:t_list let start = a:lnum[0] @@ -49,16 +49,16 @@ function! ScreenAttrs(lnum, width) abort let attrs += [map(range(1, a:width), 'screenattr(l, v:val)')] endfor return attrs -endfunction +endfunc -function! NewWindow(height, width) abort +func NewWindow(height, width) abort exe a:height . 'new' exe a:width . 'vsp' set winfixwidth winfixheight redraw! -endfunction +endfunc -function! CloseWindow() abort +func CloseWindow() abort bw! redraw! -endfunction +endfunc diff --git a/src/nvim/testdir/vim9.vim b/src/nvim/testdir/vim9.vim new file mode 100644 index 0000000000..3c0ff2b2dd --- /dev/null +++ b/src/nvim/testdir/vim9.vim @@ -0,0 +1,112 @@ + +" Use a different file name for each run. +let s:sequence = 1 + +func CheckScriptFailure(lines, error, lnum = -3) + if get(a:lines, 0, '') ==# 'vim9script' + return + endif + let cwd = getcwd() + let fname = 'XScriptFailure' .. s:sequence + let s:sequence += 1 + call writefile(a:lines, fname) + try + call assert_fails('so ' .. fname, a:error, a:lines, a:lnum) + finally + call chdir(cwd) + call delete(fname) + endtry +endfunc + +func CheckScriptSuccess(lines) + if get(a:lines, 0, '') ==# 'vim9script' + return + endif + let cwd = getcwd() + let fname = 'XScriptSuccess' .. s:sequence + let s:sequence += 1 + call writefile(a:lines, fname) + try + exe 'so ' .. fname + finally + call chdir(cwd) + call delete(fname) + endtry +endfunc + +" Check that "lines" inside a legacy function has no error. +func CheckLegacySuccess(lines) + let cwd = getcwd() + let fname = 'XlegacySuccess' .. s:sequence + let s:sequence += 1 + call writefile(['func Func()'] + a:lines + ['endfunc'], fname) + try + exe 'so ' .. fname + call Func() + finally + delfunc! Func + call chdir(cwd) + call delete(fname) + endtry +endfunc + +" Check that "lines" inside a legacy function results in the expected error +func CheckLegacyFailure(lines, error) + let cwd = getcwd() + let fname = 'XlegacyFails' .. s:sequence + let s:sequence += 1 + call writefile(['func Func()'] + a:lines + ['endfunc', 'call Func()'], fname) + try + call assert_fails('so ' .. fname, a:error) + finally + delfunc! Func + call chdir(cwd) + call delete(fname) + endtry +endfunc + +" Execute "lines" in a legacy function, translated as in +" CheckLegacyAndVim9Success() +func CheckTransLegacySuccess(lines) + let legacylines = a:lines->deepcopy()->map({_, v -> + \ v->substitute('\<VAR\>', 'let', 'g') + \ ->substitute('\<LET\>', 'let', 'g') + \ ->substitute('\<LSTART\>', '{', 'g') + \ ->substitute('\<LMIDDLE\>', '->', 'g') + \ ->substitute('\<LEND\>', '}', 'g') + \ ->substitute('\<TRUE\>', '1', 'g') + \ ->substitute('\<FALSE\>', '0', 'g') + \ ->substitute('#"', ' "', 'g') + \ }) + call CheckLegacySuccess(legacylines) +endfunc + +" Execute "lines" in a legacy function +" Use 'VAR' for a declaration. +" Use 'LET' for an assignment +" Use ' #"' for a comment +" Use LSTART arg LMIDDLE expr LEND for lambda +" Use 'TRUE' for 1 +" Use 'FALSE' for 0 +func CheckLegacyAndVim9Success(lines) + call CheckTransLegacySuccess(a:lines) +endfunc + +" Execute "lines" in a legacy function +" Use 'VAR' for a declaration. +" Use 'LET' for an assignment +" Use ' #"' for a comment +func CheckLegacyAndVim9Failure(lines, error) + if type(a:error) == type('string') + let legacyError = error + else + let legacyError = error[0] + endif + + let legacylines = a:lines->deepcopy()->map({_, v -> + \ v->substitute('\<VAR\>', 'let', 'g') + \ ->substitute('\<LET\>', 'let', 'g') + \ ->substitute('#"', ' "', 'g') + \ }) + call CheckLegacyFailure(legacylines, legacyError) +endfunc diff --git a/src/nvim/testing.c b/src/nvim/testing.c index e70e9f2cbd..edf92c78ac 100644 --- a/src/nvim/testing.c +++ b/src/nvim/testing.c @@ -3,17 +3,46 @@ // testing.c: Support for tests +#include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> + +#include "nvim/ascii.h" #include "nvim/eval.h" #include "nvim/eval/encode.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/ex_docmd.h" +#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/hashtab.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" +#include "nvim/message.h" #include "nvim/os/os.h" #include "nvim/runtime.h" +#include "nvim/strings.h" #include "nvim/testing.h" +#include "nvim/types.h" +#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "testing.c.generated.h" #endif +static char e_assert_fails_second_arg[] + = N_("E856: assert_fails() second argument must be a string or a list with one or two strings"); +static char e_assert_fails_fourth_argument[] + = N_("E1115: assert_fails() fourth argument must be a number"); +static char e_assert_fails_fifth_argument[] + = N_("E1116: assert_fails() fifth argument must be a string"); +static char e_calling_test_garbagecollect_now_while_v_testing_is_not_set[] + = N_("E1142: Calling test_garbagecollect_now() while v:testing is not set"); + /// Prepare "gap" for an assert error and add the sourcing position. static void prepare_assert_error(garray_T *gap) { @@ -39,15 +68,15 @@ static void prepare_assert_error(garray_T *gap) /// Append "p[clen]" to "gap", escaping unprintable characters. /// Changes NL to \n, CR to \r, etc. -static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) +static void ga_concat_esc(garray_T *gap, const char *p, int clen) FUNC_ATTR_NONNULL_ALL { - char_u buf[NUMBUFLEN]; + char buf[NUMBUFLEN]; if (clen > 1) { memmove(buf, p, (size_t)clen); buf[clen] = NUL; - ga_concat(gap, (char *)buf); + ga_concat(gap, buf); } else { switch (*p) { case BS: @@ -65,11 +94,11 @@ static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) case '\\': ga_concat(gap, "\\\\"); break; default: - if (*p < ' ') { - vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p); - ga_concat(gap, (char *)buf); + if ((uint8_t)(*p) < ' ' || *p == 0x7f) { + vim_snprintf(buf, NUMBUFLEN, "\\x%02x", *p); + ga_concat(gap, buf); } else { - ga_append(gap, (char)(*p)); + ga_append(gap, (uint8_t)(*p)); } break; } @@ -78,22 +107,22 @@ static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) /// Append "str" to "gap", escaping unprintable characters. /// Changes NL to \n, CR to \r, etc. -static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) +static void ga_concat_shorten_esc(garray_T *gap, const char *str) FUNC_ATTR_NONNULL_ARG(1) { - char_u buf[NUMBUFLEN]; + char buf[NUMBUFLEN]; if (str == NULL) { ga_concat(gap, "NULL"); return; } - for (const char_u *p = str; *p != NUL; p++) { + for (const char *p = str; *p != NUL; p++) { int same_len = 1; - const char_u *s = p; + const char *s = p; const int c = mb_ptr2char_adv(&s); const int clen = (int)(s - p); - while (*s != NUL && c == utf_ptr2char((char *)s)) { + while (*s != NUL && c == utf_ptr2char(s)) { same_len++; s += clen; } @@ -101,8 +130,8 @@ static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) ga_concat(gap, "\\["); ga_concat_esc(gap, p, clen); ga_concat(gap, " occurs "); - vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len); - ga_concat(gap, (char *)buf); + vim_snprintf(buf, NUMBUFLEN, "%d", same_len); + ga_concat(gap, buf); ga_concat(gap, " times]"); p = s - 1; } else { @@ -112,18 +141,21 @@ static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) } /// Fill "gap" with information about an assert error. -static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, +static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str, typval_T *exp_tv_arg, typval_T *got_tv_arg, assert_type_T atype) { - char_u *tofree; + char *tofree; typval_T *exp_tv = exp_tv_arg; typval_T *got_tv = got_tv_arg; bool did_copy = false; int omitted = 0; - if (opt_msg_tv->v_type != VAR_UNKNOWN) { - tofree = (char_u *)encode_tv2echo(opt_msg_tv, NULL); - ga_concat(gap, (char *)tofree); + if (opt_msg_tv->v_type != VAR_UNKNOWN + && !(opt_msg_tv->v_type == VAR_STRING + && (opt_msg_tv->vval.v_string == NULL + || *opt_msg_tv->vval.v_string == NUL))) { + tofree = encode_tv2echo(opt_msg_tv, NULL); + ga_concat(gap, tofree); xfree(tofree); ga_concat(gap, ": "); } @@ -156,7 +188,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s if (item2 == NULL || !tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &item2->di_tv, false, false)) { // item of exp_d not present in got_d or values differ. - const size_t key_len = STRLEN(hi->hi_key); + const size_t key_len = strlen(hi->hi_key); tv_dict_add_tv(exp_tv->vval.v_dict, (const char *)hi->hi_key, key_len, &TV_DICT_HI2DI(hi)->di_tv); if (item2 != NULL) { @@ -177,7 +209,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s dictitem_T *item2 = tv_dict_find(exp_d, (const char *)hi->hi_key, -1); if (item2 == NULL) { // item of got_d not present in exp_d - const size_t key_len = STRLEN(hi->hi_key); + const size_t key_len = strlen(hi->hi_key); tv_dict_add_tv(got_tv->vval.v_dict, (const char *)hi->hi_key, key_len, &TV_DICT_HI2DI(hi)->di_tv); } @@ -186,7 +218,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s } } - tofree = (char_u *)encode_tv2string(exp_tv, NULL); + tofree = encode_tv2string(exp_tv, NULL); ga_concat_shorten_esc(gap, tofree); xfree(tofree); } else { @@ -201,7 +233,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_s } else { ga_concat(gap, " but got "); } - tofree = (char_u *)encode_tv2string(got_tv, NULL); + tofree = encode_tv2string(got_tv, NULL); ga_concat_shorten_esc(gap, tofree); xfree(tofree); @@ -241,13 +273,12 @@ static int assert_match_common(typval_T *argvars, assert_type_T atype) { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; + const int called_emsg_before = called_emsg; const char *const pat = tv_get_string_buf_chk(&argvars[0], buf1); const char *const text = tv_get_string_buf_chk(&argvars[1], buf2); - if (pat == NULL || text == NULL) { - emsg(_(e_invarg)); - } else if (pattern_match((char *)pat, (char *)text, false) - != (atype == ASSERT_MATCH)) { + if (called_emsg == called_emsg_before + && pattern_match(pat, text, false) != (atype == ASSERT_MATCH)) { garray_T ga; prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype); @@ -275,7 +306,7 @@ static int assert_bool(typval_T *argvars, bool is_true) : kBoolVarFalse)))) { prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[1], - (char_u *)(is_true ? "True" : "False"), + is_true ? "True" : "False", NULL, &argvars[0], ASSERT_OTHER); assert_error(&ga); ga_clear(&ga); @@ -326,19 +357,19 @@ static int assert_beeps(typval_T *argvars, bool no_beep) } /// "assert_beeps(cmd [, error])" function -void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_beeps(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_beeps(argvars, false); } /// "assert_nobeep(cmd [, error])" function -void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_nobeep(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_beeps(argvars, true); } /// "assert_equal(expected, actual[, msg])" function -void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_equal(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); } @@ -348,11 +379,12 @@ static int assert_equalfile(typval_T *argvars) { char buf1[NUMBUFLEN]; char buf2[NUMBUFLEN]; + const int called_emsg_before = called_emsg; const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1); const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2); garray_T ga; - if (fname1 == NULL || fname2 == NULL) { + if (called_emsg > called_emsg_before) { return 0; } @@ -362,12 +394,12 @@ static int assert_equalfile(typval_T *argvars) char line2[200]; ptrdiff_t lineidx = 0; if (fd1 == NULL) { - snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); + snprintf(IObuff, IOSIZE, (char *)e_notread, fname1); } else { FILE *const fd2 = os_fopen(fname2, READBIN); if (fd2 == NULL) { fclose(fd1); - snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2); + snprintf(IObuff, IOSIZE, (char *)e_notread, fname2); } else { int64_t linecount = 1; for (int64_t count = 0;; count++) { @@ -386,7 +418,7 @@ static int assert_equalfile(typval_T *argvars) line2[lineidx] = (char)c2; lineidx++; if (c1 != c2) { - snprintf((char *)IObuff, IOSIZE, + snprintf(IObuff, IOSIZE, "difference at byte %" PRId64 ", line %" PRId64, count, linecount); break; @@ -413,13 +445,13 @@ static int assert_equalfile(typval_T *argvars) xfree(tofree); ga_concat(&ga, ": "); } - ga_concat(&ga, (char *)IObuff); + ga_concat(&ga, IObuff); if (lineidx > 0) { line1[lineidx] = NUL; line2[lineidx] = NUL; ga_concat(&ga, " after \""); ga_concat(&ga, line1); - if (STRCMP(line1, line2) != 0) { + if (strcmp(line1, line2) != 0) { ga_concat(&ga, "\" vs \""); ga_concat(&ga, line2); } @@ -433,19 +465,19 @@ static int assert_equalfile(typval_T *argvars) } /// "assert_equalfile(fname-one, fname-two[, msg])" function -void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_equalfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_equalfile(argvars); } /// "assert_notequal(expected, actual[, msg])" function -void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_notequal(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); } /// "assert_exception(string[, msg])" function -void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_exception(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { garray_T ga; @@ -457,7 +489,7 @@ void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_clear(&ga); rettv->vval.v_number = 1; } else if (error != NULL - && strstr((char *)get_vim_var_str(VV_EXCEPTION), error) == NULL) { + && strstr(get_vim_var_str(VV_EXCEPTION), error) == NULL) { prepare_assert_error(&ga); fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], get_vim_var_tv(VV_EXCEPTION), ASSERT_OTHER); @@ -468,17 +500,19 @@ void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "assert_fails(cmd [, error [, msg]])" function -void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; int save_trylevel = trylevel; const int called_emsg_before = called_emsg; + char *wrong_arg_msg = NULL; // trylevel must be zero for a ":throw" command to be considered failed trylevel = 0; suppress_errthrow = true; - emsg_silent = true; + in_assert_fails = true; + no_wait_return++; do_cmdline_cmd(cmd); if (called_emsg == called_emsg_before) { @@ -490,13 +524,75 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 1; } else if (argvars[1].v_type != VAR_UNKNOWN) { char buf[NUMBUFLEN]; - const char *const error = tv_get_string_buf_chk(&argvars[1], buf); + const char *expected; + bool error_found = false; + int error_found_index = 1; + char *actual = emsg_assert_fails_msg == NULL ? "[unknown]" : emsg_assert_fails_msg; + + if (argvars[1].v_type == VAR_STRING) { + expected = tv_get_string_buf_chk(&argvars[1], buf); + error_found = expected == NULL || strstr(actual, expected) == NULL; + } else if (argvars[1].v_type == VAR_LIST) { + const list_T *const list = argvars[1].vval.v_list; + if (list == NULL || tv_list_len(list) < 1 || tv_list_len(list) > 2) { + wrong_arg_msg = e_assert_fails_second_arg; + goto theend; + } + const typval_T *tv = TV_LIST_ITEM_TV(tv_list_first(list)); + expected = tv_get_string_buf_chk(tv, buf); + if (!pattern_match(expected, actual, false)) { + error_found = true; + } else if (tv_list_len(list) == 2) { + tv = TV_LIST_ITEM_TV(tv_list_last(list)); + actual = get_vim_var_str(VV_ERRMSG); + expected = tv_get_string_buf_chk(tv, buf); + if (!pattern_match(expected, actual, false)) { + error_found = true; + } + } + } else { + wrong_arg_msg = e_assert_fails_second_arg; + goto theend; + } + + if (!error_found && argvars[2].v_type != VAR_UNKNOWN + && argvars[3].v_type != VAR_UNKNOWN) { + if (argvars[3].v_type != VAR_NUMBER) { + wrong_arg_msg = e_assert_fails_fourth_argument; + goto theend; + } else if (argvars[3].vval.v_number >= 0 + && argvars[3].vval.v_number != emsg_assert_fails_lnum) { + error_found = true; + error_found_index = 3; + } + if (!error_found && argvars[4].v_type != VAR_UNKNOWN) { + if (argvars[4].v_type != VAR_STRING) { + wrong_arg_msg = e_assert_fails_fifth_argument; + goto theend; + } else if (argvars[4].vval.v_string != NULL + && !pattern_match(argvars[4].vval.v_string, + emsg_assert_fails_context, false)) { + error_found = true; + error_found_index = 4; + } + } + } - if (error == NULL - || strstr((char *)get_vim_var_str(VV_ERRMSG), error) == NULL) { + if (error_found) { + typval_T actual_tv; prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], - get_vim_var_tv(VV_ERRMSG), ASSERT_OTHER); + if (error_found_index == 3) { + actual_tv.v_type = VAR_NUMBER; + actual_tv.vval.v_number = emsg_assert_fails_lnum; + } else if (error_found_index == 4) { + actual_tv.v_type = VAR_STRING; + actual_tv.vval.v_string = emsg_assert_fails_context; + } else { + actual_tv.v_type = VAR_STRING; + actual_tv.vval.v_string = actual; + } + fill_assert_error(&ga, &argvars[2], NULL, + &argvars[error_found_index], &actual_tv, ASSERT_OTHER); ga_concat(&ga, ": "); assert_append_cmd_or_arg(&ga, argvars, cmd); assert_error(&ga); @@ -505,15 +601,27 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +theend: trylevel = save_trylevel; suppress_errthrow = false; - emsg_silent = false; + in_assert_fails = false; + did_emsg = false; + got_int = false; + msg_col = 0; + no_wait_return--; + need_wait_return = false; emsg_on_display = false; + msg_reset_scroll(); + lines_left = Rows; + XFREE_CLEAR(emsg_assert_fails_msg); set_vim_var_string(VV_ERRMSG, NULL, 0); + if (wrong_arg_msg != NULL) { + emsg(_(wrong_arg_msg)); + } } // "assert_false(actual[, msg])" function -void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_false(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_bool(argvars, false); } @@ -534,8 +642,8 @@ static int assert_inrange(typval_T *argvars) garray_T ga; prepare_assert_error(&ga); if (argvars[3].v_type != VAR_UNKNOWN) { - char_u *const tofree = (char_u *)encode_tv2string(&argvars[3], NULL); - ga_concat(&ga, (char *)tofree); + char *const tofree = encode_tv2string(&argvars[3], NULL); + ga_concat(&ga, tofree); xfree(tofree); } else { char msg[80]; @@ -563,7 +671,7 @@ static int assert_inrange(typval_T *argvars) vim_snprintf(msg, sizeof(msg), "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", lower, upper); // -V576 - fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], + fill_assert_error(&ga, &argvars[3], msg, NULL, &argvars[2], ASSERT_INRANGE); assert_error(&ga); ga_clear(&ga); @@ -574,25 +682,25 @@ static int assert_inrange(typval_T *argvars) } /// "assert_inrange(lower, upper[, msg])" function -void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_inrange(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_inrange(argvars); } /// "assert_match(pattern, actual[, msg])" function -void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_match(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); } /// "assert_notmatch(pattern, actual[, msg])" function -void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_notmatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); } /// "assert_report(msg)" function -void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_report(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { garray_T ga; @@ -604,21 +712,25 @@ void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// "assert_true(actual[, msg])" function -void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_assert_true(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { rettv->vval.v_number = assert_bool(argvars, true); } /// "test_garbagecollect_now()" function -void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr) +void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { // This is dangerous, any Lists and Dicts used internally may be freed // while still in use. - garbage_collect(true); + if (!get_vim_var_nr(VV_TESTING)) { + emsg(_(e_calling_test_garbagecollect_now_while_v_testing_is_not_set)); + } else { + garbage_collect(true); + } } /// "test_write_list_log()" function -void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, FunPtr fptr) +void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, EvalFuncData fptr) { const char *const fname = tv_get_string_chk(&argvars[0]); if (fname == NULL) { diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c new file mode 100644 index 0000000000..fbea1ccfb7 --- /dev/null +++ b/src/nvim/textformat.c @@ -0,0 +1,1138 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// textformat.c: text formatting functions + +#include <stdbool.h> +#include <stdint.h> +#include <string.h> + +#include "nvim/ascii.h" +#include "nvim/buffer_defs.h" +#include "nvim/change.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" +#include "nvim/drawscreen.h" +#include "nvim/edit.h" +#include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/getchar.h" +#include "nvim/globals.h" +#include "nvim/indent.h" +#include "nvim/indent_c.h" +#include "nvim/macros.h" +#include "nvim/mark.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/move.h" +#include "nvim/normal.h" +#include "nvim/ops.h" +#include "nvim/option.h" +#include "nvim/os/input.h" +#include "nvim/pos.h" +#include "nvim/search.h" +#include "nvim/strings.h" +#include "nvim/textformat.h" +#include "nvim/textobject.h" +#include "nvim/types.h" +#include "nvim/undo.h" +#include "nvim/vim.h" +#include "nvim/window.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "textformat.c.generated.h" +#endif + +static bool did_add_space = false; ///< auto_format() added an extra space + ///< under the cursor + +#define WHITECHAR(cc) (ascii_iswhite(cc) \ + && !utf_iscomposing(utf_ptr2char((char *)get_cursor_pos_ptr() + 1))) + +/// Return true if format option 'x' is in effect. +/// Take care of no formatting when 'paste' is set. +bool has_format_option(int x) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (p_paste) { + return false; + } + return vim_strchr(curbuf->b_p_fo, x) != NULL; +} + +/// Format text at the current insert position. +/// +/// If the INSCHAR_COM_LIST flag is present, then the value of second_indent +/// will be the comment leader length sent to open_line(). +/// +/// @param c character to be inserted (can be NUL) +void internal_format(int textwidth, int second_indent, int flags, bool format_only, int c) +{ + int cc; + int save_char = NUL; + bool haveto_redraw = false; + const bool fo_ins_blank = has_format_option(FO_INS_BLANK); + const bool fo_multibyte = has_format_option(FO_MBYTE_BREAK); + const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW); + const bool fo_white_par = has_format_option(FO_WHITE_PAR); + bool first_line = true; + colnr_T leader_len; + bool no_leader = false; + int do_comments = (flags & INSCHAR_DO_COM); + int has_lbr = curwin->w_p_lbr; + + // make sure win_lbr_chartabsize() counts correctly + curwin->w_p_lbr = false; + + // When 'ai' is off we don't want a space under the cursor to be + // deleted. Replace it with an 'x' temporarily. + if (!curbuf->b_p_ai + && !(State & VREPLACE_FLAG)) { + cc = gchar_cursor(); + if (ascii_iswhite(cc)) { + save_char = cc; + pchar_cursor('x'); + } + } + + // Repeat breaking lines, until the current line is not too long. + while (!got_int) { + int startcol; // Cursor column at entry + int wantcol; // column at textwidth border + int foundcol; // column for start of spaces + int end_foundcol = 0; // column for start of word + colnr_T len; + colnr_T virtcol; + int orig_col = 0; + char *saved_text = NULL; + colnr_T col; + colnr_T end_col; + bool did_do_comment = false; + + virtcol = get_nolist_virtcol() + + char2cells(c != NUL ? c : gchar_cursor()); + if (virtcol <= (colnr_T)textwidth) { + break; + } + + if (no_leader) { + do_comments = false; + } else if (!(flags & INSCHAR_FORMAT) + && has_format_option(FO_WRAP_COMS)) { + do_comments = true; + } + + // Don't break until after the comment leader + if (do_comments) { + char *line = get_cursor_line_ptr(); + leader_len = get_leader_len(line, NULL, false, true); + if (leader_len == 0 && curbuf->b_p_cin) { + // Check for a line comment after code. + int comment_start = check_linecomment(line); + if (comment_start != MAXCOL) { + leader_len = get_leader_len(line + comment_start, NULL, false, true); + if (leader_len != 0) { + leader_len += comment_start; + } + } + } + } else { + leader_len = 0; + } + + // If the line doesn't start with a comment leader, then don't + // start one in a following broken line. Avoids that a %word + // moved to the start of the next line causes all following lines + // to start with %. + if (leader_len == 0) { + no_leader = true; + } + if (!(flags & INSCHAR_FORMAT) + && leader_len == 0 + && !has_format_option(FO_WRAP)) { + break; + } + if ((startcol = curwin->w_cursor.col) == 0) { + break; + } + + // find column of textwidth border + coladvance((colnr_T)textwidth); + wantcol = curwin->w_cursor.col; + + curwin->w_cursor.col = startcol; + foundcol = 0; + int skip_pos = 0; + + // Find position to break at. + // Stop at first entered white when 'formatoptions' has 'v' + while ((!fo_ins_blank && !has_format_option(FO_INS_VI)) + || (flags & INSCHAR_FORMAT) + || curwin->w_cursor.lnum != Insstart.lnum + || curwin->w_cursor.col >= Insstart.col) { + if (curwin->w_cursor.col == startcol && c != NUL) { + cc = c; + } else { + cc = gchar_cursor(); + } + if (WHITECHAR(cc)) { + // remember position of blank just before text + end_col = curwin->w_cursor.col; + + // find start of sequence of blanks + int wcc = 0; // counter for whitespace chars + while (curwin->w_cursor.col > 0 && WHITECHAR(cc)) { + dec_cursor(); + cc = gchar_cursor(); + + // Increment count of how many whitespace chars in this + // group; we only need to know if it's more than one. + if (wcc < 2) { + wcc++; + } + } + if (curwin->w_cursor.col == 0 && WHITECHAR(cc)) { + break; // only spaces in front of text + } + + // Don't break after a period when 'formatoptions' has 'p' and + // there are less than two spaces. + if (has_format_option(FO_PERIOD_ABBR) && cc == '.' && wcc < 2) { + continue; + } + + // Don't break until after the comment leader + if (curwin->w_cursor.col < leader_len) { + break; + } + + if (has_format_option(FO_ONE_LETTER)) { + // do not break after one-letter words + if (curwin->w_cursor.col == 0) { + break; // one-letter word at begin + } + // do not break "#a b" when 'tw' is 2 + if (curwin->w_cursor.col <= leader_len) { + break; + } + col = curwin->w_cursor.col; + dec_cursor(); + cc = gchar_cursor(); + + if (WHITECHAR(cc)) { + continue; // one-letter, continue + } + curwin->w_cursor.col = col; + } + + inc_cursor(); + + end_foundcol = end_col + 1; + foundcol = curwin->w_cursor.col; + if (curwin->w_cursor.col <= (colnr_T)wantcol) { + break; + } + } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) && fo_multibyte) { + int ncc; + bool allow_break; + + // Break after or before a multi-byte character. + if (curwin->w_cursor.col != startcol) { + // Don't break until after the comment leader + if (curwin->w_cursor.col < leader_len) { + break; + } + col = curwin->w_cursor.col; + inc_cursor(); + ncc = gchar_cursor(); + allow_break = utf_allow_break(cc, ncc); + + // If we have already checked this position, skip! + if (curwin->w_cursor.col != skip_pos && allow_break) { + foundcol = curwin->w_cursor.col; + end_foundcol = foundcol; + if (curwin->w_cursor.col <= (colnr_T)wantcol) { + break; + } + } + curwin->w_cursor.col = col; + } + + if (curwin->w_cursor.col == 0) { + break; + } + + ncc = cc; + col = curwin->w_cursor.col; + + dec_cursor(); + cc = gchar_cursor(); + + if (WHITECHAR(cc)) { + continue; // break with space + } + // Don't break until after the comment leader. + if (curwin->w_cursor.col < leader_len) { + break; + } + + curwin->w_cursor.col = col; + skip_pos = curwin->w_cursor.col; + + allow_break = utf_allow_break(cc, ncc); + + // Must handle this to respect line break prohibition. + if (allow_break) { + foundcol = curwin->w_cursor.col; + end_foundcol = foundcol; + } + if (curwin->w_cursor.col <= (colnr_T)wantcol) { + const bool ncc_allow_break = utf_allow_break_before(ncc); + + if (allow_break) { + break; + } + if (!ncc_allow_break && !fo_rigor_tw) { + // Enable at most 1 punct hang outside of textwidth. + if (curwin->w_cursor.col == startcol) { + // We are inserting a non-breakable char, postpone + // line break check to next insert. + end_foundcol = foundcol = 0; + break; + } + + // Neither cc nor ncc is NUL if we are here, so + // it's safe to inc_cursor. + col = curwin->w_cursor.col; + + inc_cursor(); + cc = ncc; + ncc = gchar_cursor(); + // handle insert + ncc = (ncc != NUL) ? ncc : c; + + allow_break = utf_allow_break(cc, ncc); + + if (allow_break) { + // Break only when we are not at end of line. + end_foundcol = foundcol = ncc == NUL? 0 : curwin->w_cursor.col; + break; + } + curwin->w_cursor.col = col; + } + } + } + if (curwin->w_cursor.col == 0) { + break; + } + dec_cursor(); + } + + if (foundcol == 0) { // no spaces, cannot break line + curwin->w_cursor.col = startcol; + break; + } + + // Going to break the line, remove any "$" now. + undisplay_dollar(); + + // Offset between cursor position and line break is used by replace + // stack functions. MODE_VREPLACE does not use this, and backspaces + // over the text instead. + if (State & VREPLACE_FLAG) { + orig_col = startcol; // Will start backspacing from here + } else { + replace_offset = startcol - end_foundcol; + } + + // adjust startcol for spaces that will be deleted and + // characters that will remain on top line + curwin->w_cursor.col = foundcol; + while ((cc = gchar_cursor(), WHITECHAR(cc)) + && (!fo_white_par || curwin->w_cursor.col < startcol)) { + inc_cursor(); + } + startcol -= curwin->w_cursor.col; + if (startcol < 0) { + startcol = 0; + } + + if (State & VREPLACE_FLAG) { + // In MODE_VREPLACE state, we will backspace over the text to be + // wrapped, so save a copy now to put on the next line. + saved_text = xstrdup(get_cursor_pos_ptr()); + curwin->w_cursor.col = orig_col; + saved_text[startcol] = NUL; + + // Backspace over characters that will move to the next line + if (!fo_white_par) { + backspace_until_column(foundcol); + } + } else { + // put cursor after pos. to break line + if (!fo_white_par) { + curwin->w_cursor.col = foundcol; + } + } + + // Split the line just before the margin. + // Only insert/delete lines, but don't really redraw the window. + open_line(FORWARD, OPENLINE_DELSPACES + OPENLINE_MARKFIX + + (fo_white_par ? OPENLINE_KEEPTRAIL : 0) + + (do_comments ? OPENLINE_DO_COM : 0) + + OPENLINE_FORMAT + + ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0), + ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent), + &did_do_comment); + if (!(flags & INSCHAR_COM_LIST)) { + old_indent = 0; + } + + // If a comment leader was inserted, may also do this on a following + // line. + if (did_do_comment) { + no_leader = false; + } + + replace_offset = 0; + if (first_line) { + if (!(flags & INSCHAR_COM_LIST)) { + // This section is for auto-wrap of numeric lists. When not + // in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST + // flag will be set and open_line() will handle it (as seen + // above). The code here (and in get_number_indent()) will + // recognize comments if needed... + if (second_indent < 0 && has_format_option(FO_Q_NUMBER)) { + second_indent = get_number_indent(curwin->w_cursor.lnum - 1); + } + if (second_indent >= 0) { + if (State & VREPLACE_FLAG) { + change_indent(INDENT_SET, second_indent, false, NUL, true); + } else if (leader_len > 0 && second_indent - leader_len > 0) { + int padding = second_indent - leader_len; + + // We started at the first_line of a numbered list + // that has a comment. the open_line() function has + // inserted the proper comment leader and positioned + // the cursor at the end of the split line. Now we + // add the additional whitespace needed after the + // comment leader for the numbered list. + for (int i = 0; i < padding; i++) { + ins_str(" "); + } + changed_bytes(curwin->w_cursor.lnum, leader_len); + } else { + (void)set_indent(second_indent, SIN_CHANGED); + } + } + } + first_line = false; + } + + if (State & VREPLACE_FLAG) { + // In MODE_VREPLACE state we have backspaced over the text to be + // moved, now we re-insert it into the new line. + ins_bytes(saved_text); + xfree(saved_text); + } else { + // Check if cursor is not past the NUL off the line, cindent + // may have added or removed indent. + curwin->w_cursor.col += startcol; + len = (colnr_T)strlen(get_cursor_line_ptr()); + if (curwin->w_cursor.col > len) { + curwin->w_cursor.col = len; + } + } + + haveto_redraw = true; + set_can_cindent(true); + // moved the cursor, don't autoindent or cindent now + did_ai = false; + did_si = false; + can_si = false; + can_si_back = false; + line_breakcheck(); + } + + if (save_char != NUL) { // put back space after cursor + pchar_cursor((char_u)save_char); + } + + curwin->w_p_lbr = has_lbr; + + if (!format_only && haveto_redraw) { + update_topline(curwin); + redraw_curbuf_later(UPD_VALID); + } +} + +/// Blank lines, and lines containing only the comment leader, are left +/// untouched by the formatting. The function returns true in this +/// case. It also returns true when a line starts with the end of a comment +/// ('e' in comment flags), so that this line is skipped, and not joined to the +/// previous line. A new paragraph starts after a blank line, or when the +/// comment leader changes. +static int fmt_check_par(linenr_T lnum, int *leader_len, char **leader_flags, bool do_comments) +{ + char *flags = NULL; // init for GCC + char *ptr; + + ptr = ml_get(lnum); + if (do_comments) { + *leader_len = get_leader_len(ptr, leader_flags, false, true); + } else { + *leader_len = 0; + } + + if (*leader_len > 0) { + // Search for 'e' flag in comment leader flags. + flags = *leader_flags; + while (*flags && *flags != ':' && *flags != COM_END) { + flags++; + } + } + + return *skipwhite(ptr + *leader_len) == NUL + || (*leader_len > 0 && *flags == COM_END) + || startPS(lnum, NUL, false); +} + +/// @return true if line "lnum" ends in a white character. +static bool ends_in_white(linenr_T lnum) +{ + char *s = ml_get(lnum); + size_t l; + + if (*s == NUL) { + return false; + } + l = strlen(s) - 1; + return ascii_iswhite((uint8_t)s[l]); +} + +/// @return true if the two comment leaders given are the same. +/// +/// @param lnum The first line. White-space is ignored. +/// +/// @note the whole of 'leader1' must match 'leader2_len' characters from 'leader2'. +static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int leader2_len, + char *leader2_flags) +{ + int idx1 = 0, idx2 = 0; + char *p; + char *line1; + char *line2; + + if (leader1_len == 0) { + return leader2_len == 0; + } + + // If first leader has 'f' flag, the lines can be joined only if the + // second line does not have a leader. + // If first leader has 'e' flag, the lines can never be joined. + // If first leader has 's' flag, the lines can only be joined if there is + // some text after it and the second line has the 'm' flag. + if (leader1_flags != NULL) { + for (p = leader1_flags; *p && *p != ':'; p++) { + if (*p == COM_FIRST) { + return leader2_len == 0; + } + if (*p == COM_END) { + return false; + } + if (*p == COM_START) { + int line_len = (int)strlen(ml_get(lnum)); + if (line_len <= leader1_len) { + return false; + } + if (leader2_flags == NULL || leader2_len == 0) { + return false; + } + for (p = leader2_flags; *p && *p != ':'; p++) { + if (*p == COM_MIDDLE) { + return true; + } + } + return false; + } + } + } + + // Get current line and next line, compare the leaders. + // The first line has to be saved, only one line can be locked at a time. + line1 = xstrdup(ml_get(lnum)); + for (idx1 = 0; ascii_iswhite(line1[idx1]); idx1++) {} + line2 = ml_get(lnum + 1); + for (idx2 = 0; idx2 < leader2_len; idx2++) { + if (!ascii_iswhite(line2[idx2])) { + if (line1[idx1++] != line2[idx2]) { + break; + } + } else { + while (ascii_iswhite(line1[idx1])) { + idx1++; + } + } + } + xfree(line1); + + return idx2 == leader2_len && idx1 == leader1_len; +} + +/// Used for auto-formatting. +/// +/// @return true when a paragraph starts in line "lnum". +/// false when the previous line is in the same paragraph. +static bool paragraph_start(linenr_T lnum) +{ + char *p; + int leader_len = 0; // leader len of current line + char *leader_flags = NULL; // flags for leader of current line + int next_leader_len = 0; // leader len of next line + char *next_leader_flags = NULL; // flags for leader of next line + + if (lnum <= 1) { + return true; // start of the file + } + p = ml_get(lnum - 1); + if (*p == NUL) { + return true; // after empty line + } + const bool do_comments = has_format_option(FO_Q_COMS); // format comments + if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) { + return true; // after non-paragraph line + } + + if (fmt_check_par(lnum, &next_leader_len, &next_leader_flags, do_comments)) { + return true; // "lnum" is not a paragraph line + } + + if (has_format_option(FO_WHITE_PAR) && !ends_in_white(lnum - 1)) { + return true; // missing trailing space in previous line. + } + if (has_format_option(FO_Q_NUMBER) && (get_number_indent(lnum) > 0)) { + return true; // numbered item starts in "lnum". + } + if (!same_leader(lnum - 1, leader_len, leader_flags, + next_leader_len, next_leader_flags)) { + return true; // change of comment leader. + } + return false; +} + +/// Called after inserting or deleting text: When 'formatoptions' includes the +/// 'a' flag format from the current line until the end of the paragraph. +/// Keep the cursor at the same position relative to the text. +/// The caller must have saved the cursor line for undo, following ones will be +/// saved here. +/// +/// @param trailblank when true also format with trailing blank +/// @param prev_line may start in previous line +void auto_format(bool trailblank, bool prev_line) +{ + pos_T pos; + colnr_T len; + char *old; + char *new, *pnew; + int wasatend; + int cc; + + if (!has_format_option(FO_AUTO)) { + return; + } + + pos = curwin->w_cursor; + old = get_cursor_line_ptr(); + + // may remove added space + check_auto_format(false); + + // Don't format in Insert mode when the cursor is on a trailing blank, the + // user might insert normal text next. Also skip formatting when "1" is + // in 'formatoptions' and there is a single character before the cursor. + // Otherwise the line would be broken and when typing another non-white + // next they are not joined back together. + wasatend = (pos.col == (colnr_T)strlen(old)); + if (*old != NUL && !trailblank && wasatend) { + dec_cursor(); + cc = gchar_cursor(); + if (!WHITECHAR(cc) && curwin->w_cursor.col > 0 + && has_format_option(FO_ONE_LETTER)) { + dec_cursor(); + } + cc = gchar_cursor(); + if (WHITECHAR(cc)) { + curwin->w_cursor = pos; + return; + } + curwin->w_cursor = pos; + } + + // With the 'c' flag in 'formatoptions' and 't' missing: only format + // comments. + if (has_format_option(FO_WRAP_COMS) && !has_format_option(FO_WRAP) + && get_leader_len(old, NULL, false, true) == 0) { + return; + } + + // May start formatting in a previous line, so that after "x" a word is + // moved to the previous line if it fits there now. Only when this is not + // the start of a paragraph. + if (prev_line && !paragraph_start(curwin->w_cursor.lnum)) { + curwin->w_cursor.lnum--; + if (u_save_cursor() == FAIL) { + return; + } + } + + // Do the formatting and restore the cursor position. "saved_cursor" will + // be adjusted for the text formatting. + saved_cursor = pos; + format_lines((linenr_T) - 1, false); + curwin->w_cursor = saved_cursor; + saved_cursor.lnum = 0; + + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { + // "cannot happen" + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance(MAXCOL); + } else { + check_cursor_col(); + } + + // Insert mode: If the cursor is now after the end of the line while it + // previously wasn't, the line was broken. Because of the rule above we + // need to add a space when 'w' is in 'formatoptions' to keep a paragraph + // formatted. + if (!wasatend && has_format_option(FO_WHITE_PAR)) { + new = get_cursor_line_ptr(); + len = (colnr_T)strlen(new); + if (curwin->w_cursor.col == len) { + pnew = xstrnsave(new, (size_t)len + 2); + pnew[len] = ' '; + pnew[len + 1] = NUL; + ml_replace(curwin->w_cursor.lnum, pnew, false); + // remove the space later + did_add_space = true; + } else { + // may remove added space + check_auto_format(false); + } + } + + check_cursor(); +} + +/// When an extra space was added to continue a paragraph for auto-formatting, +/// delete it now. The space must be under the cursor, just after the insert +/// position. +/// +/// @param end_insert true when ending Insert mode +void check_auto_format(bool end_insert) +{ + int c = ' '; + int cc; + + if (did_add_space) { + cc = gchar_cursor(); + if (!WHITECHAR(cc)) { + // Somehow the space was removed already. + did_add_space = false; + } else { + if (!end_insert) { + inc_cursor(); + c = gchar_cursor(); + dec_cursor(); + } + if (c != NUL) { + // The space is no longer at the end of the line, delete it. + del_char(false); + did_add_space = false; + } + } + } +} + +/// Find out textwidth to be used for formatting: +/// if 'textwidth' option is set, use it +/// else if 'wrapmargin' option is set, use curwin->w_width_inner-'wrapmargin' +/// if invalid value, use 0. +/// Set default to window width (maximum 79) for "gq" operator. +/// +/// @param ff force formatting (for "gq" command) +int comp_textwidth(bool ff) +{ + int textwidth = (int)curbuf->b_p_tw; + if (textwidth == 0 && curbuf->b_p_wm) { + // The width is the window width minus 'wrapmargin' minus all the + // things that add to the margin. + textwidth = curwin->w_width_inner - (int)curbuf->b_p_wm; + if (cmdwin_type != 0) { + textwidth -= 1; + } + textwidth -= win_fdccol_count(curwin); + textwidth -= win_signcol_count(curwin); + + if (curwin->w_p_nu || curwin->w_p_rnu) { + textwidth -= 8; + } + } + if (textwidth < 0) { + textwidth = 0; + } + if (ff && textwidth == 0) { + textwidth = curwin->w_width_inner - 1; + if (textwidth > 79) { + textwidth = 79; + } + } + return textwidth; +} + +/// Implementation of the format operator 'gq'. +/// +/// @param keep_cursor keep cursor on same text char +void op_format(oparg_T *oap, bool keep_cursor) +{ + linenr_T old_line_count = curbuf->b_ml.ml_line_count; + + // Place the cursor where the "gq" or "gw" command was given, so that "u" + // can put it back there. + curwin->w_cursor = oap->cursor_start; + + if (u_save((linenr_T)(oap->start.lnum - 1), + (linenr_T)(oap->end.lnum + 1)) == FAIL) { + return; + } + curwin->w_cursor = oap->start; + + if (oap->is_VIsual) { + // When there is no change: need to remove the Visual selection + redraw_curbuf_later(UPD_INVERTED); + } + + if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { + // Set '[ mark at the start of the formatted area + curbuf->b_op_start = oap->start; + } + + // For "gw" remember the cursor position and put it back below (adjusted + // for joined and split lines). + if (keep_cursor) { + saved_cursor = oap->cursor_start; + } + + format_lines((linenr_T)oap->line_count, keep_cursor); + + // Leave the cursor at the first non-blank of the last formatted line. + // If the cursor was moved one line back (e.g. with "Q}") go to the next + // line, so "." will do the next lines. + if (oap->end_adjusted && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum++; + } + beginline(BL_WHITE | BL_FIX); + old_line_count = curbuf->b_ml.ml_line_count - old_line_count; + msgmore(old_line_count); + + if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) { + // put '] mark on the end of the formatted area + curbuf->b_op_end = curwin->w_cursor; + } + + if (keep_cursor) { + curwin->w_cursor = saved_cursor; + saved_cursor.lnum = 0; + + // formatting may have made the cursor position invalid + check_cursor(); + } + + if (oap->is_VIsual) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_old_cursor_lnum != 0) { + // When lines have been inserted or deleted, adjust the end of + // the Visual area to be redrawn. + if (wp->w_old_cursor_lnum > wp->w_old_visual_lnum) { + wp->w_old_cursor_lnum += old_line_count; + } else { + wp->w_old_visual_lnum += old_line_count; + } + } + } + } +} + +/// Implementation of the format operator 'gq' for when using 'formatexpr'. +void op_formatexpr(oparg_T *oap) +{ + if (oap->is_VIsual) { + // When there is no change: need to remove the Visual selection + redraw_curbuf_later(UPD_INVERTED); + } + + if (fex_format(oap->start.lnum, oap->line_count, NUL) != 0) { + // As documented: when 'formatexpr' returns non-zero fall back to + // internal formatting. + op_format(oap, false); + } +} + +/// @param c character to be inserted +int fex_format(linenr_T lnum, long count, int c) +{ + int use_sandbox = was_set_insecurely(curwin, "formatexpr", OPT_LOCAL); + int r; + + // Set v:lnum to the first line number and v:count to the number of lines. + // Set v:char to the character to be inserted (can be NUL). + set_vim_var_nr(VV_LNUM, (varnumber_T)lnum); + set_vim_var_nr(VV_COUNT, (varnumber_T)count); + set_vim_var_char(c); + + // Make a copy, the option could be changed while calling it. + char *fex = xstrdup(curbuf->b_p_fex); + // Evaluate the function. + if (use_sandbox) { + sandbox++; + } + r = (int)eval_to_number(fex); + if (use_sandbox) { + sandbox--; + } + + set_vim_var_string(VV_CHAR, NULL, -1); + xfree(fex); + + return r; +} + +/// @param line_count number of lines to format, starting at the cursor position. +/// when negative, format until the end of the paragraph. +/// +/// Lines after the cursor line are saved for undo, caller must have saved the +/// first line. +/// +/// @param avoid_fex don't use 'formatexpr' +void format_lines(linenr_T line_count, bool avoid_fex) +{ + bool is_not_par; // current line not part of parag. + bool next_is_not_par; // next line not part of paragraph + bool is_end_par; // at end of paragraph + bool prev_is_end_par = false; // prev. line not part of parag. + bool next_is_start_par = false; + int leader_len = 0; // leader len of current line + int next_leader_len; // leader len of next line + char *leader_flags = NULL; // flags for leader of current line + char *next_leader_flags = NULL; // flags for leader of next line + bool advance = true; + int second_indent = -1; // indent for second line (comment aware) + bool first_par_line = true; + int smd_save; + long count; + bool need_set_indent = true; // set indent of next paragraph + linenr_T first_line = curwin->w_cursor.lnum; + bool force_format = false; + const int old_State = State; + + // length of a line to force formatting: 3 * 'tw' + const int max_len = comp_textwidth(true) * 3; + + // check for 'q', '2' and '1' in 'formatoptions' + const bool do_comments = has_format_option(FO_Q_COMS); // format comments + int do_comments_list = 0; // format comments with 'n' or '2' + const bool do_second_indent = has_format_option(FO_Q_SECOND); + const bool do_number_indent = has_format_option(FO_Q_NUMBER); + const bool do_trail_white = has_format_option(FO_WHITE_PAR); + + // Get info about the previous and current line. + if (curwin->w_cursor.lnum > 1) { + is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1, + &leader_len, &leader_flags, do_comments); + } else { + is_not_par = true; + } + next_is_not_par = fmt_check_par(curwin->w_cursor.lnum, + &next_leader_len, &next_leader_flags, do_comments); + is_end_par = (is_not_par || next_is_not_par); + if (!is_end_par && do_trail_white) { + is_end_par = !ends_in_white(curwin->w_cursor.lnum - 1); + } + + curwin->w_cursor.lnum--; + for (count = line_count; count != 0 && !got_int; count--) { + // Advance to next paragraph. + if (advance) { + curwin->w_cursor.lnum++; + prev_is_end_par = is_end_par; + is_not_par = next_is_not_par; + leader_len = next_leader_len; + leader_flags = next_leader_flags; + } + + // The last line to be formatted. + if (count == 1 || curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { + next_is_not_par = true; + next_leader_len = 0; + next_leader_flags = NULL; + } else { + next_is_not_par = fmt_check_par(curwin->w_cursor.lnum + 1, + &next_leader_len, &next_leader_flags, do_comments); + if (do_number_indent) { + next_is_start_par = + (get_number_indent(curwin->w_cursor.lnum + 1) > 0); + } + } + advance = true; + is_end_par = (is_not_par || next_is_not_par || next_is_start_par); + if (!is_end_par && do_trail_white) { + is_end_par = !ends_in_white(curwin->w_cursor.lnum); + } + + // Skip lines that are not in a paragraph. + if (is_not_par) { + if (line_count < 0) { + break; + } + } else { + // For the first line of a paragraph, check indent of second line. + // Don't do this for comments and empty lines. + if (first_par_line + && (do_second_indent || do_number_indent) + && prev_is_end_par + && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + if (do_second_indent && !LINEEMPTY(curwin->w_cursor.lnum + 1)) { + if (leader_len == 0 && next_leader_len == 0) { + // no comment found + second_indent = + get_indent_lnum(curwin->w_cursor.lnum + 1); + } else { + second_indent = next_leader_len; + do_comments_list = 1; + } + } else if (do_number_indent) { + if (leader_len == 0 && next_leader_len == 0) { + // no comment found + second_indent = + get_number_indent(curwin->w_cursor.lnum); + } else { + // get_number_indent() is now "comment aware"... + second_indent = + get_number_indent(curwin->w_cursor.lnum); + do_comments_list = 1; + } + } + } + + // When the comment leader changes, it's the end of the paragraph. + if (curwin->w_cursor.lnum >= curbuf->b_ml.ml_line_count + || !same_leader(curwin->w_cursor.lnum, + leader_len, leader_flags, + next_leader_len, + next_leader_flags)) { + // Special case: If the next line starts with a line comment + // and this line has a line comment after some text, the + // paragraph doesn't really end. + if (next_leader_flags == NULL + || strncmp(next_leader_flags, "://", 3) != 0 + || check_linecomment(get_cursor_line_ptr()) == MAXCOL) { + is_end_par = true; + } + } + + // If we have got to the end of a paragraph, or the line is + // getting long, format it. + if (is_end_par || force_format) { + if (need_set_indent) { + int indent = 0; // amount of indent needed + + // Replace indent in first line of a paragraph with minimal + // number of tabs and spaces, according to current options. + // For the very first formatted line keep the current + // indent. + if (curwin->w_cursor.lnum == first_line) { + indent = get_indent(); + } else if (curbuf->b_p_lisp) { + indent = get_lisp_indent(); + } else { + if (cindent_on()) { + indent = *curbuf->b_p_inde != NUL ? get_expr_indent() : get_c_indent(); + } else { + indent = get_indent(); + } + } + (void)set_indent(indent, SIN_CHANGED); + } + + // put cursor on last non-space + State = MODE_NORMAL; // don't go past end-of-line + coladvance(MAXCOL); + while (curwin->w_cursor.col && ascii_isspace(gchar_cursor())) { + dec_cursor(); + } + + // do the formatting, without 'showmode' + State = MODE_INSERT; // for open_line() + smd_save = p_smd; + p_smd = false; + insertchar(NUL, INSCHAR_FORMAT + + (do_comments ? INSCHAR_DO_COM : 0) + + (do_comments && do_comments_list ? INSCHAR_COM_LIST : 0) + + (avoid_fex ? INSCHAR_NO_FEX : 0), second_indent); + State = old_State; + p_smd = smd_save; + second_indent = -1; + // at end of par.: need to set indent of next par. + need_set_indent = is_end_par; + if (is_end_par) { + // When called with a negative line count, break at the + // end of the paragraph. + if (line_count < 0) { + break; + } + first_par_line = true; + } + force_format = false; + } + + // When still in same paragraph, join the lines together. But + // first delete the leader from the second line. + if (!is_end_par) { + advance = false; + curwin->w_cursor.lnum++; + curwin->w_cursor.col = 0; + if (line_count < 0 && u_save_cursor() == FAIL) { + break; + } + if (next_leader_len > 0) { + (void)del_bytes(next_leader_len, false, false); + mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L, + (long)-next_leader_len, 0); + } else if (second_indent > 0) { // the "leader" for FO_Q_SECOND + int indent = (int)getwhitecols_curline(); + + if (indent > 0) { + (void)del_bytes(indent, false, false); + mark_col_adjust(curwin->w_cursor.lnum, + (colnr_T)0, 0L, (long)-indent, 0); + } + } + curwin->w_cursor.lnum--; + if (do_join(2, true, false, false, false) == FAIL) { + beep_flush(); + break; + } + first_par_line = false; + // If the line is getting long, format it next time + if (strlen(get_cursor_line_ptr()) > (size_t)max_len) { + force_format = true; + } else { + force_format = false; + } + } + } + line_breakcheck(); + } +} diff --git a/src/nvim/textformat.h b/src/nvim/textformat.h new file mode 100644 index 0000000000..fcc9c2d6f4 --- /dev/null +++ b/src/nvim/textformat.h @@ -0,0 +1,10 @@ +#ifndef NVIM_TEXTFORMAT_H +#define NVIM_TEXTFORMAT_H + +#include "nvim/normal.h" +#include "nvim/pos.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "textformat.h.generated.h" +#endif +#endif // NVIM_TEXTFORMAT_H diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c new file mode 100644 index 0000000000..8e786c271c --- /dev/null +++ b/src/nvim/textobject.c @@ -0,0 +1,1750 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +// textobject.c: functions for text objects + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "nvim/ascii.h" +#include "nvim/buffer_defs.h" +#include "nvim/cursor.h" +#include "nvim/drawscreen.h" +#include "nvim/edit.h" +#include "nvim/eval/funcs.h" +#include "nvim/fold.h" +#include "nvim/globals.h" +#include "nvim/indent.h" +#include "nvim/macros.h" +#include "nvim/mark.h" +#include "nvim/mbyte.h" +#include "nvim/memline.h" +#include "nvim/memory.h" +#include "nvim/normal.h" +#include "nvim/option_defs.h" +#include "nvim/pos.h" +#include "nvim/screen.h" +#include "nvim/search.h" +#include "nvim/strings.h" +#include "nvim/textobject.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "textobject.c.generated.h" +#endif + +/// Find the start of the next sentence, searching in the direction specified +/// by the "dir" argument. The cursor is positioned on the start of the next +/// sentence when found. If the next sentence is found, return OK. Return FAIL +/// otherwise. See ":h sentence" for the precise definition of a "sentence" +/// text object. +int findsent(Direction dir, long count) +{ + pos_T pos, tpos; + int c; + int (*func)(pos_T *); + bool noskip = false; // do not skip blanks + + pos = curwin->w_cursor; + if (dir == FORWARD) { + func = incl; + } else { + func = decl; + } + + while (count--) { + const pos_T prev_pos = pos; + + // if on an empty line, skip up to a non-empty line + if (gchar_pos(&pos) == NUL) { + do { + if ((*func)(&pos) == -1) { + break; + } + } while (gchar_pos(&pos) == NUL); + if (dir == FORWARD) { + goto found; + } + // if on the start of a paragraph or a section and searching forward, + // go to the next line + } else if (dir == FORWARD && pos.col == 0 + && startPS(pos.lnum, NUL, false)) { + if (pos.lnum == curbuf->b_ml.ml_line_count) { + return FAIL; + } + pos.lnum++; + goto found; + } else if (dir == BACKWARD) { + decl(&pos); + } + + // go back to the previous non-white non-punctuation character + bool found_dot = false; + while (c = gchar_pos(&pos), ascii_iswhite(c) + || vim_strchr(".!?)]\"'", c) != NULL) { + tpos = pos; + if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) { + break; + } + if (found_dot) { + break; + } + if (vim_strchr(".!?", c) != NULL) { + found_dot = true; + } + if (vim_strchr(")]\"'", c) != NULL + && vim_strchr(".!?)]\"'", gchar_pos(&tpos)) == NULL) { + break; + } + decl(&pos); + } + + // remember the line where the search started + const int startlnum = pos.lnum; + const bool cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL; + + for (;;) { // find end of sentence + c = gchar_pos(&pos); + if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, false))) { + if (dir == BACKWARD && pos.lnum != startlnum) { + pos.lnum++; + } + break; + } + if (c == '.' || c == '!' || c == '?') { + tpos = pos; + do { + if ((c = inc(&tpos)) == -1) { + break; + } + } while (vim_strchr(")]\"'", c = gchar_pos(&tpos)) + != NULL); + if (c == -1 || (!cpo_J && (c == ' ' || c == '\t')) || c == NUL + || (cpo_J && (c == ' ' && inc(&tpos) >= 0 + && gchar_pos(&tpos) == ' '))) { + pos = tpos; + if (gchar_pos(&pos) == NUL) { // skip NUL at EOL + inc(&pos); + } + break; + } + } + if ((*func)(&pos) == -1) { + if (count) { + return FAIL; + } + noskip = true; + break; + } + } +found: + // skip white space + while (!noskip && ((c = gchar_pos(&pos)) == ' ' || c == '\t')) { + if (incl(&pos) == -1) { + break; + } + } + + if (equalpos(prev_pos, pos)) { + // didn't actually move, advance one character and try again + if ((*func)(&pos) == -1) { + if (count) { + return FAIL; + } + break; + } + count++; + } + } + + setpcmark(); + curwin->w_cursor = pos; + return OK; +} + +/// Find the next paragraph or section in direction 'dir'. +/// Paragraphs are currently supposed to be separated by empty lines. +/// If 'what' is NUL we go to the next paragraph. +/// If 'what' is '{' or '}' we go to the next section. +/// If 'both' is true also stop at '}'. +/// +/// @param pincl Return: true if last char is to be included +/// +/// @return true if the next paragraph or section was found. +bool findpar(bool *pincl, int dir, long count, int what, bool both) +{ + linenr_T curr; + bool did_skip; // true after separating lines have been skipped + bool first; // true on first line + linenr_T fold_first; // first line of a closed fold + linenr_T fold_last; // last line of a closed fold + bool fold_skipped; // true if a closed fold was skipped this + // iteration + + curr = curwin->w_cursor.lnum; + + while (count--) { + did_skip = false; + for (first = true;; first = false) { + if (*ml_get(curr) != NUL) { + did_skip = true; + } + + // skip folded lines + fold_skipped = false; + if (first && hasFolding(curr, &fold_first, &fold_last)) { + curr = ((dir > 0) ? fold_last : fold_first) + dir; + fold_skipped = true; + } + + if (!first && did_skip && startPS(curr, what, both)) { + break; + } + + if (fold_skipped) { + curr -= dir; + } + if ((curr += dir) < 1 || curr > curbuf->b_ml.ml_line_count) { + if (count) { + return false; + } + curr -= dir; + break; + } + } + } + setpcmark(); + if (both && *ml_get(curr) == '}') { // include line with '}' + curr++; + } + curwin->w_cursor.lnum = curr; + if (curr == curbuf->b_ml.ml_line_count && what != '}') { + char *line = ml_get(curr); + + // Put the cursor on the last character in the last line and make the + // motion inclusive. + if ((curwin->w_cursor.col = (colnr_T)strlen(line)) != 0) { + curwin->w_cursor.col--; + curwin->w_cursor.col -= utf_head_off(line, line + curwin->w_cursor.col); + *pincl = true; + } + } else { + curwin->w_cursor.col = 0; + } + return true; +} + +/// check if the string 's' is a nroff macro that is in option 'opt' +static bool inmacro(char *opt, const char *s) +{ + char *macro; + + for (macro = opt; macro[0]; macro++) { + // Accept two characters in the option being equal to two characters + // in the line. A space in the option matches with a space in the + // line or the line having ended. + if ((macro[0] == s[0] + || (macro[0] == ' ' + && (s[0] == NUL || s[0] == ' '))) + && (macro[1] == s[1] + || ((macro[1] == NUL || macro[1] == ' ') + && (s[0] == NUL || s[1] == NUL || s[1] == ' ')))) { + break; + } + macro++; + if (macro[0] == NUL) { + break; + } + } + return macro[0] != NUL; +} + +/// startPS: return true if line 'lnum' is the start of a section or paragraph. +/// If 'para' is '{' or '}' only check for sections. +/// If 'both' is true also stop at '}' +bool startPS(linenr_T lnum, int para, bool both) +{ + char *s; + + s = ml_get(lnum); + if ((uint8_t)(*s) == para || *s == '\f' || (both && *s == '}')) { + return true; + } + if (*s == '.' && (inmacro(p_sections, s + 1) + || (!para && inmacro(p_para, s + 1)))) { + return true; + } + return false; +} + +// The following routines do the word searches performed by the 'w', 'W', +// 'b', 'B', 'e', and 'E' commands. + +// To perform these searches, characters are placed into one of three +// classes, and transitions between classes determine word boundaries. +// +// The classes are: +// +// 0 - white space +// 1 - punctuation +// 2 or higher - keyword characters (letters, digits and underscore) + +static bool cls_bigword; ///< true for "W", "B" or "E" + +/// cls() - returns the class of character at curwin->w_cursor +/// +/// If a 'W', 'B', or 'E' motion is being done (cls_bigword == true), chars +/// from class 2 and higher are reported as class 1 since only white space +/// boundaries are of interest. +static int cls(void) +{ + int c; + + c = gchar_cursor(); + if (c == ' ' || c == '\t' || c == NUL) { + return 0; + } + + c = utf_class(c); + + // If cls_bigword is true, report all non-blanks as class 1. + if (c != 0 && cls_bigword) { + return 1; + } + return c; +} + +/// fwd_word(count, type, eol) - move forward one word +/// +/// @return FAIL if the cursor was already at the end of the file. +/// If eol is true, last word stops at end of line (for operators). +/// +/// @param bigword "W", "E" or "B" +int fwd_word(long count, bool bigword, bool eol) +{ + int sclass; // starting class + int i; + int last_line; + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) { + // When inside a range of folded lines, move to the last char of the + // last line. + if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { + coladvance(MAXCOL); + } + sclass = cls(); + + // We always move at least one character, unless on the last + // character in the buffer. + last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count); + i = inc_cursor(); + if (i == -1 || (i >= 1 && last_line)) { // started at last char in file + return FAIL; + } + if (i >= 1 && eol && count == 0) { // started at last char in line + return OK; + } + + // Go one char past end of current word (if any) + if (sclass != 0) { + while (cls() == sclass) { + i = inc_cursor(); + if (i == -1 || (i >= 1 && eol && count == 0)) { + return OK; + } + } + } + + // go to next non-white + while (cls() == 0) { + // We'll stop if we land on a blank line + if (curwin->w_cursor.col == 0 && *get_cursor_line_ptr() == NUL) { + break; + } + + i = inc_cursor(); + if (i == -1 || (i >= 1 && eol && count == 0)) { + return OK; + } + } + } + return OK; +} + +/// bck_word() - move backward 'count' words +/// +/// If stop is true and we are already on the start of a word, move one less. +/// +/// Returns FAIL if top of the file was reached. +int bck_word(long count, bool bigword, bool stop) +{ + int sclass; // starting class + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) { + // When inside a range of folded lines, move to the first char of the + // first line. + if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL)) { + curwin->w_cursor.col = 0; + } + sclass = cls(); + if (dec_cursor() == -1) { // started at start of file + return FAIL; + } + + if (!stop || sclass == cls() || sclass == 0) { + // Skip white space before the word. + // Stop on an empty line. + while (cls() == 0) { + if (curwin->w_cursor.col == 0 + && LINEEMPTY(curwin->w_cursor.lnum)) { + goto finished; + } + if (dec_cursor() == -1) { // hit start of file, stop here + return OK; + } + } + + // Move backward to start of this word. + if (skip_chars(cls(), BACKWARD)) { + return OK; + } + } + + inc_cursor(); // overshot - forward one +finished: + stop = false; + } + return OK; +} + +/// end_word() - move to the end of the word +/// +/// There is an apparent bug in the 'e' motion of the real vi. At least on the +/// System V Release 3 version for the 80386. Unlike 'b' and 'w', the 'e' +/// motion crosses blank lines. When the real vi crosses a blank line in an +/// 'e' motion, the cursor is placed on the FIRST character of the next +/// non-blank line. The 'E' command, however, works correctly. Since this +/// appears to be a bug, I have not duplicated it here. +/// +/// Returns FAIL if end of the file was reached. +/// +/// If stop is true and we are already on the end of a word, move one less. +/// If empty is true stop on an empty line. +int end_word(long count, bool bigword, bool stop, bool empty) +{ + int sclass; // starting class + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) { + // When inside a range of folded lines, move to the last char of the + // last line. + if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) { + coladvance(MAXCOL); + } + sclass = cls(); + if (inc_cursor() == -1) { + return FAIL; + } + + // If we're in the middle of a word, we just have to move to the end + // of it. + if (cls() == sclass && sclass != 0) { + // Move forward to end of the current word + if (skip_chars(sclass, FORWARD)) { + return FAIL; + } + } else if (!stop || sclass == 0) { + // We were at the end of a word. Go to the end of the next word. + // First skip white space, if 'empty' is true, stop at empty line. + while (cls() == 0) { + if (empty && curwin->w_cursor.col == 0 + && LINEEMPTY(curwin->w_cursor.lnum)) { + goto finished; + } + if (inc_cursor() == -1) { // hit end of file, stop here + return FAIL; + } + } + + // Move forward to the end of this word. + if (skip_chars(cls(), FORWARD)) { + return FAIL; + } + } + dec_cursor(); // overshot - one char backward +finished: + stop = false; // we move only one word less + } + return OK; +} + +/// Move back to the end of the word. +/// +/// @param bigword true for "B" +/// @param eol if true, then stop at end of line. +/// +/// @return FAIL if start of the file was reached. +int bckend_word(long count, bool bigword, bool eol) +{ + int sclass; // starting class + int i; + + curwin->w_cursor.coladd = 0; + cls_bigword = bigword; + while (--count >= 0) { + sclass = cls(); + if ((i = dec_cursor()) == -1) { + return FAIL; + } + if (eol && i == 1) { + return OK; + } + + // Move backward to before the start of this word. + if (sclass != 0) { + while (cls() == sclass) { + if ((i = dec_cursor()) == -1 || (eol && i == 1)) { + return OK; + } + } + } + + // Move backward to end of the previous word + while (cls() == 0) { + if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum)) { + break; + } + if ((i = dec_cursor()) == -1 || (eol && i == 1)) { + return OK; + } + } + } + return OK; +} + +/// Skip a row of characters of the same class. +/// +/// @return true when end-of-file reached, false otherwise. +static bool skip_chars(int cclass, int dir) +{ + while (cls() == cclass) { + if ((dir == FORWARD ? inc_cursor() : dec_cursor()) == -1) { + return true; + } + } + return false; +} + +/// Go back to the start of the word or the start of white space +static void back_in_line(void) +{ + int sclass; // starting class + + sclass = cls(); + for (;;) { + if (curwin->w_cursor.col == 0) { // stop at start of line + break; + } + dec_cursor(); + if (cls() != sclass) { // stop at start of word + inc_cursor(); + break; + } + } +} + +static void find_first_blank(pos_T *posp) +{ + int c; + + while (decl(posp) != -1) { + c = gchar_pos(posp); + if (!ascii_iswhite(c)) { + incl(posp); + break; + } + } +} + +/// Skip count/2 sentences and count/2 separating white spaces. +/// +/// @param at_start_sent cursor is at start of sentence +static void findsent_forward(long count, bool at_start_sent) +{ + while (count--) { + findsent(FORWARD, 1L); + if (at_start_sent) { + find_first_blank(&curwin->w_cursor); + } + if (count == 0 || at_start_sent) { + decl(&curwin->w_cursor); + } + at_start_sent = !at_start_sent; + } +} + +/// Find word under cursor, cursor at end. +/// Used while an operator is pending, and in Visual mode. +/// +/// @param include true: include word and white space +/// @param bigword false == word, true == WORD +int current_word(oparg_T *oap, long count, bool include, bool bigword) +{ + pos_T start_pos; + pos_T pos; + bool inclusive = true; + int include_white = false; + + cls_bigword = bigword; + clearpos(&start_pos); + + // Correct cursor when 'selection' is exclusive + if (VIsual_active && *p_sel == 'e' && lt(VIsual, curwin->w_cursor)) { + dec_cursor(); + } + + // When Visual mode is not active, or when the VIsual area is only one + // character, select the word and/or white space under the cursor. + if (!VIsual_active || equalpos(curwin->w_cursor, VIsual)) { + // Go to start of current word or white space. + back_in_line(); + start_pos = curwin->w_cursor; + + // If the start is on white space, and white space should be included + // (" word"), or start is not on white space, and white space should + // not be included ("word"), find end of word. + if ((cls() == 0) == include) { + if (end_word(1L, bigword, true, true) == FAIL) { + return FAIL; + } + } else { + // If the start is not on white space, and white space should be + // included ("word "), or start is on white space and white + // space should not be included (" "), find start of word. + // If we end up in the first column of the next line (single char + // word) back up to end of the line. + fwd_word(1L, bigword, true); + if (curwin->w_cursor.col == 0) { + decl(&curwin->w_cursor); + } else { + oneleft(); + } + + if (include) { + include_white = true; + } + } + + if (VIsual_active) { + // should do something when inclusive == false ! + VIsual = start_pos; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + } else { + oap->start = start_pos; + oap->motion_type = kMTCharWise; + } + count--; + } + + // When count is still > 0, extend with more objects. + while (count > 0) { + inclusive = true; + if (VIsual_active && lt(curwin->w_cursor, VIsual)) { + // In Visual mode, with cursor at start: move cursor back. + if (decl(&curwin->w_cursor) == -1) { + return FAIL; + } + if (include != (cls() != 0)) { + if (bck_word(1L, bigword, true) == FAIL) { + return FAIL; + } + } else { + if (bckend_word(1L, bigword, true) == FAIL) { + return FAIL; + } + (void)incl(&curwin->w_cursor); + } + } else { + // Move cursor forward one word and/or white area. + if (incl(&curwin->w_cursor) == -1) { + return FAIL; + } + if (include != (cls() == 0)) { + if (fwd_word(1L, bigword, true) == FAIL && count > 1) { + return FAIL; + } + // If end is just past a new-line, we don't want to include + // the first character on the line. + // Put cursor on last char of white. + if (oneleft() == FAIL) { + inclusive = false; + } + } else { + if (end_word(1L, bigword, true, true) == FAIL) { + return FAIL; + } + } + } + count--; + } + + if (include_white && (cls() != 0 + || (curwin->w_cursor.col == 0 && !inclusive))) { + // If we don't include white space at the end, move the start + // to include some white space there. This makes "daw" work + // better on the last word in a sentence (and "2daw" on last-but-one + // word). Also when "2daw" deletes "word." at the end of the line + // (cursor is at start of next line). + // But don't delete white space at start of line (indent). + pos = curwin->w_cursor; // save cursor position + curwin->w_cursor = start_pos; + if (oneleft() == OK) { + back_in_line(); + if (cls() == 0 && curwin->w_cursor.col > 0) { + if (VIsual_active) { + VIsual = curwin->w_cursor; + } else { + oap->start = curwin->w_cursor; + } + } + } + curwin->w_cursor = pos; // put cursor back at end + } + + if (VIsual_active) { + if (*p_sel == 'e' && inclusive && ltoreq(VIsual, curwin->w_cursor)) { + inc_cursor(); + } + if (VIsual_mode == 'V') { + VIsual_mode = 'v'; + redraw_cmdline = true; // show mode later + } + } else { + oap->inclusive = inclusive; + } + + return OK; +} + +/// Find sentence(s) under the cursor, cursor at end. +/// When Visual active, extend it by one or more sentences. +int current_sent(oparg_T *oap, long count, bool include) +{ + pos_T start_pos; + pos_T pos; + bool start_blank; + int c; + bool at_start_sent; + long ncount; + + start_pos = curwin->w_cursor; + pos = start_pos; + findsent(FORWARD, 1L); // Find start of next sentence. + + // When the Visual area is bigger than one character: Extend it. + if (VIsual_active && !equalpos(start_pos, VIsual)) { +extend: + if (lt(start_pos, VIsual)) { + // Cursor at start of Visual area. + // Find out where we are: + // - in the white space before a sentence + // - in a sentence or just after it + // - at the start of a sentence + at_start_sent = true; + decl(&pos); + while (lt(pos, curwin->w_cursor)) { + c = gchar_pos(&pos); + if (!ascii_iswhite(c)) { + at_start_sent = false; + break; + } + incl(&pos); + } + if (!at_start_sent) { + findsent(BACKWARD, 1L); + if (equalpos(curwin->w_cursor, start_pos)) { + at_start_sent = true; // exactly at start of sentence + } else { + // inside a sentence, go to its end (start of next) + findsent(FORWARD, 1L); + } + } + if (include) { // "as" gets twice as much as "is" + count *= 2; + } + while (count--) { + if (at_start_sent) { + find_first_blank(&curwin->w_cursor); + } + c = gchar_cursor(); + if (!at_start_sent || (!include && !ascii_iswhite(c))) { + findsent(BACKWARD, 1L); + } + at_start_sent = !at_start_sent; + } + } else { + // Cursor at end of Visual area. + // Find out where we are: + // - just before a sentence + // - just before or in the white space before a sentence + // - in a sentence + incl(&pos); + at_start_sent = true; + if (!equalpos(pos, curwin->w_cursor)) { // not just before a sentence + at_start_sent = false; + while (lt(pos, curwin->w_cursor)) { + c = gchar_pos(&pos); + if (!ascii_iswhite(c)) { + at_start_sent = true; + break; + } + incl(&pos); + } + if (at_start_sent) { // in the sentence + findsent(BACKWARD, 1L); + } else { // in/before white before a sentence + curwin->w_cursor = start_pos; + } + } + + if (include) { // "as" gets twice as much as "is" + count *= 2; + } + findsent_forward(count, at_start_sent); + if (*p_sel == 'e') { + curwin->w_cursor.col++; + } + } + return OK; + } + + // If the cursor started on a blank, check if it is just before the start + // of the next sentence. + while (c = gchar_pos(&pos), ascii_iswhite(c)) { + incl(&pos); + } + if (equalpos(pos, curwin->w_cursor)) { + start_blank = true; + find_first_blank(&start_pos); // go back to first blank + } else { + start_blank = false; + findsent(BACKWARD, 1L); + start_pos = curwin->w_cursor; + } + if (include) { + ncount = count * 2; + } else { + ncount = count; + if (start_blank) { + ncount--; + } + } + if (ncount > 0) { + findsent_forward(ncount, true); + } else { + decl(&curwin->w_cursor); + } + + if (include) { + // If the blank in front of the sentence is included, exclude the + // blanks at the end of the sentence, go back to the first blank. + // If there are no trailing blanks, try to include leading blanks. + if (start_blank) { + find_first_blank(&curwin->w_cursor); + c = gchar_pos(&curwin->w_cursor); + if (ascii_iswhite(c)) { + decl(&curwin->w_cursor); + } + } else if (c = gchar_cursor(), !ascii_iswhite(c)) { + find_first_blank(&start_pos); + } + } + + if (VIsual_active) { + // Avoid getting stuck with "is" on a single space before a sentence. + if (equalpos(start_pos, curwin->w_cursor)) { + goto extend; + } + if (*p_sel == 'e') { + curwin->w_cursor.col++; + } + VIsual = start_pos; + VIsual_mode = 'v'; + redraw_cmdline = true; // show mode later + redraw_curbuf_later(UPD_INVERTED); // update the inversion + } else { + // include a newline after the sentence, if there is one + if (incl(&curwin->w_cursor) == -1) { + oap->inclusive = true; + } else { + oap->inclusive = false; + } + oap->start = start_pos; + oap->motion_type = kMTCharWise; + } + return OK; +} + +/// Find block under the cursor, cursor at end. +/// "what" and "other" are two matching parenthesis/brace/etc. +/// +/// @param include true == include white space +/// @param what '(', '{', etc. +/// @param other ')', '}', etc. +int current_block(oparg_T *oap, long count, bool include, int what, int other) +{ + pos_T old_pos; + pos_T *pos = NULL; + pos_T start_pos; + pos_T *end_pos; + pos_T old_start, old_end; + char *save_cpo; + bool sol = false; // '{' at start of line + + old_pos = curwin->w_cursor; + old_end = curwin->w_cursor; // remember where we started + old_start = old_end; + + // If we start on '(', '{', ')', '}', etc., use the whole block inclusive. + if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { + setpcmark(); + if (what == '{') { // ignore indent + while (inindent(1)) { + if (inc_cursor() != 0) { + break; + } + } + } + if (gchar_cursor() == what) { + // cursor on '(' or '{', move cursor just after it + curwin->w_cursor.col++; + } + } else if (lt(VIsual, curwin->w_cursor)) { + old_start = VIsual; + curwin->w_cursor = VIsual; // cursor at low end of Visual + } else { + old_end = VIsual; + } + + // Search backwards for unclosed '(', '{', etc.. + // Put this position in start_pos. + // Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the + // user wants. + save_cpo = p_cpo; + p_cpo = vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%"; + if ((pos = findmatch(NULL, what)) != NULL) { + while (count-- > 0) { + if ((pos = findmatch(NULL, what)) == NULL) { + break; + } + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos + } + } else { + while (count-- > 0) { + if ((pos = findmatchlimit(NULL, what, FM_FORWARD, 0)) == NULL) { + break; + } + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos + } + } + p_cpo = save_cpo; + + // Search for matching ')', '}', etc. + // Put this position in curwin->w_cursor. + if (pos == NULL || (end_pos = findmatch(NULL, other)) == NULL) { + curwin->w_cursor = old_pos; + return FAIL; + } + curwin->w_cursor = *end_pos; + + // Try to exclude the '(', '{', ')', '}', etc. when "include" is false. + // If the ending '}', ')' or ']' is only preceded by indent, skip that + // indent. But only if the resulting area is not smaller than what we + // started with. + while (!include) { + incl(&start_pos); + sol = (curwin->w_cursor.col == 0); + decl(&curwin->w_cursor); + while (inindent(1)) { + sol = true; + if (decl(&curwin->w_cursor) != 0) { + break; + } + } + + // In Visual mode, when the resulting area is not bigger than what we + // started with, extend it to the next block, and then exclude again. + // Don't try to expand the area if the area is empty. + if (!lt(start_pos, old_start) && !lt(old_end, curwin->w_cursor) + && !equalpos(start_pos, curwin->w_cursor) + && VIsual_active) { + curwin->w_cursor = old_start; + decl(&curwin->w_cursor); + if ((pos = findmatch(NULL, what)) == NULL) { + curwin->w_cursor = old_pos; + return FAIL; + } + start_pos = *pos; + curwin->w_cursor = *pos; + if ((end_pos = findmatch(NULL, other)) == NULL) { + curwin->w_cursor = old_pos; + return FAIL; + } + curwin->w_cursor = *end_pos; + } else { + break; + } + } + + if (VIsual_active) { + if (*p_sel == 'e') { + inc(&curwin->w_cursor); + } + if (sol && gchar_cursor() != NUL) { + inc(&curwin->w_cursor); // include the line break + } + VIsual = start_pos; + VIsual_mode = 'v'; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + showmode(); + } else { + oap->start = start_pos; + oap->motion_type = kMTCharWise; + oap->inclusive = false; + if (sol) { + incl(&curwin->w_cursor); + } else if (ltoreq(start_pos, curwin->w_cursor)) { + // Include the character under the cursor. + oap->inclusive = true; + } else { + // End is before the start (no text in between <>, [], etc.): don't + // operate on any text. + curwin->w_cursor = start_pos; + } + } + + return OK; +} + +/// @param end_tag when true, return true if the cursor is on "</aaa>". +/// +/// @return true if the cursor is on a "<aaa>" tag. Ignore "<aaa/>". +static bool in_html_tag(bool end_tag) +{ + char *line = get_cursor_line_ptr(); + char *p; + int c; + int lc = NUL; + pos_T pos; + + for (p = line + curwin->w_cursor.col; p > line;) { + if (*p == '<') { // find '<' under/before cursor + break; + } + MB_PTR_BACK(line, p); + if (*p == '>') { // find '>' before cursor + break; + } + } + if (*p != '<') { + return false; + } + + pos.lnum = curwin->w_cursor.lnum; + pos.col = (colnr_T)(p - line); + + MB_PTR_ADV(p); + if (end_tag) { + // check that there is a '/' after the '<' + return *p == '/'; + } + + // check that there is no '/' after the '<' + if (*p == '/') { + return false; + } + + // check that the matching '>' is not preceded by '/' + for (;;) { + if (inc(&pos) < 0) { + return false; + } + c = (uint8_t)(*ml_get_pos(&pos)); + if (c == '>') { + break; + } + lc = c; + } + return lc != '/'; +} + +/// Find tag block under the cursor, cursor at end. +/// +/// @param include true == include white space +int current_tagblock(oparg_T *oap, long count_arg, bool include) +{ + long count = count_arg; + pos_T old_pos; + pos_T start_pos; + pos_T end_pos; + pos_T old_start, old_end; + char *p; + char *cp; + int len; + bool do_include = include; + bool save_p_ws = p_ws; + int retval = FAIL; + int is_inclusive = true; + + p_ws = false; + + old_pos = curwin->w_cursor; + old_end = curwin->w_cursor; // remember where we started + old_start = old_end; + if (!VIsual_active || *p_sel == 'e') { + decl(&old_end); // old_end is inclusive + } + + // If we start on "<aaa>" select that block. + if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) { + setpcmark(); + + // ignore indent + while (inindent(1)) { + if (inc_cursor() != 0) { + break; + } + } + + if (in_html_tag(false)) { + // cursor on start tag, move to its '>' + while (*get_cursor_pos_ptr() != '>') { + if (inc_cursor() < 0) { + break; + } + } + } else if (in_html_tag(true)) { + // cursor on end tag, move to just before it + while (*get_cursor_pos_ptr() != '<') { + if (dec_cursor() < 0) { + break; + } + } + dec_cursor(); + old_end = curwin->w_cursor; + } + } else if (lt(VIsual, curwin->w_cursor)) { + old_start = VIsual; + curwin->w_cursor = VIsual; // cursor at low end of Visual + } else { + old_end = VIsual; + } + +again: + // Search backwards for unclosed "<aaa>". + // Put this position in start_pos. + for (long n = 0; n < count; n++) { + if (do_searchpair("<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", + "", + "</[^>]*>", BACKWARD, NULL, 0, + NULL, (linenr_T)0, 0L) <= 0) { + curwin->w_cursor = old_pos; + goto theend; + } + } + start_pos = curwin->w_cursor; + + // Search for matching "</aaa>". First isolate the "aaa". + inc_cursor(); + p = get_cursor_pos_ptr(); + for (cp = p; + *cp != NUL && *cp != '>' && !ascii_iswhite(*cp); + MB_PTR_ADV(cp)) {} + len = (int)(cp - p); + if (len == 0) { + curwin->w_cursor = old_pos; + goto theend; + } + const size_t spat_len = (size_t)len + 39; + char *const spat = xmalloc(spat_len); + const size_t epat_len = (size_t)len + 9; + char *const epat = xmalloc(epat_len); + snprintf(spat, spat_len, + "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p); + snprintf(epat, epat_len, "</%.*s>\\c", len, p); + + const int r = (int)do_searchpair(spat, "", epat, FORWARD, NULL, 0, NULL, (linenr_T)0, 0L); + + xfree(spat); + xfree(epat); + + if (r < 1 || lt(curwin->w_cursor, old_end)) { + // Can't find other end or it's before the previous end. Could be a + // HTML tag that doesn't have a matching end. Search backwards for + // another starting tag. + count = 1; + curwin->w_cursor = start_pos; + goto again; + } + + if (do_include) { + // Include up to the '>'. + while (*get_cursor_pos_ptr() != '>') { + if (inc_cursor() < 0) { + break; + } + } + } else { + char *c = get_cursor_pos_ptr(); + // Exclude the '<' of the end tag. + // If the closing tag is on new line, do not decrement cursor, but make + // operation exclusive, so that the linefeed will be selected + if (*c == '<' && !VIsual_active && curwin->w_cursor.col == 0) { + // do not decrement cursor + is_inclusive = false; + } else if (*c == '<') { + dec_cursor(); + } + } + end_pos = curwin->w_cursor; + + if (!do_include) { + // Exclude the start tag. + curwin->w_cursor = start_pos; + while (inc_cursor() >= 0) { + if (*get_cursor_pos_ptr() == '>') { + inc_cursor(); + start_pos = curwin->w_cursor; + break; + } + } + curwin->w_cursor = end_pos; + + // If we are in Visual mode and now have the same text as before set + // "do_include" and try again. + if (VIsual_active + && equalpos(start_pos, old_start) + && equalpos(end_pos, old_end)) { + do_include = true; + curwin->w_cursor = old_start; + count = count_arg; + goto again; + } + } + + if (VIsual_active) { + // If the end is before the start there is no text between tags, select + // the char under the cursor. + if (lt(end_pos, start_pos)) { + curwin->w_cursor = start_pos; + } else if (*p_sel == 'e') { + inc_cursor(); + } + VIsual = start_pos; + VIsual_mode = 'v'; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + showmode(); + } else { + oap->start = start_pos; + oap->motion_type = kMTCharWise; + if (lt(end_pos, start_pos)) { + // End is before the start: there is no text between tags; operate + // on an empty area. + curwin->w_cursor = start_pos; + oap->inclusive = false; + } else { + oap->inclusive = is_inclusive; + } + } + retval = OK; + +theend: + p_ws = save_p_ws; + return retval; +} + +/// @param include true == include white space +/// @param type 'p' for paragraph, 'S' for section +int current_par(oparg_T *oap, long count, bool include, int type) +{ + linenr_T start_lnum; + linenr_T end_lnum; + int white_in_front; + int dir; + int start_is_white; + int prev_start_is_white; + int retval = OK; + int do_white = false; + int t; + int i; + + if (type == 'S') { // not implemented yet + return FAIL; + } + + start_lnum = curwin->w_cursor.lnum; + + // When visual area is more than one line: extend it. + if (VIsual_active && start_lnum != VIsual.lnum) { +extend: + if (start_lnum < VIsual.lnum) { + dir = BACKWARD; + } else { + dir = FORWARD; + } + for (i = (int)count; --i >= 0;) { + if (start_lnum == + (dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) { + retval = FAIL; + break; + } + + prev_start_is_white = -1; + for (t = 0; t < 2; t++) { + start_lnum += dir; + start_is_white = linewhite(start_lnum); + if (prev_start_is_white == start_is_white) { + start_lnum -= dir; + break; + } + for (;;) { + if (start_lnum == (dir == BACKWARD + ? 1 : curbuf->b_ml.ml_line_count)) { + break; + } + if (start_is_white != linewhite(start_lnum + dir) + || (!start_is_white + && startPS(start_lnum + (dir > 0 + ? 1 : 0), 0, 0))) { + break; + } + start_lnum += dir; + } + if (!include) { + break; + } + if (start_lnum == (dir == BACKWARD + ? 1 : curbuf->b_ml.ml_line_count)) { + break; + } + prev_start_is_white = start_is_white; + } + } + curwin->w_cursor.lnum = start_lnum; + curwin->w_cursor.col = 0; + return retval; + } + + // First move back to the start_lnum of the paragraph or white lines + white_in_front = linewhite(start_lnum); + while (start_lnum > 1) { + if (white_in_front) { // stop at first white line + if (!linewhite(start_lnum - 1)) { + break; + } + } else { // stop at first non-white line of start of paragraph + if (linewhite(start_lnum - 1) || startPS(start_lnum, 0, 0)) { + break; + } + } + start_lnum--; + } + + // Move past the end of any white lines. + end_lnum = start_lnum; + while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) { + end_lnum++; + } + + end_lnum--; + i = (int)count; + if (!include && white_in_front) { + i--; + } + while (i--) { + if (end_lnum == curbuf->b_ml.ml_line_count) { + return FAIL; + } + + if (!include) { + do_white = linewhite(end_lnum + 1); + } + + if (include || !do_white) { + end_lnum++; + // skip to end of paragraph + while (end_lnum < curbuf->b_ml.ml_line_count + && !linewhite(end_lnum + 1) + && !startPS(end_lnum + 1, 0, 0)) { + end_lnum++; + } + } + + if (i == 0 && white_in_front && include) { + break; + } + + // skip to end of white lines after paragraph + if (include || do_white) { + while (end_lnum < curbuf->b_ml.ml_line_count + && linewhite(end_lnum + 1)) { + end_lnum++; + } + } + } + + // If there are no empty lines at the end, try to find some empty lines at + // the start (unless that has been done already). + if (!white_in_front && !linewhite(end_lnum) && include) { + while (start_lnum > 1 && linewhite(start_lnum - 1)) { + start_lnum--; + } + } + + if (VIsual_active) { + // Problem: when doing "Vipipip" nothing happens in a single white + // line, we get stuck there. Trap this here. + if (VIsual_mode == 'V' && start_lnum == curwin->w_cursor.lnum) { + goto extend; + } + if (VIsual.lnum != start_lnum) { + VIsual.lnum = start_lnum; + VIsual.col = 0; + } + VIsual_mode = 'V'; + redraw_curbuf_later(UPD_INVERTED); // update the inversion + showmode(); + } else { + oap->start.lnum = start_lnum; + oap->start.col = 0; + oap->motion_type = kMTLineWise; + } + curwin->w_cursor.lnum = end_lnum; + curwin->w_cursor.col = 0; + + return OK; +} + +/// Search quote char from string line[col]. +/// Quote character escaped by one of the characters in "escape" is not counted +/// as a quote. +/// +/// @param escape escape characters, can be NULL +/// +/// @return column number of "quotechar" or -1 when not found. +static int find_next_quote(char *line, int col, int quotechar, char *escape) +{ + int c; + + for (;;) { + c = (uint8_t)line[col]; + if (c == NUL) { + return -1; + } else if (escape != NULL && vim_strchr(escape, c)) { + col++; + if (line[col] == NUL) { + return -1; + } + } else if (c == quotechar) { + break; + } + col += utfc_ptr2len(line + col); + } + return col; +} + +/// Search backwards in "line" from column "col_start" to find "quotechar". +/// Quote character escaped by one of the characters in "escape" is not counted +/// as a quote. +/// +/// @param escape escape characters, can be NULL +/// +/// @return the found column or zero. +static int find_prev_quote(char *line, int col_start, int quotechar, char *escape) +{ + int n; + + while (col_start > 0) { + col_start--; + col_start -= utf_head_off(line, line + col_start); + n = 0; + if (escape != NULL) { + while (col_start - n > 0 && vim_strchr(escape, + (uint8_t)line[col_start - n - 1]) != NULL) { + n++; + } + } + if (n & 1) { + col_start -= n; // uneven number of escape chars, skip it + } else if ((uint8_t)line[col_start] == quotechar) { + break; + } + } + return col_start; +} + +/// Find quote under the cursor, cursor at end. +/// +/// @param include true == include quote char +/// @param quotechar Quote character +/// +/// @return true if found, else false. +bool current_quote(oparg_T *oap, long count, bool include, int quotechar) + FUNC_ATTR_NONNULL_ALL +{ + char *line = get_cursor_line_ptr(); + int col_end; + int col_start = curwin->w_cursor.col; + bool inclusive = false; + bool vis_empty = true; // Visual selection <= 1 char + bool vis_bef_curs = false; // Visual starts before cursor + bool did_exclusive_adj = false; // adjusted pos for 'selection' + bool inside_quotes = false; // Looks like "i'" done before + bool selected_quote = false; // Has quote inside selection + int i; + bool restore_vis_bef = false; // restore VIsual on abort + + // When 'selection' is "exclusive" move the cursor to where it would be + // with 'selection' "inclusive", so that the logic is the same for both. + // The cursor then is moved forward after adjusting the area. + if (VIsual_active) { + // this only works within one line + if (VIsual.lnum != curwin->w_cursor.lnum) { + return false; + } + + vis_bef_curs = lt(VIsual, curwin->w_cursor); + vis_empty = equalpos(VIsual, curwin->w_cursor); + if (*p_sel == 'e') { + if (vis_bef_curs) { + dec_cursor(); + did_exclusive_adj = true; + } else if (!vis_empty) { + dec(&VIsual); + did_exclusive_adj = true; + } + vis_empty = equalpos(VIsual, curwin->w_cursor); + if (!vis_bef_curs && !vis_empty) { + // VIsual needs to be start of Visual selection. + pos_T t = curwin->w_cursor; + + curwin->w_cursor = VIsual; + VIsual = t; + vis_bef_curs = true; + restore_vis_bef = true; + } + } + } + + if (!vis_empty) { + // Check if the existing selection exactly spans the text inside + // quotes. + if (vis_bef_curs) { + inside_quotes = VIsual.col > 0 + && (uint8_t)line[VIsual.col - 1] == quotechar + && line[curwin->w_cursor.col] != NUL + && (uint8_t)line[curwin->w_cursor.col + 1] == quotechar; + i = VIsual.col; + col_end = curwin->w_cursor.col; + } else { + inside_quotes = curwin->w_cursor.col > 0 + && (uint8_t)line[curwin->w_cursor.col - 1] == quotechar + && line[VIsual.col] != NUL + && (uint8_t)line[VIsual.col + 1] == quotechar; + i = curwin->w_cursor.col; + col_end = VIsual.col; + } + + // Find out if we have a quote in the selection. + while (i <= col_end) { + // check for going over the end of the line, which can happen if + // the line was changed after the Visual area was selected. + if (line[i] == NUL) { + break; + } + if ((uint8_t)line[i++] == quotechar) { + selected_quote = true; + break; + } + } + } + + if (!vis_empty && (uint8_t)line[col_start] == quotechar) { + // Already selecting something and on a quote character. Find the + // next quoted string. + if (vis_bef_curs) { + // Assume we are on a closing quote: move to after the next + // opening quote. + col_start = find_next_quote(line, col_start + 1, quotechar, NULL); + if (col_start < 0) { + goto abort_search; + } + col_end = find_next_quote(line, col_start + 1, quotechar, curbuf->b_p_qe); + if (col_end < 0) { + // We were on a starting quote perhaps? + col_end = col_start; + col_start = curwin->w_cursor.col; + } + } else { + col_end = find_prev_quote(line, col_start, quotechar, NULL); + if ((uint8_t)line[col_end] != quotechar) { + goto abort_search; + } + col_start = find_prev_quote(line, col_end, quotechar, curbuf->b_p_qe); + if ((uint8_t)line[col_start] != quotechar) { + // We were on an ending quote perhaps? + col_start = col_end; + col_end = curwin->w_cursor.col; + } + } + } else if ((uint8_t)line[col_start] == quotechar || !vis_empty) { + int first_col = col_start; + + if (!vis_empty) { + if (vis_bef_curs) { + first_col = find_next_quote(line, col_start, quotechar, NULL); + } else { + first_col = find_prev_quote(line, col_start, quotechar, NULL); + } + } + // The cursor is on a quote, we don't know if it's the opening or + // closing quote. Search from the start of the line to find out. + // Also do this when there is a Visual area, a' may leave the cursor + // in between two strings. + col_start = 0; + for (;;) { + // Find open quote character. + col_start = find_next_quote(line, col_start, quotechar, NULL); + if (col_start < 0 || col_start > first_col) { + goto abort_search; + } + // Find close quote character. + col_end = find_next_quote(line, col_start + 1, quotechar, curbuf->b_p_qe); + if (col_end < 0) { + goto abort_search; + } + // If is cursor between start and end quote character, it is + // target text object. + if (col_start <= first_col && first_col <= col_end) { + break; + } + col_start = col_end + 1; + } + } else { + // Search backward for a starting quote. + col_start = find_prev_quote(line, col_start, quotechar, curbuf->b_p_qe); + if ((uint8_t)line[col_start] != quotechar) { + // No quote before the cursor, look after the cursor. + col_start = find_next_quote(line, col_start, quotechar, NULL); + if (col_start < 0) { + goto abort_search; + } + } + + // Find close quote character. + col_end = find_next_quote(line, col_start + 1, quotechar, curbuf->b_p_qe); + if (col_end < 0) { + goto abort_search; + } + } + + // When "include" is true, include spaces after closing quote or before + // the starting quote. + if (include) { + if (ascii_iswhite(line[col_end + 1])) { + while (ascii_iswhite(line[col_end + 1])) { + col_end++; + } + } else { + while (col_start > 0 && ascii_iswhite(line[col_start - 1])) { + col_start--; + } + } + } + + // Set start position. After vi" another i" must include the ". + // For v2i" include the quotes. + if (!include && count < 2 && (vis_empty || !inside_quotes)) { + col_start++; + } + curwin->w_cursor.col = col_start; + if (VIsual_active) { + // Set the start of the Visual area when the Visual area was empty, we + // were just inside quotes or the Visual area didn't start at a quote + // and didn't include a quote. + if (vis_empty + || (vis_bef_curs + && !selected_quote + && (inside_quotes + || ((uint8_t)line[VIsual.col] != quotechar + && (VIsual.col == 0 + || (uint8_t)line[VIsual.col - 1] != quotechar))))) { + VIsual = curwin->w_cursor; + redraw_curbuf_later(UPD_INVERTED); + } + } else { + oap->start = curwin->w_cursor; + oap->motion_type = kMTCharWise; + } + + // Set end position. + curwin->w_cursor.col = col_end; + if ((include || count > 1 + // After vi" another i" must include the ". + || (!vis_empty && inside_quotes)) && inc_cursor() == 2) { + inclusive = true; + } + if (VIsual_active) { + if (vis_empty || vis_bef_curs) { + // decrement cursor when 'selection' is not exclusive + if (*p_sel != 'e') { + dec_cursor(); + } + } else { + // Cursor is at start of Visual area. Set the end of the Visual + // area when it was just inside quotes or it didn't end at a + // quote. + if (inside_quotes + || (!selected_quote + && (uint8_t)line[VIsual.col] != quotechar + && (line[VIsual.col] == NUL + || (uint8_t)line[VIsual.col + 1] != quotechar))) { + dec_cursor(); + VIsual = curwin->w_cursor; + } + curwin->w_cursor.col = col_start; + } + if (VIsual_mode == 'V') { + VIsual_mode = 'v'; + redraw_cmdline = true; // show mode later + } + } else { + // Set inclusive and other oap's flags. + oap->inclusive = inclusive; + } + + return true; + +abort_search: + if (VIsual_active && *p_sel == 'e') { + if (did_exclusive_adj) { + inc_cursor(); + } + if (restore_vis_bef) { + pos_T t = curwin->w_cursor; + + curwin->w_cursor = VIsual; + VIsual = t; + } + } + return false; +} diff --git a/src/nvim/textobject.h b/src/nvim/textobject.h new file mode 100644 index 0000000000..e31557f297 --- /dev/null +++ b/src/nvim/textobject.h @@ -0,0 +1,11 @@ +#ifndef NVIM_TEXTOBJECT_H +#define NVIM_TEXTOBJECT_H + +#include "nvim/normal.h" +#include "nvim/pos.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "textobject.h.generated.h" +#endif +#endif // NVIM_TEXTOBJECT_H diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 61a59bcf06..2cb39ab26b 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -1,21 +1,30 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/ascii.h" -#include "nvim/autocmd.h" #include "nvim/charset.h" -#include "nvim/ex_docmd.h" +#include "nvim/event/defs.h" +#include "nvim/log.h" #include "nvim/macros.h" #include "nvim/main.h" +#include "nvim/map.h" +#include "nvim/memory.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/tui/input.h" +#include "nvim/tui/input_defs.h" #include "nvim/tui/tui.h" -#include "nvim/vim.h" -#ifdef WIN32 +#include "nvim/types.h" +#include "nvim/ui_client.h" +#ifdef MSWIN # include "nvim/os/os_win_console.h" #endif #include "nvim/event/rstream.h" @@ -140,33 +149,17 @@ void tinput_init(TermInput *input, Loop *loop) kitty_key_map_entry[i].name); } - // If stdin is not a pty, switch to stderr. For cases like: - // echo q | nvim -es - // ls *.md | xargs nvim -#ifdef WIN32 - if (!os_isatty(input->in_fd)) { - input->in_fd = os_get_conin_fd(); - } -#else - if (!os_isatty(input->in_fd) && os_isatty(STDERR_FILENO)) { - input->in_fd = STDERR_FILENO; - } -#endif - input_global_fd_init(input->in_fd); + input->in_fd = STDIN_FILENO; const char *term = os_getenv("TERM"); if (!term) { term = ""; // termkey_new_abstract assumes non-null (#2745) } -#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 input->tk = termkey_new_abstract(term, TERMKEY_FLAG_UTF8 | TERMKEY_FLAG_NOSTART); - termkey_hook_terminfo_getstr(input->tk, input->tk_ti_hook_fn, NULL); + termkey_hook_terminfo_getstr(input->tk, input->tk_ti_hook_fn, input); termkey_start(input->tk); -#else - input->tk = termkey_new_abstract(term, TERMKEY_FLAG_UTF8); -#endif int curflags = termkey_get_canonflags(input->tk); termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS); @@ -211,16 +204,12 @@ static void tinput_wait_enqueue(void **argv) const size_t len = rbuffer_size(input->key_buffer); String keys = { .data = xmallocz(len), .size = len }; rbuffer_read(input->key_buffer, keys.data, len); - if (ui_client_channel_id) { - Array args = ARRAY_DICT_INIT; - ADD(args, STRING_OBJ(keys)); // 'data' - ADD(args, BOOLEAN_OBJ(true)); // 'crlf' - ADD(args, INTEGER_OBJ(input->paste)); // 'phase' - rpc_send_event(ui_client_channel_id, "nvim_paste", args); - } else { - multiqueue_put(main_loop.events, tinput_paste_event, 3, - keys.data, keys.size, (intptr_t)input->paste); - } + MAXSIZE_TEMP_ARRAY(args, 3); + ADD_C(args, STRING_OBJ(keys)); // 'data' + ADD_C(args, BOOLEAN_OBJ(true)); // 'crlf' + ADD_C(args, INTEGER_OBJ(input->paste)); // 'phase' + rpc_send_event(ui_client_channel_id, "nvim_paste", args); + api_free_string(keys); if (input->paste == 1) { // Paste phase: "continue" input->paste = 2; @@ -229,60 +218,22 @@ static void tinput_wait_enqueue(void **argv) } else { // enqueue input for the main thread or Nvim server RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) { const String keys = { .data = buf, .size = len }; - size_t consumed; - if (ui_client_channel_id) { - Array args = ARRAY_DICT_INIT; - Error err = ERROR_INIT; - ADD(args, STRING_OBJ(copy_string(keys))); - // TODO(bfredl): could be non-blocking now with paste? - ArenaMem res_mem = NULL; - Object result = rpc_send_call(ui_client_channel_id, "nvim_input", args, &res_mem, &err); - consumed = result.type == kObjectTypeInteger ? (size_t)result.data.integer : 0; - arena_mem_free(res_mem, NULL); - } else { - consumed = input_enqueue(keys); - } - if (consumed) { - rbuffer_consumed(input->key_buffer, consumed); - } + MAXSIZE_TEMP_ARRAY(args, 1); + ADD_C(args, STRING_OBJ(keys)); + // NOTE: This is non-blocking and won't check partially processed input, + // but should be fine as all big sends are handled with nvim_paste, not nvim_input + rpc_send_event(ui_client_channel_id, "nvim_input", args); + rbuffer_consumed(input->key_buffer, len); rbuffer_reset(input->key_buffer); - if (consumed < len) { - break; - } } } - uv_mutex_lock(&input->key_buffer_mutex); - input->waiting = false; - uv_cond_signal(&input->key_buffer_cond); - uv_mutex_unlock(&input->key_buffer_mutex); -} - -static void tinput_paste_event(void **argv) -{ - String keys = { .data = argv[0], .size = (size_t)argv[1] }; - intptr_t phase = (intptr_t)argv[2]; - - Error err = ERROR_INIT; - nvim_paste(keys, true, phase, &err); - if (ERROR_SET(&err)) { - semsg("paste: %s", err.msg); - api_clear_error(&err); - } - - api_free_string(keys); } static void tinput_flush(TermInput *input, bool wait_until_empty) { size_t drain_boundary = wait_until_empty ? 0 : 0xff; do { - uv_mutex_lock(&input->key_buffer_mutex); - loop_schedule_fast(&main_loop, event_create(tinput_wait_enqueue, 1, input)); - input->waiting = true; - while (input->waiting) { - uv_cond_wait(&input->key_buffer_cond, &input->key_buffer_mutex); - } - uv_mutex_unlock(&input->key_buffer_mutex); + tinput_wait_enqueue((void **)&input); } while (rbuffer_size(input->key_buffer) > drain_boundary); } @@ -328,15 +279,14 @@ static void forward_simple_utf8(TermInput *input, TermKeyKey *key) && map_has(KittyKey, cstr_t)(&kitty_key_map, (KittyKey)key->code.codepoint)) { handle_kitty_key_protocol(input, key); return; - } else { - while (*ptr) { - if (*ptr == '<') { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, "<lt>"); - } else { - buf[len++] = *ptr; - } - ptr++; + } + while (*ptr) { + if (*ptr == '<') { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "<lt>"); + } else { + buf[len++] = *ptr; } + ptr++; } tinput_enqueue(input, buf, len); @@ -359,21 +309,20 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key) (KittyKey)key->code.codepoint)) { handle_kitty_key_protocol(input, key); return; - } else { - // Termkey doesn't include the S- modifier for ASCII characters (e.g., - // ctrl-shift-l is <C-L> instead of <C-S-L>. Vim, on the other hand, - // treats <C-L> and <C-l> the same, requiring the S- modifier. - len = termkey_strfkey(input->tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM); - if ((key->modifiers & TERMKEY_KEYMOD_CTRL) - && !(key->modifiers & TERMKEY_KEYMOD_SHIFT) - && ASCII_ISUPPER(key->code.codepoint)) { - assert(len <= 62); - // Make room for the S- - memmove(buf + 3, buf + 1, len - 1); - buf[1] = 'S'; - buf[2] = '-'; - len += 2; - } + } + // Termkey doesn't include the S- modifier for ASCII characters (e.g., + // ctrl-shift-l is <C-L> instead of <C-S-L>. Vim, on the other hand, + // treats <C-L> and <C-l> the same, requiring the S- modifier. + len = termkey_strfkey(input->tk, buf, sizeof(buf), key, TERMKEY_FORMAT_VIM); + if ((key->modifiers & TERMKEY_KEYMOD_CTRL) + && !(key->modifiers & TERMKEY_KEYMOD_SHIFT) + && ASCII_ISUPPER(key->code.codepoint)) { + assert(len <= 62); + // Make room for the S- + memmove(buf + 3, buf + 1, len - 1); + buf[1] = 'S'; + buf[2] = '-'; + len += 2; } } @@ -398,8 +347,16 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) button = last_pressed_button; } - if (button == 0 || (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG - && ev != TERMKEY_MOUSE_RELEASE)) { + if (ev == TERMKEY_MOUSE_UNKNOWN && !(key->code.mouse[0] & 0x20)) { + int code = key->code.mouse[0] & ~0x3c; + if (code == 66 || code == 67) { + ev = TERMKEY_MOUSE_PRESS; + button = code - 60; + } + } + + if ((button == 0 && ev != TERMKEY_MOUSE_RELEASE) + || (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG && ev != TERMKEY_MOUSE_RELEASE)) { return; } @@ -431,8 +388,11 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) if (button == 4) { len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelUp"); } else if (button == 5) { - len += (size_t)snprintf(buf + len, sizeof(buf) - len, - "ScrollWheelDown"); + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelDown"); + } else if (button == 6) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelLeft"); + } else if (button == 7) { + len += (size_t)snprintf(buf + len, sizeof(buf) - len, "ScrollWheelRight"); } else { len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Mouse"); last_pressed_button = button; @@ -442,7 +402,8 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Drag"); break; case TERMKEY_MOUSE_RELEASE: - len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Release"); + len += (size_t)snprintf(buf + len, sizeof(buf) - len, button ? "Release" : "MouseMove"); + last_pressed_button = 0; break; case TERMKEY_MOUSE_UNKNOWN: abort(); @@ -552,7 +513,10 @@ static bool handle_focus_event(TermInput *input) bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I'; // Advance past the sequence rbuffer_consumed(input->read_stream.buffer, 3); - autocmd_schedule_focusgained(focus_gained); + + MAXSIZE_TEMP_ARRAY(args, 1); + ADD_C(args, BOOLEAN_OBJ(focus_gained)); + rpc_send_event(ui_client_channel_id, "nvim_ui_set_focus", args); return true; } return false; @@ -599,10 +563,14 @@ static HandleState handle_bracketed_paste(TermInput *input) return kNotApplicable; } -static void set_bg_deferred(void **argv) +static void set_bg(char *bgvalue) { - char *bgvalue = argv[0]; - set_tty_background(bgvalue); + if (ui_client_attached) { + MAXSIZE_TEMP_ARRAY(args, 2); + ADD_C(args, STRING_OBJ(cstr_as_string("term_background"))); + ADD_C(args, STRING_OBJ(cstr_as_string(bgvalue))); + rpc_send_event(ui_client_channel_id, "nvim_ui_set_option", args); + } } // During startup, tui.c requests the background color (see `ext.get_bg`). @@ -684,10 +652,11 @@ static HandleState handle_background_color(TermInput *input) double g = (double)rgb[1] / (double)rgb_max[1]; double b = (double)rgb[2] / (double)rgb_max[2]; double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601 - char *bgvalue = luminance < 0.5 ? "dark" : "light"; + bool is_dark = luminance < 0.5; + char *bgvalue = is_dark ? "dark" : "light"; DLOG("bg response: %s", bgvalue); - loop_schedule_deferred(&main_loop, - event_create(set_bg_deferred, 1, bgvalue)); + ui_client_bg_response = is_dark ? kTrue : kFalse; + set_bg(bgvalue); input->waiting_for_bg_response = 0; } else if (!done && !bad) { // An incomplete sequence was found, waiting for the next input. diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index 51df57938c..5df108b107 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -2,10 +2,14 @@ #define NVIM_TUI_INPUT_H #include <stdbool.h> +#include <stdint.h> #include <termkey.h> +#include <uv.h> +#include "nvim/event/loop.h" #include "nvim/event/stream.h" #include "nvim/event/time.h" +#include "nvim/rbuffer.h" #include "nvim/tui/input_defs.h" #include "nvim/tui/tui.h" @@ -26,9 +30,7 @@ typedef struct term_input { ExtkeysType extkeys_type; long ttimeoutlen; TermKey *tk; -#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook -#endif TimeWatcher timer_handle; Loop *loop; Stream read_stream; diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index ce48059b94..d630a34ce2 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -7,15 +7,20 @@ #include <string.h> #include <unibilium.h> -#include "nvim/globals.h" -#include "nvim/log.h" +#include "klib/kvec.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" +#include "nvim/charset.h" #include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/option.h" -#include "nvim/os/os.h" +#include "nvim/strings.h" #include "nvim/tui/terminfo.h" #include "nvim/tui/terminfo_defs.h" +#ifdef __FreeBSD__ +# include "nvim/os/os.h" +#endif + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/terminfo.c.generated.h" #endif @@ -145,82 +150,80 @@ unibi_term *terminfo_from_builtin(const char *term, char **termname) /// Serves a similar purpose as Vim `:set termcap` (removed in Nvim). /// /// @note adapted from unibilium unibi-dump.c -void terminfo_info_msg(const unibi_term *const ut) +/// @return allocated string +String terminfo_info_msg(const unibi_term *ut, const char *termname) { - if (exiting) { - return; - } - msg_puts_title("\n\n--- Terminal info --- {{{\n"); + StringBuilder data = KV_INITIAL_VALUE; - char *term; - get_tty_option("term", &term); - msg_printf_attr(0, "&term: %s\n", term); - msg_printf_attr(0, "Description: %s\n", unibi_get_name(ut)); + kv_printf(data, "&term: %s\n", termname); + kv_printf(data, "Description: %s\n", unibi_get_name(ut)); const char **a = unibi_get_aliases(ut); if (*a) { - msg_puts("Aliases: "); + kv_printf(data, "Aliases: "); do { - msg_printf_attr(0, "%s%s\n", *a, a[1] ? " | " : ""); + kv_printf(data, "%s%s\n", *a, a[1] ? " | " : ""); a++; } while (*a); } - msg_puts("Boolean capabilities:\n"); + kv_printf(data, "Boolean capabilities:\n"); for (enum unibi_boolean i = unibi_boolean_begin_ + 1; i < unibi_boolean_end_; i++) { - msg_printf_attr(0, " %-25s %-10s = %s\n", unibi_name_bool(i), - unibi_short_name_bool(i), - unibi_get_bool(ut, i) ? "true" : "false"); + kv_printf(data, " %-25s %-10s = %s\n", unibi_name_bool(i), + unibi_short_name_bool(i), + unibi_get_bool(ut, i) ? "true" : "false"); } - msg_puts("Numeric capabilities:\n"); + kv_printf(data, "Numeric capabilities:\n"); for (enum unibi_numeric i = unibi_numeric_begin_ + 1; i < unibi_numeric_end_; i++) { int n = unibi_get_num(ut, i); // -1 means "empty" - msg_printf_attr(0, " %-25s %-10s = %d\n", unibi_name_num(i), - unibi_short_name_num(i), n); + kv_printf(data, " %-25s %-10s = %d\n", unibi_name_num(i), + unibi_short_name_num(i), n); } - msg_puts("String capabilities:\n"); + kv_printf(data, "String capabilities:\n"); for (enum unibi_string i = unibi_string_begin_ + 1; i < unibi_string_end_; i++) { const char *s = unibi_get_str(ut, i); if (s) { - msg_printf_attr(0, " %-25s %-10s = ", unibi_name_str(i), - unibi_short_name_str(i)); + kv_printf(data, " %-25s %-10s = ", unibi_name_str(i), + unibi_short_name_str(i)); // Most of these strings will contain escape sequences. - msg_outtrans_special((char_u *)s, false, 0); - msg_putchar('\n'); + kv_transstr(&data, s, false); + kv_push(data, '\n'); } } if (unibi_count_ext_bool(ut)) { - msg_puts("Extended boolean capabilities:\n"); + kv_printf(data, "Extended boolean capabilities:\n"); for (size_t i = 0; i < unibi_count_ext_bool(ut); i++) { - msg_printf_attr(0, " %-25s = %s\n", - unibi_get_ext_bool_name(ut, i), - unibi_get_ext_bool(ut, i) ? "true" : "false"); + kv_printf(data, " %-25s = %s\n", + unibi_get_ext_bool_name(ut, i), + unibi_get_ext_bool(ut, i) ? "true" : "false"); } } if (unibi_count_ext_num(ut)) { - msg_puts("Extended numeric capabilities:\n"); + kv_printf(data, "Extended numeric capabilities:\n"); for (size_t i = 0; i < unibi_count_ext_num(ut); i++) { - msg_printf_attr(0, " %-25s = %d\n", - unibi_get_ext_num_name(ut, i), - unibi_get_ext_num(ut, i)); + kv_printf(data, " %-25s = %d\n", + unibi_get_ext_num_name(ut, i), + unibi_get_ext_num(ut, i)); } } if (unibi_count_ext_str(ut)) { - msg_puts("Extended string capabilities:\n"); + kv_printf(data, "Extended string capabilities:\n"); for (size_t i = 0; i < unibi_count_ext_str(ut); i++) { - msg_printf_attr(0, " %-25s = ", unibi_get_ext_str_name(ut, i)); - msg_outtrans_special((char_u *)unibi_get_ext_str(ut, i), false, 0); - msg_putchar('\n'); + kv_printf(data, " %-25s = ", unibi_get_ext_str_name(ut, i)); + // NOTE: unibi_get_ext_str(ut, i) might be NULL, as termcap + // might include junk data on mac os. kv_transstr will handle this. + kv_transstr(&data, unibi_get_ext_str(ut, i), false); + kv_push(data, '\n'); } } + kv_push(data, NUL); - msg_puts("}}}\n"); - xfree(term); + return cbuf_as_string(data.items, data.size - 1); } diff --git a/src/nvim/tui/terminfo.h b/src/nvim/tui/terminfo.h index 099df8967f..178d384457 100644 --- a/src/nvim/tui/terminfo.h +++ b/src/nvim/tui/terminfo.h @@ -3,6 +3,8 @@ #include <unibilium.h> +#include "nvim/api/private/defs.h" + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/terminfo.h.generated.h" #endif diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 38e8c15762..a50e44f7a3 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1,48 +1,48 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -// Terminal UI functions. Invoked (by ui_bridge.c) on the TUI thread. +// Terminal UI functions. Invoked (by ui_client.c) on the UI process. #include <assert.h> -#include <limits.h> +#include <signal.h> #include <stdbool.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <unibilium.h> #include <uv.h> -#if defined(HAVE_TERMIOS_H) -# include <termios.h> -#endif +#include "auto/config.h" +#include "klib/kvec.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/ascii.h" +#include "nvim/cursor_shape.h" +#include "nvim/event/defs.h" #include "nvim/event/loop.h" +#include "nvim/event/multiqueue.h" #include "nvim/event/signal.h" -#include "nvim/highlight.h" -#include "nvim/lib/kvec.h" +#include "nvim/event/stream.h" +#include "nvim/globals.h" +#include "nvim/grid_defs.h" +#include "nvim/highlight_defs.h" #include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/map.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" -#include "nvim/option.h" +#include "nvim/msgpack_rpc/channel.h" #include "nvim/os/input.h" #include "nvim/os/os.h" -#include "nvim/os/signal.h" -#include "nvim/os/tty.h" -#include "nvim/ui.h" -#include "nvim/vim.h" -#ifdef WIN32 +#include "nvim/ui_client.h" +#ifdef MSWIN # include "nvim/os/os_win_console.h" #endif -#include "nvim/cursor_shape.h" -#include "nvim/macros.h" -#include "nvim/strings.h" -#include "nvim/syntax.h" #include "nvim/tui/input.h" #include "nvim/tui/terminfo.h" #include "nvim/tui/tui.h" #include "nvim/ugrid.h" -#include "nvim/ui_bridge.h" +#include "nvim/ui.h" // Space reserved in two output buffers to make the cursor normal or invisible // when flushing. No existing terminal will require 32 bytes to do that. @@ -63,8 +63,21 @@ do { \ (var) = unibi_var_from_num((num)); \ } while (0) +# define UNIBI_SET_STR_VAR(var, str) \ + do { \ + (var) = unibi_var_from_str((str)); \ + } while (0) #else -# define UNIBI_SET_NUM_VAR(var, num) (var).i = (num); +# define UNIBI_SET_NUM_VAR(var, num) \ + do { \ + (var).p = NULL; \ + (var).i = (num); \ + } while (0) +# define UNIBI_SET_STR_VAR(var, str) \ + do { \ + (var).i = INT_MIN; \ + (var).p = str; \ + } while (0) #endif typedef struct { @@ -72,7 +85,6 @@ typedef struct { } Rect; struct TUIData { - UIBridgeData *bridge; Loop *loop; unibi_var_t params[9]; char buf[OUTBUF_SIZE]; @@ -83,13 +95,14 @@ struct TUIData { TermInput input; uv_loop_t write_loop; unibi_term *ut; + char *term; // value of $TERM union { uv_tty_t tty; uv_pipe_t pipe; } output_handle; bool out_isatty; - SignalWatcher winch_handle, cont_handle; - bool cont_received; + SignalWatcher winch_handle; + uv_timer_t startup_delay_timer; UGrid grid; kvec_t(Rect) invalid_regions; int row, col; @@ -103,8 +116,10 @@ struct TUIData { bool immediate_wrap_after_last_column; bool bce; bool mouse_enabled; + bool mouse_move_enabled; bool busy, is_invisible, want_invisible; bool cork, overflow; + bool set_cursor_color_as_str; bool cursor_color_changed; bool is_starting; FILE *screenshot; @@ -115,11 +130,14 @@ struct TUIData { bool default_attr; bool can_clear_attr; ModeShape showing_mode; + Integer verbose; struct { int enable_mouse, disable_mouse; + int enable_mouse_move, disable_mouse_move; int enable_bracketed_paste, disable_bracketed_paste; int enable_lr_margin, disable_lr_margin; int enter_strikethrough_mode; + int enter_altfont_mode; int set_rgb_foreground, set_rgb_background; int set_cursor_color; int reset_cursor_color; @@ -135,153 +153,143 @@ struct TUIData { int get_extkeys; } unibi_ext; char *space_buf; + bool stopped; + int width; + int height; + bool rgb; }; static int got_winch = 0; static bool cursor_style_enabled = false; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/tui.c.generated.h" #endif -UI *tui_start(void) -{ - UI *ui = xcalloc(1, sizeof(UI)); // Freed by ui_bridge_stop(). - ui->stop = tui_stop; - ui->grid_resize = tui_grid_resize; - ui->grid_clear = tui_grid_clear; - ui->grid_cursor_goto = tui_grid_cursor_goto; - ui->mode_info_set = tui_mode_info_set; - ui->update_menu = tui_update_menu; - ui->busy_start = tui_busy_start; - ui->busy_stop = tui_busy_stop; - ui->mouse_on = tui_mouse_on; - ui->mouse_off = tui_mouse_off; - ui->mode_change = tui_mode_change; - ui->grid_scroll = tui_grid_scroll; - ui->hl_attr_define = tui_hl_attr_define; - ui->bell = tui_bell; - ui->visual_bell = tui_visual_bell; - ui->default_colors_set = tui_default_colors_set; - ui->flush = tui_flush; - ui->suspend = tui_suspend; - ui->set_title = tui_set_title; - ui->set_icon = tui_set_icon; - ui->screenshot = tui_screenshot; - ui->option_set = tui_option_set; - ui->raw_line = tui_raw_line; - - CLEAR_FIELD(ui->ui_ext); - ui->ui_ext[kUILinegrid] = true; - ui->ui_ext[kUITermColors] = true; - - return ui_bridge_attach(ui, tui_main, tui_scheduler); -} - -void tui_enable_extkeys(TUIData *data) -{ - TermInput input = data->input; - unibi_term *ut = data->ut; - UI *ui = data->bridge->ui; +TUIData *tui_start(int *width, int *height, char **term) +{ + TUIData *tui = xcalloc(1, sizeof(TUIData)); + tui->is_starting = true; + tui->screenshot = NULL; + tui->stopped = false; + tui->loop = &main_loop; + kv_init(tui->invalid_regions); + signal_watcher_init(tui->loop, &tui->winch_handle, tui); + + // TODO(bfredl): zero hl is empty, send this explicitly? + kv_push(tui->attrs, HLATTRS_INIT); + + tui->input.tk_ti_hook_fn = tui_tk_ti_getstr; + tinput_init(&tui->input, &main_loop); + ugrid_init(&tui->grid); + tui_terminal_start(tui); + + uv_timer_init(&tui->loop->uv, &tui->startup_delay_timer); + tui->startup_delay_timer.data = tui; + uv_timer_start(&tui->startup_delay_timer, after_startup_cb, + 100, 0); + + loop_poll_events(&main_loop, 1); + *width = tui->width; + *height = tui->height; + *term = tui->term; + return tui; +} + +void tui_enable_extkeys(TUIData *tui) +{ + TermInput input = tui->input; + unibi_term *ut = tui->ut; switch (input.extkeys_type) { case kExtkeysCSIu: - data->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", - "\x1b[>1u"); - data->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", - "\x1b[<1u"); + tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", + "\x1b[>1u"); + tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", + "\x1b[<1u"); break; case kExtkeysXterm: - data->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", - "\x1b[>4;2m"); - data->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", - "\x1b[>4;0m"); + tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", + "\x1b[>4;2m"); + tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", + "\x1b[>4;0m"); break; default: break; } - unibi_out_ext(ui, data->unibi_ext.enable_extended_keys); + unibi_out_ext(tui, tui->unibi_ext.enable_extended_keys); } -static size_t unibi_pre_fmt_str(TUIData *data, unsigned int unibi_index, char *buf, size_t len) +static size_t unibi_pre_fmt_str(TUIData *tui, unsigned int unibi_index, char *buf, size_t len) { - const char *str = unibi_get_str(data->ut, unibi_index); + const char *str = unibi_get_str(tui->ut, unibi_index); if (!str) { return 0U; } - return unibi_run(str, data->params, buf, len); -} - -static void termname_set_event(void **argv) -{ - char *termname = argv[0]; - set_tty_option("term", termname); - // Do not free termname, it is freed by set_tty_option. -} - -static void terminfo_start(UI *ui) -{ - TUIData *data = ui->data; - data->scroll_region_is_full_screen = true; - data->bufpos = 0; - data->default_attr = false; - data->can_clear_attr = false; - data->is_invisible = true; - data->want_invisible = false; - data->busy = false; - data->cork = false; - data->overflow = false; - data->cursor_color_changed = false; - data->showing_mode = SHAPE_IDX_N; - data->unibi_ext.enable_mouse = -1; - data->unibi_ext.disable_mouse = -1; - data->unibi_ext.set_cursor_color = -1; - data->unibi_ext.reset_cursor_color = -1; - data->unibi_ext.enable_bracketed_paste = -1; - data->unibi_ext.disable_bracketed_paste = -1; - data->unibi_ext.enter_strikethrough_mode = -1; - data->unibi_ext.enable_lr_margin = -1; - data->unibi_ext.disable_lr_margin = -1; - data->unibi_ext.enable_focus_reporting = -1; - data->unibi_ext.disable_focus_reporting = -1; - data->unibi_ext.resize_screen = -1; - data->unibi_ext.reset_scroll_region = -1; - data->unibi_ext.set_cursor_style = -1; - data->unibi_ext.reset_cursor_style = -1; - data->unibi_ext.get_bg = -1; - data->unibi_ext.set_underline_color = -1; - data->unibi_ext.enable_extended_keys = -1; - data->unibi_ext.disable_extended_keys = -1; - data->unibi_ext.get_extkeys = -1; - data->out_fd = STDOUT_FILENO; - data->out_isatty = os_isatty(data->out_fd); - data->input.tui_data = data; + return unibi_run(str, tui->params, buf, len); +} + +static void terminfo_start(TUIData *tui) +{ + tui->scroll_region_is_full_screen = true; + tui->bufpos = 0; + tui->default_attr = false; + tui->can_clear_attr = false; + tui->is_invisible = true; + tui->want_invisible = false; + tui->busy = false; + tui->cork = false; + tui->overflow = false; + tui->set_cursor_color_as_str = false; + tui->cursor_color_changed = false; + tui->showing_mode = SHAPE_IDX_N; + tui->unibi_ext.enable_mouse = -1; + tui->unibi_ext.disable_mouse = -1; + tui->unibi_ext.enable_mouse_move = -1; + tui->unibi_ext.disable_mouse_move = -1; + tui->unibi_ext.set_cursor_color = -1; + tui->unibi_ext.reset_cursor_color = -1; + tui->unibi_ext.enable_bracketed_paste = -1; + tui->unibi_ext.disable_bracketed_paste = -1; + tui->unibi_ext.enter_strikethrough_mode = -1; + tui->unibi_ext.enter_altfont_mode = -1; + tui->unibi_ext.enable_lr_margin = -1; + tui->unibi_ext.disable_lr_margin = -1; + tui->unibi_ext.enable_focus_reporting = -1; + tui->unibi_ext.disable_focus_reporting = -1; + tui->unibi_ext.resize_screen = -1; + tui->unibi_ext.reset_scroll_region = -1; + tui->unibi_ext.set_cursor_style = -1; + tui->unibi_ext.reset_cursor_style = -1; + tui->unibi_ext.get_bg = -1; + tui->unibi_ext.set_underline_color = -1; + tui->unibi_ext.enable_extended_keys = -1; + tui->unibi_ext.disable_extended_keys = -1; + tui->unibi_ext.get_extkeys = -1; + tui->out_fd = STDOUT_FILENO; + tui->out_isatty = os_isatty(tui->out_fd); + tui->input.tui_data = tui; const char *term = os_getenv("TERM"); -#ifdef WIN32 - os_tty_guess_term(&term, data->out_fd); +#ifdef MSWIN + os_tty_guess_term(&term, tui->out_fd); os_setenv("TERM", term, 1); // Old os_getenv() pointer is invalid after os_setenv(), fetch it again. term = os_getenv("TERM"); #endif // Set up unibilium/terminfo. - char *termname = NULL; if (term) { - os_env_var_lock(); - data->ut = unibi_from_term(term); - os_env_var_unlock(); - if (data->ut) { - termname = xstrdup(term); + tui->ut = unibi_from_term(term); + if (tui->ut) { + if (!tui->term) { + tui->term = xstrdup(term); + } } } - if (!data->ut) { - data->ut = terminfo_from_builtin(term, &termname); + if (!tui->ut) { + tui->ut = terminfo_from_builtin(term, &tui->term); } - // Update 'term' option. - loop_schedule_deferred(&main_loop, - event_create(termname_set_event, 1, termname)); // None of the following work over SSH; see :help TERM . const char *colorterm = os_getenv("COLORTERM"); @@ -298,59 +306,59 @@ static void terminfo_start(UI *ui) long konsolev = konsolev_env ? strtol(konsolev_env, NULL, 10) : (konsole ? 1 : 0); - patch_terminfo_bugs(data, term, colorterm, vtev, konsolev, iterm_env, nsterm); - augment_terminfo(data, term, vtev, konsolev, iterm_env, nsterm); - data->can_change_scroll_region = - !!unibi_get_str(data->ut, unibi_change_scroll_region); - data->can_set_lr_margin = - !!unibi_get_str(data->ut, unibi_set_lr_margin); - data->can_set_left_right_margin = - !!unibi_get_str(data->ut, unibi_set_left_margin_parm) - && !!unibi_get_str(data->ut, unibi_set_right_margin_parm); - data->can_scroll = - !!unibi_get_str(data->ut, unibi_delete_line) - && !!unibi_get_str(data->ut, unibi_parm_delete_line) - && !!unibi_get_str(data->ut, unibi_insert_line) - && !!unibi_get_str(data->ut, unibi_parm_insert_line); - data->can_erase_chars = !!unibi_get_str(data->ut, unibi_erase_chars); - data->immediate_wrap_after_last_column = + patch_terminfo_bugs(tui, term, colorterm, vtev, konsolev, iterm_env, nsterm); + augment_terminfo(tui, term, vtev, konsolev, iterm_env, nsterm); + tui->can_change_scroll_region = + !!unibi_get_str(tui->ut, unibi_change_scroll_region); + tui->can_set_lr_margin = + !!unibi_get_str(tui->ut, unibi_set_lr_margin); + tui->can_set_left_right_margin = + !!unibi_get_str(tui->ut, unibi_set_left_margin_parm) + && !!unibi_get_str(tui->ut, unibi_set_right_margin_parm); + tui->can_scroll = + !!unibi_get_str(tui->ut, unibi_delete_line) + && !!unibi_get_str(tui->ut, unibi_parm_delete_line) + && !!unibi_get_str(tui->ut, unibi_insert_line) + && !!unibi_get_str(tui->ut, unibi_parm_insert_line); + tui->can_erase_chars = !!unibi_get_str(tui->ut, unibi_erase_chars); + tui->immediate_wrap_after_last_column = terminfo_is_term_family(term, "conemu") || terminfo_is_term_family(term, "cygwin") || terminfo_is_term_family(term, "win32con") || terminfo_is_term_family(term, "interix"); - data->bce = unibi_get_bool(data->ut, unibi_back_color_erase); - data->normlen = unibi_pre_fmt_str(data, unibi_cursor_normal, - data->norm, sizeof data->norm); - data->invislen = unibi_pre_fmt_str(data, unibi_cursor_invisible, - data->invis, sizeof data->invis); + tui->bce = unibi_get_bool(tui->ut, unibi_back_color_erase); + tui->normlen = unibi_pre_fmt_str(tui, unibi_cursor_normal, + tui->norm, sizeof tui->norm); + tui->invislen = unibi_pre_fmt_str(tui, unibi_cursor_invisible, + tui->invis, sizeof tui->invis); // Set 't_Co' from the result of unibilium & fix_terminfo. - t_colors = unibi_get_num(data->ut, unibi_max_colors); + t_colors = unibi_get_num(tui->ut, unibi_max_colors); // Enter alternate screen, save title, and clear. // NOTE: Do this *before* changing terminal settings. #6433 - unibi_out(ui, unibi_enter_ca_mode); + unibi_out(tui, unibi_enter_ca_mode); // Save title/icon to the "stack". #4063 - unibi_out_ext(ui, data->unibi_ext.save_title); - unibi_out(ui, unibi_keypad_xmit); - unibi_out(ui, unibi_clear_screen); + unibi_out_ext(tui, tui->unibi_ext.save_title); + unibi_out(tui, unibi_keypad_xmit); + unibi_out(tui, unibi_clear_screen); // Ask the terminal to send us the background color. - data->input.waiting_for_bg_response = 5; - unibi_out_ext(ui, data->unibi_ext.get_bg); + tui->input.waiting_for_bg_response = 5; + unibi_out_ext(tui, tui->unibi_ext.get_bg); // Enable bracketed paste - unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); + unibi_out_ext(tui, tui->unibi_ext.enable_bracketed_paste); // Query the terminal to see if it supports CSI u - data->input.waiting_for_csiu_response = 5; - unibi_out_ext(ui, data->unibi_ext.get_extkeys); + tui->input.waiting_for_csiu_response = 5; + unibi_out_ext(tui, tui->unibi_ext.get_extkeys); int ret; - uv_loop_init(&data->write_loop); - if (data->out_isatty) { - ret = uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); + uv_loop_init(&tui->write_loop); + if (tui->out_isatty) { + ret = uv_tty_init(&tui->write_loop, &tui->output_handle.tty, tui->out_fd, 0); if (ret) { ELOG("uv_tty_init failed: %s", uv_strerror(ret)); } -#ifdef WIN32 - ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW); +#ifdef MSWIN + ret = uv_tty_set_mode(&tui->output_handle.tty, UV_TTY_MODE_RAW); if (ret) { ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); } @@ -358,7 +366,7 @@ static void terminfo_start(UI *ui) int retry_count = 10; // A signal may cause uv_tty_set_mode() to fail (e.g., SIGCONT). Retry a // few times. #12322 - while ((ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO)) == UV_EINTR + while ((ret = uv_tty_set_mode(&tui->output_handle.tty, UV_TTY_MODE_IO)) == UV_EINTR && retry_count > 0) { retry_count--; } @@ -367,193 +375,135 @@ static void terminfo_start(UI *ui) } #endif } else { - ret = uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); + ret = uv_pipe_init(&tui->write_loop, &tui->output_handle.pipe, 0); if (ret) { ELOG("uv_pipe_init failed: %s", uv_strerror(ret)); } - ret = uv_pipe_open(&data->output_handle.pipe, data->out_fd); + ret = uv_pipe_open(&tui->output_handle.pipe, tui->out_fd); if (ret) { ELOG("uv_pipe_open failed: %s", uv_strerror(ret)); } } - flush_buf(ui); + flush_buf(tui); } -static void terminfo_stop(UI *ui) +static void terminfo_stop(TUIData *tui) { - TUIData *data = ui->data; // Destroy output stuff - tui_mode_change(ui, (String)STRING_INIT, SHAPE_IDX_N); - tui_mouse_off(ui); - unibi_out(ui, unibi_exit_attribute_mode); + tui_mode_change(tui, (String)STRING_INIT, SHAPE_IDX_N); + tui_mouse_off(tui); + unibi_out(tui, unibi_exit_attribute_mode); // Reset cursor to normal before exiting alternate screen. - unibi_out(ui, unibi_cursor_normal); - unibi_out(ui, unibi_keypad_local); + unibi_out(tui, unibi_cursor_normal); + unibi_out(tui, unibi_keypad_local); // Disable extended keys before exiting alternate screen. - unibi_out_ext(ui, data->unibi_ext.disable_extended_keys); - unibi_out(ui, unibi_exit_ca_mode); + unibi_out_ext(tui, tui->unibi_ext.disable_extended_keys); + unibi_out(tui, unibi_exit_ca_mode); // Restore title/icon from the "stack". #4063 - unibi_out_ext(ui, data->unibi_ext.restore_title); - if (data->cursor_color_changed) { - unibi_out_ext(ui, data->unibi_ext.reset_cursor_color); + unibi_out_ext(tui, tui->unibi_ext.restore_title); + if (tui->cursor_color_changed) { + unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } // Disable bracketed paste - unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste); + unibi_out_ext(tui, tui->unibi_ext.disable_bracketed_paste); // Disable focus reporting - unibi_out_ext(ui, data->unibi_ext.disable_focus_reporting); - flush_buf(ui); + unibi_out_ext(tui, tui->unibi_ext.disable_focus_reporting); + flush_buf(tui); uv_tty_reset_mode(); - uv_close((uv_handle_t *)&data->output_handle, NULL); - uv_run(&data->write_loop, UV_RUN_DEFAULT); - if (uv_loop_close(&data->write_loop)) { + uv_close((uv_handle_t *)&tui->output_handle, NULL); + uv_run(&tui->write_loop, UV_RUN_DEFAULT); + if (uv_loop_close(&tui->write_loop)) { abort(); } - unibi_destroy(data->ut); + unibi_destroy(tui->ut); } -static void tui_terminal_start(UI *ui) +static void tui_terminal_start(TUIData *tui) { - TUIData *data = ui->data; - data->print_attr_id = -1; - ugrid_init(&data->grid); - terminfo_start(ui); - tui_guess_size(ui); - signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH); - tinput_start(&data->input); + tui->print_attr_id = -1; + terminfo_start(tui); + tui_guess_size(tui); + signal_watcher_start(&tui->winch_handle, sigwinch_cb, SIGWINCH); + tinput_start(&tui->input); } -static void tui_terminal_after_startup(UI *ui) - FUNC_ATTR_NONNULL_ALL +static void after_startup_cb(uv_timer_t *handle) { - TUIData *data = ui->data; + TUIData *tui = handle->data; + tui_terminal_after_startup(tui); +} +static void tui_terminal_after_startup(TUIData *tui) + FUNC_ATTR_NONNULL_ALL +{ // Emit this after Nvim startup, not during. This works around a tmux // 2.3 bug(?) which caused slow drawing during startup. #7649 - unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); - flush_buf(ui); + unibi_out_ext(tui, tui->unibi_ext.enable_focus_reporting); + flush_buf(tui); } -static void tui_terminal_stop(UI *ui) +/// stop the terminal but allow it to restart later (like after suspend) +static void tui_terminal_stop(TUIData *tui) { - TUIData *data = ui->data; - if (uv_is_closing(STRUCT_CAST(uv_handle_t, &data->output_handle))) { + if (uv_is_closing(STRUCT_CAST(uv_handle_t, &tui->output_handle))) { // Race between SIGCONT (tui.c) and SIGHUP (os/signal.c)? #8075 ELOG("TUI already stopped (race?)"); - ui->data = NULL; // Flag UI as "stopped". + tui->stopped = true; return; } - tinput_stop(&data->input); - signal_watcher_stop(&data->winch_handle); - terminfo_stop(ui); - ugrid_free(&data->grid); + tinput_stop(&tui->input); + signal_watcher_stop(&tui->winch_handle); + terminfo_stop(tui); } -static void tui_stop(UI *ui) +void tui_stop(TUIData *tui) { - tui_terminal_stop(ui); - ui->data = NULL; // Flag UI as "stopped". + tui_terminal_stop(tui); + stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598) + tinput_destroy(&tui->input); + tui->stopped = true; + signal_watcher_close(&tui->winch_handle, NULL); + uv_close((uv_handle_t *)&tui->startup_delay_timer, NULL); } /// Returns true if UI `ui` is stopped. -static bool tui_is_stopped(UI *ui) -{ - return ui->data == NULL; -} - -/// Main function of the TUI thread. -static void tui_main(UIBridgeData *bridge, UI *ui) -{ - Loop tui_loop; - loop_init(&tui_loop, NULL); - TUIData *data = xcalloc(1, sizeof(TUIData)); - ui->data = data; - data->bridge = bridge; - data->loop = &tui_loop; - data->is_starting = true; - data->screenshot = NULL; - kv_init(data->invalid_regions); - signal_watcher_init(data->loop, &data->winch_handle, ui); - signal_watcher_init(data->loop, &data->cont_handle, data); -#ifdef UNIX - signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT); -#endif - - // TODO(bfredl): zero hl is empty, send this explicitly? - kv_push(data->attrs, HLATTRS_INIT); - -#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 - data->input.tk_ti_hook_fn = tui_tk_ti_getstr; -#endif - tinput_init(&data->input, &tui_loop); - tui_terminal_start(ui); - - // Allow main thread to continue, we are ready to handle UI callbacks. - CONTINUE(bridge); - - loop_schedule_deferred(&main_loop, - event_create(show_termcap_event, 1, data->ut)); - - // "Active" loop: first ~100 ms of startup. - for (size_t ms = 0; ms < 100 && !tui_is_stopped(ui);) { - ms += (loop_poll_events(&tui_loop, 20) ? 20 : 1); - } - if (!tui_is_stopped(ui)) { - tui_terminal_after_startup(ui); - } - // "Passive" (I/O-driven) loop: TUI thread "main loop". - while (!tui_is_stopped(ui)) { - loop_poll_events(&tui_loop, -1); // tui_loop.events is never processed - } - - ui_bridge_stopped(bridge); - tinput_destroy(&data->input); - signal_watcher_stop(&data->cont_handle); - signal_watcher_close(&data->cont_handle, NULL); - signal_watcher_close(&data->winch_handle, NULL); - loop_close(&tui_loop, false); - kv_destroy(data->invalid_regions); - kv_destroy(data->attrs); - xfree(data->space_buf); - xfree(data); -} - -/// Handoff point between the main (ui_bridge) thread and the TUI thread. -static void tui_scheduler(Event event, void *d) +static bool tui_is_stopped(TUIData *tui) { - UI *ui = d; - TUIData *data = ui->data; - loop_schedule_fast(data->loop, event); // `tui_loop` local to tui_main(). + return tui->stopped; } -#ifdef UNIX -static void sigcont_cb(SignalWatcher *watcher, int signum, void *data) +#ifdef EXITFREE +void tui_free_all_mem(TUIData *tui) { - ((TUIData *)data)->cont_received = true; + ugrid_free(&tui->grid); + kv_destroy(tui->invalid_regions); + kv_destroy(tui->attrs); + xfree(tui->space_buf); + xfree(tui->term); + xfree(tui); } #endif -static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) +static void sigwinch_cb(SignalWatcher *watcher, int signum, void *cbdata) { got_winch++; - UI *ui = data; - if (tui_is_stopped(ui)) { + TUIData *tui = cbdata; + if (tui_is_stopped(tui)) { return; } - tui_guess_size(ui); - ui_schedule_refresh(); + tui_guess_size(tui); } -static bool attrs_differ(UI *ui, int id1, int id2, bool rgb) +static bool attrs_differ(TUIData *tui, int id1, int id2, bool rgb) { - TUIData *data = ui->data; if (id1 == id2) { return false; } else if (id1 < 0 || id2 < 0) { return true; } - HlAttrs a1 = kv_A(data->attrs, (size_t)id1); - HlAttrs a2 = kv_A(data->attrs, (size_t)id2); + HlAttrs a1 = kv_A(tui->attrs, (size_t)id1); + HlAttrs a2 = kv_A(tui->attrs, (size_t)id2); if (rgb) { return a1.rgb_fg_color != a2.rgb_fg_color @@ -564,42 +514,42 @@ static bool attrs_differ(UI *ui, int id1, int id2, bool rgb) return a1.cterm_fg_color != a2.cterm_fg_color || a1.cterm_bg_color != a2.cterm_bg_color || a1.cterm_ae_attr != a2.cterm_ae_attr - || (a1.cterm_ae_attr & HL_ANY_UNDERLINE + || (a1.cterm_ae_attr & HL_UNDERLINE_MASK && a1.rgb_sp_color != a2.rgb_sp_color); } } -static void update_attrs(UI *ui, int attr_id) +static void update_attrs(TUIData *tui, int attr_id) { - TUIData *data = ui->data; - - if (!attrs_differ(ui, attr_id, data->print_attr_id, ui->rgb)) { - data->print_attr_id = attr_id; + if (!attrs_differ(tui, attr_id, tui->print_attr_id, tui->rgb)) { + tui->print_attr_id = attr_id; return; } - data->print_attr_id = attr_id; - HlAttrs attrs = kv_A(data->attrs, (size_t)attr_id); - int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; + tui->print_attr_id = attr_id; + HlAttrs attrs = kv_A(tui->attrs, (size_t)attr_id); + int attr = tui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; bool bold = attr & HL_BOLD; bool italic = attr & HL_ITALIC; bool reverse = attr & HL_INVERSE; bool standout = attr & HL_STANDOUT; bool strikethrough = attr & HL_STRIKETHROUGH; + bool altfont = attr & HL_ALTFONT; bool underline; bool undercurl; bool underdouble; bool underdotted; bool underdashed; - if (data->unibi_ext.set_underline_style != -1) { - underline = attr & HL_UNDERLINE; - undercurl = attr & HL_UNDERCURL; - underdouble = attr & HL_UNDERDOUBLE; - underdashed = attr & HL_UNDERDASHED; - underdotted = attr & HL_UNDERDOTTED; + if (tui->unibi_ext.set_underline_style != -1) { + int ul = attr & HL_UNDERLINE_MASK; + underline = ul == HL_UNDERLINE; + undercurl = ul == HL_UNDERCURL; + underdouble = ul == HL_UNDERDOUBLE; + underdashed = ul == HL_UNDERDASHED; + underdotted = ul == HL_UNDERDOTTED; } else { - underline = attr & HL_ANY_UNDERLINE; + underline = attr & HL_UNDERLINE_MASK; undercurl = false; underdouble = false; underdotted = false; @@ -609,126 +559,128 @@ static void update_attrs(UI *ui, int attr_id) bool has_any_underline = undercurl || underline || underdouble || underdotted || underdashed; - if (unibi_get_str(data->ut, unibi_set_attributes)) { + if (unibi_get_str(tui->ut, unibi_set_attributes)) { if (bold || reverse || underline || standout) { - UNIBI_SET_NUM_VAR(data->params[0], standout); - UNIBI_SET_NUM_VAR(data->params[1], underline); - UNIBI_SET_NUM_VAR(data->params[2], reverse); - UNIBI_SET_NUM_VAR(data->params[3], 0); // blink - UNIBI_SET_NUM_VAR(data->params[4], 0); // dim - UNIBI_SET_NUM_VAR(data->params[5], bold); - UNIBI_SET_NUM_VAR(data->params[6], 0); // blank - UNIBI_SET_NUM_VAR(data->params[7], 0); // protect - UNIBI_SET_NUM_VAR(data->params[8], 0); // alternate character set - unibi_out(ui, unibi_set_attributes); - } else if (!data->default_attr) { - unibi_out(ui, unibi_exit_attribute_mode); + UNIBI_SET_NUM_VAR(tui->params[0], standout); + UNIBI_SET_NUM_VAR(tui->params[1], underline); + UNIBI_SET_NUM_VAR(tui->params[2], reverse); + UNIBI_SET_NUM_VAR(tui->params[3], 0); // blink + UNIBI_SET_NUM_VAR(tui->params[4], 0); // dim + UNIBI_SET_NUM_VAR(tui->params[5], bold); + UNIBI_SET_NUM_VAR(tui->params[6], 0); // blank + UNIBI_SET_NUM_VAR(tui->params[7], 0); // protect + UNIBI_SET_NUM_VAR(tui->params[8], 0); // alternate character set + unibi_out(tui, unibi_set_attributes); + } else if (!tui->default_attr) { + unibi_out(tui, unibi_exit_attribute_mode); } } else { - if (!data->default_attr) { - unibi_out(ui, unibi_exit_attribute_mode); + if (!tui->default_attr) { + unibi_out(tui, unibi_exit_attribute_mode); } if (bold) { - unibi_out(ui, unibi_enter_bold_mode); + unibi_out(tui, unibi_enter_bold_mode); } if (underline) { - unibi_out(ui, unibi_enter_underline_mode); + unibi_out(tui, unibi_enter_underline_mode); } if (standout) { - unibi_out(ui, unibi_enter_standout_mode); + unibi_out(tui, unibi_enter_standout_mode); } if (reverse) { - unibi_out(ui, unibi_enter_reverse_mode); + unibi_out(tui, unibi_enter_reverse_mode); } } if (italic) { - unibi_out(ui, unibi_enter_italics_mode); + unibi_out(tui, unibi_enter_italics_mode); } - if (strikethrough && data->unibi_ext.enter_strikethrough_mode != -1) { - unibi_out_ext(ui, data->unibi_ext.enter_strikethrough_mode); + if (altfont && tui->unibi_ext.enter_altfont_mode != -1) { + unibi_out_ext(tui, tui->unibi_ext.enter_altfont_mode); } - if (undercurl && data->unibi_ext.set_underline_style != -1) { - UNIBI_SET_NUM_VAR(data->params[0], 3); - unibi_out_ext(ui, data->unibi_ext.set_underline_style); + if (strikethrough && tui->unibi_ext.enter_strikethrough_mode != -1) { + unibi_out_ext(tui, tui->unibi_ext.enter_strikethrough_mode); } - if (underdouble && data->unibi_ext.set_underline_style != -1) { - UNIBI_SET_NUM_VAR(data->params[0], 2); - unibi_out_ext(ui, data->unibi_ext.set_underline_style); + if (undercurl && tui->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(tui->params[0], 3); + unibi_out_ext(tui, tui->unibi_ext.set_underline_style); } - if (underdotted && data->unibi_ext.set_underline_style != -1) { - UNIBI_SET_NUM_VAR(data->params[0], 4); - unibi_out_ext(ui, data->unibi_ext.set_underline_style); + if (underdouble && tui->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(tui->params[0], 2); + unibi_out_ext(tui, tui->unibi_ext.set_underline_style); } - if (underdashed && data->unibi_ext.set_underline_style != -1) { - UNIBI_SET_NUM_VAR(data->params[0], 5); - unibi_out_ext(ui, data->unibi_ext.set_underline_style); + if (underdotted && tui->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(tui->params[0], 4); + unibi_out_ext(tui, tui->unibi_ext.set_underline_style); + } + if (underdashed && tui->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(tui->params[0], 5); + unibi_out_ext(tui, tui->unibi_ext.set_underline_style); } - if (has_any_underline && data->unibi_ext.set_underline_color != -1) { + if (has_any_underline && tui->unibi_ext.set_underline_color != -1) { int color = attrs.rgb_sp_color; if (color != -1) { - UNIBI_SET_NUM_VAR(data->params[0], (color >> 16) & 0xff); // red - UNIBI_SET_NUM_VAR(data->params[1], (color >> 8) & 0xff); // green - UNIBI_SET_NUM_VAR(data->params[2], color & 0xff); // blue - unibi_out_ext(ui, data->unibi_ext.set_underline_color); + UNIBI_SET_NUM_VAR(tui->params[0], (color >> 16) & 0xff); // red + UNIBI_SET_NUM_VAR(tui->params[1], (color >> 8) & 0xff); // green + UNIBI_SET_NUM_VAR(tui->params[2], color & 0xff); // blue + unibi_out_ext(tui, tui->unibi_ext.set_underline_color); } } int fg, bg; - if (ui->rgb && !(attr & HL_FG_INDEXED)) { + if (tui->rgb && !(attr & HL_FG_INDEXED)) { fg = ((attrs.rgb_fg_color != -1) - ? attrs.rgb_fg_color : data->clear_attrs.rgb_fg_color); + ? attrs.rgb_fg_color : tui->clear_attrs.rgb_fg_color); if (fg != -1) { - UNIBI_SET_NUM_VAR(data->params[0], (fg >> 16) & 0xff); // red - UNIBI_SET_NUM_VAR(data->params[1], (fg >> 8) & 0xff); // green - UNIBI_SET_NUM_VAR(data->params[2], fg & 0xff); // blue - unibi_out_ext(ui, data->unibi_ext.set_rgb_foreground); + UNIBI_SET_NUM_VAR(tui->params[0], (fg >> 16) & 0xff); // red + UNIBI_SET_NUM_VAR(tui->params[1], (fg >> 8) & 0xff); // green + UNIBI_SET_NUM_VAR(tui->params[2], fg & 0xff); // blue + unibi_out_ext(tui, tui->unibi_ext.set_rgb_foreground); } } else { fg = (attrs.cterm_fg_color - ? attrs.cterm_fg_color - 1 : (data->clear_attrs.cterm_fg_color - 1)); + ? attrs.cterm_fg_color - 1 : (tui->clear_attrs.cterm_fg_color - 1)); if (fg != -1) { - UNIBI_SET_NUM_VAR(data->params[0], fg); - unibi_out(ui, unibi_set_a_foreground); + UNIBI_SET_NUM_VAR(tui->params[0], fg); + unibi_out(tui, unibi_set_a_foreground); } } - if (ui->rgb && !(attr & HL_BG_INDEXED)) { + if (tui->rgb && !(attr & HL_BG_INDEXED)) { bg = ((attrs.rgb_bg_color != -1) - ? attrs.rgb_bg_color : data->clear_attrs.rgb_bg_color); + ? attrs.rgb_bg_color : tui->clear_attrs.rgb_bg_color); if (bg != -1) { - UNIBI_SET_NUM_VAR(data->params[0], (bg >> 16) & 0xff); // red - UNIBI_SET_NUM_VAR(data->params[1], (bg >> 8) & 0xff); // green - UNIBI_SET_NUM_VAR(data->params[2], bg & 0xff); // blue - unibi_out_ext(ui, data->unibi_ext.set_rgb_background); + UNIBI_SET_NUM_VAR(tui->params[0], (bg >> 16) & 0xff); // red + UNIBI_SET_NUM_VAR(tui->params[1], (bg >> 8) & 0xff); // green + UNIBI_SET_NUM_VAR(tui->params[2], bg & 0xff); // blue + unibi_out_ext(tui, tui->unibi_ext.set_rgb_background); } } else { bg = (attrs.cterm_bg_color - ? attrs.cterm_bg_color - 1 : (data->clear_attrs.cterm_bg_color - 1)); + ? attrs.cterm_bg_color - 1 : (tui->clear_attrs.cterm_bg_color - 1)); if (bg != -1) { - UNIBI_SET_NUM_VAR(data->params[0], bg); - unibi_out(ui, unibi_set_a_background); + UNIBI_SET_NUM_VAR(tui->params[0], bg); + unibi_out(tui, unibi_set_a_background); } } - data->default_attr = fg == -1 && bg == -1 - && !bold && !italic && !has_any_underline && !reverse && !standout - && !strikethrough; + tui->default_attr = fg == -1 && bg == -1 + && !bold && !italic && !has_any_underline && !reverse && !standout + && !strikethrough; // Non-BCE terminals can't clear with non-default background color. Some BCE // terminals don't support attributes either, so don't rely on it. But assume // italic and bold has no effect if there is no text. - data->can_clear_attr = !reverse && !standout && !has_any_underline - && !strikethrough && (data->bce || bg == -1); + tui->can_clear_attr = !reverse && !standout && !has_any_underline + && !strikethrough && (tui->bce || bg == -1); } -static void final_column_wrap(UI *ui) +static void final_column_wrap(TUIData *tui) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; - if (grid->row != -1 && grid->col == ui->width) { + UGrid *grid = &tui->grid; + if (grid->row != -1 && grid->col == tui->width) { grid->col = 0; - if (grid->row < MIN(ui->height, grid->height - 1)) { + if (grid->row < MIN(tui->height, grid->height - 1)) { grid->row++; } } @@ -737,33 +689,31 @@ static void final_column_wrap(UI *ui) /// It is undocumented, but in the majority of terminals and terminal emulators /// printing at the right margin does not cause an automatic wrap until the /// next character is printed, holding the cursor in place until then. -static void print_cell(UI *ui, UCell *ptr) +static void print_cell(TUIData *tui, UCell *ptr) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; - if (!data->immediate_wrap_after_last_column) { + UGrid *grid = &tui->grid; + if (!tui->immediate_wrap_after_last_column) { // Printing the next character finally advances the cursor. - final_column_wrap(ui); + final_column_wrap(tui); } - update_attrs(ui, ptr->attr); - out(ui, ptr->data, strlen(ptr->data)); + update_attrs(tui, ptr->attr); + out(tui, ptr->data, strlen(ptr->data)); grid->col++; - if (data->immediate_wrap_after_last_column) { + if (tui->immediate_wrap_after_last_column) { // Printing at the right margin immediately advances the cursor. - final_column_wrap(ui); + final_column_wrap(tui); } } -static bool cheap_to_print(UI *ui, int row, int col, int next) +static bool cheap_to_print(TUIData *tui, int row, int col, int next) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; UCell *cell = grid->cells[row] + col; while (next) { next--; - if (attrs_differ(ui, cell->attr, - data->print_attr_id, ui->rgb)) { - if (data->default_attr) { + if (attrs_differ(tui, cell->attr, + tui->print_attr_id, tui->rgb)) { + if (tui->default_attr) { return false; } } @@ -784,15 +734,14 @@ static bool cheap_to_print(UI *ui, int row, int col, int next) /// (ASCII 0/12) means the same thing and does not mean home. VT, CVT, and /// TAB also stop at software-defined tabulation stops, not at a fixed set /// of row/column positions. -static void cursor_goto(UI *ui, int row, int col) +static void cursor_goto(TUIData *tui, int row, int col) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; if (row == grid->row && col == grid->col) { return; } if (0 == row && 0 == col) { - unibi_out(ui, unibi_cursor_home); + unibi_out(tui, unibi_cursor_home); ugrid_goto(grid, row, col); return; } @@ -801,12 +750,12 @@ static void cursor_goto(UI *ui, int row, int col) } if (0 == col ? col != grid->col : row != grid->row ? false : - 1 == col ? 2 < grid->col && cheap_to_print(ui, grid->row, 0, col) : - 2 == col ? 5 < grid->col && cheap_to_print(ui, grid->row, 0, col) : + 1 == col ? 2 < grid->col && cheap_to_print(tui, grid->row, 0, col) : + 2 == col ? 5 < grid->col && cheap_to_print(tui, grid->row, 0, col) : false) { // Motion to left margin from anywhere else, or CR + printing chars is // even less expensive than using BSes or CUB. - unibi_out(ui, unibi_carriage_return); + unibi_out(tui, unibi_carriage_return); ugrid_goto(grid, grid->row, 0); } if (row == grid->row) { @@ -816,15 +765,15 @@ static void cursor_goto(UI *ui, int row, int col) // motion calculations have OBOEs that cannot be compensated for, // because two terminals that claim to be the same will implement // different cursor positioning rules. - && (data->immediate_wrap_after_last_column || grid->col < ui->width)) { + && (tui->immediate_wrap_after_last_column || grid->col < tui->width)) { int n = grid->col - col; if (n <= 4) { // This might be just BS, so it is considered really cheap. while (n--) { - unibi_out(ui, unibi_cursor_left); + unibi_out(tui, unibi_cursor_left); } } else { - UNIBI_SET_NUM_VAR(data->params[0], n); - unibi_out(ui, unibi_parm_left_cursor); + UNIBI_SET_NUM_VAR(tui->params[0], n); + unibi_out(tui, unibi_parm_left_cursor); } ugrid_goto(grid, row, col); return; @@ -832,11 +781,11 @@ static void cursor_goto(UI *ui, int row, int col) int n = col - grid->col; if (n <= 2) { while (n--) { - unibi_out(ui, unibi_cursor_right); + unibi_out(tui, unibi_cursor_right); } } else { - UNIBI_SET_NUM_VAR(data->params[0], n); - unibi_out(ui, unibi_parm_right_cursor); + UNIBI_SET_NUM_VAR(tui->params[0], n); + unibi_out(tui, unibi_parm_right_cursor); } ugrid_goto(grid, row, col); return; @@ -847,11 +796,11 @@ static void cursor_goto(UI *ui, int row, int col) int n = row - grid->row; if (n <= 4) { // This might be just LF, so it is considered really cheap. while (n--) { - unibi_out(ui, unibi_cursor_down); + unibi_out(tui, unibi_cursor_down); } } else { - UNIBI_SET_NUM_VAR(data->params[0], n); - unibi_out(ui, unibi_parm_down_cursor); + UNIBI_SET_NUM_VAR(tui->params[0], n); + unibi_out(tui, unibi_parm_down_cursor); } ugrid_goto(grid, row, col); return; @@ -859,11 +808,11 @@ static void cursor_goto(UI *ui, int row, int col) int n = grid->row - row; if (n <= 2) { while (n--) { - unibi_out(ui, unibi_cursor_up); + unibi_out(tui, unibi_cursor_up); } } else { - UNIBI_SET_NUM_VAR(data->params[0], n); - unibi_out(ui, unibi_parm_up_cursor); + UNIBI_SET_NUM_VAR(tui->params[0], n); + unibi_out(tui, unibi_parm_up_cursor); } ugrid_goto(grid, row, col); return; @@ -871,20 +820,19 @@ static void cursor_goto(UI *ui, int row, int col) } safe_move: - unibi_goto(ui, row, col); + unibi_goto(tui, row, col); ugrid_goto(grid, row, col); } -static void print_spaces(UI *ui, int width) +static void print_spaces(TUIData *tui, int width) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; - out(ui, data->space_buf, (size_t)width); + out(tui, tui->space_buf, (size_t)width); grid->col += width; - if (data->immediate_wrap_after_last_column) { + if (tui->immediate_wrap_after_last_column) { // Printing at the right margin immediately advances the cursor. - final_column_wrap(ui); + final_column_wrap(tui); } } @@ -893,28 +841,27 @@ static void print_spaces(UI *ui, int width) /// /// @param is_doublewidth whether the character is double-width on the grid. /// If true and the character is ambiguous-width, clear two cells. -static void print_cell_at_pos(UI *ui, int row, int col, UCell *cell, bool is_doublewidth) +static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool is_doublewidth) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; if (grid->row == -1 && cell->data[0] == NUL) { // If cursor needs to repositioned and there is nothing to print, don't move cursor. return; } - cursor_goto(ui, row, col); + cursor_goto(tui, row, col); bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(cell->data)); if (is_ambiwidth && is_doublewidth) { // Clear the two screen cells. // If the character is single-width in the host terminal it won't change the second cell. - update_attrs(ui, cell->attr); - print_spaces(ui, 2); - cursor_goto(ui, row, col); + update_attrs(tui, cell->attr); + print_spaces(tui, 2); + cursor_goto(tui, row, col); } - print_cell(ui, cell); + print_cell(tui, cell); if (is_ambiwidth) { // Force repositioning cursor after printing an ambiguous-width character. @@ -922,120 +869,116 @@ static void print_cell_at_pos(UI *ui, int row, int col, UCell *cell, bool is_dou } } -static void clear_region(UI *ui, int top, int bot, int left, int right, int attr_id) +static void clear_region(TUIData *tui, int top, int bot, int left, int right, int attr_id) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; - update_attrs(ui, attr_id); + update_attrs(tui, attr_id); // Background is set to the default color and the right edge matches the // screen end, try to use terminal codes for clearing the requested area. - if (data->can_clear_attr - && left == 0 && right == ui->width && bot == ui->height) { + if (tui->can_clear_attr + && left == 0 && right == tui->width && bot == tui->height) { if (top == 0) { - unibi_out(ui, unibi_clear_screen); + unibi_out(tui, unibi_clear_screen); ugrid_goto(grid, top, left); } else { - cursor_goto(ui, top, 0); - unibi_out(ui, unibi_clr_eos); + cursor_goto(tui, top, 0); + unibi_out(tui, unibi_clr_eos); } } else { int width = right - left; // iterate through each line and clear for (int row = top; row < bot; row++) { - cursor_goto(ui, row, left); - if (data->can_clear_attr && right == ui->width) { - unibi_out(ui, unibi_clr_eol); - } else if (data->can_erase_chars && data->can_clear_attr && width >= 5) { - UNIBI_SET_NUM_VAR(data->params[0], width); - unibi_out(ui, unibi_erase_chars); + cursor_goto(tui, row, left); + if (tui->can_clear_attr && right == tui->width) { + unibi_out(tui, unibi_clr_eol); + } else if (tui->can_erase_chars && tui->can_clear_attr && width >= 5) { + UNIBI_SET_NUM_VAR(tui->params[0], width); + unibi_out(tui, unibi_erase_chars); } else { - print_spaces(ui, width); + print_spaces(tui, width); } } } } -static void set_scroll_region(UI *ui, int top, int bot, int left, int right) +static void set_scroll_region(TUIData *tui, int top, int bot, int left, int right) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; - UNIBI_SET_NUM_VAR(data->params[0], top); - UNIBI_SET_NUM_VAR(data->params[1], bot); - unibi_out(ui, unibi_change_scroll_region); - if (left != 0 || right != ui->width - 1) { - unibi_out_ext(ui, data->unibi_ext.enable_lr_margin); - if (data->can_set_lr_margin) { - UNIBI_SET_NUM_VAR(data->params[0], left); - UNIBI_SET_NUM_VAR(data->params[1], right); - unibi_out(ui, unibi_set_lr_margin); + UNIBI_SET_NUM_VAR(tui->params[0], top); + UNIBI_SET_NUM_VAR(tui->params[1], bot); + unibi_out(tui, unibi_change_scroll_region); + if (left != 0 || right != tui->width - 1) { + unibi_out_ext(tui, tui->unibi_ext.enable_lr_margin); + if (tui->can_set_lr_margin) { + UNIBI_SET_NUM_VAR(tui->params[0], left); + UNIBI_SET_NUM_VAR(tui->params[1], right); + unibi_out(tui, unibi_set_lr_margin); } else { - UNIBI_SET_NUM_VAR(data->params[0], left); - unibi_out(ui, unibi_set_left_margin_parm); - UNIBI_SET_NUM_VAR(data->params[0], right); - unibi_out(ui, unibi_set_right_margin_parm); + UNIBI_SET_NUM_VAR(tui->params[0], left); + unibi_out(tui, unibi_set_left_margin_parm); + UNIBI_SET_NUM_VAR(tui->params[0], right); + unibi_out(tui, unibi_set_right_margin_parm); } } grid->row = -1; } -static void reset_scroll_region(UI *ui, bool fullwidth) +static void reset_scroll_region(TUIData *tui, bool fullwidth) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; - if (0 <= data->unibi_ext.reset_scroll_region) { - unibi_out_ext(ui, data->unibi_ext.reset_scroll_region); + if (0 <= tui->unibi_ext.reset_scroll_region) { + unibi_out_ext(tui, tui->unibi_ext.reset_scroll_region); } else { - UNIBI_SET_NUM_VAR(data->params[0], 0); - UNIBI_SET_NUM_VAR(data->params[1], ui->height - 1); - unibi_out(ui, unibi_change_scroll_region); + UNIBI_SET_NUM_VAR(tui->params[0], 0); + UNIBI_SET_NUM_VAR(tui->params[1], tui->height - 1); + unibi_out(tui, unibi_change_scroll_region); } if (!fullwidth) { - if (data->can_set_lr_margin) { - UNIBI_SET_NUM_VAR(data->params[0], 0); - UNIBI_SET_NUM_VAR(data->params[1], ui->width - 1); - unibi_out(ui, unibi_set_lr_margin); + if (tui->can_set_lr_margin) { + UNIBI_SET_NUM_VAR(tui->params[0], 0); + UNIBI_SET_NUM_VAR(tui->params[1], tui->width - 1); + unibi_out(tui, unibi_set_lr_margin); } else { - UNIBI_SET_NUM_VAR(data->params[0], 0); - unibi_out(ui, unibi_set_left_margin_parm); - UNIBI_SET_NUM_VAR(data->params[0], ui->width - 1); - unibi_out(ui, unibi_set_right_margin_parm); + UNIBI_SET_NUM_VAR(tui->params[0], 0); + unibi_out(tui, unibi_set_left_margin_parm); + UNIBI_SET_NUM_VAR(tui->params[0], tui->width - 1); + unibi_out(tui, unibi_set_right_margin_parm); } - unibi_out_ext(ui, data->unibi_ext.disable_lr_margin); + unibi_out_ext(tui, tui->unibi_ext.disable_lr_margin); } grid->row = -1; } -static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) +void tui_grid_resize(TUIData *tui, Integer g, Integer width, Integer height) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; ugrid_resize(grid, (int)width, (int)height); - xfree(data->space_buf); - data->space_buf = xmalloc((size_t)width * sizeof(*data->space_buf)); - memset(data->space_buf, ' ', (size_t)width); + xfree(tui->space_buf); + tui->space_buf = xmalloc((size_t)width * sizeof(*tui->space_buf)); + memset(tui->space_buf, ' ', (size_t)width); // resize might not always be followed by a clear before flush // so clip the invalid region - for (size_t i = 0; i < kv_size(data->invalid_regions); i++) { - Rect *r = &kv_A(data->invalid_regions, i); + for (size_t i = 0; i < kv_size(tui->invalid_regions); i++) { + Rect *r = &kv_A(tui->invalid_regions, i); r->bot = MIN(r->bot, grid->height); r->right = MIN(r->right, grid->width); } - if (!got_winch && !data->is_starting) { + if (!got_winch && !tui->is_starting) { // Resize the _host_ terminal. - UNIBI_SET_NUM_VAR(data->params[0], (int)height); - UNIBI_SET_NUM_VAR(data->params[1], (int)width); - unibi_out_ext(ui, data->unibi_ext.resize_screen); + UNIBI_SET_NUM_VAR(tui->params[0], (int)height); + UNIBI_SET_NUM_VAR(tui->params[1], (int)width); + unibi_out_ext(tui, tui->unibi_ext.resize_screen); // DECSLPP does not reset the scroll region. - if (data->scroll_region_is_full_screen) { - reset_scroll_region(ui, ui->width == grid->width); + if (tui->scroll_region_is_full_screen) { + reset_scroll_region(tui, tui->width == grid->width); } } else { // Already handled the SIGWINCH signal; avoid double-resize. got_winch = got_winch > 0 ? got_winch - 1 : 0; @@ -1043,22 +986,19 @@ static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) } } -static void tui_grid_clear(UI *ui, Integer g) +void tui_grid_clear(TUIData *tui, Integer g) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; ugrid_clear(grid); - kv_size(data->invalid_regions) = 0; - clear_region(ui, 0, grid->height, 0, grid->width, 0); + kv_size(tui->invalid_regions) = 0; + clear_region(tui, 0, grid->height, 0, grid->width, 0); } -static void tui_grid_cursor_goto(UI *ui, Integer grid, Integer row, Integer col) +void tui_grid_cursor_goto(TUIData *tui, Integer grid, Integer row, Integer col) { - TUIData *data = ui->data; - // cursor position is validated in tui_flush - data->row = (int)row; - data->col = (int)col; + tui->row = (int)row; + tui->col = (int)col; } CursorShape tui_cursor_decode_shape(const char *shape_str) @@ -1098,13 +1038,12 @@ static cursorentry_T decode_cursor_entry(Dictionary args) return r; } -static void tui_mode_info_set(UI *ui, bool guicursor_enabled, Array args) +void tui_mode_info_set(TUIData *tui, bool guicursor_enabled, Array args) { cursor_style_enabled = guicursor_enabled; if (!guicursor_enabled) { return; // Do not send cursor style control codes. } - TUIData *data = ui->data; assert(args.size); @@ -1112,72 +1051,81 @@ static void tui_mode_info_set(UI *ui, bool guicursor_enabled, Array args) for (size_t i = 0; i < args.size; i++) { assert(args.items[i].type == kObjectTypeDictionary); cursorentry_T r = decode_cursor_entry(args.items[i].data.dictionary); - data->cursor_shapes[i] = r; + tui->cursor_shapes[i] = r; } - tui_set_mode(ui, data->showing_mode); + tui_set_mode(tui, tui->showing_mode); } -static void tui_update_menu(UI *ui) +void tui_update_menu(TUIData *tui) { // Do nothing; menus are for GUI only } -static void tui_busy_start(UI *ui) +void tui_busy_start(TUIData *tui) { - ((TUIData *)ui->data)->busy = true; + tui->busy = true; } -static void tui_busy_stop(UI *ui) +void tui_busy_stop(TUIData *tui) { - ((TUIData *)ui->data)->busy = false; + tui->busy = false; } -static void tui_mouse_on(UI *ui) +void tui_mouse_on(TUIData *tui) { - TUIData *data = ui->data; - if (!data->mouse_enabled) { - unibi_out_ext(ui, data->unibi_ext.enable_mouse); - data->mouse_enabled = true; + if (!tui->mouse_enabled) { + unibi_out_ext(tui, tui->unibi_ext.enable_mouse); + if (tui->mouse_move_enabled) { + unibi_out_ext(tui, tui->unibi_ext.enable_mouse_move); + } + tui->mouse_enabled = true; } } -static void tui_mouse_off(UI *ui) +void tui_mouse_off(TUIData *tui) { - TUIData *data = ui->data; - if (data->mouse_enabled) { - unibi_out_ext(ui, data->unibi_ext.disable_mouse); - data->mouse_enabled = false; + if (tui->mouse_enabled) { + if (tui->mouse_move_enabled) { + unibi_out_ext(tui, tui->unibi_ext.disable_mouse_move); + } + unibi_out_ext(tui, tui->unibi_ext.disable_mouse); + tui->mouse_enabled = false; } } -static void tui_set_mode(UI *ui, ModeShape mode) +void tui_set_mode(TUIData *tui, ModeShape mode) { if (!cursor_style_enabled) { return; } - TUIData *data = ui->data; - cursorentry_T c = data->cursor_shapes[mode]; + cursorentry_T c = tui->cursor_shapes[mode]; - if (c.id != 0 && c.id < (int)kv_size(data->attrs) && ui->rgb) { - HlAttrs aep = kv_A(data->attrs, c.id); + if (c.id != 0 && c.id < (int)kv_size(tui->attrs) && tui->rgb) { + HlAttrs aep = kv_A(tui->attrs, c.id); - data->want_invisible = aep.hl_blend == 100; - if (data->want_invisible) { - unibi_out(ui, unibi_cursor_invisible); + tui->want_invisible = aep.hl_blend == 100; + if (tui->want_invisible) { + unibi_out(tui, unibi_cursor_invisible); } else if (aep.rgb_ae_attr & HL_INVERSE) { // We interpret "inverse" as "default" (no termcode for "inverse"...). // Hopefully the user's default cursor color is inverse. - unibi_out_ext(ui, data->unibi_ext.reset_cursor_color); + unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } else { - UNIBI_SET_NUM_VAR(data->params[0], aep.rgb_bg_color); - unibi_out_ext(ui, data->unibi_ext.set_cursor_color); - data->cursor_color_changed = true; + if (tui->set_cursor_color_as_str) { + char hexbuf[8]; + snprintf(hexbuf, 7 + 1, "#%06x", aep.rgb_bg_color); + UNIBI_SET_STR_VAR(tui->params[0], hexbuf); + } else { + UNIBI_SET_NUM_VAR(tui->params[0], aep.rgb_bg_color); + } + unibi_out_ext(tui, tui->unibi_ext.set_cursor_color); + tui->cursor_color_changed = true; } } else if (c.id == 0) { // No cursor color for this mode; reset to default. - data->want_invisible = false; - unibi_out_ext(ui, data->unibi_ext.reset_cursor_color); + tui->want_invisible = false; + unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color); } int shape; @@ -1191,83 +1139,86 @@ static void tui_set_mode(UI *ui, ModeShape mode) case SHAPE_VER: shape = 5; break; } - UNIBI_SET_NUM_VAR(data->params[0], shape + (int)(c.blinkon == 0)); - unibi_out_ext(ui, data->unibi_ext.set_cursor_style); + UNIBI_SET_NUM_VAR(tui->params[0], shape + (int)(c.blinkon == 0)); + unibi_out_ext(tui, tui->unibi_ext.set_cursor_style); } /// @param mode editor mode -static void tui_mode_change(UI *ui, String mode, Integer mode_idx) +void tui_mode_change(TUIData *tui, String mode, Integer mode_idx) { - TUIData *data = ui->data; #ifdef UNIX // If stdin is not a TTY, the LHS of pipe may change the state of the TTY // after calling uv_tty_set_mode. So, set the mode of the TTY again here. // #13073 - if (data->is_starting && data->input.in_fd == STDERR_FILENO) { - int ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_NORMAL); + if (tui->is_starting && tui->input.in_fd == STDERR_FILENO) { + int ret = uv_tty_set_mode(&tui->output_handle.tty, UV_TTY_MODE_NORMAL); if (ret) { ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); } - ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO); + ret = uv_tty_set_mode(&tui->output_handle.tty, UV_TTY_MODE_IO); if (ret) { ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); } } #endif - tui_set_mode(ui, (ModeShape)mode_idx); - data->is_starting = false; // mode entered, no longer starting - data->showing_mode = (ModeShape)mode_idx; + tui_set_mode(tui, (ModeShape)mode_idx); + if (tui->is_starting) { + if (tui->verbose >= 3) { + show_verbose_terminfo(tui); + } + } + tui->is_starting = false; // mode entered, no longer starting + tui->showing_mode = (ModeShape)mode_idx; } -static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, // -V751 - Integer endrow, Integer startcol, Integer endcol, Integer rows, - Integer cols FUNC_ATTR_UNUSED) +void tui_grid_scroll(TUIData *tui, Integer g, Integer startrow, // -V751 + Integer endrow, Integer startcol, Integer endcol, Integer rows, + Integer cols FUNC_ATTR_UNUSED) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; int top = (int)startrow, bot = (int)endrow - 1; int left = (int)startcol, right = (int)endcol - 1; - bool fullwidth = left == 0 && right == ui->width - 1; - data->scroll_region_is_full_screen = fullwidth - && top == 0 && bot == ui->height - 1; + bool fullwidth = left == 0 && right == tui->width - 1; + tui->scroll_region_is_full_screen = fullwidth + && top == 0 && bot == tui->height - 1; ugrid_scroll(grid, top, bot, left, right, (int)rows); - bool can_scroll = data->can_scroll - && (data->scroll_region_is_full_screen - || (data->can_change_scroll_region - && ((left == 0 && right == ui->width - 1) - || data->can_set_lr_margin - || data->can_set_left_right_margin))); + bool can_scroll = tui->can_scroll + && (tui->scroll_region_is_full_screen + || (tui->can_change_scroll_region + && ((left == 0 && right == tui->width - 1) + || tui->can_set_lr_margin + || tui->can_set_left_right_margin))); if (can_scroll) { // Change terminal scroll region and move cursor to the top - if (!data->scroll_region_is_full_screen) { - set_scroll_region(ui, top, bot, left, right); + if (!tui->scroll_region_is_full_screen) { + set_scroll_region(tui, top, bot, left, right); } - cursor_goto(ui, top, left); - update_attrs(ui, 0); + cursor_goto(tui, top, left); + update_attrs(tui, 0); if (rows > 0) { if (rows == 1) { - unibi_out(ui, unibi_delete_line); + unibi_out(tui, unibi_delete_line); } else { - UNIBI_SET_NUM_VAR(data->params[0], (int)rows); - unibi_out(ui, unibi_parm_delete_line); + UNIBI_SET_NUM_VAR(tui->params[0], (int)rows); + unibi_out(tui, unibi_parm_delete_line); } } else { if (rows == -1) { - unibi_out(ui, unibi_insert_line); + unibi_out(tui, unibi_insert_line); } else { - UNIBI_SET_NUM_VAR(data->params[0], -(int)rows); - unibi_out(ui, unibi_parm_insert_line); + UNIBI_SET_NUM_VAR(tui->params[0], -(int)rows); + unibi_out(tui, unibi_parm_insert_line); } } // Restore terminal scroll region and cursor - if (!data->scroll_region_is_full_screen) { - reset_scroll_region(ui, fullwidth); + if (!tui->scroll_region_is_full_screen) { + reset_scroll_region(tui, fullwidth); } } else { // Mark the moved region as invalid for redrawing later @@ -1276,47 +1227,46 @@ static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, // -V751 } else { startrow = startrow - rows; } - invalidate(ui, (int)startrow, (int)endrow, (int)startcol, (int)endcol); + invalidate(tui, (int)startrow, (int)endrow, (int)startcol, (int)endcol); } } -static void tui_hl_attr_define(UI *ui, Integer id, HlAttrs attrs, HlAttrs cterm_attrs, Array info) +void tui_hl_attr_define(TUIData *tui, Integer id, HlAttrs attrs, HlAttrs cterm_attrs, Array info) { - TUIData *data = ui->data; - kv_a(data->attrs, (size_t)id) = attrs; + attrs.cterm_ae_attr = cterm_attrs.cterm_ae_attr; + attrs.cterm_fg_color = cterm_attrs.cterm_fg_color; + attrs.cterm_bg_color = cterm_attrs.cterm_bg_color; + kv_a(tui->attrs, (size_t)id) = attrs; } -static void tui_bell(UI *ui) +void tui_bell(TUIData *tui) { - unibi_out(ui, unibi_bell); + unibi_out(tui, unibi_bell); } -static void tui_visual_bell(UI *ui) +void tui_visual_bell(TUIData *tui) { - unibi_out(ui, unibi_flash_screen); + unibi_out(tui, unibi_flash_screen); } -static void tui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, - Integer cterm_fg, Integer cterm_bg) +void tui_default_colors_set(TUIData *tui, Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) { - TUIData *data = ui->data; - - data->clear_attrs.rgb_fg_color = (int)rgb_fg; - data->clear_attrs.rgb_bg_color = (int)rgb_bg; - data->clear_attrs.rgb_sp_color = (int)rgb_sp; - data->clear_attrs.cterm_fg_color = (int)cterm_fg; - data->clear_attrs.cterm_bg_color = (int)cterm_bg; + tui->clear_attrs.rgb_fg_color = (int)rgb_fg; + tui->clear_attrs.rgb_bg_color = (int)rgb_bg; + tui->clear_attrs.rgb_sp_color = (int)rgb_sp; + tui->clear_attrs.cterm_fg_color = (int)cterm_fg; + tui->clear_attrs.cterm_bg_color = (int)cterm_bg; - data->print_attr_id = -1; - invalidate(ui, 0, data->grid.height, 0, data->grid.width); + tui->print_attr_id = -1; + invalidate(tui, 0, tui->grid.height, 0, tui->grid.width); } -static void tui_flush(UI *ui) +void tui_flush(TUIData *tui) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; - size_t nrevents = loop_size(data->loop); + size_t nrevents = loop_size(tui->loop); if (nrevents > TOO_MANY_EVENTS) { WLOG("TUI event-queue flooded (thread_events=%zu); purging", nrevents); // Back-pressure: UI events may accumulate much faster than the terminal @@ -1324,12 +1274,12 @@ static void tui_flush(UI *ui) // wait for the TUI event-queue to drain, and if there are ~millions of // events in the queue, it could take hours. Clearing the queue allows the // UI to recover. #1234 #5396 - loop_purge(data->loop); - tui_busy_stop(ui); // avoid hidden cursor + loop_purge(tui->loop); + tui_busy_stop(tui); // avoid hidden cursor } - while (kv_size(data->invalid_regions)) { - Rect r = kv_pop(data->invalid_regions); + while (kv_size(tui->invalid_regions)) { + Rect r = kv_pop(tui->invalid_regions); assert(r.bot <= grid->height && r.right <= grid->width); for (int row = r.top; row < r.bot; row++) { @@ -1344,155 +1294,175 @@ static void tui_flush(UI *ui) } UGRID_FOREACH_CELL(grid, row, r.left, clear_col, { - print_cell_at_pos(ui, row, curcol, cell, + print_cell_at_pos(tui, row, curcol, cell, curcol < clear_col - 1 && (cell + 1)->data[0] == NUL); }); if (clear_col < r.right) { - clear_region(ui, row, row + 1, clear_col, r.right, clear_attr); + clear_region(tui, row, row + 1, clear_col, r.right, clear_attr); } } } - cursor_goto(ui, data->row, data->col); + cursor_goto(tui, tui->row, tui->col); - flush_buf(ui); + flush_buf(tui); } /// Dumps termcap info to the messages area, if 'verbose' >= 3. -static void show_termcap_event(void **argv) +static void show_verbose_terminfo(TUIData *tui) { - if (p_verbose < 3) { - return; - } - const unibi_term *const ut = argv[0]; + const unibi_term *const ut = tui->ut; if (!ut) { abort(); } - verbose_enter(); - // XXX: (future) if unibi_term is modified (e.g. after a terminal - // query-response) this is a race condition. - terminfo_info_msg(ut); - verbose_leave(); - verbose_stop(); // flush now + + Array chunks = ARRAY_DICT_INIT; + Array title = ARRAY_DICT_INIT; + ADD(title, STRING_OBJ(cstr_to_string("\n\n--- Terminal info --- {{{\n"))); + ADD(title, STRING_OBJ(cstr_to_string("Title"))); + ADD(chunks, ARRAY_OBJ(title)); + Array info = ARRAY_DICT_INIT; + String str = terminfo_info_msg(ut, tui->term); + ADD(info, STRING_OBJ(str)); + ADD(chunks, ARRAY_OBJ(info)); + Array end_fold = ARRAY_DICT_INIT; + ADD(end_fold, STRING_OBJ(cstr_to_string("}}}\n"))); + ADD(end_fold, STRING_OBJ(cstr_to_string("Title"))); + ADD(chunks, ARRAY_OBJ(end_fold)); + + Array args = ARRAY_DICT_INIT; + ADD(args, ARRAY_OBJ(chunks)); + ADD(args, BOOLEAN_OBJ(true)); // history + Dictionary opts = ARRAY_DICT_INIT; + PUT(opts, "verbose", BOOLEAN_OBJ(true)); + ADD(args, DICTIONARY_OBJ(opts)); + rpc_send_event(ui_client_channel_id, "nvim_echo", args); + api_free_array(args); } #ifdef UNIX static void suspend_event(void **argv) { - UI *ui = argv[0]; - TUIData *data = ui->data; - bool enable_mouse = data->mouse_enabled; - tui_terminal_stop(ui); - data->cont_received = false; - stream_set_blocking(input_global_fd(), true); // normalize stream (#2598) - signal_stop(); + TUIData *tui = argv[0]; + bool enable_mouse = tui->mouse_enabled; + tui_terminal_stop(tui); + stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598) + kill(0, SIGTSTP); - signal_start(); - while (!data->cont_received) { - // poll the event loop until SIGCONT is received - loop_poll_events(data->loop, -1); - } - tui_terminal_start(ui); - tui_terminal_after_startup(ui); + + tui_terminal_start(tui); + tui_terminal_after_startup(tui); if (enable_mouse) { - tui_mouse_on(ui); + tui_mouse_on(tui); } - stream_set_blocking(input_global_fd(), false); // libuv expects this - // resume the main thread - CONTINUE(data->bridge); + stream_set_blocking(tui->input.in_fd, false); // libuv expects this } #endif -static void tui_suspend(UI *ui) +void tui_suspend(TUIData *tui) { +// on a non-UNIX system, this is a no-op #ifdef UNIX - TUIData *data = ui->data; // kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT // before continuing. This is done in another callback to avoid // loop_poll_events recursion - multiqueue_put_event(data->loop->fast_events, - event_create(suspend_event, 1, ui)); + multiqueue_put_event(resize_events, + event_create(suspend_event, 1, tui)); #endif } -static void tui_set_title(UI *ui, String title) +void tui_set_title(TUIData *tui, String title) { - TUIData *data = ui->data; - if (!(title.data && unibi_get_str(data->ut, unibi_to_status_line) - && unibi_get_str(data->ut, unibi_from_status_line))) { + if (!(title.data && unibi_get_str(tui->ut, unibi_to_status_line) + && unibi_get_str(tui->ut, unibi_from_status_line))) { return; } - unibi_out(ui, unibi_to_status_line); - out(ui, title.data, title.size); - unibi_out(ui, unibi_from_status_line); + unibi_out(tui, unibi_to_status_line); + out(tui, title.data, title.size); + unibi_out(tui, unibi_from_status_line); } -static void tui_set_icon(UI *ui, String icon) +void tui_set_icon(TUIData *tui, String icon) {} -static void tui_screenshot(UI *ui, String path) +void tui_screenshot(TUIData *tui, String path) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; - flush_buf(ui); + UGrid *grid = &tui->grid; + flush_buf(tui); grid->row = 0; grid->col = 0; FILE *f = fopen(path.data, "w"); - data->screenshot = f; + tui->screenshot = f; fprintf(f, "%d,%d\n", grid->height, grid->width); - unibi_out(ui, unibi_clear_screen); + unibi_out(tui, unibi_clear_screen); for (int i = 0; i < grid->height; i++) { - cursor_goto(ui, i, 0); + cursor_goto(tui, i, 0); for (int j = 0; j < grid->width; j++) { - print_cell(ui, &grid->cells[i][j]); + print_cell(tui, &grid->cells[i][j]); } } - flush_buf(ui); - data->screenshot = NULL; + flush_buf(tui); + tui->screenshot = NULL; fclose(f); } -static void tui_option_set(UI *ui, String name, Object value) +void tui_option_set(TUIData *tui, String name, Object value) { - TUIData *data = ui->data; - if (strequal(name.data, "termguicolors")) { - ui->rgb = value.data.boolean; + if (strequal(name.data, "mousemoveevent")) { + if (tui->mouse_move_enabled != value.data.boolean) { + if (tui->mouse_enabled) { + tui_mouse_off(tui); + tui->mouse_move_enabled = value.data.boolean; + tui_mouse_on(tui); + } else { + tui->mouse_move_enabled = value.data.boolean; + } + } + } else if (strequal(name.data, "termguicolors")) { + tui->rgb = value.data.boolean; + tui->print_attr_id = -1; + invalidate(tui, 0, tui->grid.height, 0, tui->grid.width); - data->print_attr_id = -1; - invalidate(ui, 0, data->grid.height, 0, data->grid.width); + if (ui_client_channel_id) { + MAXSIZE_TEMP_ARRAY(args, 2); + ADD_C(args, STRING_OBJ(cstr_as_string("rgb"))); + ADD_C(args, BOOLEAN_OBJ(value.data.boolean)); + rpc_send_event(ui_client_channel_id, "nvim_ui_set_option", args); + } } else if (strequal(name.data, "ttimeout")) { - data->input.ttimeout = value.data.boolean; + tui->input.ttimeout = value.data.boolean; } else if (strequal(name.data, "ttimeoutlen")) { - data->input.ttimeoutlen = (long)value.data.integer; + tui->input.ttimeoutlen = (long)value.data.integer; + } else if (strequal(name.data, "verbose")) { + tui->verbose = value.data.integer; } } -static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, Integer endcol, - Integer clearcol, Integer clearattr, LineFlags flags, const schar_T *chunk, - const sattr_T *attrs) +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) { - TUIData *data = ui->data; - UGrid *grid = &data->grid; + UGrid *grid = &tui->grid; for (Integer c = startcol; c < endcol; c++) { memcpy(grid->cells[linerow][c].data, chunk[c - startcol], sizeof(schar_T)); - assert((size_t)attrs[c - startcol] < kv_size(data->attrs)); + assert((size_t)attrs[c - startcol] < kv_size(tui->attrs)); grid->cells[linerow][c].attr = attrs[c - startcol]; } UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, { - print_cell_at_pos(ui, (int)linerow, curcol, cell, + print_cell_at_pos(tui, (int)linerow, curcol, cell, curcol < endcol - 1 && (cell + 1)->data[0] == NUL); }); if (clearcol > endcol) { ugrid_clear_chunk(grid, (int)linerow, (int)endcol, (int)clearcol, (sattr_T)clearattr); - clear_region(ui, (int)linerow, (int)linerow + 1, (int)endcol, (int)clearcol, + clear_region(tui, (int)linerow, (int)linerow + 1, (int)endcol, (int)clearcol, (int)clearattr); } - if (flags & kLineFlagWrap && ui->width == grid->width + if (flags & kLineFlagWrap && tui->width == grid->width && linerow + 1 < grid->height) { // Only do line wrapping if the grid width is equal to the terminal // width and the line continuation is within the grid. @@ -1500,23 +1470,22 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol, I if (endcol != grid->width) { // Print the last char of the row, if we haven't already done so. int size = grid->cells[linerow][grid->width - 1].data[0] == NUL ? 2 : 1; - print_cell_at_pos(ui, (int)linerow, grid->width - size, + print_cell_at_pos(tui, (int)linerow, grid->width - size, &grid->cells[linerow][grid->width - size], size == 2); } // Wrap the cursor over to the next line. The next line will be // printed immediately without an intervening newline. - final_column_wrap(ui); + final_column_wrap(tui); } } -static void invalidate(UI *ui, int top, int bot, int left, int right) +static void invalidate(TUIData *tui, int top, int bot, int left, int right) { - TUIData *data = ui->data; Rect *intersects = NULL; - for (size_t i = 0; i < kv_size(data->invalid_regions); i++) { - Rect *r = &kv_A(data->invalid_regions, i); + for (size_t i = 0; i < kv_size(tui->invalid_regions); i++) { + Rect *r = &kv_A(tui->invalid_regions, i); // adjacent regions are treated as overlapping if (!(top > r->bot || bot < r->top) && !(left > r->right || right < r->left)) { @@ -1534,20 +1503,19 @@ static void invalidate(UI *ui, int top, int bot, int left, int right) intersects->right = MAX(right, intersects->right); } else { // Else just add a new entry; - kv_push(data->invalid_regions, ((Rect) { top, bot, left, right })); + kv_push(tui->invalid_regions, ((Rect) { top, bot, left, right })); } } /// Tries to get the user's wanted dimensions (columns and rows) for the entire /// application (i.e., the host terminal). -static void tui_guess_size(UI *ui) +void tui_guess_size(TUIData *tui) { - TUIData *data = ui->data; int width = 0, height = 0; // 1 - try from a system call(ioctl/TIOCGWINSZ on unix) - if (data->out_isatty - && !uv_tty_get_winsize(&data->output_handle.tty, &width, &height)) { + if (tui->out_isatty + && !uv_tty_get_winsize(&tui->output_handle.tty, &width, &height)) { goto end; } @@ -1562,8 +1530,8 @@ static void tui_guess_size(UI *ui) } // 3 - read from terminfo if available - height = unibi_get_num(data->ut, unibi_lines); - width = unibi_get_num(data->ut, unibi_columns); + height = unibi_get_num(tui->ut, unibi_lines); + width = unibi_get_num(tui->ut, unibi_columns); end: if (width <= 0 || height <= 0) { @@ -1572,45 +1540,49 @@ static void tui_guess_size(UI *ui) height = DFLT_ROWS; } - data->bridge->bridge.width = ui->width = width; - data->bridge->bridge.height = ui->height = height; + if (tui->width != width || tui->height != height) { + tui->width = width; + tui->height = height; + + ui_client_set_size(width, height); + } } -static void unibi_goto(UI *ui, int row, int col) +static void unibi_goto(TUIData *tui, int row, int col) { - TUIData *data = ui->data; - UNIBI_SET_NUM_VAR(data->params[0], row); - UNIBI_SET_NUM_VAR(data->params[1], col); - unibi_out(ui, unibi_cursor_address); + UNIBI_SET_NUM_VAR(tui->params[0], row); + UNIBI_SET_NUM_VAR(tui->params[1], col); + unibi_out(tui, unibi_cursor_address); } #define UNIBI_OUT(fn) \ do { \ - TUIData *data = ui->data; \ const char *str = NULL; \ if (unibi_index >= 0) { \ - str = fn(data->ut, (unsigned)unibi_index); \ + str = fn(tui->ut, (unsigned)unibi_index); \ } \ if (str) { \ unibi_var_t vars[26 + 26]; \ - size_t orig_pos = data->bufpos; \ + unibi_var_t params[9]; \ + size_t orig_pos = tui->bufpos; \ memset(&vars, 0, sizeof(vars)); \ - data->cork = true; \ + tui->cork = true; \ retry: \ - unibi_format(vars, vars + 26, str, data->params, out, ui, NULL, NULL); \ - if (data->overflow) { \ - data->bufpos = orig_pos; \ - flush_buf(ui); \ + memcpy(params, tui->params, sizeof(params)); \ + unibi_format(vars, vars + 26, str, params, out, tui, pad, tui); \ + if (tui->overflow) { \ + tui->bufpos = orig_pos; \ + flush_buf(tui); \ goto retry; \ } \ - data->cork = false; \ + tui->cork = false; \ } \ } while (0) -static void unibi_out(UI *ui, int unibi_index) +static void unibi_out(TUIData *tui, int unibi_index) { UNIBI_OUT(unibi_get_str); } -static void unibi_out_ext(UI *ui, int unibi_index) +static void unibi_out_ext(TUIData *tui, int unibi_index) { UNIBI_OUT(unibi_get_ext_str); } @@ -1618,25 +1590,47 @@ static void unibi_out_ext(UI *ui, int unibi_index) static void out(void *ctx, const char *str, size_t len) { - UI *ui = ctx; - TUIData *data = ui->data; - size_t available = sizeof(data->buf) - data->bufpos; + TUIData *tui = ctx; + size_t available = sizeof(tui->buf) - tui->bufpos; - if (data->cork && data->overflow) { + if (tui->cork && tui->overflow) { return; } if (len > available) { - if (data->cork) { - data->overflow = true; + if (tui->cork) { + // Called by unibi_format(): avoid flush_buf() halfway an escape sequence. + tui->overflow = true; return; - } else { - flush_buf(ui); } + flush_buf(tui); + } + + memcpy(tui->buf + tui->bufpos, str, len); + tui->bufpos += len; +} + +/// Called by unibi_format() for padding instructions. +/// The following parameter descriptions are extracted from unibi_format(3) and terminfo(5). +/// +/// @param ctx the same as `ctx2` passed to unibi_format() +/// @param delay the delay in tenths of milliseconds +/// @param scale padding is proportional to the number of lines affected +/// @param force padding is mandatory +static void pad(void *ctx, size_t delay, int scale FUNC_ATTR_UNUSED, int force) +{ + if (!force) { + return; + } + + TUIData *tui = ctx; + + if (tui->overflow) { + return; } - memcpy(data->buf + data->bufpos, str, len); - data->bufpos += len; + flush_buf(tui); + uv_sleep((unsigned int)(delay/10)); } static void unibi_set_if_empty(unibi_term *ut, enum unibi_string str, const char *val) @@ -1673,10 +1667,10 @@ static int unibi_find_ext_bool(unibi_term *ut, const char *name) /// Patches the terminfo records after loading from system or built-in db. /// Several entries in terminfo are known to be deficient or outright wrong; /// and several terminal emulators falsely announce incorrect terminal types. -static void patch_terminfo_bugs(TUIData *data, const char *term, const char *colorterm, +static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colorterm, long vte_version, long konsolev, bool iterm_env, bool nsterm) { - unibi_term *ut = data->ut; + unibi_term *ut = tui->ut; const char *xterm_version = os_getenv("XTERM_VERSION"); #if 0 // We don't need to identify this specifically, for now. bool roxterm = !!os_getenv("ROXTERM_ID"); @@ -1791,7 +1785,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col } } -#ifdef WIN32 +#ifdef MSWIN // XXX: workaround libuv implicit LF => CRLF conversion. #10558 unibi_set_str(ut, unibi_cursor_down, "\x1b[B"); #endif @@ -1873,14 +1867,14 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col #define XTERM_SETAB_16 \ "\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m" - data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", - "\x1b]11;?\x07"); + tui->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", + "\x1b]11;?\x07"); // Query the terminal to see if it supports CSI u key encoding by writing CSI // ? u followed by a request for the primary device attributes (CSI c) // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol - data->unibi_ext.get_extkeys = (int)unibi_add_ext_str(ut, "ext.get_extkeys", - "\x1b[?u\x1b[c"); + tui->unibi_ext.get_extkeys = (int)unibi_add_ext_str(ut, "ext.get_extkeys", + "\x1b[?u\x1b[c"); // Terminals with 256-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 256) { @@ -1911,14 +1905,14 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col // Blacklist of terminals that cannot be trusted to report DECSCUSR support. if (!(st || (vte_version != 0 && vte_version < 3900) || konsolev)) { - data->unibi_ext.reset_cursor_style = unibi_find_ext_str(ut, "Se"); - data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss"); + tui->unibi_ext.reset_cursor_style = unibi_find_ext_str(ut, "Se"); + tui->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss"); } // Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So // adding them to terminal types, that have such control sequences but lack // the correct terminfo entries, is a fixup, not an augmentation. - if (-1 == data->unibi_ext.set_cursor_style) { + if (-1 == tui->unibi_ext.set_cursor_style) { // DECSCUSR (cursor shape) is widely supported. // https://github.com/gnachman/iTerm2/pull/92 if ((!bsdvt && (!konsolev || konsolev >= 180770)) @@ -1944,59 +1938,59 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col // Example: console-terminal-emulator from the nosh toolset. || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) { - data->unibi_ext.set_cursor_style = + tui->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q"); - if (-1 == data->unibi_ext.reset_cursor_style) { - data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", - ""); + if (-1 == tui->unibi_ext.reset_cursor_style) { + tui->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", + ""); } - unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, + unibi_set_ext_str(ut, (size_t)tui->unibi_ext.reset_cursor_style, "\x1b[ q"); } else if (linuxvt) { // Linux uses an idiosyncratic escape code to set the cursor shape and // does not support DECSCUSR. // See http://linuxgazette.net/137/anonymous.html for more info - data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", - "\x1b[?" - "%?" - // The parameter passed to Ss is the DECSCUSR parameter, so the - // terminal capability has to translate into the Linux idiosyncratic - // parameter. - // - // linuxvt only supports block and underline. It is also only - // possible to have a steady block (no steady underline) - "%p1%{2}%<" "%t%{8}" // blink block - "%e%p1%{2}%=" "%t%{112}" // steady block - "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half block) - "%e%p1%{4}%=" "%t%{4}" // steady underline - "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline) - "%e%p1%{6}%=" "%t%{2}" // steady bar - "%e%{0}" // anything else - "%;" "%dc"); - if (-1 == data->unibi_ext.reset_cursor_style) { - data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", - ""); + tui->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", + "\x1b[?" + "%?" + // The parameter passed to Ss is the DECSCUSR parameter, so the + // terminal capability has to translate into the Linux idiosyncratic + // parameter. + // + // linuxvt only supports block and underline. It is also only + // possible to have a steady block (no steady underline) + "%p1%{2}%<" "%t%{8}" // blink block + "%e%p1%{2}%=" "%t%{112}" // steady block + "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half block) + "%e%p1%{4}%=" "%t%{4}" // steady underline + "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline) + "%e%p1%{6}%=" "%t%{2}" // steady bar + "%e%{0}" // anything else + "%;" "%dc"); + if (-1 == tui->unibi_ext.reset_cursor_style) { + tui->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", + ""); } - unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, + unibi_set_ext_str(ut, (size_t)tui->unibi_ext.reset_cursor_style, "\x1b[?c"); } else if (konsolev > 0 && konsolev < 180770) { // Konsole before version 18.07.70: set up a nonce profile. This has // side effects on temporary font resizing. #6798 - data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", - TMUX_WRAP(tmux, - "\x1b]50;CursorShape=%?" - "%p1%{3}%<" "%t%{0}" // block - "%e%p1%{5}%<" "%t%{2}" // underline - "%e%{1}" // everything else is bar - "%;%d;BlinkingCursorEnabled=%?" - "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude zero as special, - "%e%p1%{1}%&" // in all other cases we can treat bit #0 as a flag. - "%;%d\x07")); - if (-1 == data->unibi_ext.reset_cursor_style) { - data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", - ""); + tui->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", + TMUX_WRAP(tmux, + "\x1b]50;CursorShape=%?" + "%p1%{3}%<" "%t%{0}" // block + "%e%p1%{5}%<" "%t%{2}" // underline + "%e%{1}" // everything else is bar + "%;%d;BlinkingCursorEnabled=%?" + "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude zero as special, + "%e%p1%{1}%&" // in all other cases we can treat bit #0 as a flag. + "%;%d\x07")); + if (-1 == tui->unibi_ext.reset_cursor_style) { + tui->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se", + ""); } - unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style, + unibi_set_ext_str(ut, (size_t)tui->unibi_ext.reset_cursor_style, "\x1b]50;\x07"); } } @@ -2004,10 +1998,10 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col /// This adds stuff that is not in standard terminfo as extended unibilium /// capabilities. -static void augment_terminfo(TUIData *data, const char *term, long vte_version, long konsolev, +static void augment_terminfo(TUIData *tui, const char *term, long vte_version, long konsolev, bool iterm_env, bool nsterm) { - unibi_term *ut = data->ut; + unibi_term *ut = tui->ut; bool xterm = terminfo_is_term_family(term, "xterm") // Treat Terminal.app as generic xterm-like, for now. || nsterm; @@ -2037,24 +2031,29 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, || konsolev // per commentary in VT102Emulation.cpp || teraterm // per TeraTerm "Supported Control Functions" doco || rxvt) { // per command.C - data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, - "ext.resize_screen", - "\x1b[8;%p1%d;%p2%dt"); + tui->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, + "ext.resize_screen", + "\x1b[8;%p1%d;%p2%dt"); } if (putty || xterm || hterm || rxvt) { - data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, - "ext.reset_scroll_region", - "\x1b[r"); + tui->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, + "ext.reset_scroll_region", + "\x1b[r"); } // terminfo describes strikethrough modes as rmxx/smxx with respect // to the ECMA-48 strikeout/crossed-out attributes. - data->unibi_ext.enter_strikethrough_mode = unibi_find_ext_str(ut, "smxx"); + tui->unibi_ext.enter_strikethrough_mode = unibi_find_ext_str(ut, "smxx"); + + // It should be pretty safe to always enable this, as terminals will ignore + // unrecognised SGR numbers. + tui->unibi_ext.enter_altfont_mode = (int)unibi_add_ext_str(ut, "ext.enter_altfont_mode", + "\x1b[11m"); // Dickey ncurses terminfo does not include the setrgbf and setrgbb // capabilities, proposed by Rüdiger Sonderfeld on 2013-10-15. Adding // them here when terminfo lacks them is an augmentation, not a fixup. - // https://gist.github.com/XVilka/8346728 + // https://github.com/termstandard/colors // At this time (2017-07-12) it seems like all terminals that support rgb // color codes can use semicolons in the terminal code and be fine. @@ -2069,106 +2068,119 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, // per http://invisible-island.net/xterm/xterm.log.html#xterm_282 || true_xterm); - data->unibi_ext.set_rgb_foreground = unibi_find_ext_str(ut, "setrgbf"); - if (-1 == data->unibi_ext.set_rgb_foreground) { + tui->unibi_ext.set_rgb_foreground = unibi_find_ext_str(ut, "setrgbf"); + if (-1 == tui->unibi_ext.set_rgb_foreground) { if (has_colon_rgb) { - data->unibi_ext.set_rgb_foreground = (int)unibi_add_ext_str(ut, "setrgbf", - "\x1b[38:2:%p1%d:%p2%d:%p3%dm"); + tui->unibi_ext.set_rgb_foreground = (int)unibi_add_ext_str(ut, "setrgbf", + "\x1b[38:2:%p1%d:%p2%d:%p3%dm"); } else { - data->unibi_ext.set_rgb_foreground = (int)unibi_add_ext_str(ut, "setrgbf", - "\x1b[38;2;%p1%d;%p2%d;%p3%dm"); + tui->unibi_ext.set_rgb_foreground = (int)unibi_add_ext_str(ut, "setrgbf", + "\x1b[38;2;%p1%d;%p2%d;%p3%dm"); } } - data->unibi_ext.set_rgb_background = unibi_find_ext_str(ut, "setrgbb"); - if (-1 == data->unibi_ext.set_rgb_background) { + tui->unibi_ext.set_rgb_background = unibi_find_ext_str(ut, "setrgbb"); + if (-1 == tui->unibi_ext.set_rgb_background) { if (has_colon_rgb) { - data->unibi_ext.set_rgb_background = (int)unibi_add_ext_str(ut, "setrgbb", - "\x1b[48:2:%p1%d:%p2%d:%p3%dm"); + tui->unibi_ext.set_rgb_background = (int)unibi_add_ext_str(ut, "setrgbb", + "\x1b[48:2:%p1%d:%p2%d:%p3%dm"); } else { - data->unibi_ext.set_rgb_background = (int)unibi_add_ext_str(ut, "setrgbb", - "\x1b[48;2;%p1%d;%p2%d;%p3%dm"); + tui->unibi_ext.set_rgb_background = (int)unibi_add_ext_str(ut, "setrgbb", + "\x1b[48;2;%p1%d;%p2%d;%p3%dm"); } } - data->unibi_ext.set_cursor_color = unibi_find_ext_str(ut, "Cs"); - if (-1 == data->unibi_ext.set_cursor_color) { + tui->unibi_ext.set_cursor_color = unibi_find_ext_str(ut, "Cs"); + if (-1 == tui->unibi_ext.set_cursor_color) { if (iterm || iterm_pretending_xterm) { // FIXME: Bypassing tmux like this affects the cursor colour globally, in // all panes, which is not particularly desirable. A better approach // would use a tmux control sequence and an extra if(screen) test. - data->unibi_ext.set_cursor_color = + tui->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\")); } else if ((xterm || hterm || rxvt || tmux || alacritty) && (vte_version == 0 || vte_version >= 3900)) { // Supported in urxvt, newer VTE. - data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(ut, "ext.set_cursor_color", - "\033]12;#%p1%06x\007"); + tui->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(ut, "ext.set_cursor_color", + "\033]12;%p1%s\007"); } } - if (-1 != data->unibi_ext.set_cursor_color) { - data->unibi_ext.reset_cursor_color = unibi_find_ext_str(ut, "Cr"); - if (-1 == data->unibi_ext.reset_cursor_color) { - data->unibi_ext.reset_cursor_color = (int)unibi_add_ext_str(ut, "ext.reset_cursor_color", - "\x1b]112\x07"); + if (-1 != tui->unibi_ext.set_cursor_color) { + // Some terminals supporting cursor color changing specify their Cs + // capability to take a string parameter. Others take a numeric parameter. + // If and only if the format string contains `%s` we assume a string + // parameter. #20628 + const char *set_cursor_color = + unibi_get_ext_str(ut, (unsigned)tui->unibi_ext.set_cursor_color); + if (set_cursor_color) { + tui->set_cursor_color_as_str = strstr(set_cursor_color, "%s") != NULL; + } + + tui->unibi_ext.reset_cursor_color = unibi_find_ext_str(ut, "Cr"); + if (-1 == tui->unibi_ext.reset_cursor_color) { + tui->unibi_ext.reset_cursor_color = (int)unibi_add_ext_str(ut, "ext.reset_cursor_color", + "\x1b]112\x07"); } } - data->unibi_ext.save_title = (int)unibi_add_ext_str(ut, "ext.save_title", "\x1b[22;0t"); - data->unibi_ext.restore_title = (int)unibi_add_ext_str(ut, "ext.restore_title", "\x1b[23;0t"); + tui->unibi_ext.save_title = (int)unibi_add_ext_str(ut, "ext.save_title", "\x1b[22;0t"); + tui->unibi_ext.restore_title = (int)unibi_add_ext_str(ut, "ext.restore_title", "\x1b[23;0t"); /// Terminals usually ignore unrecognized private modes, and there is no /// known ambiguity with these. So we just set them unconditionally. - data->unibi_ext.enable_lr_margin = + tui->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(ut, "ext.enable_lr_margin", "\x1b[?69h"); - data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, "ext.disable_lr_margin", - "\x1b[?69l"); - data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, "ext.enable_bpaste", - "\x1b[?2004h"); - data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, "ext.disable_bpaste", - "\x1b[?2004l"); + tui->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, "ext.disable_lr_margin", + "\x1b[?69l"); + tui->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, "ext.enable_bpaste", + "\x1b[?2004h"); + tui->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, "ext.disable_bpaste", + "\x1b[?2004l"); // For urxvt send BOTH xterm and old urxvt sequences. #8695 - data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.enable_focus", - rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h"); - data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.disable_focus", - rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l"); - data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, "ext.enable_mouse", - "\x1b[?1002h\x1b[?1006h"); - data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, "ext.disable_mouse", - "\x1b[?1002l\x1b[?1006l"); + tui->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.enable_focus", + rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h"); + tui->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.disable_focus", + rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l"); + tui->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, "ext.enable_mouse", + "\x1b[?1002h\x1b[?1006h"); + tui->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, "ext.disable_mouse", + "\x1b[?1002l\x1b[?1006l"); + tui->unibi_ext.enable_mouse_move = (int)unibi_add_ext_str(ut, "ext.enable_mouse_move", + "\x1b[?1003h"); + tui->unibi_ext.disable_mouse_move = (int)unibi_add_ext_str(ut, "ext.disable_mouse_move", + "\x1b[?1003l"); // Extended underline. // terminfo will have Smulx for this (but no support for colors yet). - data->unibi_ext.set_underline_style = unibi_find_ext_str(ut, "Smulx"); - if (data->unibi_ext.set_underline_style == -1) { + tui->unibi_ext.set_underline_style = unibi_find_ext_str(ut, "Smulx"); + if (tui->unibi_ext.set_underline_style == -1) { int ext_bool_Su = unibi_find_ext_bool(ut, "Su"); // used by kitty - if (vte_version >= 5102 + if (vte_version >= 5102 || konsolev >= 221170 || (ext_bool_Su != -1 && unibi_get_ext_bool(ut, (size_t)ext_bool_Su))) { - data->unibi_ext.set_underline_style = (int)unibi_add_ext_str(ut, "ext.set_underline_style", - "\x1b[4:%p1%dm"); + tui->unibi_ext.set_underline_style = (int)unibi_add_ext_str(ut, "ext.set_underline_style", + "\x1b[4:%p1%dm"); } } - if (data->unibi_ext.set_underline_style != -1) { + if (tui->unibi_ext.set_underline_style != -1) { // Only support colon syntax. #9270 - data->unibi_ext.set_underline_color = (int)unibi_add_ext_str(ut, "ext.set_underline_color", - "\x1b[58:2::%p1%d:%p2%d:%p3%dm"); + tui->unibi_ext.set_underline_color = (int)unibi_add_ext_str(ut, "ext.set_underline_color", + "\x1b[58:2::%p1%d:%p2%d:%p3%dm"); } if (!kitty && (vte_version == 0 || vte_version >= 5400)) { // Fallback to Xterm's modifyOtherKeys if terminal does not support CSI u - data->input.extkeys_type = kExtkeysXterm; + tui->input.extkeys_type = kExtkeysXterm; } } -static void flush_buf(UI *ui) +static void flush_buf(TUIData *tui) { uv_write_t req; uv_buf_t bufs[3]; uv_buf_t *bufp = &bufs[0]; - TUIData *data = ui->data; // The content of the output for each condition is shown in the following - // table. Therefore, if data->bufpos == 0 and N/A or invis + norm, there is + // table. Therefore, if tui->bufpos == 0 and N/A or invis + norm, there is // no need to output it. // // | is_invisible | !is_invisible @@ -2180,73 +2192,72 @@ static void flush_buf(UI *ui) // | !want_invisible | norm | invis + norm // ------+-----------------+--------------+--------------- // - if (data->bufpos <= 0 - && ((data->is_invisible && data->busy) - || (data->is_invisible && !data->busy && data->want_invisible) - || (!data->is_invisible && !data->busy && !data->want_invisible))) { + if (tui->bufpos <= 0 + && ((tui->is_invisible && tui->busy) + || (tui->is_invisible && !tui->busy && tui->want_invisible) + || (!tui->is_invisible && !tui->busy && !tui->want_invisible))) { return; } - if (!data->is_invisible) { + if (!tui->is_invisible) { // cursor is visible. Write a "cursor invisible" command before writing the // buffer. - bufp->base = data->invis; - bufp->len = UV_BUF_LEN(data->invislen); + bufp->base = tui->invis; + bufp->len = UV_BUF_LEN(tui->invislen); bufp++; - data->is_invisible = true; + tui->is_invisible = true; } - if (data->bufpos > 0) { - bufp->base = data->buf; - bufp->len = UV_BUF_LEN(data->bufpos); + if (tui->bufpos > 0) { + bufp->base = tui->buf; + bufp->len = UV_BUF_LEN(tui->bufpos); bufp++; } - if (!data->busy) { - assert(data->is_invisible); + if (!tui->busy) { + assert(tui->is_invisible); // not busy and the cursor is invisible. Write a "cursor normal" command // after writing the buffer. - if (!data->want_invisible) { - bufp->base = data->norm; - bufp->len = UV_BUF_LEN(data->normlen); + if (!tui->want_invisible) { + bufp->base = tui->norm; + bufp->len = UV_BUF_LEN(tui->normlen); bufp++; - data->is_invisible = false; + tui->is_invisible = false; } } - if (data->screenshot) { + if (tui->screenshot) { for (size_t i = 0; i < (size_t)(bufp - bufs); i++) { - fwrite(bufs[i].base, bufs[i].len, 1, data->screenshot); + fwrite(bufs[i].base, bufs[i].len, 1, tui->screenshot); } } else { - int ret = uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), + int ret = uv_write(&req, STRUCT_CAST(uv_stream_t, &tui->output_handle), bufs, (unsigned)(bufp - bufs), NULL); if (ret) { ELOG("uv_write failed: %s", uv_strerror(ret)); } - uv_run(&data->write_loop, UV_RUN_DEFAULT); + uv_run(&tui->write_loop, UV_RUN_DEFAULT); } - data->bufpos = 0; - data->overflow = false; + tui->bufpos = 0; + tui->overflow = false; } -#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 /// Try to get "kbs" code from stty because "the terminfo kbs entry is extremely /// unreliable." (Vim, Bash, and tmux also do this.) /// /// @see tmux/tty-keys.c fe4e9470bb504357d073320f5d305b22663ee3fd /// @see https://bugzilla.redhat.com/show_bug.cgi?id=142659 -static const char *tui_get_stty_erase(void) +static const char *tui_get_stty_erase(int fd) { static char stty_erase[2] = { 0 }; -# if defined(HAVE_TERMIOS_H) +#if defined(HAVE_TERMIOS_H) struct termios t; - if (tcgetattr(input_global_fd(), &t) != -1) { + if (tcgetattr(fd, &t) != -1) { stty_erase[0] = (char)t.c_cc[VERASE]; stty_erase[1] = '\0'; DLOG("stty/termios:erase=%s", stty_erase); } -# endif +#endif return stty_erase; } @@ -2254,9 +2265,10 @@ static const char *tui_get_stty_erase(void) /// @see TermInput.tk_ti_hook_fn static const char *tui_tk_ti_getstr(const char *name, const char *value, void *data) { + TermInput *input = data; static const char *stty_erase = NULL; if (stty_erase == NULL) { - stty_erase = tui_get_stty_erase(); + stty_erase = tui_get_stty_erase(input->in_fd); } if (strequal(name, "key_backspace")) { @@ -2280,4 +2292,3 @@ static const char *tui_tk_ti_getstr(const char *name, const char *value, void *d return value; } -#endif diff --git a/src/nvim/types.h b/src/nvim/types.h index 477102276c..d90c623955 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -22,7 +22,16 @@ typedef int handle_T; // absent callback etc. typedef int LuaRef; -typedef void (*FunPtr)(void); +/// Type used for VimL VAR_FLOAT values +typedef double float_T; + +typedef struct MsgpackRpcRequestHandler MsgpackRpcRequestHandler; + +typedef union { + float_T (*float_func)(float_T); + const MsgpackRpcRequestHandler *api_handler; + void *nullptr; +} EvalFuncData; typedef handle_T NS; @@ -36,6 +45,9 @@ typedef enum { kTrue = 1, } TriState; +#define TRISTATE_TO_BOOL(val, \ + default) ((val) == kTrue ? true : ((val) == kFalse ? false : (default))) + typedef struct Decoration Decoration; #endif // NVIM_TYPES_H diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c index d96da3f2bb..be1746983c 100644 --- a/src/nvim/ugrid.c +++ b/src/nvim/ugrid.c @@ -2,14 +2,10 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <limits.h> -#include <stdbool.h> -#include <stdio.h> +#include <string.h> -#include "nvim/assert.h" +#include "nvim/memory.h" #include "nvim/ugrid.h" -#include "nvim/ui.h" -#include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ugrid.c.generated.h" diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h index ae11153c61..a85a6fb4a8 100644 --- a/src/nvim/ugrid.h +++ b/src/nvim/ugrid.h @@ -2,8 +2,12 @@ #define NVIM_UGRID_H #include "nvim/globals.h" +#include "nvim/grid_defs.h" #include "nvim/ui.h" +struct ucell; +struct ugrid; + typedef struct ucell UCell; typedef struct ugrid UGrid; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index da671a3ad1..9f1cb87eb0 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -2,47 +2,40 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> -#include <inttypes.h> #include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> #include <string.h> +#include "klib/kvec.h" +#include "nvim/api/private/helpers.h" +#include "nvim/api/ui.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" -#include "nvim/charset.h" -#include "nvim/cursor.h" +#include "nvim/buffer_defs.h" #include "nvim/cursor_shape.h" -#include "nvim/diff.h" #include "nvim/drawscreen.h" -#include "nvim/event/loop.h" #include "nvim/ex_getln.h" -#include "nvim/fold.h" -#include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" +#include "nvim/highlight_defs.h" #include "nvim/log.h" -#include "nvim/main.h" -#include "nvim/mbyte.h" +#include "nvim/lua/executor.h" +#include "nvim/map.h" #include "nvim/memory.h" -#include "nvim/move.h" -#include "nvim/msgpack_rpc/channel.h" -#include "nvim/normal.h" +#include "nvim/message.h" #include "nvim/option.h" -#include "nvim/os/input.h" -#include "nvim/os/signal.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" -#include "nvim/popupmenu.h" +#include "nvim/strings.h" #include "nvim/ui.h" +#include "nvim/ui_client.h" #include "nvim/ui_compositor.h" #include "nvim/vim.h" #include "nvim/window.h" -#ifdef FEAT_TUI -# include "nvim/tui/tui.h" -#else -# include "nvim/msgpack_rpc/server.h" -#endif -#include "nvim/api/private/helpers.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui.c.generated.h" @@ -61,6 +54,9 @@ static bool pending_mode_info_update = false; static bool pending_mode_update = false; static handle_T cursor_grid_handle = DEFAULT_GRID_HANDLE; +static PMap(uint32_t) ui_event_cbs = MAP_INIT; +bool ui_cb_ext[kUIExtCount]; ///< Internalized UI capabilities. + static bool has_mouse = false; static int pending_has_mouse = -1; @@ -102,8 +98,8 @@ static char uilog_last_event[1024] = { 0 }; bool any_call = false; \ for (size_t i = 0; i < ui_count; i++) { \ UI *ui = uis[i]; \ - if (ui->funname && (cond)) { \ - ui->funname(__VA_ARGS__); \ + if ((cond)) { \ + remote_ui_##funname(__VA_ARGS__); \ any_call = true; \ } \ } \ @@ -116,10 +112,6 @@ static char uilog_last_event[1024] = { 0 }; # include "ui_events_call.generated.h" #endif -#ifndef EXITFREE -# undef entered_free_all_mem -#endif - void ui_init(void) { default_grid.handle = 1; @@ -128,36 +120,25 @@ void ui_init(void) kv_ensure_space(call_buf, 16); } +#ifdef EXITFREE void ui_free_all_mem(void) { kv_destroy(call_buf); -} -void ui_builtin_start(void) -{ -#ifdef FEAT_TUI - tui_start(); -#else - fprintf(stderr, "Nvim headless-mode started.\n"); - size_t len; - char **addrs = server_address_list(&len); - if (addrs != NULL) { - fprintf(stderr, "Listening on:\n"); - for (size_t i = 0; i < len; i++) { - fprintf(stderr, "\t%s\n", addrs[i]); - } - xfree(addrs); - } - fprintf(stderr, "Press CTRL+C to exit.\n"); -#endif + UIEventCallback *event_cb; + map_foreach_value(&ui_event_cbs, event_cb, { + free_ui_event_callback(event_cb); + }) + pmap_destroy(uint32_t)(&ui_event_cbs); } +#endif bool ui_rgb_attached(void) { if (!headless_mode && p_tgc) { return true; } - for (size_t i = 1; i < ui_count; i++) { + for (size_t i = 0; i < ui_count; i++) { if (uis[i]->rgb) { return true; } @@ -168,7 +149,7 @@ bool ui_rgb_attached(void) /// Returns true if any UI requested `override=true`. bool ui_override(void) { - for (size_t i = 1; i < ui_count; i++) { + for (size_t i = 0; i < ui_count; i++) { if (uis[i]->override) { return true; } @@ -178,17 +159,21 @@ bool ui_override(void) bool ui_active(void) { - return ui_count > 1; + return ui_count > 0; } void ui_refresh(void) { + if (ui_client_channel_id) { + abort(); + } + if (!ui_active()) { return; } if (updating_screen) { - deferred_refresh_event(NULL); + ui_schedule_refresh(); return; } @@ -212,6 +197,9 @@ void ui_refresh(void) pending_cursor_update = true; for (UIExtension i = 0; (int)i < kUIExtCount; i++) { + if (i < kUIGlobalCount) { + ext_widgets[i] |= ui_cb_ext[i]; + } ui_ext[i] = ext_widgets[i]; if (i < kUIGlobalCount) { ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]), @@ -221,20 +209,13 @@ void ui_refresh(void) ui_default_colors_set(); - if (!ui_client_channel_id) { - int save_p_lz = p_lz; - p_lz = false; // convince redrawing() to return true ... - screen_resize(width, height); - p_lz = save_p_lz; - } else { - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ((int)width)); - ADD(args, INTEGER_OBJ((int)height)); - rpc_send_event(ui_client_channel_id, "nvim_ui_try_resize", args); - } + int save_p_lz = p_lz; + p_lz = false; // convince redrawing() to return true ... + screen_resize(width, height); + p_lz = save_p_lz; if (ext_widgets[kUIMessages]) { - p_ch = 0; + set_option_value("cmdheight", 0L, NULL, 0); command_height(); } ui_mode_info_set(); @@ -246,7 +227,7 @@ void ui_refresh(void) int ui_pum_get_height(void) { int pum_height = 0; - for (size_t i = 1; i < ui_count; i++) { + for (size_t i = 0; i < ui_count; i++) { int ui_pum_height = uis[i]->pum_nlines; if (ui_pum_height) { pum_height = @@ -258,7 +239,7 @@ int ui_pum_get_height(void) bool ui_pum_get_pos(double *pwidth, double *pheight, double *prow, double *pcol) { - for (size_t i = 1; i < ui_count; i++) { + for (size_t i = 0; i < ui_count; i++) { if (!uis[i]->pum_pos) { continue; } @@ -278,10 +259,6 @@ static void ui_refresh_event(void **argv) void ui_schedule_refresh(void) { - loop_schedule_fast(&main_loop, event_create(deferred_refresh_event, 0)); -} -static void deferred_refresh_event(void **argv) -{ multiqueue_put(resize_events, ui_refresh_event, 0); } @@ -312,34 +289,36 @@ void vim_beep(unsigned val) { called_vim_beep = true; - if (emsg_silent == 0) { - if (!((bo_flags & val) || (bo_flags & BO_ALL))) { - static int beeps = 0; - static uint64_t start_time = 0; + if (emsg_silent != 0 || in_assert_fails) { + return; + } - // Only beep up to three times per half a second, - // otherwise a sequence of beeps would freeze Vim. - if (start_time == 0 || os_hrtime() - start_time > 500000000u) { - beeps = 0; - start_time = os_hrtime(); - } - beeps++; - if (beeps <= 3) { - if (p_vb) { - ui_call_visual_bell(); - } else { - ui_call_bell(); - } + if (!((bo_flags & val) || (bo_flags & BO_ALL))) { + static int beeps = 0; + static uint64_t start_time = 0; + + // Only beep up to three times per half a second, + // otherwise a sequence of beeps would freeze Vim. + if (start_time == 0 || os_hrtime() - start_time > 500000000U) { + beeps = 0; + start_time = os_hrtime(); + } + beeps++; + if (beeps <= 3) { + if (p_vb) { + ui_call_visual_bell(); + } else { + ui_call_bell(); } } + } - // When 'debug' contains "beep" produce a message. If we are sourcing - // a script or executing a function give the user a hint where the beep - // comes from. - if (vim_strchr((char *)p_debug, 'e') != NULL) { - msg_source(HL_ATTR(HLF_W)); - msg_attr(_("Beep!"), HL_ATTR(HLF_W)); - } + // When 'debug' contains "beep" produce a message. If we are sourcing + // a script or executing a function give the user a hint where the beep + // comes from. + if (vim_strchr(p_debug, 'e') != NULL) { + msg_source(HL_ATTR(HLF_W)); + msg_attr(_("Beep!"), HL_ATTR(HLF_W)); } } @@ -369,10 +348,7 @@ void ui_attach_impl(UI *ui, uint64_t chanid) } ui_refresh(); - bool is_compositor = (ui == uis[0]); - if (!is_compositor) { - do_autocmd_uienter(chanid, true); - } + do_autocmd_uienter(chanid, true); } void ui_detach_impl(UI *ui, uint64_t chanid) @@ -417,9 +393,9 @@ void ui_set_ext_option(UI *ui, UIExtension ext, bool active) ui_refresh(); return; } - if (ui->option_set && (ui_ext_names[ext][0] != '_' || active)) { - ui->option_set(ui, cstr_as_string((char *)ui_ext_names[ext]), - BOOLEAN_OBJ(active)); + if (ui_ext_names[ext][0] != '_' || active) { + remote_ui_option_set(ui, cstr_as_string((char *)ui_ext_names[ext]), + BOOLEAN_OBJ(active)); } if (ext == kUITermColors) { ui_default_colors_set(); @@ -449,7 +425,7 @@ void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol, MIN(clearcol, (int)grid->cols - 1)); ui_call_flush(); uint64_t wd = (uint64_t)labs(p_wd); - os_microdelay(wd * 1000u, true); + os_microdelay(wd * 1000U, true); pending_cursor_update = true; // restore the cursor later } } @@ -503,11 +479,12 @@ handle_T ui_cursor_grid(void) void ui_flush(void) { + assert(!ui_client_channel_id); if (!ui_active()) { return; } cmdline_ui_flush(); - win_ui_flush(); + win_ui_flush(false); msg_ext_ui_flush(); msg_scroll_flush(); @@ -517,11 +494,10 @@ void ui_flush(void) } if (pending_mode_info_update) { Arena arena = ARENA_EMPTY; - arena_start(&arena, &ui_ext_fixblk); Array style = mode_style_array(&arena); bool enabled = (*p_guicursor != NUL); ui_call_mode_info_set(enabled, style); - arena_mem_free(arena_finish(&arena), &ui_ext_fixblk); + arena_mem_free(arena_finish(&arena)); pending_mode_info_update = false; } if (pending_mode_update && !starting) { @@ -566,7 +542,7 @@ void ui_check_mouse(void) // - 'a' is in 'mouse' and "c" is in MOUSE_A, or // - the current buffer is a help file and 'h' is in 'mouse' and we are in a // normal editing mode (not at hit-return message). - for (char_u *p = p_mouse; *p; p++) { + for (char *p = p_mouse; *p; p++) { switch (*p) { case 'a': if (vim_strchr(MOUSE_A, checkfor) != NULL) { @@ -612,16 +588,10 @@ bool ui_has(UIExtension ext) return ui_ext[ext]; } -/// Returns true if the UI has messages area. -bool ui_has_messages(void) -{ - return p_ch > 0 || ui_has(kUIMessages); -} - Array ui_array(void) { Array all_uis = ARRAY_DICT_INIT; - for (size_t i = 1; i < ui_count; i++) { + for (size_t i = 0; i < ui_count; i++) { UI *ui = uis[i]; Dictionary info = ARRAY_DICT_INIT; PUT(info, "width", INTEGER_OBJ(ui->width)); @@ -633,7 +603,7 @@ Array ui_array(void) PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j])); } } - ui->inspect(ui, &info); + remote_ui_inspect(ui, &info); ADD(all_uis, DICTIONARY_OBJ(info)); } return all_uis; @@ -666,3 +636,75 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) win_set_inner_size(wp, true); } } + +void ui_call_event(char *name, Array args) +{ + UIEventCallback *event_cb; + bool handled = false; + map_foreach_value(&ui_event_cbs, event_cb, { + Error err = ERROR_INIT; + Object res = nlua_call_ref(event_cb->cb, name, args, false, &err); + if (res.type == kObjectTypeBoolean && res.data.boolean == true) { + handled = true; + } + if (ERROR_SET(&err)) { + ELOG("Error while executing ui_comp_event callback: %s", err.msg); + } + api_clear_error(&err); + }) + + if (!handled) { + UI_CALL(true, event, ui, name, args); + } +} + +void ui_cb_update_ext(void) +{ + memset(ui_cb_ext, 0, ARRAY_SIZE(ui_cb_ext)); + + for (size_t i = 0; i < kUIGlobalCount; i++) { + UIEventCallback *event_cb; + + map_foreach_value(&ui_event_cbs, event_cb, { + if (event_cb->ext_widgets[i]) { + ui_cb_ext[i] = true; + break; + } + }) + } +} + +void free_ui_event_callback(UIEventCallback *event_cb) +{ + api_free_luaref(event_cb->cb); + xfree(event_cb); +} + +void ui_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets) +{ + UIEventCallback *event_cb = xcalloc(1, sizeof(UIEventCallback)); + event_cb->cb = cb; + memcpy(event_cb->ext_widgets, ext_widgets, ARRAY_SIZE(event_cb->ext_widgets)); + if (event_cb->ext_widgets[kUIMessages]) { + event_cb->ext_widgets[kUICmdline] = true; + } + + UIEventCallback **item = (UIEventCallback **)pmap_ref(uint32_t)(&ui_event_cbs, ns_id, true); + if (*item) { + free_ui_event_callback(*item); + } + *item = event_cb; + + ui_cb_update_ext(); + ui_refresh(); +} + +void ui_remove_cb(uint32_t ns_id) +{ + if (pmap_has(uint32_t)(&ui_event_cbs, ns_id)) { + free_ui_event_callback(pmap_get(uint32_t)(&ui_event_cbs, ns_id)); + pmap_del(uint32_t)(&ui_event_cbs, ns_id); + } + ui_cb_update_ext(); + ui_refresh(); +} diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 7dd2f5bce3..9140a9f1f3 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -6,9 +6,14 @@ #include <stdint.h> #include "nvim/api/private/defs.h" +#include "nvim/event/multiqueue.h" #include "nvim/globals.h" #include "nvim/highlight_defs.h" +#include "nvim/macros.h" #include "nvim/memory.h" +#include "nvim/types.h" + +struct ui_t; typedef enum { kUICmdline = 0, @@ -47,7 +52,40 @@ enum { typedef int LineFlags; -EXTERN ArenaMem ui_ext_fixblk INIT(= NULL); +typedef struct { + uint64_t channel_id; + +#define UI_BUF_SIZE 4096 ///< total buffer size for pending msgpack data. + /// guaranteed size available for each new event (so packing of simple events + /// and the header of grid_line will never fail) +#define EVENT_BUF_SIZE 256 + char buf[UI_BUF_SIZE]; ///< buffer of packed but not yet sent msgpack data + char *buf_wptr; ///< write head of buffer + const char *cur_event; ///< name of current event (might get multiple arglists) + Array call_buf; ///< buffer for constructing a single arg list (max 16 elements!) + + // state for write_cb, while packing a single arglist to msgpack. This + // might fail due to buffer overflow. + size_t pack_totlen; + bool buf_overflow; + char *temp_buf; + + // We start packing the two outermost msgpack arrays before knowing the total + // number of elements. Thus track the location where array size will need + // to be written in the msgpack buffer, once the specific array is finished. + char *nevents_pos; + char *ncalls_pos; + uint32_t nevents; ///< number of distinct events (top-level args to "redraw" + uint32_t ncalls; ///< number of calls made to the current event (plus one for the name!) + bool flushed_events; ///< events where sent to client without "flush" event + + int hl_id; // Current highlight for legacy put event. + Integer cursor_row, cursor_col; // Intended visible cursor position. + + // Position of legacy cursor, used both for drawing and visible user cursor. + Integer client_row, client_col; + bool wildmenu_active; +} UIData; struct ui_t { bool rgb; @@ -62,20 +100,22 @@ struct ui_t { double pum_col; double pum_height; double pum_width; - void *data; -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "ui_events.generated.h" -#endif - - void (*inspect)(UI *ui, Dictionary *info); + // TODO(bfredl): integrate into struct! + UIData data[1]; }; +typedef struct ui_event_callback { + LuaRef cb; + bool ext_widgets[kUIGlobalCount]; +} UIEventCallback; + +// uncrustify:off #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui.h.generated.h" - # include "ui_events_call.h.generated.h" #endif +// uncrustify:on EXTERN MultiQueue *resize_events; #endif // NVIM_UI_H diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c deleted file mode 100644 index 84098e9476..0000000000 --- a/src/nvim/ui_bridge.c +++ /dev/null @@ -1,223 +0,0 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - -// UI wrapper that sends requests to the UI thread. -// Used by the built-in TUI and libnvim-based UIs. - -#include <assert.h> -#include <limits.h> -#include <stdbool.h> -#include <stdio.h> - -#include "nvim/api/private/helpers.h" -#include "nvim/log.h" -#include "nvim/main.h" -#include "nvim/memory.h" -#include "nvim/ugrid.h" -#include "nvim/ui.h" -#include "nvim/ui_bridge.h" -#include "nvim/vim.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "ui_bridge.c.generated.h" -#endif - -#define UI(b) (((UIBridgeData *)b)->ui) - -// Schedule a function call on the UI bridge thread. -#define UI_BRIDGE_CALL(ui, name, argc, ...) \ - ((UIBridgeData *)ui)->scheduler(event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui)) - -#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) -{ - UIBridgeData *rv = xcalloc(1, sizeof(UIBridgeData)); - rv->ui = ui; - rv->bridge.rgb = ui->rgb; - rv->bridge.width = ui->width; - rv->bridge.height = ui->height; - rv->bridge.stop = ui_bridge_stop; - rv->bridge.grid_resize = ui_bridge_grid_resize; - rv->bridge.grid_clear = ui_bridge_grid_clear; - rv->bridge.grid_cursor_goto = ui_bridge_grid_cursor_goto; - rv->bridge.mode_info_set = ui_bridge_mode_info_set; - rv->bridge.update_menu = ui_bridge_update_menu; - rv->bridge.busy_start = ui_bridge_busy_start; - rv->bridge.busy_stop = ui_bridge_busy_stop; - rv->bridge.mouse_on = ui_bridge_mouse_on; - rv->bridge.mouse_off = ui_bridge_mouse_off; - rv->bridge.mode_change = ui_bridge_mode_change; - rv->bridge.grid_scroll = ui_bridge_grid_scroll; - rv->bridge.hl_attr_define = ui_bridge_hl_attr_define; - rv->bridge.bell = ui_bridge_bell; - rv->bridge.visual_bell = ui_bridge_visual_bell; - rv->bridge.default_colors_set = ui_bridge_default_colors_set; - rv->bridge.flush = ui_bridge_flush; - rv->bridge.suspend = ui_bridge_suspend; - rv->bridge.set_title = ui_bridge_set_title; - rv->bridge.set_icon = ui_bridge_set_icon; - rv->bridge.screenshot = ui_bridge_screenshot; - rv->bridge.option_set = ui_bridge_option_set; - rv->bridge.raw_line = ui_bridge_raw_line; - rv->bridge.inspect = ui_bridge_inspect; - rv->scheduler = scheduler; - - for (UIExtension i = 0; (int)i < kUIExtCount; i++) { - rv->bridge.ui_ext[i] = ui->ui_ext[i]; - } - - rv->ui_main = ui_main; - uv_mutex_init(&rv->mutex); - uv_cond_init(&rv->cond); - uv_mutex_lock(&rv->mutex); - rv->ready = false; - - if (uv_thread_create(&rv->ui_thread, ui_thread_run, rv)) { - abort(); - } - - // Suspend the main thread until CONTINUE is called by the UI thread. - while (!rv->ready) { - uv_cond_wait(&rv->cond, &rv->mutex); - } - uv_mutex_unlock(&rv->mutex); - - ui_attach_impl(&rv->bridge, 0); - - return &rv->bridge; -} - -void ui_bridge_stopped(UIBridgeData *bridge) -{ - uv_mutex_lock(&bridge->mutex); - bridge->stopped = true; - uv_mutex_unlock(&bridge->mutex); -} - -static void ui_thread_run(void *data) -{ - UIBridgeData *bridge = data; - bridge->ui_main(bridge, bridge->ui); -} - -static void ui_bridge_stop(UI *b) -{ - // Detach bridge first, so that "stop" is the last event the TUI loop - // receives from the main thread. #8041 - ui_detach_impl(b, 0); - - UIBridgeData *bridge = (UIBridgeData *)b; - bool stopped = bridge->stopped = false; - UI_BRIDGE_CALL(b, stop, 1, b); - for (;;) { - uv_mutex_lock(&bridge->mutex); - stopped = bridge->stopped; - uv_mutex_unlock(&bridge->mutex); - if (stopped) { // -V547 - break; - } - // TODO(justinmk): Remove this. Use a cond-wait above. #9274 - loop_poll_events(&main_loop, 10); // Process one event. - } - uv_thread_join(&bridge->ui_thread); - uv_mutex_destroy(&bridge->mutex); - uv_cond_destroy(&bridge->cond); - xfree(bridge->ui); // Threads joined, now safe to free UI container. #7922 - xfree(b); -} -static void ui_bridge_stop_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->stop(ui); -} - -static void ui_bridge_hl_attr_define(UI *ui, Integer id, HlAttrs attrs, HlAttrs cterm_attrs, - Array info) -{ - HlAttrs *a = xmalloc(sizeof(HlAttrs)); - *a = attrs; - UI_BRIDGE_CALL(ui, hl_attr_define, 3, ui, INT2PTR(id), a); -} -static void ui_bridge_hl_attr_define_event(void **argv) -{ - UI *ui = UI(argv[0]); - Array info = ARRAY_DICT_INIT; - ui->hl_attr_define(ui, PTR2INT(argv[1]), *((HlAttrs *)argv[2]), - *((HlAttrs *)argv[2]), info); - xfree(argv[2]); -} - -static void ui_bridge_raw_line_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->raw_line(ui, PTR2INT(argv[1]), PTR2INT(argv[2]), PTR2INT(argv[3]), - PTR2INT(argv[4]), PTR2INT(argv[5]), PTR2INT(argv[6]), - (LineFlags)PTR2INT(argv[7]), argv[8], argv[9]); - xfree(argv[8]); - xfree(argv[9]); -} -static void ui_bridge_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, - Integer clearcol, Integer clearattr, LineFlags flags, - const schar_T *chunk, const sattr_T *attrs) -{ - size_t ncol = (size_t)(endcol - startcol); - schar_T *c = xmemdup(chunk, ncol * sizeof(schar_T)); - sattr_T *hl = xmemdup(attrs, ncol * sizeof(sattr_T)); - UI_BRIDGE_CALL(ui, raw_line, 10, ui, INT2PTR(grid), INT2PTR(row), - INT2PTR(startcol), INT2PTR(endcol), INT2PTR(clearcol), - INT2PTR(clearattr), INT2PTR(flags), c, hl); -} - -static void ui_bridge_suspend(UI *b) -{ - UIBridgeData *data = (UIBridgeData *)b; - uv_mutex_lock(&data->mutex); - UI_BRIDGE_CALL(b, suspend, 1, b); - data->ready = false; - // Suspend the main thread until CONTINUE is called by the UI thread. - while (!data->ready) { - uv_cond_wait(&data->cond, &data->mutex); - } - uv_mutex_unlock(&data->mutex); -} -static void ui_bridge_suspend_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->suspend(ui); -} - -static void ui_bridge_option_set(UI *ui, String name, Object value) -{ - String copy_name = copy_string(name); - Object *copy_value = xmalloc(sizeof(Object)); - *copy_value = copy_object(value); - UI_BRIDGE_CALL(ui, option_set, 4, ui, copy_name.data, - INT2PTR(copy_name.size), copy_value); - // TODO(bfredl): when/if TUI/bridge teardown is refactored to use events, the - // commit that introduced this special case can be reverted. - // For now this is needed for nvim_list_uis(). - if (strequal(name.data, "termguicolors")) { - ui->rgb = value.data.boolean; - } -} -static void ui_bridge_option_set_event(void **argv) -{ - UI *ui = UI(argv[0]); - String name = (String){ .data = argv[1], .size = (size_t)argv[2] }; - Object value = *(Object *)argv[3]; - ui->option_set(ui, name, value); - api_free_string(name); - api_free_object(value); - xfree(argv[3]); -} - -static void ui_bridge_inspect(UI *ui, Dictionary *info) -{ - PUT(*info, "chan", INTEGER_OBJ(0)); -} diff --git a/src/nvim/ui_bridge.h b/src/nvim/ui_bridge.h deleted file mode 100644 index c18600a857..0000000000 --- a/src/nvim/ui_bridge.h +++ /dev/null @@ -1,44 +0,0 @@ -// Bridge for communication between a UI thread and nvim core. -// Used by the built-in TUI and libnvim-based UIs. -#ifndef NVIM_UI_BRIDGE_H -#define NVIM_UI_BRIDGE_H - -#include <uv.h> - -#include "nvim/event/defs.h" -#include "nvim/ui.h" - -typedef struct ui_bridge_data UIBridgeData; -typedef void (*ui_main_fn)(UIBridgeData *bridge, UI *ui); -struct ui_bridge_data { - UI bridge; // actual UI passed to ui_attach - UI *ui; // UI pointer that will have its callback called in - // another thread - event_scheduler scheduler; - uv_thread_t ui_thread; - ui_main_fn ui_main; - uv_mutex_t mutex; - uv_cond_t cond; - // When the UI thread is called, the main thread will suspend until - // the call returns. This flag is used as a condition for the main - // thread to continue. - bool ready; - // When a stop request is sent from the main thread, it must wait until the UI - // thread finishes handling all events. This flag is set by the UI thread as a - // signal that it will no longer send messages to the main thread. - bool stopped; -}; - -#define CONTINUE(b) \ - do { \ - UIBridgeData *d = (UIBridgeData *)b; \ - uv_mutex_lock(&d->mutex); \ - d->ready = true; \ - uv_cond_signal(&d->cond); \ - uv_mutex_unlock(&d->mutex); \ - } while (0) - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "ui_bridge.h.generated.h" -#endif -#endif // NVIM_UI_BRIDGE_H diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c index a586fec3bf..378c0e4831 100644 --- a/src/nvim/ui_client.c +++ b/src/nvim/ui_client.c @@ -1,44 +1,126 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -#include <assert.h> #include <stdbool.h> #include <stdint.h> +#include <stdlib.h> -#include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" +#include "nvim/channel.h" +#include "nvim/eval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/event/loop.h" +#include "nvim/globals.h" #include "nvim/highlight.h" #include "nvim/log.h" -#include "nvim/map.h" +#include "nvim/main.h" +#include "nvim/memory.h" #include "nvim/msgpack_rpc/channel.h" -#include "nvim/screen.h" +#include "nvim/msgpack_rpc/channel_defs.h" +#include "nvim/os/os_defs.h" +#include "nvim/tui/tui.h" #include "nvim/ui.h" #include "nvim/ui_client.h" -#include "nvim/vim.h" +static TUIData *tui = NULL; + +// uncrustify:off #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_client.c.generated.h" - # include "ui_events_client.generated.h" #endif +// uncrustify:on +// -void ui_client_init(uint64_t chan) +uint64_t ui_client_start_server(int argc, char **argv) { - Array args = ARRAY_DICT_INIT; - int width = Columns; - int height = Rows; - Dictionary opts = ARRAY_DICT_INIT; + varnumber_T exit_status; + char **args = xmalloc(((size_t)(2 + argc)) * sizeof(char *)); + int args_idx = 0; + args[args_idx++] = xstrdup((const char *)get_vim_var_str(VV_PROGPATH)); + args[args_idx++] = xstrdup("--embed"); + for (int i = 1; i < argc; i++) { + args[args_idx++] = xstrdup(argv[i]); + } + args[args_idx++] = NULL; + + CallbackReader on_err = CALLBACK_READER_INIT; + on_err.fwd_err = true; + + Channel *channel = channel_job_start(args, CALLBACK_READER_INIT, + on_err, CALLBACK_NONE, + false, true, true, false, kChannelStdinPipe, + NULL, 0, 0, NULL, &exit_status); + + // If stdin is not a pty, it is forwarded to the client. + // Replace stdin in the TUI process with the tty fd. + if (ui_client_forward_stdin) { + close(0); +#ifdef MSWIN + os_open_conin_fd(); +#else + dup(stderr_isatty ? STDERR_FILENO : STDOUT_FILENO); +#endif + } - PUT(opts, "rgb", BOOLEAN_OBJ(true)); - PUT(opts, "ext_linegrid", BOOLEAN_OBJ(true)); - PUT(opts, "ext_termcolors", BOOLEAN_OBJ(true)); + return channel->id; +} - ADD(args, INTEGER_OBJ((int)width)); - ADD(args, INTEGER_OBJ((int)height)); - ADD(args, DICTIONARY_OBJ(opts)); +void ui_client_run(bool remote_ui) + FUNC_ATTR_NORETURN +{ + int width, height; + char *term; + tui = tui_start(&width, &height, &term); + + MAXSIZE_TEMP_ARRAY(args, 3); + ADD_C(args, INTEGER_OBJ(width)); + ADD_C(args, INTEGER_OBJ(height)); + + MAXSIZE_TEMP_DICT(opts, 9); + PUT_C(opts, "rgb", BOOLEAN_OBJ(true)); + PUT_C(opts, "ext_linegrid", BOOLEAN_OBJ(true)); + PUT_C(opts, "ext_termcolors", BOOLEAN_OBJ(true)); + if (term) { + PUT(opts, "term_name", STRING_OBJ(cstr_to_string(term))); + } + if (ui_client_bg_response != kNone) { + bool is_dark = (ui_client_bg_response == kTrue); + PUT_C(opts, "term_background", STRING_OBJ(cstr_as_string(is_dark ? "dark" : "light"))); + } + PUT_C(opts, "term_colors", INTEGER_OBJ(t_colors)); + if (!remote_ui) { + PUT_C(opts, "stdin_tty", BOOLEAN_OBJ(stdin_isatty)); + PUT_C(opts, "stdout_tty", BOOLEAN_OBJ(stdout_isatty)); + if (ui_client_forward_stdin) { + PUT_C(opts, "stdin_fd", INTEGER_OBJ(UI_CLIENT_STDIN_FD)); + } + } + ADD_C(args, DICTIONARY_OBJ(opts)); + + rpc_send_event(ui_client_channel_id, "nvim_ui_attach", args); + ui_client_attached = true; - rpc_send_event(chan, "nvim_ui_attach", args); - ui_client_channel_id = chan; + // os_exit() will be invoked when the client channel detaches + while (true) { + LOOP_PROCESS_EVENTS(&main_loop, resize_events, -1); + } +} + +void ui_client_stop(void) +{ + tui_stop(tui); +} + +void ui_client_set_size(int width, int height) +{ + // The currently known size will be sent when attaching + if (ui_client_attached) { + MAXSIZE_TEMP_ARRAY(args, 2); + ADD_C(args, INTEGER_OBJ((int)width)); + ADD_C(args, INTEGER_OBJ((int)height)); + rpc_send_event(ui_client_channel_id, "nvim_ui_try_resize", args); + } } UIClientHandler ui_client_get_redraw_handler(const char *name, size_t name_len, Error *error) @@ -55,26 +137,12 @@ UIClientHandler ui_client_get_redraw_handler(const char *name, size_t name_len, /// async 'redraw' events, which are expected when nvim acts as an ui client. /// get handled in msgpack_rpc/unpacker.c and directly dispatched to handlers /// of specific ui events, like ui_client_event_grid_resize and so on. -Object handle_ui_client_redraw(uint64_t channel_id, Array args, Error *error) +Object handle_ui_client_redraw(uint64_t channel_id, Array args, Arena *arena, Error *error) { api_set_error(error, kErrorTypeValidation, "'redraw' cannot be sent as a request"); return NIL; } -/// run the main thread in ui client mode -/// -/// This is just a stub. the full version will handle input, resizing, etc -void ui_client_execute(uint64_t chan) - FUNC_ATTR_NORETURN -{ - while (true) { - loop_poll_events(&main_loop, -1); - multiqueue_process_events(resize_events); - } - - getout(0); -} - static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb) { Error err = ERROR_INIT; @@ -83,7 +151,7 @@ static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb) // TODO(bfredl): log "err" return HLATTRS_INIT; } - return dict2hlattrs(&dict, true, NULL, &err); + return dict2hlattrs(&dict, rgb, NULL, &err); } void ui_client_event_grid_resize(Array args) @@ -99,7 +167,7 @@ void ui_client_event_grid_resize(Array args) Integer grid = args.items[0].data.integer; Integer width = args.items[1].data.integer; Integer height = args.items[2].data.integer; - ui_call_grid_resize(grid, width, height); + tui_grid_resize(tui, grid, width, height); if (grid_line_buf_size < (size_t)width) { xfree(grid_line_buf_char); @@ -125,6 +193,6 @@ void ui_client_event_raw_line(GridLineEvent *g) // TODO(hlpr98): Accommodate other LineFlags when included in grid_line LineFlags lineflags = 0; - ui_call_raw_line(grid, row, startcol, endcol, clearcol, g->cur_attr, lineflags, - (const schar_T *)grid_line_buf_char, grid_line_buf_attr); + tui_raw_line(tui, grid, row, startcol, endcol, clearcol, g->cur_attr, lineflags, + (const schar_T *)grid_line_buf_char, grid_line_buf_attr); } diff --git a/src/nvim/ui_client.h b/src/nvim/ui_client.h index 311dafaa0b..7e5f847039 100644 --- a/src/nvim/ui_client.h +++ b/src/nvim/ui_client.h @@ -1,8 +1,14 @@ #ifndef NVIM_UI_CLIENT_H #define NVIM_UI_CLIENT_H +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + #include "nvim/api/private/defs.h" #include "nvim/grid_defs.h" +#include "nvim/macros.h" +#include "nvim/types.h" typedef struct { const char *name; @@ -14,10 +20,29 @@ EXTERN size_t grid_line_buf_size INIT(= 0); EXTERN schar_T *grid_line_buf_char INIT(= NULL); EXTERN sattr_T *grid_line_buf_attr INIT(= NULL); +// ID of the ui client channel. If zero, the client is not running. +EXTERN uint64_t ui_client_channel_id INIT(= 0); + +// TODO(bfredl): the current structure for how tui and ui_client.c communicate is a bit awkward. +// This will be restructured as part of The UI Devirtualization Project. + +/// Whether ui client has sent nvim_ui_attach yet +EXTERN bool ui_client_attached INIT(= false); + +/// Whether ui client has gotten a response about the bg color of the terminal, +/// kTrue=dark, kFalse=light, kNone=no response yet +EXTERN TriState ui_client_bg_response INIT(= kNone); + +/// The ui client should forward its stdin to the nvim process +/// by convention, this uses fd=3 (next free number after stdio) +EXTERN bool ui_client_forward_stdin INIT(= false); + +#define UI_CLIENT_STDIN_FD 3 +// uncrustify:off #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_client.h.generated.h" - # include "ui_events_client.h.generated.h" #endif +// uncrustify:on #endif // NVIM_UI_CLIENT_H diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index 2216e25db9..9ff9eabff8 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -7,24 +7,28 @@ // Layer-based compositing: https://en.wikipedia.org/wiki/Digital_compositing #include <assert.h> +#include <inttypes.h> #include <limits.h> #include <stdbool.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> -#include "nvim/api/private/helpers.h" +#include "klib/kvec.h" +#include "nvim/api/private/defs.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" +#include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/highlight.h" #include "nvim/highlight_group.h" -#include "nvim/lib/kvec.h" #include "nvim/log.h" -#include "nvim/lua/executor.h" -#include "nvim/main.h" +#include "nvim/macros.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/os/os.h" -#include "nvim/popupmenu.h" -#include "nvim/ugrid.h" +#include "nvim/option_defs.h" +#include "nvim/os/time.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/vim.h" @@ -33,7 +37,6 @@ # include "ui_compositor.c.generated.h" #endif -static UI *compositor = NULL; static int composed_uis = 0; kvec_t(ScreenGrid *) layers = KV_INITIAL_VALUE; @@ -58,33 +61,8 @@ static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose; void ui_comp_init(void) { - if (compositor != NULL) { - return; - } - compositor = xcalloc(1, sizeof(UI)); - - compositor->rgb = true; - compositor->grid_resize = ui_comp_grid_resize; - compositor->grid_scroll = ui_comp_grid_scroll; - compositor->grid_cursor_goto = ui_comp_grid_cursor_goto; - compositor->raw_line = ui_comp_raw_line; - compositor->msg_set_pos = ui_comp_msg_set_pos; - - // Be unopinionated: will be attached together with a "real" ui anyway - compositor->width = INT_MAX; - compositor->height = INT_MAX; - for (UIExtension i = 0; (int)i < kUIExtCount; i++) { - compositor->ui_ext[i] = true; - } - - // TODO(bfredl): this will be more complicated if we implement - // hlstate per UI (i e reduce hl ids for non-hlstate UIs) - compositor->ui_ext[kUIHlState] = false; - kv_push(layers, &default_grid); curgrid = &default_grid; - - ui_attach_impl(compositor, 0); } void ui_comp_syn_init(void) @@ -122,7 +100,7 @@ bool ui_comp_should_draw(void) /// /// TODO(bfredl): later on the compositor should just use win_float_pos events, /// though that will require slight event order adjustment: emit the win_pos -/// events in the beginning of update_screen(0), rather than in ui_flush() +/// events in the beginning of update_screen(), rather than in ui_flush() bool ui_comp_put_grid(ScreenGrid *grid, int row, int col, int height, int width, bool valid, bool on_top) { @@ -237,7 +215,7 @@ bool ui_comp_set_grid(handle_T handle) return false; } -static void ui_comp_raise_grid(ScreenGrid *grid, size_t new_index) +void ui_comp_raise_grid(ScreenGrid *grid, size_t new_index) { size_t old_index = grid->comp_index; for (size_t i = old_index; i < new_index; i++) { @@ -257,7 +235,7 @@ static void ui_comp_raise_grid(ScreenGrid *grid, size_t new_index) } } -static void ui_comp_grid_cursor_goto(UI *ui, Integer grid_handle, Integer r, Integer c) +void ui_comp_grid_cursor_goto(Integer grid_handle, Integer r, Integer c) { if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid_handle)) { return; @@ -469,6 +447,10 @@ static void compose_debug(Integer startrow, Integer endrow, Integer startcol, In endcol = MIN(endcol, default_grid.cols); int attr = syn_id2attr(syn_id); + if (delay) { + debug_delay(endrow - startrow); + } + for (int row = (int)startrow; row < endrow; row++) { ui_composed_call_raw_line(1, row, startcol, startcol, endcol, attr, false, (const schar_T *)linebuf, @@ -485,7 +467,7 @@ static void debug_delay(Integer lines) ui_call_flush(); uint64_t wd = (uint64_t)labs(p_wd); uint64_t factor = (uint64_t)MAX(MIN(lines, 5), 1); - os_microdelay(factor * wd * 1000u, true); + os_microdelay(factor * wd * 1000U, true); } static void compose_area(Integer startrow, Integer endrow, Integer startcol, Integer endcol) @@ -513,9 +495,9 @@ void ui_comp_compose_grid(ScreenGrid *grid) } } -static void ui_comp_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Integer endcol, - Integer clearcol, Integer clearattr, LineFlags flags, - const schar_T *chunk, const sattr_T *attrs) +void ui_comp_raw_line(Integer grid, Integer row, Integer startcol, Integer endcol, Integer clearcol, + Integer clearattr, LineFlags flags, const schar_T *chunk, + const sattr_T *attrs) { if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) { return; @@ -570,22 +552,23 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row, Integer startcol /// The screen is invalid and will soon be cleared /// /// Don't redraw floats until screen is cleared -void ui_comp_set_screen_valid(bool valid) +bool ui_comp_set_screen_valid(bool valid) { + bool old_val = valid_screen; valid_screen = valid; if (!valid) { msg_sep_row = -1; } + return old_val; } -static void ui_comp_msg_set_pos(UI *ui, Integer grid, Integer row, Boolean scrolled, - String sep_char) +void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char) { msg_grid.comp_row = (int)row; if (scrolled && row > 0) { msg_sep_row = (int)row - 1; if (sep_char.data) { - STRLCPY(msg_sep_char, sep_char.data, sizeof(msg_sep_char)); + xstrlcpy(msg_sep_char, sep_char.data, sizeof(msg_sep_char)); } } else { msg_sep_row = -1; @@ -594,7 +577,7 @@ static void ui_comp_msg_set_pos(UI *ui, Integer grid, Integer row, Boolean scrol if (row > msg_current_row && ui_comp_should_draw()) { compose_area(MAX(msg_current_row - 1, 0), row, 0, default_grid.cols); } else if (row < msg_current_row && ui_comp_should_draw() - && msg_current_row < Rows) { + && (msg_current_row < Rows || (scrolled && !msg_was_scrolled))) { int delta = msg_current_row - (int)row; if (msg_grid.blending) { int first_row = MAX((int)row - (scrolled?1:0), 0); @@ -623,8 +606,8 @@ static bool curgrid_covered_above(int row) return kv_size(layers) - (above_msg?1:0) > curgrid->comp_index + 1; } -static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, Integer left, - Integer right, Integer rows, Integer cols) +void ui_comp_grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, + Integer rows, Integer cols) { if (!ui_comp_should_draw() || !ui_comp_set_grid((int)grid)) { return; @@ -658,7 +641,7 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, Integer bot, } } -static void ui_comp_grid_resize(UI *ui, Integer grid, Integer width, Integer height) +void ui_comp_grid_resize(Integer grid, Integer width, Integer height) { if (grid == 1) { ui_composed_call_grid_resize(1, width, height); diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 75a09b244c..2b0bb1d243 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -76,41 +76,56 @@ #include <assert.h> #include <fcntl.h> #include <inttypes.h> -#include <limits.h> #include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include <time.h> +#include <uv.h> #include "auto/config.h" +#include "klib/kvec.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" +#include "nvim/ex_cmds_defs.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/getchar.h" -#include "nvim/lib/kvec.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/option.h" +#include "nvim/os/fs_defs.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/os_defs.h" #include "nvim/os/time.h" -#include "nvim/os_unix.h" #include "nvim/path.h" -#include "nvim/pos.h" // MAXLNUM +#include "nvim/pos.h" +#include "nvim/screen.h" #include "nvim/sha256.h" #include "nvim/state.h" #include "nvim/strings.h" #include "nvim/types.h" #include "nvim/undo.h" +#include "nvim/undo_defs.h" +#include "nvim/vim.h" /// Structure passed around between undofile functions. typedef struct { @@ -558,7 +573,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re } if (size > 0) { - uep->ue_array = xmalloc(sizeof(char_u *) * (size_t)size); + uep->ue_array = xmalloc(sizeof(char *) * (size_t)size); linenr_T lnum; long i; for (i = 0, lnum = top + 1; i < size; i++) { @@ -609,20 +624,20 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re // extra fields for uhp #define UHP_SAVE_NR 1 -static char_u e_not_open[] = N_("E828: Cannot open undo file for writing: %s"); +static char e_not_open[] = N_("E828: Cannot open undo file for writing: %s"); /// Compute the hash for a buffer text into hash[UNDO_HASH_SIZE]. /// /// @param[in] buf The buffer used to compute the hash /// @param[in] hash Array of size UNDO_HASH_SIZE in which to store the value of /// the hash -void u_compute_hash(buf_T *buf, char_u *hash) +void u_compute_hash(buf_T *buf, uint8_t *hash) { context_sha256_T ctx; sha256_start(&ctx); for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - char_u *p = ml_get_buf(buf, lnum, false); - sha256_update(&ctx, p, (uint32_t)(STRLEN(p) + 1)); + char *p = ml_get_buf(buf, lnum, false); + sha256_update(&ctx, (uint8_t *)p, strlen(p) + 1); } sha256_finish(&ctx, hash); } @@ -651,7 +666,7 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) char fname_buf[MAXPATHL]; // Expand symlink in the file name, so that we put the undo file with the // actual file instead of with the symlink. - if (resolve_symlink((const char_u *)ffname, (char_u *)fname_buf) == OK) { + if (resolve_symlink(ffname, fname_buf) == OK) { ffname = fname_buf; } #endif @@ -662,7 +677,7 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) // Loop over 'undodir'. When reading find the first file that exists. // When not reading use the first directory that exists or ".". - char *dirp = (char *)p_udir; + char *dirp = p_udir; while (*dirp != NUL) { size_t dir_len = copy_option_part(&dirp, dir_name, MAXPATHL, ","); if (dir_len == 1 && dir_name[0] == '.') { @@ -685,7 +700,7 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) *p-- = NUL; } - bool has_directory = os_isdir((char_u *)dir_name); + bool has_directory = os_isdir(dir_name); if (!has_directory && *dirp == NUL && !reading) { // Last directory in the list does not exist, create it. int ret; @@ -713,7 +728,7 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading) // When reading check if the file exists. if (undo_file_name != NULL - && (!reading || os_path_exists((char_u *)undo_file_name))) { + && (!reading || os_path_exists(undo_file_name))) { break; } XFREE_CLEAR(undo_file_name); @@ -750,7 +765,7 @@ static void u_free_uhp(u_header_T *uhp) /// @param hash The hash of the buffer contents // /// @returns false in case of an error. -static bool serialize_header(bufinfo_T *bi, char_u *hash) +static bool serialize_header(bufinfo_T *bi, uint8_t *hash) FUNC_ATTR_NONNULL_ALL { buf_T *buf = bi->bi_buf; @@ -771,9 +786,9 @@ static bool serialize_header(bufinfo_T *bi, char_u *hash) // Write buffer-specific data. undo_write_bytes(bi, (uintmax_t)buf->b_ml.ml_line_count, 4); - size_t len = buf->b_u_line_ptr ? STRLEN(buf->b_u_line_ptr) : 0; + size_t len = buf->b_u_line_ptr ? strlen(buf->b_u_line_ptr) : 0; undo_write_bytes(bi, len, 4); - if (len > 0 && !undo_write(bi, buf->b_u_line_ptr, len)) { + if (len > 0 && !undo_write(bi, (uint8_t *)buf->b_u_line_ptr, len)) { return false; } undo_write_bytes(bi, (uintmax_t)buf->b_u_line_lnum, 4); @@ -1034,11 +1049,11 @@ static bool serialize_uep(bufinfo_T *bi, u_entry_T *uep) undo_write_bytes(bi, (uintmax_t)uep->ue_size, 4); for (size_t i = 0; i < (size_t)uep->ue_size; i++) { - size_t len = STRLEN(uep->ue_array[i]); + size_t len = strlen(uep->ue_array[i]); if (!undo_write_bytes(bi, len, 4)) { return false; } - if (len > 0 && !undo_write(bi, uep->ue_array[i], len)) { + if (len > 0 && !undo_write(bi, (uint8_t *)uep->ue_array[i], len)) { return false; } } @@ -1057,18 +1072,18 @@ static u_entry_T *unserialize_uep(bufinfo_T *bi, bool *error, const char *file_n uep->ue_lcount = undo_read_4c(bi); uep->ue_size = undo_read_4c(bi); - char_u **array = NULL; + char **array = NULL; if (uep->ue_size > 0) { - if ((size_t)uep->ue_size < SIZE_MAX / sizeof(char_u *)) { // -V547 - array = xmalloc(sizeof(char_u *) * (size_t)uep->ue_size); - memset(array, 0, sizeof(char_u *) * (size_t)uep->ue_size); + if ((size_t)uep->ue_size < SIZE_MAX / sizeof(char *)) { // -V547 + array = xmalloc(sizeof(char *) * (size_t)uep->ue_size); + memset(array, 0, sizeof(char *) * (size_t)uep->ue_size); } } uep->ue_array = array; for (size_t i = 0; i < (size_t)uep->ue_size; i++) { int line_len = undo_read_4c(bi); - char_u *line; + char *line; if (line_len >= 0) { line = undo_read_string(bi, (size_t)line_len); } else { @@ -1135,7 +1150,7 @@ static void unserialize_visualinfo(bufinfo_T *bi, visualinfo_T *info) /// @param[in] buf Buffer for which undo file is written. /// @param[in] hash Hash value of the buffer text. Must have #UNDO_HASH_SIZE /// size. -void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, char_u *const hash) +void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, uint8_t *const hash) FUNC_ATTR_NONNULL_ARG(3, 4) { char *file_name; @@ -1177,7 +1192,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, // If the undo file already exists, verify that it actually is an undo // file, and delete it. - if (os_path_exists((char_u *)file_name)) { + if (os_path_exists(file_name)) { if (name == NULL || !forceit) { // Check we can read it and it's an undo file. fd = os_open(file_name, O_RDONLY, 0); @@ -1194,7 +1209,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf, } goto theend; } else { - char_u mbuf[UF_START_MAGIC_LEN]; + char mbuf[UF_START_MAGIC_LEN]; ssize_t len = read_eintr(fd, mbuf, UF_START_MAGIC_LEN); close(fd); if (len < UF_START_MAGIC_LEN @@ -1327,9 +1342,9 @@ write_error: #ifdef HAVE_ACL if (buf->b_ffname != NULL) { // For systems that support ACL: get the ACL from the original file. - vim_acl_T acl = mch_get_acl((char_u *)buf->b_ffname); - mch_set_acl((char_u *)file_name, acl); - mch_free_acl(acl); + vim_acl_T acl = os_get_acl(buf->b_ffname); + os_set_acl(file_name, acl); + os_free_acl(acl); } #endif @@ -1344,11 +1359,11 @@ theend: /// a bit more verbose. /// Otherwise use curbuf->b_ffname to generate the undo file name. /// "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text. -void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_ATTR_UNUSED) +void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATTR_UNUSED) FUNC_ATTR_NONNULL_ARG(2) { u_header_T **uhp_table = NULL; - char_u *line_ptr = NULL; + char *line_ptr = NULL; char *file_name; if (name == NULL) { @@ -1362,7 +1377,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT // owner of the text file or equal to the current user. FileInfo file_info_orig; FileInfo file_info_undo; - if (os_fileinfo((const char *)orig_name, &file_info_orig) + if (os_fileinfo(orig_name, &file_info_orig) && os_fileinfo(file_name, &file_info_undo) && file_info_orig.stat.st_uid != file_info_undo.stat.st_uid && file_info_undo.stat.st_uid != getuid()) { @@ -1399,7 +1414,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT }; // Read the undo file header. - char_u magic_buf[UF_START_MAGIC_LEN]; + char magic_buf[UF_START_MAGIC_LEN]; if (fread(magic_buf, UF_START_MAGIC_LEN, 1, fp) != 1 || memcmp(magic_buf, UF_START_MAGIC, UF_START_MAGIC_LEN) != 0) { semsg(_("E823: Not an undo file: %s"), file_name); @@ -1411,7 +1426,7 @@ void u_read_undo(char *name, const char_u *hash, const char_u *orig_name FUNC_AT goto error; } - char_u read_hash[UNDO_HASH_SIZE]; + uint8_t read_hash[UNDO_HASH_SIZE]; if (!undo_read(&bi, read_hash, UNDO_HASH_SIZE)) { corruption_error("hash", file_name); goto error; @@ -1728,10 +1743,10 @@ static bool undo_read(bufinfo_T *bi, uint8_t *buffer, size_t size) /// @param len can be zero to allocate an empty line. /// /// @returns a pointer to allocated memory or NULL in case of an error. -static uint8_t *undo_read_string(bufinfo_T *bi, size_t len) +static char *undo_read_string(bufinfo_T *bi, size_t len) { - uint8_t *ptr = xmallocz(len); - if (len > 0 && !undo_read(bi, ptr, len)) { + char *ptr = xmallocz(len); + if (len > 0 && !undo_read(bi, (uint8_t *)ptr, len)) { xfree(ptr); return NULL; } @@ -1771,16 +1786,16 @@ void u_redo(int count) /// Undo and remove the branch from the undo tree. /// Also moves the cursor (as a "normal" undo would). -bool u_undo_and_forget(int count) +/// +/// @param do_buf_event If `true`, send the changedtick with the buffer updates +bool u_undo_and_forget(int count, bool do_buf_event) { if (curbuf->b_u_synced == false) { u_sync(true); count = 1; } undo_undoes = true; - u_doit(count, true, - // Don't send nvim_buf_lines_event for u_undo_and_forget(). - false); + u_doit(count, true, do_buf_event); if (curbuf->b_u_curhead == NULL) { // nothing was undone. @@ -2223,7 +2238,7 @@ target_zero: /// @param do_buf_event If `true`, send buffer updates. static void u_undoredo(int undo, bool do_buf_event) { - char_u **newarray = NULL; + char **newarray = NULL; linenr_T newlnum = MAXLNUM; u_entry_T *nuep; u_entry_T *newlist = NULL; @@ -2283,7 +2298,7 @@ static void u_undoredo(int undo, bool do_buf_event) // line. long i; for (i = 0; i < newsize && i < oldsize; i++) { - if (STRCMP(uep->ue_array[i], ml_get(top + 1 + (linenr_T)i)) != 0) { + if (strcmp(uep->ue_array[i], ml_get(top + 1 + (linenr_T)i)) != 0) { break; } } @@ -2301,11 +2316,11 @@ static void u_undoredo(int undo, bool do_buf_event) // delete the lines between top and bot and save them in newarray if (oldsize > 0) { - newarray = xmalloc(sizeof(char_u *) * (size_t)oldsize); + newarray = xmalloc(sizeof(char *) * (size_t)oldsize); // delete backwards, it goes faster in most cases long i; linenr_T lnum; - for (lnum = bot - 1, i = oldsize; --i >= 0; --lnum) { + for (lnum = bot - 1, i = oldsize; --i >= 0; lnum--) { // what can we do when we run out of memory? newarray[i] = u_save_line(lnum); // remember we deleted the last line in the buffer, and a @@ -2327,13 +2342,13 @@ static void u_undoredo(int undo, bool do_buf_event) // If the file is empty, there is an empty line 1 that we // should get rid of, by replacing it with the new line if (empty_buffer && lnum == 0) { - ml_replace((linenr_T)1, (char *)uep->ue_array[i], true); + ml_replace((linenr_T)1, uep->ue_array[i], true); } else { - ml_append(lnum, (char *)uep->ue_array[i], (colnr_T)0, false); + ml_append(lnum, uep->ue_array[i], (colnr_T)0, false); } xfree(uep->ue_array[i]); } - xfree((char_u *)uep->ue_array); + xfree(uep->ue_array); } // Adjust marks @@ -2550,7 +2565,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) uhp = curbuf->b_u_newhead; } - char_u msgbuf[80]; + char msgbuf[80]; if (uhp == NULL) { *msgbuf = NUL; } else { @@ -2560,7 +2575,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer == curbuf && wp->w_p_cole > 0) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } } } @@ -2579,21 +2594,25 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) } /// Put the timestamp of an undo header in "buf[buflen]" in a nice format. -void undo_fmt_time(char_u *buf, size_t buflen, time_t tt) +void undo_fmt_time(char *buf, size_t buflen, time_t tt) { if (time(NULL) - tt >= 100) { struct tm curtime; os_localtime_r(&tt, &curtime); + size_t n; if (time(NULL) - tt < (60L * 60L * 12L)) { // within 12 hours - (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime); + n = strftime(buf, buflen, "%H:%M:%S", &curtime); } else { // longer ago - (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); + n = strftime(buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); + } + if (n == 0) { + buf[0] = NUL; } } else { int64_t seconds = time(NULL) - tt; - vim_snprintf((char *)buf, buflen, + vim_snprintf(buf, buflen, NGETTEXT("%" PRId64 " second ago", "%" PRId64 " seconds ago", (uint32_t)seconds), seconds); @@ -2635,15 +2654,15 @@ void ex_undolist(exarg_T *eap) while (uhp != NULL) { if (uhp->uh_prev.ptr == NULL && uhp->uh_walk != nomark && uhp->uh_walk != mark) { - vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d ", uhp->uh_seq, changes); - undo_fmt_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff), uhp->uh_time); + vim_snprintf(IObuff, IOSIZE, "%6ld %7d ", uhp->uh_seq, changes); + undo_fmt_time(IObuff + strlen(IObuff), IOSIZE - strlen(IObuff), uhp->uh_time); if (uhp->uh_save_nr > 0) { - while (STRLEN(IObuff) < 33) { + while (strlen(IObuff) < 33) { STRCAT(IObuff, " "); } - vim_snprintf_add((char *)IObuff, IOSIZE, " %3ld", uhp->uh_save_nr); + vim_snprintf_add(IObuff, IOSIZE, " %3ld", uhp->uh_save_nr); } - GA_APPEND(char_u *, &ga, vim_strsave(IObuff)); + GA_APPEND(char *, &ga, xstrdup(IObuff)); } uhp->uh_walk = mark; @@ -2713,9 +2732,8 @@ void ex_undojoin(exarg_T *eap) } if (get_undolevel(curbuf) < 0) { return; // no entries, nothing to do - } else { - curbuf->b_u_synced = false; // Append next change to last entry } + curbuf->b_u_synced = false; // Append next change to last entry } /// Called after writing or reloading the file and setting b_changed to false. @@ -2744,7 +2762,7 @@ void u_find_first_changed(void) linenr_T lnum; for (lnum = 1; lnum < curbuf->b_ml.ml_line_count && lnum <= uep->ue_size; lnum++) { - if (STRCMP(ml_get_buf(curbuf, lnum, false), uep->ue_array[lnum - 1]) != 0) { + if (strcmp(ml_get_buf(curbuf, lnum, false), uep->ue_array[lnum - 1]) != 0) { clearpos(&(uhp->uh_cursor)); uhp->uh_cursor.lnum = lnum; return; @@ -2917,7 +2935,7 @@ static void u_freeentries(buf_T *buf, u_header_T *uhp, u_header_T **uhpp) #ifdef U_DEBUG uhp->uh_magic = 0; #endif - xfree((char_u *)uhp); + xfree(uhp); buf->b_u_numhead--; } @@ -2927,11 +2945,11 @@ static void u_freeentry(u_entry_T *uep, long n) while (n > 0) { xfree(uep->ue_array[--n]); } - xfree((char_u *)uep->ue_array); + xfree(uep->ue_array); #ifdef U_DEBUG uep->ue_magic = 0; #endif - xfree((char_u *)uep); + xfree(uep); } /// invalidate the undo buffer; called when storage has already been released @@ -2991,11 +3009,11 @@ void u_undoline(void) return; } - char_u *oldp = u_save_line(curbuf->b_u_line_lnum); - ml_replace(curbuf->b_u_line_lnum, (char *)curbuf->b_u_line_ptr, true); + char *oldp = u_save_line(curbuf->b_u_line_lnum); + ml_replace(curbuf->b_u_line_lnum, curbuf->b_u_line_ptr, true); changed_bytes(curbuf->b_u_line_lnum, 0); - extmark_splice_cols(curbuf, (int)curbuf->b_u_line_lnum - 1, 0, (colnr_T)STRLEN(oldp), - (colnr_T)STRLEN(curbuf->b_u_line_ptr), kExtmarkUndo); + extmark_splice_cols(curbuf, (int)curbuf->b_u_line_lnum - 1, 0, (colnr_T)strlen(oldp), + (colnr_T)strlen(curbuf->b_u_line_ptr), kExtmarkUndo); xfree(curbuf->b_u_line_ptr); curbuf->b_u_line_ptr = oldp; @@ -3025,7 +3043,7 @@ void u_blockfree(buf_T *buf) /// Allocate memory and copy curbuf line into it. /// /// @param lnum the line to copy -static char_u *u_save_line(linenr_T lnum) +static char *u_save_line(linenr_T lnum) { return u_save_line_buf(curbuf, lnum); } @@ -3034,9 +3052,9 @@ static char_u *u_save_line(linenr_T lnum) /// /// @param lnum line to copy /// @param buf buffer to copy from -static char_u *u_save_line_buf(buf_T *buf, linenr_T lnum) +static char *u_save_line_buf(buf_T *buf, linenr_T lnum) { - return vim_strsave(ml_get_buf(buf, lnum, false)); + return xstrdup(ml_get_buf(buf, lnum, false)); } /// Check if the 'modified' flag is set, or 'ff' has changed (only need to diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index 4b64f97919..7c065c540b 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -21,11 +21,11 @@ typedef struct { typedef struct u_entry u_entry_T; struct u_entry { - u_entry_T *ue_next; // pointer to next entry in list + u_entry_T *ue_next; // pointer to next entry in list linenr_T ue_top; // number of line above undo block linenr_T ue_bot; // number of line below undo block linenr_T ue_lcount; // linecount when u_save called - char_u **ue_array; // array of lines in undo block + char **ue_array; // array of lines in undo block long ue_size; // number of lines in ue_array #ifdef U_DEBUG int ue_magic; // magic number to check allocation diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c index 59b8d10200..31cb1e8936 100644 --- a/src/nvim/usercmd.c +++ b/src/nvim/usercmd.c @@ -6,19 +6,36 @@ #include <assert.h> #include <inttypes.h> #include <stdbool.h> -#include <stdlib.h> +#include <stdio.h> #include <string.h> +#include "auto/config.h" +#include "lauxlib.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/garray.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" +#include "nvim/highlight_defs.h" +#include "nvim/keycodes.h" #include "nvim/lua/executor.h" +#include "nvim/macros.h" +#include "nvim/mapping.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" +#include "nvim/menu.h" +#include "nvim/message.h" +#include "nvim/option_defs.h" #include "nvim/os/input.h" #include "nvim/runtime.h" +#include "nvim/strings.h" #include "nvim/usercmd.h" +#include "nvim/vim.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -36,8 +53,7 @@ static char e_no_such_user_defined_command_in_current_buffer_str[] /// List of names for completion for ":command" with the EXPAND_ flag. /// Must be alphabetical for completion. -static const char *command_complete[] = -{ +static const char *command_complete[] = { [EXPAND_ARGLIST] = "arglist", [EXPAND_AUGROUP] = "augroup", [EXPAND_BEHAVE] = "behave", @@ -46,7 +62,6 @@ static const char *command_complete[] = [EXPAND_COLORS] = "color", [EXPAND_COMMANDS] = "command", [EXPAND_COMPILER] = "compiler", - [EXPAND_CSCOPE] = "cscope", [EXPAND_USER_DEFINED] = "custom", [EXPAND_USER_LIST] = "customlist", [EXPAND_USER_LUA] = "<Lua function>", @@ -80,6 +95,8 @@ static const char *command_complete[] = [EXPAND_TAGS_LISTFILES] = "tag_listfiles", [EXPAND_USER] = "user", [EXPAND_USER_VARS] = "var", + [EXPAND_BREAKPOINT] = "breakpoint", + [EXPAND_SCRIPTNAMES] = "scriptnames", }; /// List of names of address types. Must be alphabetical for completion. @@ -87,8 +104,7 @@ static struct { cmd_addr_T expand; char *name; char *shortname; -} addr_type_complete[] = -{ +} addr_type_complete[] = { { ADDR_ARGUMENTS, "arguments", "arg" }, { ADDR_LINES, "lines", "line" }, { ADDR_LOADED_BUFFERS, "loaded_buffers", "load" }, @@ -106,7 +122,7 @@ static struct { /// Return NULL if there is no matching command. /// /// @param *p end of the command (possibly including count) -/// @param full set to TRUE for a full match +/// @param full set to true for a full match /// @param xp used for completion, NULL otherwise /// @param complp completion flags or NULL char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) @@ -127,7 +143,7 @@ char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) for (j = 0; j < gap->ga_len; j++) { uc = USER_CMD_GA(gap, j); cp = eap->cmd; - np = (char *)uc->uc_name; + np = uc->uc_name; k = 0; while (k < len && *np != NUL && *cp++ == *np++) { k++; @@ -167,7 +183,7 @@ char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) } if (xp != NULL) { xp->xp_luaref = uc->uc_compl_luaref; - xp->xp_arg = (char *)uc->uc_compl_arg; + xp->xp_arg = uc->uc_compl_arg; xp->xp_script_ctx = uc->uc_script_ctx; xp->xp_script_ctx.sc_lnum += SOURCING_LNUM; } @@ -208,6 +224,7 @@ char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp) return p; } +/// Set completion context for :command const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in) { const char *arg = arg_in; @@ -216,7 +233,7 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in) // Check for attributes while (*arg == '-') { arg++; // Skip "-". - p = (const char *)skiptowhite((const char_u *)arg); + p = (const char *)skiptowhite(arg); if (*p == NUL) { // Cursor is still in the attribute. p = strchr(arg, '='); @@ -248,7 +265,7 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in) } // After the attributes comes the new command name. - p = (const char *)skiptowhite((const char_u *)arg); + p = (const char *)skiptowhite(arg); if (*p == NUL) { xp->xp_context = EXPAND_USER_COMMANDS; xp->xp_pattern = (char *)arg; @@ -259,6 +276,47 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in) return (const char *)skipwhite(p); } +/// Set the completion context for the argument of a user defined command. +const char *set_context_in_user_cmdarg(const char *cmd FUNC_ATTR_UNUSED, const char *arg, + uint32_t argt, int context, expand_T *xp, bool forceit) +{ + if (context == EXPAND_NOTHING) { + return NULL; + } + + if (argt & EX_XFILE) { + // EX_XFILE: file names are handled above. + xp->xp_context = context; + return NULL; + } + + if (context == EXPAND_MENUS) { + return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit); + } + if (context == EXPAND_COMMANDS) { + return arg; + } + if (context == EXPAND_MAPPINGS) { + return (const char *)set_context_in_map_cmd(xp, "map", (char *)arg, forceit, false, false, + CMD_map); + } + // Find start of last argument. + const char *p = arg; + while (*p) { + if (*p == ' ') { + // argument starts after a space + arg = p + 1; + } else if (*p == '\\' && *(p + 1) != NUL) { + p++; // skip over escaped character + } + MB_PTR_ADV(p); + } + xp->xp_pattern = (char *)arg; + xp->xp_context = context; + + return NULL; +} + char *expand_user_command_name(int idx) { return get_user_commands(NULL, idx - CMD_SIZE); @@ -272,15 +330,15 @@ char *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) const buf_T *const buf = prevwin_curwin()->w_buffer; if (idx < buf->b_ucmds.ga_len) { - return (char *)USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; } idx -= buf->b_ucmds.ga_len; if (idx < ucmds.ga_len) { - char *name = (char *)USER_CMD(idx)->uc_name; + char *name = USER_CMD(idx)->uc_name; for (int i = 0; i < buf->b_ucmds.ga_len; i++) { - if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0) { + if (strcmp(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0) { // global command is overruled by buffer-local one return ""; } @@ -297,14 +355,14 @@ char *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) char *get_user_command_name(int idx, int cmdidx) { if (cmdidx == CMD_USER && idx < ucmds.ga_len) { - return (char *)USER_CMD(idx)->uc_name; + return USER_CMD(idx)->uc_name; } if (cmdidx == CMD_USER_BUF) { // In cmdwin, the alternative buffer should be used. const buf_T *const buf = prevwin_curwin()->w_buffer; if (idx < buf->b_ucmds.ga_len) { - return (char *)USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; } } return NULL; @@ -346,9 +404,8 @@ static char *get_command_complete(int arg) { if (arg >= (int)(ARRAY_SIZE(command_complete))) { return NULL; - } else { - return (char *)command_complete[arg]; } + return (char *)command_complete[arg]; } /// Function given to ExpandGeneric() to obtain the list of values for -complete. @@ -360,9 +417,8 @@ char *get_user_cmd_complete(expand_T *xp, int idx) char *cmd_compl = get_command_complete(idx); if (cmd_compl == NULL) { return ""; - } else { - return cmd_compl; } + return cmd_compl; } int cmdcomplete_str_to_type(const char *complete_str) @@ -396,7 +452,7 @@ static void uc_list(char *name, size_t name_len) // Skip commands which don't match the requested prefix and // commands filtered out. - if (STRNCMP(name, cmd->uc_name, name_len) != 0 + if (strncmp(name, cmd->uc_name, name_len) != 0 || message_filtered(cmd->uc_name)) { continue; } @@ -435,7 +491,7 @@ static void uc_list(char *name, size_t name_len) } msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D)); - len = (int)STRLEN(cmd->uc_name) + 4; + len = (int)strlen(cmd->uc_name) + 4; do { msg_putchar(' '); @@ -474,16 +530,16 @@ static void uc_list(char *name, size_t name_len) if (a & (EX_RANGE | EX_COUNT)) { if (a & EX_COUNT) { // -count=N - snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "c", + snprintf(IObuff + len, IOSIZE, "%" PRId64 "c", (int64_t)cmd->uc_def); - len += (int)STRLEN(IObuff + len); + len += (int)strlen(IObuff + len); } else if (a & EX_DFLALL) { IObuff[len++] = '%'; } else if (cmd->uc_def >= 0) { // -range=N - snprintf((char *)IObuff + len, IOSIZE, "%" PRId64 "", + snprintf(IObuff + len, IOSIZE, "%" PRId64 "", (int64_t)cmd->uc_def); - len += (int)STRLEN(IObuff + len); + len += (int)strlen(IObuff + len); } else { IObuff[len++] = '.'; } @@ -498,7 +554,7 @@ static void uc_list(char *name, size_t name_len) if (addr_type_complete[j].expand != ADDR_LINES && addr_type_complete[j].expand == cmd->uc_addr_type) { STRCPY(IObuff + len, addr_type_complete[j].shortname); - len += (int)STRLEN(IObuff + len); + len += (int)strlen(IObuff + len); break; } } @@ -511,7 +567,7 @@ static void uc_list(char *name, size_t name_len) char *cmd_compl = get_command_complete(cmd->uc_compl); if (cmd_compl != NULL) { STRCPY(IObuff + len, get_command_complete(cmd->uc_compl)); - len += (int)STRLEN(IObuff + len); + len += (int)strlen(IObuff + len); } do { @@ -519,7 +575,7 @@ static void uc_list(char *name, size_t name_len) } while (len < 25 - over); IObuff[len] = '\0'; - msg_outtrans((char *)IObuff); + msg_outtrans(IObuff); if (cmd->uc_luaref != LUA_NOREF) { char *fn = nlua_funcref_str(cmd->uc_luaref); @@ -559,8 +615,8 @@ int parse_addr_type_arg(char *value, int vallen, cmd_addr_T *addr_type_arg) int i, a, b; for (i = 0; addr_type_complete[i].expand != ADDR_NONE; i++) { - a = (int)STRLEN(addr_type_complete[i].name) == vallen; - b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0; + a = (int)strlen(addr_type_complete[i].name) == vallen; + b = strncmp(value, addr_type_complete[i].name, (size_t)vallen) == 0; if (a && b) { *addr_type_arg = addr_type_complete[i].expand; break; @@ -607,8 +663,8 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt, if (get_command_complete(i) == NULL) { continue; } - if ((int)STRLEN(command_complete[i]) == valend - && STRNCMP(value, command_complete[i], valend) == 0) { + if ((int)strlen(command_complete[i]) == valend + && strncmp(value, command_complete[i], (size_t)valend) == 0) { *complp = i; if (i == EXPAND_BUFFERS) { *argt |= EX_BUFNAME; @@ -643,7 +699,7 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt, } static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, long *def, int *flags, int *complp, - char_u **compl_arg, cmd_addr_T *addr_type_arg) + char **compl_arg, cmd_addr_T *addr_type_arg) FUNC_ATTR_NONNULL_ALL { char *p; @@ -754,7 +810,7 @@ invalid_count: return FAIL; } - if (parse_compl_arg(val, (int)vallen, complp, argt, (char **)compl_arg) + if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg) == FAIL) { return FAIL; } @@ -816,7 +872,7 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, char *rep_buf = NULL; garray_T *gap; - replace_termcodes(rep, STRLEN(rep), &rep_buf, 0, NULL, CPO_TO_CPO_FLAGS); + replace_termcodes(rep, strlen(rep), &rep_buf, 0, NULL, CPO_TO_CPO_FLAGS); if (rep_buf == NULL) { // Can't replace termcodes - try using the string as is rep_buf = xstrdup(rep); @@ -837,8 +893,8 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, size_t len; cmd = USER_CMD_GA(gap, i); - len = STRLEN(cmd->uc_name); - cmp = STRNCMP(name, cmd->uc_name, name_len); + len = strlen(cmd->uc_name); + cmp = strncmp(name, cmd->uc_name, name_len); if (cmp == 0) { if (name_len < len) { cmp = -1; @@ -883,17 +939,17 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, gap->ga_len++; - cmd->uc_name = (char_u *)p; + cmd->uc_name = p; } - cmd->uc_rep = (char_u *)rep_buf; + cmd->uc_rep = rep_buf; cmd->uc_argt = argt; cmd->uc_def = def; cmd->uc_compl = compl; cmd->uc_script_ctx = current_sctx; cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM; nlua_set_sctx(&cmd->uc_script_ctx); - cmd->uc_compl_arg = (char_u *)compl_arg; + cmd->uc_compl_arg = compl_arg; cmd->uc_compl_luaref = compl_luaref; cmd->uc_preview_luaref = preview_luaref; cmd->uc_addr_type = addr_type; @@ -930,10 +986,10 @@ void ex_command(exarg_T *eap) // Check for attributes while (*p == '-') { p++; - end = (char *)skiptowhite((char_u *)p); - if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &compl, (char_u **)&compl_arg, + end = skiptowhite(p); + if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &compl, &compl_arg, &addr_type_arg) == FAIL) { - return; + goto theend; } p = skipwhite(end); } @@ -943,7 +999,7 @@ void ex_command(exarg_T *eap) end = uc_validate_name(name); if (!end) { emsg(_("E182: Invalid command name")); - return; + goto theend; } name_len = (size_t)(end - name); @@ -954,14 +1010,19 @@ void ex_command(exarg_T *eap) uc_list(name, name_len); } else if (!ASCII_ISUPPER(*name)) { emsg(_("E183: User defined commands must start with an uppercase letter")); - } else if (name_len <= 4 && STRNCMP(name, "Next", name_len) == 0) { + } else if (name_len <= 4 && strncmp(name, "Next", name_len) == 0) { emsg(_("E841: Reserved name, cannot be used for user defined command")); } else if (compl > 0 && (argt & EX_EXTRA) == 0) { emsg(_(e_complete_used_without_allowing_arguments)); } else { uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF, LUA_NOREF, addr_type_arg, LUA_NOREF, eap->forceit); + + return; // success } + +theend: + xfree(compl_arg); } /// ":comclear" @@ -997,7 +1058,7 @@ void ex_delcommand(exarg_T *eap) const char *arg = eap->arg; bool buffer_only = false; - if (STRNCMP(arg, "-buffer", 7) == 0 && ascii_iswhite(arg[7])) { + if (strncmp(arg, "-buffer", 7) == 0 && ascii_iswhite(arg[7])) { buffer_only = true; arg = skipwhite(arg + 7); } @@ -1006,7 +1067,7 @@ void ex_delcommand(exarg_T *eap) for (;;) { for (i = 0; i < gap->ga_len; i++) { cmd = USER_CMD_GA(gap, i); - res = STRCMP(arg, cmd->uc_name); + res = strcmp(arg, cmd->uc_name); if (res <= 0) { break; } @@ -1061,11 +1122,11 @@ bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf, buf[l++] = arg[++pos]; } else { buf[l++] = arg[pos]; - if (ascii_iswhite(arg[pos + 1])) { - *end = pos + 1; - *len = l; - return false; - } + } + if (ascii_iswhite(arg[pos + 1])) { + *end = pos + 1; + *len = l; + return false; } } @@ -1078,7 +1139,7 @@ bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf, } /// split and quote args for <f-args> -static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc, size_t *lenp) +static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t argc, size_t *lenp) { char *buf; char *p; @@ -1198,7 +1259,7 @@ static char *uc_split_args(char *arg, char **args, size_t *arglens, size_t argc, static size_t add_cmd_modifier(char *buf, char *mod_str, bool *multi_mods) { - size_t result = STRLEN(mod_str); + size_t result = strlen(mod_str); if (*multi_mods) { result++; } @@ -1218,7 +1279,7 @@ static size_t add_cmd_modifier(char *buf, char *mod_str, bool *multi_mods) /// was added. /// /// @return the number of bytes added -size_t add_win_cmd_modifers(char *buf, const cmdmod_T *cmod, bool *multi_mods) +size_t add_win_cmd_modifiers(char *buf, const cmdmod_T *cmod, bool *multi_mods) { size_t result = 0; @@ -1237,8 +1298,18 @@ size_t add_win_cmd_modifers(char *buf, const cmdmod_T *cmod, bool *multi_mods) // :tab if (cmod->cmod_tab > 0) { - result += add_cmd_modifier(buf, "tab", multi_mods); + int tabnr = cmod->cmod_tab - 1; + if (tabnr == tabpage_index(curtab)) { + // For compatibility, don't add a tabpage number if it is the same + // as the default number for :tab. + result += add_cmd_modifier(buf, "tab", multi_mods); + } else { + char tab_buf[NUMBUFLEN + 3]; + snprintf(tab_buf, sizeof(tab_buf), "%dtab", tabnr); + result += add_cmd_modifier(buf, tab_buf, multi_mods); + } } + // :topleft if (cmod->cmod_split & WSP_TOP) { result += add_cmd_modifier(buf, "topleft", multi_mods); @@ -1247,6 +1318,10 @@ size_t add_win_cmd_modifers(char *buf, const cmdmod_T *cmod, bool *multi_mods) if (cmod->cmod_split & WSP_VERT) { result += add_cmd_modifier(buf, "vertical", multi_mods); } + // :horizontal + if (cmod->cmod_split & WSP_HOR) { + result += add_cmd_modifier(buf, "horizontal", multi_mods); + } return result; } @@ -1304,12 +1379,12 @@ size_t uc_mods(char *buf, const cmdmod_T *cmod, bool quote) result += add_cmd_modifier(buf, "verbose", &multi_mods); } else { char verbose_buf[NUMBUFLEN]; - snprintf(verbose_buf, NUMBUFLEN, "%dverbose", verbose_value); + snprintf(verbose_buf, sizeof(verbose_buf), "%dverbose", verbose_value); result += add_cmd_modifier(buf, verbose_buf, &multi_mods); } } // flags from cmod->cmod_split - result += add_win_cmd_modifers(buf, cmod, &multi_mods); + result += add_win_cmd_modifiers(buf, cmod, &multi_mods); if (quote && buf != NULL) { buf += result - 2; @@ -1349,7 +1424,7 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar ct_NONE, } type = ct_NONE; - if ((vim_strchr("qQfF", *p) != NULL) && p[1] == '-') { + if ((vim_strchr("qQfF", (uint8_t)(*p)) != NULL) && p[1] == '-') { quote = (*p == 'q' || *p == 'Q') ? 1 : 2; p += 2; l -= 2; @@ -1357,7 +1432,7 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar l++; if (l <= 1) { - type = ct_NONE; + // type = ct_NONE; } else if (STRNICMP(p, "args>", l) == 0) { type = ct_ARGS; } else if (STRNICMP(p, "bang>", l) == 0) { @@ -1401,13 +1476,13 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar switch (quote) { case 0: // No quoting, no splitting - result = STRLEN(eap->arg); + result = strlen(eap->arg); if (buf != NULL) { STRCPY(buf, eap->arg); } break; case 1: // Quote, but don't split - result = STRLEN(eap->arg) + 2; + result = strlen(eap->arg) + 2; for (p = eap->arg; *p; p++) { if (*p == '\\' || *p == '"') { result++; @@ -1471,7 +1546,7 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar size_t num_len; snprintf(num_buf, sizeof(num_buf), "%" PRId64, (int64_t)num); - num_len = STRLEN(num_buf); + num_len = strlen(num_buf); result = num_len; if (quote) { @@ -1569,7 +1644,7 @@ int do_ucmd(exarg_T *eap, bool preview) // Second round: copy result into "buf". buf = NULL; for (;;) { - p = (char *)cmd->uc_rep; // source + p = cmd->uc_rep; // source q = buf; // destination totlen = 0; @@ -1579,10 +1654,10 @@ int do_ucmd(exarg_T *eap, bool preview) end = vim_strchr(start + 1, '>'); } if (buf != NULL) { - for (ksp = p; *ksp != NUL && (char_u)(*ksp) != K_SPECIAL; ksp++) {} - if ((char_u)(*ksp) == K_SPECIAL + for (ksp = p; *ksp != NUL && (uint8_t)(*ksp) != K_SPECIAL; ksp++) {} + if ((uint8_t)(*ksp) == K_SPECIAL && (start == NULL || ksp < start || end == NULL) - && ((char_u)ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)) { + && ((uint8_t)ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)) { // K_SPECIAL has been put in the buffer as K_SPECIAL // KS_SPECIAL KE_FILLER, like for mappings, but // do_cmdline() doesn't handle that, so convert it back. @@ -1633,7 +1708,7 @@ int do_ucmd(exarg_T *eap, bool preview) break; } - totlen += STRLEN(p); // Add on the trailing characters + totlen += strlen(p); // Add on the trailing characters buf = xmalloc(totlen + 1); } diff --git a/src/nvim/usercmd.h b/src/nvim/usercmd.h index 637862b216..b6bf6c1e33 100644 --- a/src/nvim/usercmd.h +++ b/src/nvim/usercmd.h @@ -1,17 +1,22 @@ #ifndef NVIM_USERCMD_H #define NVIM_USERCMD_H +#include <stdint.h> + +#include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/garray.h" +#include "nvim/types.h" typedef struct ucmd { - char_u *uc_name; // The command name + char *uc_name; // The command name uint32_t uc_argt; // The argument type - char_u *uc_rep; // The command's replacement string + char *uc_rep; // The command's replacement string long uc_def; // The default value for a range/count int uc_compl; // completion type cmd_addr_T uc_addr_type; // The command's address type sctx_T uc_script_ctx; // SCTX where the command was defined - char_u *uc_compl_arg; // completion argument if any + char *uc_compl_arg; // completion argument if any LuaRef uc_compl_luaref; // Reference to Lua completion function LuaRef uc_preview_luaref; // Reference to Lua preview function LuaRef uc_luaref; // Reference to Lua function diff --git a/src/nvim/version.c b/src/nvim/version.c index 0667243bc3..417e5116a5 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -7,34 +7,43 @@ /// Vim originated from Stevie version 3.6 (Fish disk 217) by GRWalter (Fred). #include <assert.h> -#include <inttypes.h> #include <limits.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include "auto/config.h" +#include "auto/versiondef.h" // version info generated by the build system +#include "auto/versiondef_git.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/drawscreen.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/gettext.h" +#include "nvim/globals.h" #include "nvim/grid.h" -#include "nvim/iconv.h" +#include "nvim/highlight_defs.h" #include "nvim/lua/executor.h" -#include "nvim/memline.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/option_defs.h" +#include "nvim/os/os_defs.h" #include "nvim/strings.h" #include "nvim/version.h" #include "nvim/vim.h" -// version info generated by the build system -#include "auto/versiondef.h" - // for ":version", ":intro", and "nvim --version" #ifndef NVIM_VERSION_MEDIUM # define NVIM_VERSION_MEDIUM "v" STR(NVIM_VERSION_MAJOR) \ "." STR(NVIM_VERSION_MINOR) "." STR(NVIM_VERSION_PATCH) \ NVIM_VERSION_PRERELEASE #endif -#define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM +#define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM // NOLINT(bugprone-suspicious-missing-comma) char *Version = VIM_VERSION_SHORT; char *longVersion = NVIM_VERSION_LONG; @@ -55,29 +64,593 @@ static char *features[] = { "-acl", #endif -#if defined(HAVE_ICONV) - "+iconv", -#else - "-iconv", -#endif - -#ifdef FEAT_TUI "+tui", -#else - "-tui", -#endif NULL }; // clang-format off static const int included_patches[] = { - 1850, + 2424, + 2423, + 2422, + 2421, + // 2420, + 2419, + // 2418, + 2417, + 2416, + // 2415, + 2414, + 2413, + 2412, + 2411, + 2410, + 2409, + 2408, + 2407, + 2406, + 2405, + 2404, + // 2403, + 2402, + 2401, + 2400, + // 2399, + 2398, + 2397, + 2396, + 2395, + 2394, + 2393, + 2392, + 2391, + 2390, + 2389, + 2388, + 2387, + // 2386, + 2385, + 2384, + 2383, + 2382, + // 2381, + 2380, + 2379, + 2378, + 2377, + 2376, + 2375, + 2374, + // 2373, + 2372, + // 2371, + 2370, + // 2369, + 2368, + 2367, + 2366, + 2365, + 2364, + 2363, + // 2362, + 2361, + 2360, + 2359, + 2358, + 2357, + 2356, + 2355, + 2354, + 2353, + 2352, + // 2351, + 2350, + 2349, + 2348, + 2347, + 2346, + 2345, + 2344, + 2343, + 2342, + 2341, + 2340, + 2339, + 2338, + // 2337, + 2336, + 2335, + // 2334, + 2333, + 2332, + 2331, + 2330, + 2329, + 2328, + 2327, + 2326, + 2325, + // 2324, + 2323, + 2322, + 2321, + 2320, + 2319, + 2318, + 2317, + 2316, + 2315, + 2314, + 2313, + 2312, + 2311, + 2310, + 2309, + // 2308, + // 2307, + 2306, + 2305, + 2304, + 2303, + 2302, + 2301, + // 2300, + // 2299, + // 2298, + 2297, + // 2296, + // 2295, + 2294, + 2293, + // 2292, + 2291, + 2290, + 2289, + // 2288, + // 2287, + // 2286, + 2285, + 2284, + 2283, + 2282, + 2281, + 2280, + // 2279, + 2278, + // 2277, + // 2276, + 2275, + 2274, + // 2273, + 2272, + 2271, + 2270, + 2269, + 2268, + 2267, + // 2266, + // 2265, + 2264, + 2263, + 2262, + 2261, + 2260, + 2259, + 2258, + 2257, + 2256, + 2255, + 2254, + 2253, + 2252, + // 2251, + // 2250, + 2249, + 2248, + 2247, + 2246, + 2245, + 2244, + 2243, + 2242, + // 2241, + // 2240, + 2239, + 2238, + 2237, + 2236, + 2235, + 2234, + 2233, + // 2232, + 2231, + // 2230, + 2229, + 2228, + 2227, + 2226, + 2225, + 2224, + 2223, + 2222, + 2221, + 2220, + // 2219, + 2218, + 2217, + // 2216, + // 2215, + 2214, + // 2213, + 2212, + // 2211, + // 2210, + 2209, + // 2208, + 2207, + 2206, + 2205, + 2204, + 2203, + 2202, + 2201, + // 2200, + 2199, + 2198, + 2197, + 2196, + // 2195, + // 2194, + // 2193, + // 2192, + 2191, + 2190, + // 2189, + 2188, + 2187, + 2186, + 2185, + 2184, + 2183, + 2182, + // 2181, + 2180, + 2179, + 2178, + 2177, + // 2176, + 2175, + 2174, + 2173, + 2172, + 2171, + 2170, + 2169, + 2168, + 2167, + 2166, + 2165, + // 2164, + 2163, + 2162, + 2161, + 2160, + 2159, + // 2158, + 2157, + // 2156, + // 2155, + 2154, + // 2153, + 2152, + 2151, + 2150, + 2149, + // 2148, + 2147, + // 2146, + 2145, + 2144, + 2143, + // 2142, + 2141, + 2140, + // 2139, + 2138, + 2137, + 2136, + 2135, + 2134, + 2133, + 2132, + 2131, + 2130, + 2129, + 2128, + // 2127, + 2126, + 2125, + 2124, + 2123, + 2122, + // 2121, + 2120, + 2119, + 2118, + 2117, + 2116, + 2115, + // 2114, + 2113, + 2112, + 2111, + // 2110, + // 2109, + 2108, + // 2107, + 2106, + 2105, + 2104, + 2103, + 2102, + 2101, + 2100, + // 2099, + 2098, + 2097, + 2096, + 2095, + // 2094, + // 2093, + // 2092, + 2091, + 2090, + 2089, + 2088, + 2087, + 2086, + // 2085, + 2084, + 2083, + 2082, + 2081, + // 2080, + 2079, + 2078, + // 2077, + // 2076, + 2075, + 2074, + 2073, + 2072, + // 2071, + // 2070, + // 2069, + // 2068, + // 2067, + // 2066, + 2065, + 2064, + 2063, + // 2062, + 2061, + 2060, + 2059, + 2058, + 2057, + 2056, + 2055, + 2054, + // 2053, + 2052, + 2051, + 2050, + 2049, + // 2048, + // 2047, + // 2046, + 2045, + // 2044, + 2043, + 2042, + 2041, + // 2040, + // 2039, + 2038, + 2037, + 2036, + 2035, + 2034, + 2033, + // 2032, + 2031, + 2030, + 2029, + 2028, + 2027, + 2026, + 2025, + 2024, + 2023, + // 2022, + // 2021, + 2020, + 2019, + 2018, + 2017, + 2016, + 2015, + 2014, + 2013, + 2012, + 2011, + 2010, + // 2009, + // 2008, + 2007, + 2006, + 2005, + // 2004, + 2003, + 2002, + 2001, + 2000, + // 1999, + // 1998, + // 1997, + // 1996, + 1995, + 1994, + // 1993, + 1992, + 1991, + 1990, + // 1989, + 1988, + // 1987, + // 1986, + // 1985, + 1984, + 1983, + // 1982, + // 1981, + 1980, + // 1979, + // 1978, + 1977, + 1976, + 1975, + 1974, + 1973, + 1972, + 1971, + 1970, + // 1969, + // 1968, + 1967, + 1966, + 1965, + // 1964, + // 1963, + 1962, + 1961, + 1960, + // 1959, + 1958, + // 1957, + 1956, + 1955, + // 1954, + 1953, + 1952, + 1951, + 1950, + 1949, + 1948, + 1947, + 1946, + // 1945, + // 1944, + // 1943, + 1942, + 1941, + // 1940, + // 1939, + 1938, + 1937, + // 1936, + 1935, + // 1934, + 1933, + 1932, + 1931, + 1930, + // 1929, + // 1928, + 1927, + 1926, + 1925, + 1924, + 1923, + 1922, + 1921, + // 1920, + // 1919, + // 1918, + // 1917, + 1916, + 1915, + 1914, + 1913, + 1912, + 1911, + 1910, + 1909, + // 1908, + // 1907, + // 1906, + // 1905, + // 1904, + 1903, + // 1902, + 1901, + 1900, + 1899, + 1898, + 1897, + 1896, + 1895, + 1894, + 1893, + // 1892, + // 1891, + 1890, + 1889, + 1888, + 1887, + 1886, + 1885, + // 1884, + 1883, + // 1882, + 1881, + // 1880, + 1879, + 1878, + 1877, + 1876, + 1875, + // 1874, + 1873, + 1872, + // 1871, + 1870, + 1869, + 1868, + 1867, + // 1866, + 1865, + 1864, + 1863, + 1862, + 1861, + 1860, + 1859, + 1858, + 1857, + 1856, + 1855, + 1854, + // 1853, + 1852, + // 1851, + // 1850, 1849, 1848, 1847, 1846, - 1845, - 1844, + // 1845, + // 1844, 1843, 1842, 1841, @@ -94,7 +667,7 @@ static const int included_patches[] = { 1830, 1829, 1828, - 1827, + // 1827, 1826, 1825, 1824, @@ -102,58 +675,58 @@ static const int included_patches[] = { 1822, 1821, 1820, - 1819, + // 1819, 1818, 1817, 1816, 1815, - 1814, - 1813, + // 1814, + // 1813, 1812, - 1811, - 1810, + // 1811, + // 1810, 1809, 1808, 1807, 1806, 1805, - // 1804, + 1804, 1803, - 1802, + // 1802, 1801, 1800, - 1799, + // 1799, 1798, 1797, 1796, 1795, - // 1794, + 1794, 1793, 1792, 1791, 1790, - 1789, + // 1789, 1788, - 1787, - 1786, + // 1787, + // 1786, 1785, - 1784, + // 1784, 1783, 1782, 1781, 1780, - 1779, - 1778, + // 1779, + // 1778, 1777, 1776, 1775, 1774, - 1773, + // 1773, 1772, 1771, - 1770, + // 1770, 1769, - 1768, + // 1768, 1767, 1766, 1765, @@ -166,34 +739,34 @@ static const int included_patches[] = { 1758, 1757, 1756, - 1755, - 1754, - 1753, - 1752, - 1751, + // 1755, + // 1754, + // 1753, + // 1752, + // 1751, 1750, 1749, 1748, 1747, 1746, 1745, - // 1744, - // 1743, + 1744, + 1743, 1742, 1741, 1740, 1739, 1738, 1737, - 1736, + // 1736, 1735, 1734, - 1733, - // 1732, + // 1733, + 1732, 1731, - 1730, + // 1730, 1729, - 1728, + // 1728, 1727, 1726, 1725, @@ -202,27 +775,27 @@ static const int included_patches[] = { 1722, 1721, 1720, - 1719, - 1718, + // 1719, + // 1718, 1717, 1716, 1715, - 1714, - 1713, + // 1714, + // 1713, 1712, - 1711, - 1710, - 1709, + // 1711, + // 1710, + // 1709, 1708, - 1707, + // 1707, 1706, 1705, 1704, - 1703, + // 1703, 1702, 1701, - 1700, - 1699, + // 1700, + // 1699, 1698, 1697, 1696, @@ -231,7 +804,7 @@ static const int included_patches[] = { 1693, 1692, 1691, - 1690, + // 1690, 1689, 1688, 1687, @@ -240,140 +813,140 @@ static const int included_patches[] = { 1684, 1683, 1682, - 1681, + // 1681, 1680, 1679, - 1678, + // 1678, 1677, - 1676, - 1675, + // 1676, + // 1675, 1674, - 1673, + // 1673, 1672, 1671, 1670, 1669, 1668, 1667, - 1666, - 1665, + // 1666, + // 1665, 1664, - 1663, + // 1663, 1662, 1661, 1660, - 1659, + // 1659, 1658, 1657, - 1656, - 1655, + // 1656, + // 1655, 1654, 1653, 1652, 1651, 1650, - 1649, + // 1649, 1648, 1647, - 1646, + // 1646, 1645, 1644, 1643, 1642, - 1641, + // 1641, 1640, 1639, 1638, 1637, - 1636, + // 1636, 1635, 1634, 1633, 1632, 1631, 1630, - 1629, - 1628, + // 1629, + // 1628, 1627, - 1626, + // 1626, 1625, 1624, 1623, - 1622, + // 1622, 1621, - 1620, + // 1620, 1619, 1618, - 1617, + // 1617, 1616, - 1615, + // 1615, 1614, 1613, - 1612, + // 1612, 1611, 1610, - 1609, + // 1609, 1608, - 1607, + // 1607, 1606, 1605, 1604, - 1603, - 1602, + // 1603, + // 1602, 1601, - 1600, - 1599, + // 1600, + // 1599, 1598, - 1597, - 1596, + // 1597, + // 1596, 1595, 1594, 1593, - // 1592, + 1592, 1591, 1590, - 1589, + // 1589, 1588, 1587, - 1586, + // 1586, 1585, - 1584, - 1583, + // 1584, + // 1583, 1582, 1581, - 1580, + // 1580, 1579, 1578, - 1577, + // 1577, 1576, 1575, - 1574, - 1573, + // 1574, + // 1573, 1572, - 1571, + // 1571, 1570, 1569, 1568, 1567, 1566, - 1565, + // 1565, 1564, 1563, - 1562, - 1561, - 1560, - 1559, - 1558, + // 1562, + // 1561, + // 1560, + // 1559, + // 1558, 1557, 1556, - 1555, + // 1555, 1554, - 1553, + // 1553, 1552, - 1551, - 1550, + // 1551, + // 1550, 1549, - 1548, + // 1548, 1547, 1546, 1545, @@ -383,59 +956,59 @@ static const int included_patches[] = { 1541, 1540, 1539, - 1538, - 1537, + // 1538, + // 1537, 1536, 1535, - 1534, + // 1534, 1533, 1532, 1531, 1530, 1529, 1528, - 1527, - 1526, - 1525, + // 1527, + // 1526, + // 1525, 1524, - 1523, - 1522, - 1521, - 1520, + // 1523, + // 1522, + // 1521, + // 1520, 1519, - 1518, - 1517, + // 1518, + // 1517, 1516, - 1515, + // 1515, 1514, - 1513, + // 1513, 1512, - 1511, + // 1511, 1510, 1509, - 1508, + // 1508, 1507, 1506, 1505, 1504, 1503, - 1502, + // 1502, 1501, 1500, - 1499, + // 1499, 1498, - 1497, - 1496, - 1495, - 1494, - 1493, + // 1497, + // 1496, + // 1495, + // 1494, + // 1493, 1492, 1491, 1490, 1489, 1488, 1487, - 1486, + // 1486, 1485, 1484, 1483, @@ -448,7 +1021,7 @@ static const int included_patches[] = { 1476, 1475, 1474, - 1473, + // 1473, 1472, 1471, 1470, @@ -458,83 +1031,83 @@ static const int included_patches[] = { 1466, 1465, 1464, - 1463, + // 1463, 1462, 1461, - 1460, - 1459, + // 1460, + // 1459, 1458, 1457, 1456, - 1455, + // 1455, 1454, - 1453, - 1452, - 1451, - 1450, - 1449, - 1448, - 1447, - 1446, - 1445, - 1444, - 1443, - 1442, - 1441, + // 1453, + // 1452, + // 1451, + // 1450, + // 1449, + // 1448, + // 1447, + // 1446, + // 1445, + // 1444, + // 1443, + // 1442, + // 1441, 1440, 1439, - 1438, + // 1438, 1437, 1436, 1435, 1434, 1433, - 1432, - 1431, - 1430, - 1429, - 1428, - 1427, - 1426, + // 1432, + // 1431, + // 1430, + // 1429, + // 1428, + // 1427, + // 1426, 1425, 1424, - 1423, - 1422, - 1421, - 1420, - 1419, + // 1423, + // 1422, + // 1421, + // 1420, + // 1419, 1418, - 1417, - 1416, + // 1417, + // 1416, 1415, - 1414, - 1413, + // 1414, + // 1413, 1412, 1411, - 1410, + // 1410, 1409, - 1408, - 1407, - 1406, - 1405, + // 1408, + // 1407, + // 1406, + // 1405, 1404, 1403, - 1402, + // 1402, 1401, - 1400, - 1399, + // 1400, + // 1399, 1398, 1397, - 1396, - 1395, + // 1396, + // 1395, 1394, 1393, 1392, - 1391, + // 1391, 1390, - 1389, - 1388, - 1387, + // 1389, + // 1388, + // 1387, 1386, 1385, 1384, @@ -545,7 +1118,7 @@ static const int included_patches[] = { 1379, 1378, 1377, - 1376, + // 1376, 1375, 1374, 1373, @@ -557,12 +1130,12 @@ static const int included_patches[] = { 1367, 1366, 1365, - 1364, + // 1364, 1363, 1362, 1361, 1360, - 1359, + // 1359, 1358, 1357, 1356, @@ -570,38 +1143,38 @@ static const int included_patches[] = { 1354, 1353, 1352, - 1351, + // 1351, 1350, 1349, 1348, 1347, 1346, 1345, - 1344, - 1343, + // 1344, + // 1343, 1342, - 1341, - 1340, + // 1341, + // 1340, 1339, 1338, - 1337, + // 1337, 1336, - 1335, + // 1335, 1334, - 1333, - 1332, + // 1333, + // 1332, 1331, 1330, - 1329, - 1328, + // 1329, + // 1328, 1327, - 1326, + // 1326, 1325, 1324, 1323, 1322, - 1321, - 1320, + // 1321, + // 1320, 1319, 1318, 1317, @@ -621,7 +1194,7 @@ static const int included_patches[] = { 1303, 1302, 1301, - // 1300, + 1300, 1299, 1298, 1297, @@ -641,11 +1214,11 @@ static const int included_patches[] = { 1283, 1282, 1281, - 1280, + // 1280, 1279, - 1278, + // 1278, 1277, - 1276, + // 1276, 1275, 1274, 1273, @@ -654,15 +1227,15 @@ static const int included_patches[] = { 1270, 1269, 1268, - 1267, + // 1267, 1266, - 1265, + // 1265, 1264, 1263, 1262, 1261, 1260, - 1259, + // 1259, 1258, 1257, 1256, @@ -695,15 +1268,15 @@ static const int included_patches[] = { 1229, 1228, 1227, - 1226, + // 1226, 1225, - 1224, + // 1224, 1223, 1222, 1221, 1220, 1219, - 1218, + // 1218, 1217, 1216, 1215, @@ -785,10 +1358,10 @@ static const int included_patches[] = { 1139, 1138, 1137, - 1136, + // 1136, 1135, 1134, - 1133, + // 1133, 1132, 1131, 1130, @@ -815,10 +1388,10 @@ static const int included_patches[] = { 1109, 1108, 1107, - 1106, + // 1106, 1105, 1104, - 1103, + // 1103, 1102, 1101, 1100, @@ -831,14 +1404,14 @@ static const int included_patches[] = { 1093, 1092, 1091, - 1090, + // 1090, 1089, 1088, 1087, 1086, 1085, 1084, - 1083, + // 1083, 1082, 1081, 1080, @@ -877,8 +1450,8 @@ static const int included_patches[] = { 1047, 1046, 1045, - 1044, - 1043, + // 1044, + // 1043, 1042, 1041, 1040, @@ -886,14 +1459,14 @@ static const int included_patches[] = { 1038, 1037, 1036, - 1035, + // 1035, 1034, 1033, 1032, 1031, 1030, 1029, - 1028, + // 1028, 1027, 1026, 1025, @@ -902,7 +1475,7 @@ static const int included_patches[] = { 1022, 1021, 1020, - 1019, + // 1019, 1018, 1017, 1016, @@ -917,7 +1490,7 @@ static const int included_patches[] = { 1007, 1006, 1005, - 1004, + // 1004, 1003, 1002, 1001, @@ -944,15 +1517,15 @@ static const int included_patches[] = { 980, 979, 978, - 977, + // 977, 976, 975, 974, 973, 972, 971, - 970, - 969, + // 970, + // 969, 968, 967, 966, @@ -968,7 +1541,7 @@ static const int included_patches[] = { 956, 955, 954, - 953, + // 953, 952, 951, 950, @@ -981,7 +1554,7 @@ static const int included_patches[] = { 943, 942, 941, - 940, + // 940, 939, 938, 937, @@ -993,7 +1566,7 @@ static const int included_patches[] = { 931, 930, 929, - 928, + // 928, 927, 926, 925, @@ -1003,19 +1576,19 @@ static const int included_patches[] = { 921, 920, 919, - 918, + // 918, 917, 916, 915, - 914, + // 914, 913, 912, 911, 910, - 909, + // 909, 908, 907, - 906, + // 906, 905, 904, 903, @@ -1026,12 +1599,12 @@ static const int included_patches[] = { 898, 897, 896, - 895, - 894, + // 895, + // 894, 893, 892, 891, - 890, + // 890, 889, 888, 887, @@ -1041,30 +1614,30 @@ static const int included_patches[] = { 883, 882, 881, - 880, - 879, + // 880, + // 879, 878, 877, - 876, + // 876, 875, 874, 873, 872, 871, - 870, + // 870, 869, 868, 867, 866, 865, 864, - 863, + // 863, 862, 861, 860, 859, 858, - 857, + // 857, 856, 855, 854, @@ -1076,7 +1649,7 @@ static const int included_patches[] = { 848, 847, 846, - 845, + // 845, 844, 843, 842, @@ -1097,16 +1670,16 @@ static const int included_patches[] = { 827, 826, 825, - 824, + // 824, 823, 822, 821, - 820, + // 820, 819, 818, 817, 816, - 815, + // 815, 814, 813, 812, @@ -1151,16 +1724,16 @@ static const int included_patches[] = { 773, 772, 771, - 770, + // 770, 769, - 768, + // 768, 767, 766, 765, 764, 763, 762, - 761, + // 761, 760, 759, 758, @@ -1211,14 +1784,14 @@ static const int included_patches[] = { 713, 712, 711, - 710, + // 710, 709, 708, - 707, + // 707, 706, 705, 704, - 703, + // 703, 702, 701, 700, @@ -1227,26 +1800,26 @@ static const int included_patches[] = { 697, 696, 695, - 694, + // 694, 693, 692, - 691, - 690, - 689, - 688, + // 691, + // 690, + // 689, + // 688, 687, 686, 685, - 684, + // 684, 683, - 682, - 681, + // 682, + // 681, 680, 679, 678, 677, - 676, - 675, + // 676, + // 675, 674, 673, 672, @@ -1254,11 +1827,11 @@ static const int included_patches[] = { 670, 669, 668, - 667, + // 667, 666, - 665, + // 665, 664, - 663, + // 663, 662, 661, 660, @@ -1266,8 +1839,8 @@ static const int included_patches[] = { 658, 657, 656, - 655, - 654, + // 655, + // 654, 653, 652, 651, @@ -1278,16 +1851,16 @@ static const int included_patches[] = { 646, 645, 644, - 643, + // 643, 642, 641, 640, - 639, - 638, + // 639, + // 638, 637, - 636, + // 636, 635, - 634, + // 634, 633, 632, 631, @@ -1309,7 +1882,7 @@ static const int included_patches[] = { 615, 614, 613, - 612, + // 612, 611, 610, 609, @@ -1320,7 +1893,7 @@ static const int included_patches[] = { 604, 603, 602, - 601, + // 601, 600, 599, 598, @@ -1339,10 +1912,10 @@ static const int included_patches[] = { 585, 584, 583, - 582, + // 582, 581, 580, - 579, + // 579, 578, 577, 576, @@ -1496,7 +2069,7 @@ static const int included_patches[] = { 428, 427, 426, - 425, + // 425, 424, 423, 422, @@ -1554,7 +2127,7 @@ static const int included_patches[] = { 370, 369, 368, - 367, + // 367, 366, 365, 364, @@ -1593,7 +2166,7 @@ static const int included_patches[] = { 331, 330, 329, - 328, + // 328, 327, 326, 325, @@ -1609,7 +2182,7 @@ static const int included_patches[] = { 315, 314, 313, - 312, + // 312, 311, 310, 309, @@ -1617,7 +2190,7 @@ static const int included_patches[] = { 307, 306, 305, - 304, + // 304, 303, 302, 301, @@ -1763,7 +2336,7 @@ static const int included_patches[] = { 161, 160, 159, - 158, + // 158, 157, 156, 155, @@ -1834,7 +2407,7 @@ static const int included_patches[] = { 90, 89, 88, - 87, + // 87, 86, 85, 84, @@ -1849,21 +2422,21 @@ static const int included_patches[] = { 75, 74, 73, - 72, + // 72, 71, 70, 69, 68, 67, 66, - 65, + // 65, 64, 63, - 62, + // 62, 61, 60, 59, - 58, + // 58, 57, 56, 55, @@ -1871,8 +2444,8 @@ static const int included_patches[] = { 53, 52, 51, - 50, - 49, + // 50, + // 49, 48, 47, 46, @@ -1921,7 +2494,7 @@ static const int included_patches[] = { 3, 2, 1, - 0, + // 0, }; // clang-format on @@ -2016,7 +2589,7 @@ void ex_version(exarg_T *eap) /// Output a string for the version message. If it's going to wrap, output a /// newline, unless the message is too long to fit on the screen anyway. -/// When "wrap" is TRUE wrap the string in []. +/// When "wrap" is true wrap the string in []. /// @param s /// @param wrap static void version_msg_wrap(char *s, int wrap) @@ -2072,7 +2645,7 @@ void list_in_columns(char **items, int size, int current) // Find the length of the longest item, use that + 1 as the column width. int i; for (i = 0; size < 0 ? items[i] != NULL : i < size; i++) { - int l = vim_strsize((char *)items[i]) + (i == current ? 2 : 0); + int l = vim_strsize(items[i]) + (i == current ? 2 : 0); if (l > width) { width = l; @@ -2084,7 +2657,7 @@ void list_in_columns(char **items, int size, int current) if (Columns < width) { // Not enough screen columns - show one per line for (i = 0; i < item_count; i++) { - version_msg_wrap((char *)items[i], i == current); + version_msg_wrap(items[i], i == current); if (msg_col > 0 && i < item_count - 1) { msg_putchar('\n'); } @@ -2106,7 +2679,7 @@ void list_in_columns(char **items, int size, int current) if (idx == current) { msg_putchar('['); } - msg_puts((char *)items[idx]); + msg_puts(items[idx]); if (idx == current) { msg_putchar(']'); } @@ -2200,7 +2773,7 @@ void maybe_intro_message(void) if (buf_is_empty(curbuf) && (curbuf->b_fname == NULL) && (firstwin->w_next == NULL) - && (vim_strchr((char *)p_shm, SHM_INTRO) == NULL)) { + && (vim_strchr(p_shm, SHM_INTRO) == NULL)) { intro_message(false); } } @@ -2209,7 +2782,7 @@ void maybe_intro_message(void) /// Only used when starting Vim on an empty file, without a file name. /// Or with the ":intro" command (for Sven :-). /// -/// @param colon TRUE for ":intro" +/// @param colon true for ":intro" void intro_message(int colon) { int i; @@ -2217,17 +2790,21 @@ void intro_message(int colon) long blanklines; int sponsor; char *p; + char *mesg; + int mesg_size; static char *(lines[]) = { N_(NVIM_VERSION_LONG), "", N_("Nvim is open source and freely distributable"), - N_("https://neovim.io/#chat"), + "https://neovim.io/#chat", "", N_("type :help nvim<Enter> if you are new! "), N_("type :checkhealth<Enter> to optimize Nvim"), N_("type :q<Enter> to exit "), N_("type :help<Enter> for help "), "", + N_("type :help news<Enter> to see changes in v%s.%s"), + "", N_("Help poor children in Uganda!"), N_("type :help iccf<Enter> for information "), }; @@ -2236,7 +2813,7 @@ void intro_message(int colon) size_t lines_size = ARRAY_SIZE(lines); assert(lines_size <= LONG_MAX); - blanklines = Rows - ((long)lines_size - 1l); + blanklines = Rows - ((long)lines_size - 1L); // Don't overwrite a statusline. Depends on 'cmdheight'. if (p_ls > 1) { @@ -2256,27 +2833,50 @@ void intro_message(int colon) row = blanklines / 2; if (((row >= 2) && (Columns >= 50)) || colon) { - for (i = 0; i < (int)ARRAY_SIZE(lines); ++i) { + for (i = 0; i < (int)ARRAY_SIZE(lines); i++) { p = lines[i]; + mesg = NULL; + mesg_size = 0; + + if (strstr(p, "news") != NULL) { + p = _(p); + mesg_size = snprintf(NULL, 0, p, + STR(NVIM_VERSION_MAJOR), STR(NVIM_VERSION_MINOR)); + assert(mesg_size > 0); + mesg = xmallocz((size_t)mesg_size); + snprintf(mesg, (size_t)mesg_size + 1, p, + STR(NVIM_VERSION_MAJOR), STR(NVIM_VERSION_MINOR)); + } if (sponsor != 0) { if (strstr(p, "children") != NULL) { - p = sponsor < 0 - ? N_("Sponsor Vim development!") - : N_("Become a registered Vim user!"); - } else if (strstr(p, "iccf") != NULL) { - p = sponsor < 0 - ? N_("type :help sponsor<Enter> for information ") - : N_("type :help register<Enter> for information "); - } else if (strstr(p, "Orphans") != NULL) { - p = N_("menu Help->Sponsor/Register for information "); + mesg = sponsor < 0 + ? _("Sponsor Vim development!") + : _("Become a registered Vim user!"); + } + if (strstr(p, "iccf") != NULL) { + mesg = sponsor < 0 + ? _("type :help sponsor<Enter> for information ") + : _("type :help register<Enter> for information "); + } + } + + if (mesg == NULL) { + if (*p != NUL) { + mesg = _(p); + } else { + mesg = ""; } } - if (*p != NUL) { - do_intro_line(row, (char_u *)_(p), 0); + if (*mesg != NUL) { + do_intro_line(row, mesg, 0); } row++; + + if (mesg_size > 0) { + XFREE_CLEAR(mesg); + } } } @@ -2287,15 +2887,14 @@ void intro_message(int colon) } } -static void do_intro_line(long row, char_u *mesg, int attr) +static void do_intro_line(long row, char *mesg, int attr) { - long col; - char_u *p; + char *p; int l; int clen; // Center the message horizontally. - col = vim_strsize((char *)mesg); + long col = vim_strsize(mesg); col = (Columns - col) / 2; @@ -2310,8 +2909,8 @@ static void do_intro_line(long row, char_u *mesg, int attr) for (l = 0; p[l] != NUL && (l == 0 || (p[l] != '<' && p[l - 1] != '>')); l++) { - clen += ptr2cells((char *)p + l); - l += utfc_ptr2len((char *)p + l) - 1; + clen += ptr2cells(p + l); + l += utfc_ptr2len(p + l) - 1; } assert(row <= INT_MAX && col <= INT_MAX); grid_puts_len(&default_grid, p, l, (int)row, (int)col, @@ -2325,7 +2924,8 @@ static void do_intro_line(long row, char_u *mesg, int attr) /// @param eap void ex_intro(exarg_T *eap) { + // TODO(bfredl): use msg_grid instead! screenclear(); - intro_message(TRUE); - wait_return(TRUE); + intro_message(true); + wait_return(true); } diff --git a/src/nvim/version.h b/src/nvim/version.h index a19e863152..484350edee 100644 --- a/src/nvim/version.h +++ b/src/nvim/version.h @@ -14,7 +14,7 @@ extern char *longVersion; // Values that change for a new release #define VIM_VERSION_MAJOR 8 -#define VIM_VERSION_MINOR 0 +#define VIM_VERSION_MINOR 1 // Values based on the above #define VIM_VERSION_MAJOR_STR STR(VIM_VERSION_MAJOR) diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 09b949bb20..c4baa911f1 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -1,7 +1,7 @@ #ifndef NVIM_VIM_H #define NVIM_VIM_H -#include "nvim/pos.h" // for linenr_T, MAXCOL, etc... +#include "nvim/pos.h" #include "nvim/types.h" // Some defines from the old feature.h @@ -138,7 +138,6 @@ enum { EXPAND_USER_LIST, EXPAND_USER_LUA, EXPAND_SHELLCMD, - EXPAND_CSCOPE, EXPAND_SIGN, EXPAND_PROFILE, EXPAND_BEHAVE, @@ -155,6 +154,8 @@ enum { EXPAND_MAPCLEAR, EXPAND_ARGLIST, EXPAND_DIFF_BUFFERS, + EXPAND_BREAKPOINT, + EXPAND_SCRIPTNAMES, EXPAND_CHECKHEALTH, EXPAND_LUA, }; @@ -167,15 +168,6 @@ enum { #define MIN_SWAP_PAGE_SIZE 1048 #define MAX_SWAP_PAGE_SIZE 50000 -// Boolean constants - -#ifndef TRUE -# define FALSE 0 // note: this is an int, not a long! -# define TRUE 1 -#endif - -#define MAYBE 2 // sometimes used for a variant on TRUE - #define STATUS_HEIGHT 1 // height of a status line under a window #define QF_WINHEIGHT 10 // default height for quickfix window @@ -205,17 +197,11 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() // defines to avoid typecasts from (char_u *) to (char *) and back // (vim_strchr() is now in strings.c) -#define STRLEN(s) strlen((char *)(s)) -#ifdef HAVE_STRNLEN -# define STRNLEN(s, n) strnlen((char *)(s), (size_t)(n)) -#else -# define STRNLEN(s, n) xstrnlen((char *)(s), (size_t)(n)) +#ifndef HAVE_STRNLEN +# define strnlen xstrnlen // Older versions of SunOS may not have strnlen #endif -#define STRCPY(d, s) strcpy((char *)(d), (char *)(s)) -#define STRNCPY(d, s, n) strncpy((char *)(d), (char *)(s), (size_t)(n)) -#define STRLCPY(d, s, n) xstrlcpy((char *)(d), (char *)(s), (size_t)(n)) -#define STRCMP(d, s) strcmp((char *)(d), (char *)(s)) -#define STRNCMP(d, s, n) strncmp((char *)(d), (char *)(s), (size_t)(n)) + +#define STRCPY(d, s) strcpy((char *)(d), (char *)(s)) // NOLINT(runtime/printf) #ifdef HAVE_STRCASECMP # define STRICMP(d, s) strcasecmp((char *)(d), (char *)(s)) #else @@ -227,7 +213,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() #endif // Like strcpy() but allows overlapped source and destination. -#define STRMOVE(d, s) memmove((d), (s), STRLEN(s) + 1) +#define STRMOVE(d, s) memmove((d), (s), strlen(s) + 1) #ifdef HAVE_STRNCASECMP # define STRNICMP(d, s, n) strncasecmp((char *)(d), (char *)(s), (size_t)(n)) @@ -239,11 +225,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() # endif #endif -#define STRRCHR(s, c) (char_u *)strrchr((const char *)(s), (c)) - -#define STRCAT(d, s) strcat((char *)(d), (char *)(s)) -#define STRNCAT(d, s, n) strncat((char *)(d), (char *)(s), (size_t)(n)) -#define STRLCAT(d, s, n) xstrlcat((char *)(d), (char *)(s), (size_t)(n)) +#define STRCAT(d, s) strcat((char *)(d), (char *)(s)) // NOLINT(runtime/printf) // Character used as separated in autoload function/variable names. #define AUTOLOAD_CHAR '#' @@ -254,27 +236,8 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() // destination and mess up the screen. #define PERROR(msg) (void)semsg("%s: %s", (msg), strerror(errno)) -#define SHOWCMD_COLS 10 // columns needed by shown command - #include "nvim/path.h" -/// Compare file names -/// -/// On some systems case in a file name does not matter, on others it does. -/// -/// @note Does not account for maximum name lengths and things like "../dir", -/// thus it is not 100% accurate. OS may also use different algorithm for -/// case-insensitive comparison. -/// -/// @param[in] x First file name to compare. -/// @param[in] y Second file name to compare. -/// -/// @return 0 for equal file names, non-zero otherwise. -#define FNAMECMP(x, y) path_fnamecmp((const char *)(x), (const char *)(y)) -#define FNAMENCMP(x, y, n) path_fnamencmp((const char *)(x), \ - (const char *)(y), \ - (size_t)(n)) - // Enums need a typecast to be used as array index. #define HL_ATTR(n) hl_attr_active[(int)(n)] @@ -283,13 +246,11 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() /// plus six following composing characters of three bytes each. #define MB_MAXBYTES 21 -// This has to go after the include of proto.h, as proto/gui.pro declares -// functions of these names. The declarations would break if the defines had -// been seen at that stage. But it must be before globals.h, where error_ga -// is declared. -#ifndef WIN32 -# define mch_errmsg(str) fprintf(stderr, "%s", (str)) -# define mch_msg(str) printf("%s", (str)) +#ifndef MSWIN +/// Headless (no UI) error message handler. +# define os_errmsg(str) fprintf(stderr, "%s", (str)) +/// Headless (no UI) message handler. +# define os_msg(str) printf("%s", (str)) #endif #include "nvim/buffer_defs.h" // buffer and windows diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 387b9d61f2..53224f2ee9 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -53,21 +53,25 @@ #include <assert.h> #include <stdbool.h> #include <stddef.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> +#include "klib/kvec.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/charset.h" #include "nvim/eval/typval.h" -#include "nvim/lib/kvec.h" +#include "nvim/gettext.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" +#include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/types.h" #include "nvim/vim.h" #include "nvim/viml/parser/expressions.h" #include "nvim/viml/parser/parser.h" -#define VIM_STR2NR(s, ...) vim_str2nr((const char_u *)(s), __VA_ARGS__) - typedef kvec_withinit_t(ExprASTNode **, 16) ExprASTStack; /// Which nodes may be wanted @@ -371,7 +375,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) significand_part = significand_part * 10 + (pline.data[i] - '0'); } if (exp_start) { - VIM_STR2NR(pline.data + exp_start, NULL, NULL, 0, NULL, &exp_part, + vim_str2nr(pline.data + exp_start, NULL, NULL, 0, NULL, &exp_part, (int)(ret.len - exp_start), false); } if (exp_negative) { @@ -389,7 +393,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) } else { int len; int prep; - VIM_STR2NR(pline.data, &prep, &len, STR2NR_ALL, NULL, + vim_str2nr(pline.data, &prep, &len, STR2NR_ALL, NULL, &ret.data.num.val.integer, (int)pline.size, false); ret.len = (size_t)len; const uint8_t bases[] = { @@ -630,8 +634,8 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) GET_CCS(ret, pline); ret.data.cmp.inv = (schar == '<'); ret.data.cmp.type = ((ret.data.cmp.inv ^ haseqsign) - ? kExprCmpGreaterOrEqual - : kExprCmpGreater); + ? kExprCmpGreaterOrEqual + : kExprCmpGreater); break; } @@ -696,8 +700,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) // Everything else is not valid. default: - ret.len = (size_t)utfc_ptr2len_len((const char_u *)pline.data, - (int)pline.size); + ret.len = (size_t)utfc_ptr2len_len(pline.data, (int)pline.size); ret.type = kExprLexInvalid; ret.data.err.type = kExprLexPlainIdentifier; ret.data.err.msg = _("E15: Unidentified character: %.*s"); @@ -1822,8 +1825,8 @@ static void parse_quoted_string(ParserState *const pstate, ExprASTNode *const no if (p[1] != '*') { flags |= FSK_SIMPLIFY; } - const size_t special_len = trans_special((const char_u **)&p, (size_t)(e - p), - (char_u *)v_p, flags, false, NULL); + const size_t special_len = trans_special(&p, (size_t)(e - p), + v_p, flags, false, NULL); if (special_len != 0) { v_p += special_len; } else { @@ -1966,8 +1969,8 @@ ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) || ((*kv_Z(ast_stack, 1))->type != kExprNodeConcat && ((*kv_Z(ast_stack, 1))->type != kExprNodeConcatOrSubscript)))) - ? kELFlagAllowFloat - : 0)); + ? kELFlagAllowFloat + : 0)); LexExprToken cur_token = viml_pexpr_next_token(pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); @@ -2034,9 +2037,9 @@ viml_pexpr_parse_process_token: const bool node_is_key = ( is_concat_or_subscript && (cur_token.type == kExprLexPlainIdentifier - ? (!cur_token.data.var.autoload - && cur_token.data.var.scope == kExprVarScopeMissing) - : (cur_token.type == kExprLexNumber)) + ? (!cur_token.data.var.autoload + && cur_token.data.var.scope == kExprVarScopeMissing) + : (cur_token.type == kExprLexNumber)) && prev_token.type != kExprLexSpacing); if (is_concat_or_subscript && !node_is_key) { // Note: in Vim "d. a" (this is the reason behind `prev_token.type != @@ -2123,6 +2126,22 @@ viml_pexpr_parse_process_token: assert(kv_size(pt_stack)); const ExprASTParseType cur_pt = kv_last(pt_stack); assert(lambda_node == NULL || cur_pt == kEPTLambdaArguments); +#define SIMPLE_UB_OP(op) \ + case kExprLex##op: { \ + if (want_node == kENodeValue) { \ + /* Value level: assume unary operator. */ \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children); \ + HL_CUR_TOKEN(Unary##op); \ + } else { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ + ADD_OP_NODE(cur_node); \ + HL_CUR_TOKEN(Binary##op); \ + } \ + want_node = kENodeValue; \ + break; \ + } switch (tok_type) { case kExprLexMissing: case kExprLexSpacing: @@ -2144,22 +2163,6 @@ viml_pexpr_parse_process_token: HL_CUR_TOKEN(Register); break; } -#define SIMPLE_UB_OP(op) \ - case kExprLex##op: { \ - if (want_node == kENodeValue) { \ - /* Value level: assume unary operator. */ \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ - *top_node_p = cur_node; \ - kvi_push(ast_stack, &cur_node->children); \ - HL_CUR_TOKEN(Unary##op); \ - } else { \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ - ADD_OP_NODE(cur_node); \ - HL_CUR_TOKEN(Binary##op); \ - } \ - want_node = kENodeValue; \ - break; \ - } SIMPLE_UB_OP(Plus) SIMPLE_UB_OP(Minus) #undef SIMPLE_UB_OP @@ -2494,7 +2497,6 @@ viml_pexpr_parse_bracket_closing_error: NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); *top_node_p = cur_node; kvi_push(ast_stack, &cur_node->children); - want_node = kENodeValue; if (cur_pt == kEPTAssignment) { // Additional assignment parse type allows to easily forbid nested // lists. @@ -2710,14 +2712,14 @@ viml_pexpr_parse_figure_brace_closing_error: break; case kExprLexPlainIdentifier: { const ExprVarScope scope = (cur_token.type == kExprLexInvalid - ? kExprVarScopeMissing - : cur_token.data.var.scope); + ? kExprVarScopeMissing + : cur_token.data.var.scope); if (want_node == kENodeValue) { want_node = kENodeOperator; NEW_NODE_WITH_CUR_POS(cur_node, (node_is_key - ? kExprNodePlainKey - : kExprNodePlainIdentifier)); + ? kExprNodePlainKey + : kExprNodePlainIdentifier)); cur_node->data.var.scope = scope; const size_t scope_shift = (scope == kExprVarScopeMissing ? 0 : 2); cur_node->data.var.ident = (pline.data + cur_token.start.col @@ -2735,8 +2737,8 @@ viml_pexpr_parse_figure_brace_closing_error: scope_shift), cur_token.len - scope_shift, (node_is_key - ? HL(IdentifierKey) - : HL(IdentifierName))); + ? HL(IdentifierKey) + : HL(IdentifierName))); } else { if (scope == kExprVarScopeMissing) { // uncrustify:off @@ -2905,15 +2907,15 @@ viml_pexpr_parse_no_paren_closing_error: {} // different error numbers: "E114: Missing quote" and // "E115: Missing quote". ERROR_FROM_TOKEN_AND_MSG(cur_token, (is_double - ? _("E114: Missing double quote: %.*s") - : _("E115: Missing single quote: %.*s"))); + ? _("E114: Missing double quote: %.*s") + : _("E115: Missing single quote: %.*s"))); } if (want_node == kENodeOperator) { OP_MISSING; } NEW_NODE_WITH_CUR_POS(cur_node, (is_double - ? kExprNodeDoubleQuotedString - : kExprNodeSingleQuotedString)); + ? kExprNodeDoubleQuotedString + : kExprNodeSingleQuotedString)); *top_node_p = cur_node; parse_quoted_string(pstate, cur_node, cur_token, &ast_stack, is_invalid); want_node = kENodeOperator; diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index 9d0bc9d468..6fe6a784a0 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -6,9 +6,12 @@ #include <stdint.h> #include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/types.h" #include "nvim/viml/parser/parser.h" +struct expr_ast_node; + // Defines whether to ignore case: // == kCCStrategyUseOption // ==# kCCStrategyMatchCase @@ -357,7 +360,7 @@ typedef struct { int arg_len; } ExprASTError; -/// Structure representing complety AST for one expression +/// Structure representing complete AST for one expression typedef struct { /// When AST is not correct this message will be printed. /// diff --git a/src/nvim/viml/parser/parser.c b/src/nvim/viml/parser/parser.c index a41b750e76..1547feba90 100644 --- a/src/nvim/viml/parser/parser.c +++ b/src/nvim/viml/parser/parser.c @@ -4,7 +4,7 @@ #include "nvim/viml/parser/parser.h" #ifdef INCLUDE_GENERATED_DECLARATIONS -# include "viml/parser/parser.c.generated.h" +# include "viml/parser/parser.c.generated.h" // IWYU pragma: export #endif void parser_simple_get_line(void *cookie, ParserLine *ret_pline) diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index b8835127e7..f387301c2d 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -5,9 +5,10 @@ #include <stdbool.h> #include <stddef.h> +#include "klib/kvec.h" #include "nvim/func_attr.h" -#include "nvim/lib/kvec.h" #include "nvim/mbyte.h" +#include "nvim/mbyte_defs.h" #include "nvim/memory.h" /// One parsed line @@ -81,8 +82,8 @@ typedef struct { bool can_continuate; } ParserState; -static inline void viml_parser_init(ParserState *const ret_pstate, const ParserLineGetter get_line, - void *const cookie, ParserHighlight *const colors) +static inline void viml_parser_init(ParserState *ret_pstate, ParserLineGetter get_line, + void *cookie, ParserHighlight *colors) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1, 2); /// Initialize a new parser state instance @@ -109,7 +110,7 @@ static inline void viml_parser_init(ParserState *const ret_pstate, const ParserL kvi_init(ret_pstate->stack); } -static inline void viml_parser_destroy(ParserState *const pstate) +static inline void viml_parser_destroy(ParserState *pstate) REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; /// Free all memory allocated by the parser on heap @@ -127,8 +128,7 @@ static inline void viml_parser_destroy(ParserState *const pstate) kvi_destroy(pstate->stack); } -static inline void viml_preader_get_line(ParserInputReader *const preader, - ParserLine *const ret_pline) +static inline void viml_preader_get_line(ParserInputReader *preader, ParserLine *ret_pline) REAL_FATTR_NONNULL_ALL; /// Get one line from ParserInputReader @@ -142,9 +142,7 @@ static inline void viml_preader_get_line(ParserInputReader *const preader, .allocated = true, .size = pline.size, }; - cpline.data = (char *)string_convert(&preader->conv, - (char_u *)pline.data, - &cpline.size); + cpline.data = string_convert(&preader->conv, (char *)pline.data, &cpline.size); if (pline.allocated) { xfree((void *)pline.data); } @@ -154,8 +152,7 @@ static inline void viml_preader_get_line(ParserInputReader *const preader, *ret_pline = pline; } -static inline bool viml_parser_get_remaining_line(ParserState *const pstate, - ParserLine *const ret_pline) +static inline bool viml_parser_get_remaining_line(ParserState *pstate, ParserLine *ret_pline) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; /// Get currently parsed line, shifted to pstate->pos.col @@ -180,8 +177,7 @@ static inline bool viml_parser_get_remaining_line(ParserState *const pstate, return ret_pline->data != NULL; } -static inline void viml_parser_advance(ParserState *const pstate, - const size_t len) +static inline void viml_parser_advance(ParserState *pstate, size_t len) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; /// Advance position by a given number of bytes @@ -202,10 +198,8 @@ static inline void viml_parser_advance(ParserState *const pstate, const size_t l } } -static inline void viml_parser_highlight(ParserState *const pstate, - const ParserPosition start, - const size_t end_col, - const char *const group) +static inline void viml_parser_highlight(ParserState *pstate, ParserPosition start, size_t len, + const char *group) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; /// Record highlighting of some region of text diff --git a/src/nvim/window.c b/src/nvim/window.c index 68c5b42e30..05f84b5a91 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2,21 +2,34 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <assert.h> +#include <ctype.h> #include <inttypes.h> +#include <limits.h> #include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "klib/kvec.h" +#include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" -#include "nvim/api/vim.h" #include "nvim/arglist.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/decoration.h" #include "nvim/diff.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" +#include "nvim/eval/typval.h" +#include "nvim/eval/typval_defs.h" #include "nvim/eval/vars.h" +#include "nvim/eval/window.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" @@ -27,15 +40,19 @@ #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/gettext.h" #include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/hashtab.h" -#include "nvim/highlight.h" +#include "nvim/keycodes.h" +#include "nvim/macros.h" #include "nvim/main.h" -#include "nvim/mapping.h" +#include "nvim/map.h" +#include "nvim/mapping.h" // IWYU pragma: keep (langmap_adjust_mb) #include "nvim/mark.h" #include "nvim/match.h" -#include "nvim/memline.h" +#include "nvim/mbyte.h" +#include "nvim/memline_defs.h" #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/mouse.h" @@ -44,16 +61,19 @@ #include "nvim/option.h" #include "nvim/optionstr.h" #include "nvim/os/os.h" -#include "nvim/os_unix.h" +#include "nvim/os/os_defs.h" #include "nvim/path.h" #include "nvim/plines.h" +#include "nvim/pos.h" #include "nvim/quickfix.h" -#include "nvim/regexp.h" +#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/state.h" +#include "nvim/statusline.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/terminal.h" +#include "nvim/types.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/undo.h" @@ -125,15 +145,10 @@ win_T *prevwin_curwin(void) /// @param xchar extra char from ":wincmd gx" or NUL void do_window(int nchar, long Prenum, int xchar) { - long Prenum1; - win_T *wp; - char *ptr; - linenr_T lnum = -1; int type = FIND_DEFINE; - size_t len; char cbuf[40]; - Prenum1 = Prenum == 0 ? 1 : Prenum; + long Prenum1 = Prenum == 0 ? 1 : Prenum; #define CHECK_CMDWIN \ do { \ @@ -215,7 +230,7 @@ newwindow: case Ctrl_Q: case 'q': reset_VIsual_and_resel(); // stop Visual mode - cmd_with_count("quit", (char_u *)cbuf, sizeof(cbuf), Prenum); + cmd_with_count("quit", cbuf, sizeof(cbuf), Prenum); do_cmdline_cmd(cbuf); break; @@ -223,7 +238,7 @@ newwindow: case Ctrl_C: case 'c': reset_VIsual_and_resel(); // stop Visual mode - cmd_with_count("close", (char_u *)cbuf, sizeof(cbuf), Prenum); + cmd_with_count("close", cbuf, sizeof(cbuf), Prenum); do_cmdline_cmd(cbuf); break; @@ -236,8 +251,8 @@ newwindow: break; // cursor to preview window - case 'P': - wp = NULL; + case 'P': { + win_T *wp = NULL; FOR_ALL_WINDOWS_IN_TAB(wp2, curtab) { if (wp2->w_p_pvw) { wp = wp2; @@ -250,13 +265,14 @@ newwindow: win_goto(wp); } break; + } // close all but current window case Ctrl_O: case 'o': CHECK_CMDWIN; reset_VIsual_and_resel(); // stop Visual mode - cmd_with_count("only", (char_u *)cbuf, sizeof(cbuf), Prenum); + cmd_with_count("only", cbuf, sizeof(cbuf), Prenum); do_cmdline_cmd(cbuf); break; @@ -269,13 +285,13 @@ newwindow: if (ONE_WINDOW && Prenum != 1) { // just one window beep_flush(); } else { + win_T *wp; if (Prenum) { // go to specified window for (wp = firstwin; --Prenum > 0;) { if (wp->w_next == NULL) { break; - } else { - wp = wp->w_next; } + wp = wp->w_next; } } else { if (nchar == 'W') { // go to previous window @@ -346,7 +362,7 @@ newwindow: // First create a new tab with the window, then go back to // the old tab and close the window there. - wp = curwin; + win_T *wp = curwin; if (win_new_tabpage((int)Prenum, NULL) == OK && valid_tabpage(oldtab)) { newtab = curtab; @@ -417,10 +433,12 @@ newwindow: | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT)); break; - // make all windows the same height - case '=': - win_equal(NULL, false, 'b'); + // make all windows the same width and/or height + case '=': { + int mod = cmdmod.cmod_split & (WSP_VERT | WSP_HOR); + win_equal(NULL, false, mod == WSP_VERT ? 'v' : mod == WSP_HOR ? 'h' : 'b'); break; + } // increase current window height case '+': @@ -479,16 +497,18 @@ newwindow: // Execute the command right here, required when // "wincmd ]" was used in a function. do_nv_ident(Ctrl_RSB, NUL); + postponed_split = 0; break; // edit file name under cursor in a new window case 'f': case 'F': - case Ctrl_F: + case Ctrl_F: { wingotofile: CHECK_CMDWIN; - ptr = (char *)grab_file_name(Prenum1, &lnum); + linenr_T lnum = -1; + char *ptr = grab_file_name(Prenum1, &lnum); if (ptr != NULL) { tabpage_T *oldtab = curtab; win_T *oldwin = curwin; @@ -508,6 +528,7 @@ wingotofile: xfree(ptr); } break; + } // Go to the first occurrence of the identifier under cursor along path in a // new window -- webb @@ -516,8 +537,10 @@ wingotofile: type = FIND_ANY; FALLTHROUGH; case 'd': // Go to definition, using 'define' - case Ctrl_D: + case Ctrl_D: { CHECK_CMDWIN; + size_t len; + char *ptr; if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) { break; } @@ -525,11 +548,12 @@ wingotofile: // Make a copy, if the line was changed it will be freed. ptr = xstrnsave(ptr, len); - find_pattern_in_path((char_u *)ptr, 0, len, true, Prenum == 0, + find_pattern_in_path(ptr, 0, len, true, Prenum == 0, type, Prenum1, ACTION_SPLIT, 1, MAXLNUM); xfree(ptr); curwin->w_set_curswant = true; break; + } // Quickfix window only: view the result under the cursor in a new split. case K_KENTER: @@ -552,6 +576,7 @@ wingotofile: no_mapping--; allow_keys--; (void)add_to_showcmd(xchar); + switch (xchar) { case '}': xchar = Ctrl_RSB; @@ -573,6 +598,7 @@ wingotofile: // Execute the command right here, required when // "wincmd g}" was used in a function. do_nv_ident('g', xchar); + postponed_split = 0; break; case 'f': // CTRL-W gf: "gf" in a new tab page @@ -580,6 +606,7 @@ wingotofile: cmdmod.cmod_tab = tabpage_index(curtab) + 1; nchar = xchar; goto wingotofile; + case 't': // CTRL-W gt: go to next tab page goto_tabpage((int)Prenum); break; @@ -622,12 +649,12 @@ wingotofile: } } -static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, int64_t Prenum) +static void cmd_with_count(char *cmd, char *bufp, size_t bufsize, int64_t Prenum) { - size_t len = STRLCPY(bufp, cmd, bufsize); + size_t len = xstrlcpy(bufp, cmd, bufsize); if (Prenum > 0 && len < bufsize) { - vim_snprintf((char *)bufp + len, bufsize - len, "%" PRId64, Prenum); + vim_snprintf(bufp + len, bufsize - len, "%" PRId64, Prenum); } } @@ -635,11 +662,13 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) { win_T *win = find_window_by_handle(window, err); buf_T *buf = find_buffer_by_handle(buffer, err); - tabpage_T *tab = win_find_tabpage(win); if (!win || !buf) { return; } + + tabpage_T *tab = win_find_tabpage(win); + if (noautocmd) { block_autocmds(); } @@ -712,7 +741,7 @@ win_T *win_new_float(win_T *wp, bool last, FloatConfig fconfig, Error *err) win_config_float(wp, fconfig); win_set_inner_size(wp, true); wp->w_pos_changed = true; - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); return wp; } @@ -727,38 +756,44 @@ void win_set_minimal_style(win_T *wp) // Hide EOB region: use " " fillchar and cleared highlighting if (wp->w_p_fcs_chars.eob != ' ') { - char_u *old = wp->w_p_fcs; + char *old = wp->w_p_fcs; wp->w_p_fcs = ((*old == NUL) - ? (char_u *)xstrdup("eob: ") - : concat_str(old, (char_u *)",eob: ")); + ? xstrdup("eob: ") + : concat_str(old, ",eob: ")); free_string_option(old); } // TODO(bfredl): this could use a highlight namespace directly, - // and avoid pecularities around window options - char_u *old = wp->w_p_winhl; + // and avoid peculiarities around window options + char *old = wp->w_p_winhl; wp->w_p_winhl = ((*old == NUL) - ? (char_u *)xstrdup("EndOfBuffer:") - : concat_str(old, (char_u *)",EndOfBuffer:")); + ? xstrdup("EndOfBuffer:") + : concat_str(old, ",EndOfBuffer:")); free_string_option(old); parse_winhl_opt(wp); // signcolumn: use 'auto' - if (wp->w_p_scl[0] != 'a' || STRLEN(wp->w_p_scl) >= 8) { + if (wp->w_p_scl[0] != 'a' || strlen(wp->w_p_scl) >= 8) { free_string_option(wp->w_p_scl); - wp->w_p_scl = (char_u *)xstrdup("auto"); + wp->w_p_scl = xstrdup("auto"); } // foldcolumn: use '0' if (wp->w_p_fdc[0] != '0') { free_string_option(wp->w_p_fdc); - wp->w_p_fdc = (char_u *)xstrdup("0"); + wp->w_p_fdc = xstrdup("0"); } // colorcolumn: cleared if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) { free_string_option(wp->w_p_cc); - wp->w_p_cc = (char_u *)xstrdup(""); + wp->w_p_cc = xstrdup(""); + } + + // statuscolumn: cleared + if (wp->w_p_stc != NULL && *wp->w_p_stc != NUL) { + free_string_option(wp->w_p_stc); + wp->w_p_stc = xstrdup(""); } } @@ -772,13 +807,22 @@ void win_config_float(win_T *wp, FloatConfig fconfig) fconfig.row += curwin->w_wrow; fconfig.col += curwin->w_wcol; fconfig.window = curwin->handle; + } else if (fconfig.relative == kFloatRelativeMouse) { + int row = mouse_row, col = mouse_col, grid = mouse_grid; + win_T *mouse_win = mouse_find_win(&grid, &row, &col); + if (mouse_win != NULL) { + fconfig.relative = kFloatRelativeWindow; + fconfig.row += row; + fconfig.col += col; + fconfig.window = mouse_win->handle; + } } bool change_external = fconfig.external != wp->w_float_config.external; bool change_border = (fconfig.border != wp->w_float_config.border || memcmp(fconfig.border_hl_ids, wp->w_float_config.border_hl_ids, - sizeof fconfig.border_hl_ids)); + sizeof fconfig.border_hl_ids) != 0); wp->w_float_config = fconfig; @@ -797,12 +841,12 @@ void win_config_float(win_T *wp, FloatConfig fconfig) } win_set_inner_size(wp, true); - must_redraw = MAX(must_redraw, VALID); + must_redraw = MAX(must_redraw, UPD_VALID); wp->w_pos_changed = true; if (change_external || change_border) { wp->w_hl_needs_update = true; - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } // compute initial position @@ -819,16 +863,16 @@ void win_config_float(win_T *wp, FloatConfig fconfig) grid_adjust(&grid, &row_off, &col_off); row += row_off; col += col_off; + if (wp->w_float_config.bufpos.lnum >= 0) { + pos_T pos = { wp->w_float_config.bufpos.lnum + 1, + wp->w_float_config.bufpos.col, 0 }; + int trow, tcol, tcolc, tcole; + textpos2screenpos(parent, &pos, &trow, &tcol, &tcolc, &tcole, true); + row += trow - 1; + col += tcol - 1; + } } api_clear_error(&dummy); - if (wp->w_float_config.bufpos.lnum >= 0) { - pos_T pos = { wp->w_float_config.bufpos.lnum + 1, - wp->w_float_config.bufpos.col, 0 }; - int trow, tcol, tcolc, tcole; - textpos2screenpos(wp, &pos, &trow, &tcol, &tcolc, &tcole, true); - row += trow - 1; - col += tcol - 1; - } wp->w_winrow = row; wp->w_wincol = col; } else { @@ -839,7 +883,7 @@ void win_config_float(win_T *wp, FloatConfig fconfig) // changing border style while keeping border only requires redrawing border if (fconfig.border) { wp->w_redr_border = true; - redraw_later(wp, VALID); + redraw_later(wp, UPD_VALID); } } @@ -864,13 +908,13 @@ int win_fdccol_count(win_T *wp) const int fdccol = fdc[4] == ':' ? fdc[5] - '0' : 1; int needed_fdccols = getDeepestNesting(wp); return MIN(fdccol, needed_fdccols); - } else { - return fdc[0] - '0'; } + return fdc[0] - '0'; } -void ui_ext_win_position(win_T *wp) +void ui_ext_win_position(win_T *wp, bool validate) { + wp->w_pos_changed = false; if (!wp->w_floating) { ui_call_win_pos(wp->w_grid_alloc.handle, wp->handle, wp->w_winrow, wp->w_wincol, wp->w_width, wp->w_height); @@ -908,6 +952,11 @@ void ui_ext_win_position(win_T *wp) grid->handle, row, col, c.focusable, wp->w_grid_alloc.zindex); } else { + bool valid = (wp->w_redr_type == 0); + if (!valid && !validate) { + wp->w_pos_changed = true; + return; + } // TODO(bfredl): ideally, compositor should work like any multigrid UI // and use standard win_pos events. bool east = c.anchor & kFloatAnchorEast; @@ -921,14 +970,13 @@ void ui_ext_win_position(win_T *wp) comp_col = MAX(MIN(comp_col, Columns - wp->w_width_outer), 0); wp->w_winrow = comp_row; wp->w_wincol = comp_col; - bool valid = (wp->w_redr_type == 0); ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col, wp->w_height_outer, wp->w_width_outer, valid, false); ui_check_cursor_grid(wp->w_grid_alloc.handle); wp->w_grid_alloc.focusable = wp->w_float_config.focusable; if (!valid) { wp->w_grid_alloc.valid = false; - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } } } else { @@ -968,21 +1016,19 @@ static int check_split_disallowed(void) return OK; } -/* - * split the current window, implements CTRL-W s and :split - * - * "size" is the height or width for the new window, 0 to use half of current - * height or width. - * - * "flags": - * WSP_ROOM: require enough room for new window - * WSP_VERT: vertical split. - * WSP_TOP: open window at the top-left of the screen (help window). - * WSP_BOT: open window at the bottom-right of the screen (quickfix window). - * WSP_HELP: creating the help window, keep layout snapshot - * - * return FAIL for failure, OK otherwise - */ +// split the current window, implements CTRL-W s and :split +// +// "size" is the height or width for the new window, 0 to use half of current +// height or width. +// +// "flags": +// WSP_ROOM: require enough room for new window +// WSP_VERT: vertical split. +// WSP_TOP: open window at the top-left of the screen (help window). +// WSP_BOT: open window at the bottom-right of the screen (quickfix window). +// WSP_HELP: creating the help window, keep layout snapshot +// +// return FAIL for failure, OK otherwise int win_split(int size, int flags) { if (check_split_disallowed() == FAIL) { @@ -1012,35 +1058,20 @@ int win_split(int size, int flags) return win_split_ins(size, flags, NULL, 0); } -/* - * When "new_wp" is NULL: split the current window in two. - * When "new_wp" is not NULL: insert this window at the far - * top/left/right/bottom. - * return FAIL for failure, OK otherwise - */ +// When "new_wp" is NULL: split the current window in two. +// When "new_wp" is not NULL: insert this window at the far +// top/left/right/bottom. +// return FAIL for failure, OK otherwise int win_split_ins(int size, int flags, win_T *new_wp, int dir) { win_T *wp = new_wp; - win_T *oldwin; - int new_size = size; - int i; - int need_status = 0; - bool do_equal = false; - int needed; - int available; - int oldwin_height = 0; - int layout; - frame_T *frp, *curfrp, *frp2, *prevfrp; - int before; - int minheight; - int wmh1; - bool did_set_fraction = false; - // aucmd_win should always remain floating - if (new_wp != NULL && new_wp == aucmd_win) { + // aucmd_win[] should always remain floating + if (new_wp != NULL && is_aucmd_win(new_wp)) { return FAIL; } + win_T *oldwin; if (flags & WSP_TOP) { oldwin = firstwin; } else if (flags & WSP_BOT || curwin->w_floating) { @@ -1050,6 +1081,8 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) oldwin = curwin; } + int need_status = 0; + int new_size = size; bool new_in_layout = (new_wp == NULL || new_wp->w_floating); // add a status line when p_ls == 1 and splitting the first window @@ -1061,32 +1094,33 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) need_status = STATUS_HEIGHT; } - if (flags & WSP_VERT) { - int wmw1; - int minwidth; - - layout = FR_ROW; + bool do_equal = false; + int oldwin_height = 0; + const int layout = flags & WSP_VERT ? FR_ROW : FR_COL; + bool did_set_fraction = false; - /* - * Check if we are able to split the current window and compute its - * width. - */ + if (flags & WSP_VERT) { + // Check if we are able to split the current window and compute its + // width. // Current window requires at least 1 space. - wmw1 = (p_wmw == 0 ? 1 : (int)p_wmw); - needed = wmw1 + 1; + int wmw1 = (p_wmw == 0 ? 1 : (int)p_wmw); + int needed = wmw1 + 1; if (flags & WSP_ROOM) { needed += (int)p_wiw - wmw1; } + int minwidth; + int available; if (flags & (WSP_BOT | WSP_TOP)) { minwidth = frame_minwidth(topframe, NOWIN); available = topframe->fr_width; needed += minwidth; } else if (p_ea) { minwidth = frame_minwidth(oldwin->w_frame, NOWIN); - prevfrp = oldwin->w_frame; - for (frp = oldwin->w_frame->fr_parent; frp != NULL; + frame_T *prevfrp = oldwin->w_frame; + for (frame_T *frp = oldwin->w_frame->fr_parent; frp != NULL; frp = frp->fr_parent) { if (frp->fr_layout == FR_ROW) { + frame_T *frp2; FOR_ALL_FRAMES(frp2, frp->fr_child) { if (frp2 != prevfrp) { minwidth += frame_minwidth(frp2, NOWIN); @@ -1132,7 +1166,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) // is wider than one of the split windows. if (!do_equal && p_ea && size == 0 && *p_ead != 'v' && oldwin->w_frame->fr_parent != NULL) { - frp = oldwin->w_frame->fr_parent->fr_child; + frame_T *frp = oldwin->w_frame->fr_parent->fr_child; while (frp != NULL) { if (frp->fr_win != oldwin && frp->fr_win != NULL && (frp->fr_win->w_width > new_size @@ -1145,27 +1179,28 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } } } else { - layout = FR_COL; - // Check if we are able to split the current window and compute its height. // Current window requires at least 1 space plus space for the window bar. - wmh1 = MAX((int)p_wmh, 1) + oldwin->w_winbar_height; - needed = wmh1 + STATUS_HEIGHT; + int wmh1 = MAX((int)p_wmh, 1) + oldwin->w_winbar_height; + int needed = wmh1 + STATUS_HEIGHT; if (flags & WSP_ROOM) { needed += (int)p_wh - wmh1 + oldwin->w_winbar_height; } if (p_ch < 1) { needed += 1; // Adjust for cmdheight=0. } + int minheight; + int available; if (flags & (WSP_BOT | WSP_TOP)) { minheight = frame_minheight(topframe, NOWIN) + need_status; available = topframe->fr_height; needed += minheight; } else if (p_ea) { minheight = frame_minheight(oldwin->w_frame, NOWIN) + need_status; - prevfrp = oldwin->w_frame; - for (frp = oldwin->w_frame->fr_parent; frp != NULL; frp = frp->fr_parent) { + frame_T *prevfrp = oldwin->w_frame; + for (frame_T *frp = oldwin->w_frame->fr_parent; frp != NULL; frp = frp->fr_parent) { if (frp->fr_layout == FR_COL) { + frame_T *frp2; FOR_ALL_FRAMES(frp2, frp->fr_child) { if (frp2 != prevfrp) { minheight += frame_minheight(frp2, NOWIN); @@ -1228,7 +1263,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) if (!do_equal && p_ea && size == 0 && *p_ead != 'h' && oldwin->w_frame->fr_parent != NULL) { - frp = oldwin->w_frame->fr_parent->fr_child; + frame_T *frp = oldwin->w_frame->fr_parent->fr_child; while (frp != NULL) { if (frp->fr_win != oldwin && frp->fr_win != NULL && (frp->fr_win->w_height > new_size @@ -1241,9 +1276,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } } - /* - * allocate new window structure and link it in the window list - */ + // allocate new window structure and link it in the window list if ((flags & WSP_TOP) == 0 && ((flags & WSP_BOT) || (flags & WSP_BELOW) @@ -1280,9 +1313,10 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) CLEAR_FIELD(wp->w_border_adj); } - /* - * Reorganise the tree of frames to insert the new window. - */ + int before; + frame_T *curfrp; + + // Reorganise the tree of frames to insert the new window. if (flags & (WSP_TOP | WSP_BOT)) { if ((topframe->fr_layout == FR_COL && (flags & WSP_VERT) == 0) || (topframe->fr_layout == FR_ROW && (flags & WSP_VERT) != 0)) { @@ -1299,9 +1333,9 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } else { curfrp = oldwin->w_frame; if (flags & WSP_BELOW) { - before = FALSE; + before = false; } else if (flags & WSP_ABOVE) { - before = TRUE; + before = true; } else if (flags & WSP_VERT) { before = !p_spr; } else { @@ -1310,7 +1344,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } if (curfrp->fr_parent == NULL || curfrp->fr_parent->fr_layout != layout) { // Need to create a new frame in the tree to make a branch. - frp = xcalloc(1, sizeof(frame_T)); + frame_T *frp = xcalloc(1, sizeof(frame_T)); *frp = *curfrp; curfrp->fr_layout = (char)layout; frp->fr_parent = curfrp; @@ -1328,6 +1362,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) } } + frame_T *frp; if (new_wp == NULL) { frp = wp->w_frame; } else { @@ -1474,8 +1509,8 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) // Both windows need redrawing. Update all status lines, in case they // show something related to the window count or position. - redraw_later(wp, NOT_VALID); - redraw_later(oldwin, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); + redraw_later(oldwin, UPD_NOT_VALID); status_redraw_all(); if (need_status) { @@ -1487,13 +1522,15 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) msg_col = 0; // put position back at start of line } - /* - * equalize the window sizes. - */ + // equalize the window sizes. if (do_equal || dir != 0) { win_equal(wp, true, (flags & WSP_VERT) ? (dir == 'v' ? 'b' : 'h') : (dir == 'h' ? 'b' : 'v')); + } else if (*p_spk != 'c' && !is_aucmd_win(wp)) { + win_fix_scroll(false); } + int i; + // Don't change the window height/width to 'winheight' / 'winwidth' if a // size was given. if (flags & WSP_VERT) { @@ -1525,17 +1562,13 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) return OK; } -/* - * Initialize window "newp" from window "oldp". - * Used when splitting a window and when creating a new tab page. - * The windows will both edit the same buffer. - * WSP_NEWLOC may be specified in flags to prevent the location list from - * being copied. - */ +// Initialize window "newp" from window "oldp". +// Used when splitting a window and when creating a new tab page. +// The windows will both edit the same buffer. +// WSP_NEWLOC may be specified in flags to prevent the location list from +// being copied. static void win_init(win_T *newp, win_T *oldp, int flags) { - int i; - newp->w_buffer = oldp->w_buffer; newp->w_s = &(oldp->w_buffer->b_s); oldp->w_buffer->b_nwindows++; @@ -1565,15 +1598,21 @@ static void win_init(win_T *newp, win_T *oldp, int flags) newp->w_prevdir = (oldp->w_prevdir == NULL) ? NULL : xstrdup(oldp->w_prevdir); + if (*p_spk != 'c') { + newp->w_botline = oldp->w_botline; + newp->w_prev_height = oldp->w_height; + newp->w_prev_winrow = oldp->w_winrow; + } + // copy tagstack and folds - for (i = 0; i < oldp->w_tagstacklen; i++) { + for (int i = 0; i < oldp->w_tagstacklen; i++) { taggy_T *tag = &newp->w_tagstack[i]; *tag = oldp->w_tagstack[i]; if (tag->tagname != NULL) { - tag->tagname = vim_strsave(tag->tagname); + tag->tagname = xstrdup(tag->tagname); } if (tag->user_data != NULL) { - tag->user_data = vim_strsave(tag->user_data); + tag->user_data = xstrdup(tag->user_data); } } newp->w_tagstackidx = oldp->w_tagstackidx; @@ -1589,15 +1628,13 @@ static void win_init(win_T *newp, win_T *oldp, int flags) newp->w_winbar_height = oldp->w_winbar_height; } -/* - * Initialize window "newp" from window "old". - * Only the essential things are copied. - */ +// Initialize window "newp" from window "old". +// Only the essential things are copied. static void win_init_some(win_T *newp, win_T *oldp) { // Use the same argument list. newp->w_alist = oldp->w_alist; - ++newp->w_alist->al_refcount; + newp->w_alist->al_refcount++; newp->w_arg_idx = oldp->w_arg_idx; // copy options from existing window @@ -1627,11 +1664,20 @@ bool win_valid_floating(const win_T *win) /// @param win window to check bool win_valid(const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { + return tabpage_win_valid(curtab, win); +} + +/// Check if "win" is a pointer to an existing window in tabpage "tp". +/// +/// @param win window to check +static bool tabpage_win_valid(const tabpage_T *tp, const win_T *win) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ if (win == NULL) { return false; } - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + FOR_ALL_WINDOWS_IN_TAB(wp, tp) { if (wp == win) { return true; } @@ -1669,9 +1715,7 @@ bool win_valid_any_tab(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT return false; } -/* - * Return the number of windows. - */ +// Return the number of windows. int win_count(void) { int count = 0; @@ -1692,7 +1736,6 @@ int win_count(void) int make_windows(int count, bool vertical) { int maxcount; - int todo; if (vertical) { // Each window needs at least 'winminwidth' lines and a separator column. @@ -1722,8 +1765,10 @@ int make_windows(int count, bool vertical) // when putting the buffers in the windows. block_autocmds(); + int todo; + // todo is number of windows left to create - for (todo = count - 1; todo > 0; --todo) { + for (todo = count - 1; todo > 0; todo--) { if (vertical) { if (win_split(curwin->w_width - (curwin->w_width - todo) / (todo + 1) - 1, WSP_VERT | WSP_ABOVE) == FAIL) { @@ -1744,17 +1789,9 @@ int make_windows(int count, bool vertical) return count - todo; } -/* - * Exchange current and next window - */ +// Exchange current and next window static void win_exchange(long Prenum) { - frame_T *frp; - frame_T *frp2; - win_T *wp; - win_T *wp2; - int temp; - if (curwin->w_floating) { emsg(e_floatexchange); return; @@ -1766,9 +1803,9 @@ static void win_exchange(long Prenum) return; } - /* - * find window to exchange with - */ + frame_T *frp; + + // find window to exchange with if (Prenum) { frp = curwin->w_frame->fr_parent->fr_child; while (frp != NULL && --Prenum > 0) { @@ -1785,18 +1822,16 @@ static void win_exchange(long Prenum) if (frp == NULL || frp->fr_win == NULL || frp->fr_win == curwin) { return; } - wp = frp->fr_win; - - /* - * 1. remove curwin from the list. Remember after which window it was in wp2 - * 2. insert curwin before wp in the list - * if wp != wp2 - * 3. remove wp from the list - * 4. insert wp after wp2 - * 5. exchange the status line height, winbar height, hsep height and vsep width. - */ - wp2 = curwin->w_prev; - frp2 = curwin->w_frame->fr_prev; + win_T *wp = frp->fr_win; + + // 1. remove curwin from the list. Remember after which window it was in wp2 + // 2. insert curwin before wp in the list + // if wp != wp2 + // 3. remove wp from the list + // 4. insert wp after wp2 + // 5. exchange the status line height, winbar height, hsep height and vsep width. + win_T *wp2 = curwin->w_prev; + frame_T *frp2 = curwin->w_frame->fr_prev; if (wp->w_prev != curwin) { win_remove(curwin, NULL); frame_remove(curwin->w_frame); @@ -1813,7 +1848,7 @@ static void win_exchange(long Prenum) frame_append(frp2, wp->w_frame); } } - temp = curwin->w_status_height; + int temp = curwin->w_status_height; curwin->w_status_height = wp->w_status_height; wp->w_status_height = temp; temp = curwin->w_vsep_width; @@ -1837,19 +1872,14 @@ static void win_exchange(long Prenum) } win_enter(wp, true); - redraw_later(curwin, NOT_VALID); - redraw_later(wp, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } // rotate windows: if upwards true the second window becomes the first one // if upwards false the first window becomes the second one static void win_rotate(bool upwards, int count) { - win_T *wp1; - win_T *wp2; - frame_T *frp; - int n; - if (curwin->w_floating) { emsg(e_floatexchange); return; @@ -1862,6 +1892,7 @@ static void win_rotate(bool upwards, int count) } // Check if all frames in this row/col have one window. + frame_T *frp; FOR_ALL_FRAMES(frp, curwin->w_frame->fr_parent->fr_child) { if (frp->fr_win == NULL) { emsg(_("E443: Cannot rotate when another window is split")); @@ -1869,6 +1900,9 @@ static void win_rotate(bool upwards, int count) } } + win_T *wp1; + win_T *wp2; + while (count--) { if (upwards) { // first window becomes last window // remove first window/frame from the list @@ -1901,7 +1935,7 @@ static void win_rotate(bool upwards, int count) } // exchange status height, winbar height, hsep height and vsep width of old and new last window - n = wp2->w_status_height; + int n = wp2->w_status_height; wp2->w_status_height = wp1->w_status_height; wp1->w_status_height = n; n = wp2->w_hsep_height; @@ -1922,12 +1956,10 @@ static void win_rotate(bool upwards, int count) wp1->w_pos_changed = true; wp2->w_pos_changed = true; - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } -/* - * Move the current window to the very top/bottom/left/right of the screen. - */ +// Move the current window to the very top/bottom/left/right of the screen. static void win_totop(int size, int flags) { int dir = 0; @@ -1937,7 +1969,7 @@ static void win_totop(int size, int flags) beep_flush(); return; } - if (curwin == aucmd_win) { + if (is_aucmd_win(curwin)) { return; } if (check_split_disallowed() == FAIL) { @@ -1971,14 +2003,10 @@ static void win_totop(int size, int flags) } } -/* - * Move window "win1" to below/right of "win2" and make "win1" the current - * window. Only works within the same frame! - */ +// Move window "win1" to below/right of "win2" and make "win1" the current +// window. Only works within the same frame! void win_move_after(win_T *win1, win_T *win2) { - int height; - // check if the arguments are reasonable if (win1 == win2) { return; @@ -1994,7 +2022,7 @@ void win_move_after(win_T *win1, win_T *win2) // may need to move the status line, window bar, horizontal or vertical separator of the last // window if (win1 == lastwin) { - height = win1->w_prev->w_status_height; + int height = win1->w_prev->w_status_height; win1->w_prev->w_status_height = win1->w_status_height; win1->w_status_height = height; @@ -2011,7 +2039,7 @@ void win_move_after(win_T *win1, win_T *win2) win1->w_frame->fr_width += 1; } } else if (win2 == lastwin) { - height = win1->w_status_height; + int height = win1->w_status_height; win1->w_status_height = win2->w_status_height; win2->w_status_height = height; @@ -2034,7 +2062,7 @@ void win_move_after(win_T *win1, win_T *win2) frame_append(win2->w_frame, win1->w_frame); (void)win_comp_pos(); // recompute w_winrow for all windows - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } win_enter(win1, false); @@ -2074,7 +2102,7 @@ static int get_maximum_wincount(frame_T *fr, int height) } /// Make all windows the same height. -///'next_curwin' will soon be the current window, make sure it has enough rows. +/// 'next_curwin' will soon be the current window, make sure it has enough rows. /// /// @param next_curwin pointer to current window to be or NULL /// @param current do only frame with current window @@ -2082,11 +2110,14 @@ static int get_maximum_wincount(frame_T *fr, int height) void win_equal(win_T *next_curwin, bool current, int dir) { if (dir == 0) { - dir = *p_ead; + dir = (unsigned char)(*p_ead); } win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current, topframe, dir, 0, tabline_height(), Columns, topframe->fr_height); + if (*p_spk != 'c' && !is_aucmd_win(next_curwin)) { + win_fix_scroll(true); + } } /// Set a frame to a new position and height, spreading the available room @@ -2105,15 +2136,11 @@ void win_equal(win_T *next_curwin, bool current, int dir) static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int dir, int col, int row, int width, int height) { - int n, m; int extra_sep = 0; - int wincount, totwincount = 0; - frame_T *fr; + int totwincount = 0; int next_curwin_size = 0; int room = 0; - int new_size; int has_next_curwin = 0; - bool hnc; if (topfr->fr_layout == FR_LEAF) { // Set the width/height of this frame. @@ -2125,7 +2152,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int frame_new_height(topfr, height, false, false); topfr->fr_win->w_wincol = col; frame_new_width(topfr, width, false, false); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } } else if (topfr->fr_layout == FR_ROW) { topfr->fr_width = width; @@ -2134,7 +2161,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int if (dir != 'v') { // equalize frame widths // Compute the maximum number of windows horizontally in this // frame. - n = frame_minwidth(topfr, NOWIN); + int n = frame_minwidth(topfr, NOWIN); // add one for the rightmost window, it doesn't have a separator if (col + width == Columns) { extra_sep = 1; @@ -2144,18 +2171,17 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int totwincount = (n + extra_sep) / ((int)p_wmw + 1); has_next_curwin = frame_has_win(topfr, next_curwin); - /* - * Compute width for "next_curwin" window and room available for - * other windows. - * "m" is the minimal width when counting p_wiw for "next_curwin". - */ - m = frame_minwidth(topfr, next_curwin); + // Compute width for "next_curwin" window and room available for + // other windows. + // "m" is the minimal width when counting p_wiw for "next_curwin". + int m = frame_minwidth(topfr, next_curwin); room = width - m; if (room < 0) { next_curwin_size = (int)p_wiw + room; room = 0; } else { next_curwin_size = -1; + frame_T *fr; FOR_ALL_FRAMES(fr, topfr->fr_child) { if (!frame_fixed_width(fr)) { continue; @@ -2163,7 +2189,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int // If 'winfixwidth' set keep the window width if possible. // Watch out for this window being the next_curwin. n = frame_minwidth(fr, NOWIN); - new_size = fr->fr_width; + int new_size = fr->fr_width; if (frame_has_win(fr, next_curwin)) { room += (int)p_wiw - (int)p_wmw; next_curwin_size = 0; @@ -2200,12 +2226,14 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } if (has_next_curwin) { - --totwincount; // don't count curwin + totwincount--; // don't count curwin } } + frame_T *fr; FOR_ALL_FRAMES(fr, topfr->fr_child) { - wincount = 1; + int wincount = 1; + int new_size; if (fr->fr_next == NULL) { // last frame gets all that remains (avoid roundoff error) new_size = width; @@ -2216,14 +2244,10 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int wincount = 0; // doesn't count as a sizeable window } else { // Compute the maximum number of windows horiz. in "fr". - n = frame_minwidth(fr, NOWIN); + int n = frame_minwidth(fr, NOWIN); wincount = (n + (fr->fr_next == NULL ? extra_sep : 0)) / ((int)p_wmw + 1); - m = frame_minwidth(fr, next_curwin); - if (has_next_curwin) { - hnc = frame_has_win(fr, next_curwin); - } else { - hnc = false; - } + int m = frame_minwidth(fr, next_curwin); + bool hnc = has_next_curwin && frame_has_win(fr, next_curwin); if (hnc) { // don't count next_curwin wincount--; } @@ -2260,9 +2284,9 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int if (dir != 'h') { // equalize frame heights // Compute maximum number of windows vertically in this frame. - n = frame_minheight(topfr, NOWIN); + int n = frame_minheight(topfr, NOWIN); // add one for the bottom window if it doesn't have a statusline or separator - if (row + height == cmdline_row && p_ls == 0) { + if (row + height >= cmdline_row && p_ls == 0) { extra_sep = STATUS_HEIGHT; } else if (global_stl_height() > 0) { extra_sep = 1; @@ -2272,12 +2296,10 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int totwincount = get_maximum_wincount(topfr, n + extra_sep); has_next_curwin = frame_has_win(topfr, next_curwin); - /* - * Compute height for "next_curwin" window and room available for - * other windows. - * "m" is the minimal height when counting p_wh for "next_curwin". - */ - m = frame_minheight(topfr, next_curwin); + // Compute height for "next_curwin" window and room available for + // other windows. + // "m" is the minimal height when counting p_wh for "next_curwin". + int m = frame_minheight(topfr, next_curwin); room = height - m; if (room < 0) { // The room is less than 'winheight', use all space for the @@ -2286,6 +2308,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int room = 0; } else { next_curwin_size = -1; + frame_T *fr; FOR_ALL_FRAMES(fr, topfr->fr_child) { if (!frame_fixed_height(fr)) { continue; @@ -2293,7 +2316,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int // If 'winfixheight' set keep the window height if possible. // Watch out for this window being the next_curwin. n = frame_minheight(fr, NOWIN); - new_size = fr->fr_height; + int new_size = fr->fr_height; if (frame_has_win(fr, next_curwin)) { room += (int)p_wh - (int)p_wmh; next_curwin_size = 0; @@ -2330,12 +2353,14 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } if (has_next_curwin) { - --totwincount; // don't count curwin + totwincount--; // don't count curwin } } + frame_T *fr; FOR_ALL_FRAMES(fr, topfr->fr_child) { - wincount = 1; + int new_size; + int wincount = 1; if (fr->fr_next == NULL) { // last frame gets all that remains (avoid roundoff error) new_size = height; @@ -2346,14 +2371,10 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int wincount = 0; // doesn't count as a sizeable window } else { // Compute the maximum number of windows vert. in "fr". - n = frame_minheight(fr, NOWIN); + int n = frame_minheight(fr, NOWIN); wincount = get_maximum_wincount(fr, (n + (fr->fr_next == NULL ? extra_sep : 0))); - m = frame_minheight(fr, next_curwin); - if (has_next_curwin) { - hnc = frame_has_win(fr, next_curwin); - } else { - hnc = false; - } + int m = frame_minheight(fr, next_curwin); + bool hnc = has_next_curwin && frame_has_win(fr, next_curwin); if (hnc) { // don't count next_curwin wincount--; } @@ -2436,7 +2457,7 @@ void entering_window(win_T *const win) void win_init_empty(win_T *wp) { - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); wp->w_lines_valid = 0; wp->w_cursor.lnum = 1; wp->w_curswant = wp->w_cursor.col = 0; @@ -2463,14 +2484,11 @@ void curwin_init(void) /// @param keep_curwin don't close `curwin` void close_windows(buf_T *buf, bool keep_curwin) { - tabpage_T *tp, *nexttp; - int h = tabline_height(); - RedrawingDisabled++; // Start from lastwin to close floating windows with the same buffer first. // When the autocommand window is involved win_close() may need to print an error message. - for (win_T *wp = lastwin; wp != NULL && (lastwin == aucmd_win || !one_window(wp));) { + for (win_T *wp = lastwin; wp != NULL && (is_aucmd_win(lastwin) || !one_window(wp));) { if (wp->w_buffer == buf && (!keep_curwin || wp != curwin) && !(wp->w_closing || wp->w_buffer->b_locked > 0)) { if (win_close(wp, false, false) == FAIL) { @@ -2485,11 +2503,14 @@ void close_windows(buf_T *buf, bool keep_curwin) } } + tabpage_T *nexttp; + // Also check windows in other tab pages. - for (tp = first_tabpage; tp != NULL; tp = nexttp) { + for (tabpage_T *tp = first_tabpage; tp != NULL; tp = nexttp) { nexttp = tp->tp_next; if (tp != curtab) { - FOR_ALL_WINDOWS_IN_TAB(wp, tp) { + // Start from tp_lastwin to close floating windows with the same buffer first. + for (win_T *wp = tp->tp_lastwin; wp != NULL; wp = wp->w_prev) { if (wp->w_buffer == buf && !(wp->w_closing || wp->w_buffer->b_locked > 0)) { win_close_othertab(wp, false, tp); @@ -2504,31 +2525,25 @@ void close_windows(buf_T *buf, bool keep_curwin) } RedrawingDisabled--; - - redraw_tabline = true; - if (h != tabline_height()) { - win_new_screen_rows(); - } } -/// Check that the specified window is the last one. -/// @param win counted even if floating -/// -/// @return true if the specified window is the only window that exists, -/// false if there is another, possibly in another tab page. +/// Check if "win" is the last non-floating window that exists. bool last_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return one_window(win) && first_tabpage->tp_next == NULL; } -/// Check that current tab page contains no more then one window other than `aucmd_win`. -/// @param counted_float counted even if floating, but not if it is `aucmd_win` -bool one_window(win_T *counted_float) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +/// Check if "win" is the only non-floating window in the current tabpage. +bool one_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { + if (win->w_floating) { + return false; + } + bool seen_one = false; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp != aucmd_win && (!wp->w_floating || wp == counted_float)) { + if (!wp->w_floating) { if (seen_one) { return false; } @@ -2558,7 +2573,7 @@ bool last_nonfloat(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT /// @return true if all floating windows can be closed static bool can_close_floating_windows(void) { - assert(lastwin != aucmd_win); + assert(!is_aucmd_win(lastwin)); for (win_T *wp = lastwin; wp->w_floating; wp = wp->w_prev) { buf_T *buf = wp->w_buffer; int need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); @@ -2592,30 +2607,22 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev free_buf = false; } - /* - * Closing the last window in a tab page. First go to another tab - * page and then close the window and the tab page. This avoids that - * curwin and curtab are invalid while we are freeing memory, they may - * be used in GUI events. - * Don't trigger autocommands yet, they may use wrong values, so do - * that below. - */ + // Closing the last window in a tab page. First go to another tab + // page and then close the window and the tab page. This avoids that + // curwin and curtab are invalid while we are freeing memory, they may + // be used in GUI events. + // Don't trigger autocommands yet, they may use wrong values, so do + // that below. goto_tabpage_tp(alt_tabpage(), false, true); - redraw_tabline = true; // save index for tabclosed event - char_u prev_idx[NUMBUFLEN]; - sprintf((char *)prev_idx, "%i", tabpage_index(prev_curtab)); + char prev_idx[NUMBUFLEN]; + snprintf(prev_idx, NUMBUFLEN, "%i", tabpage_index(prev_curtab)); // Safety check: Autocommands may have closed the window when jumping // to the other tab page. if (valid_tabpage(prev_curtab) && prev_curtab->tp_firstwin == win) { - int h = tabline_height(); - win_close_othertab(win, free_buf, prev_curtab); - if (h != tabline_height()) { - win_new_screen_rows(); - } } entering_window(curwin); @@ -2671,11 +2678,6 @@ static void win_close_buffer(win_T *win, bool free_buf, bool abort_if_last) // Returns FAIL when the window was not closed. int win_close(win_T *win, bool free_buf, bool force) { - win_T *wp; - bool other_buffer = false; - bool close_curwin = false; - int dir; - bool help_window = false; tabpage_T *prev_curtab = curtab; frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent; const bool had_diffmode = win->w_p_diff; @@ -2689,12 +2691,12 @@ int win_close(win_T *win, bool free_buf, bool force) || (win->w_buffer != NULL && win->w_buffer->b_locked > 0)) { return FAIL; // window is already being closed } - if (win == aucmd_win) { + if (is_aucmd_win(win)) { emsg(_(e_autocmd_close)); return FAIL; } if (lastwin->w_floating && one_window(win)) { - if (lastwin == aucmd_win) { + if (is_aucmd_win(lastwin)) { emsg(_("E814: Cannot close window, only autocmd window would remain")); return FAIL; } @@ -2720,6 +2722,8 @@ int win_close(win_T *win, bool free_buf, bool force) return FAIL; } + bool help_window = false; + // When closing the help window, try restoring a snapshot after closing // the window. Otherwise clear the snapshot, it's now invalid. if (bt_help(win->w_buffer)) { @@ -2728,6 +2732,9 @@ int win_close(win_T *win, bool free_buf, bool force) clear_snapshot(curtab, SNAP_HELP_IDX); } + win_T *wp; + bool other_buffer = false; + if (win == curwin) { leaving_window(curwin); @@ -2743,10 +2750,8 @@ int win_close(win_T *win, bool free_buf, bool force) } } - /* - * Be careful: If autocommands delete the window or cause this window - * to be the last one left, return now. - */ + // Be careful: If autocommands delete the window or cause this window + // to be the last one left, return now. if (wp->w_buffer != curbuf) { reset_VIsual_and_resel(); // stop Visual mode @@ -2776,27 +2781,6 @@ int win_close(win_T *win, bool free_buf, bool force) } } - bool was_floating = win->w_floating; - if (ui_has(kUIMultigrid)) { - ui_call_win_close(win->w_grid_alloc.handle); - } - - if (win->w_floating) { - ui_comp_remove_grid(&win->w_grid_alloc); - if (win->w_float_config.external) { - for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) { - if (tp == curtab) { - continue; - } - if (tp->tp_curwin == win) { - // NB: an autocmd can still abort the closing of this window, - // bur carring out this change anyway shouldn't be a catastrophe. - tp->tp_curwin = tp->tp_firstwin; - } - } - } - } - // Fire WinClosed just before starting to free window-related resources. do_autocmd_winclosed(win); // autocmd may have freed the window already. @@ -2821,7 +2805,10 @@ int win_close(win_T *win, bool free_buf, bool force) if (curtab != prev_curtab && win_valid_any_tab(win) && win->w_buffer == NULL) { // Need to close the window anyway, since the buffer is NULL. + // Don't trigger autocmds with a NULL buffer. + block_autocmds(); win_close_othertab(win, false, prev_curtab); + unblock_autocmds(); return FAIL; } @@ -2839,8 +2826,31 @@ int win_close(win_T *win, bool free_buf, bool force) // let terminal buffers know that this window dimensions may be ignored win->w_closing = true; + bool was_floating = win->w_floating; + if (ui_has(kUIMultigrid)) { + ui_call_win_close(win->w_grid_alloc.handle); + } + + if (win->w_floating) { + ui_comp_remove_grid(&win->w_grid_alloc); + assert(first_tabpage != NULL); // suppress clang "Dereference of NULL pointer" + if (win->w_float_config.external) { + for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) { + if (tp == curtab) { + continue; + } + if (tp->tp_curwin == win) { + // NB: an autocmd can still abort the closing of this window, + // bur carring out this change anyway shouldn't be a catastrophe. + tp->tp_curwin = tp->tp_firstwin; + } + } + } + } + // Free the memory used for the window and get the window that received // the screen space. + int dir; wp = win_free_mem(win, &dir, NULL); if (help_window) { @@ -2852,16 +2862,16 @@ int win_close(win_T *win, bool free_buf, bool force) } } + bool close_curwin = false; + // Make sure curwin isn't invalid. It can cause severe trouble when // printing an error message. For win_equal() curbuf needs to be valid // too. if (win == curwin) { curwin = wp; if (wp->w_p_pvw || bt_quickfix(wp->w_buffer)) { - /* - * If the cursor goes to the preview or the quickfix window, try - * finding another window to go to. - */ + // If the cursor goes to the preview or the quickfix window, try + // finding another window to go to. for (;;) { if (wp->w_next == NULL) { wp = firstwin; @@ -2886,12 +2896,20 @@ int win_close(win_T *win, bool free_buf, bool force) } if (!was_floating) { + // If last window has a status line now and we don't want one, + // remove the status line. Do this before win_equal(), because + // it may change the height of a window. + last_status(false); + if (!curwin->w_floating && p_ea && (*p_ead == 'b' || *p_ead == dir)) { // If the frame of the closed window contains the new current window, // only resize that frame. Otherwise resize all windows. win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir); } else { (void)win_comp_pos(); + if (*p_spk != 'c') { + win_fix_scroll(false); + } } } @@ -2906,12 +2924,6 @@ int win_close(win_T *win, bool free_buf, bool force) split_disallowed--; - /* - * If last window has a status line now and we don't want one, - * remove the status line. - */ - last_status(false); - // After closing the help window, try restoring the window layout from // before it was opened. if (help_window) { @@ -2935,7 +2947,10 @@ int win_close(win_T *win, bool free_buf, bool force) } curwin->w_pos_changed = true; - redraw_all_later(NOT_VALID); + if (!was_floating) { + // TODO(bfredl): how about no? + redraw_all_later(UPD_NOT_VALID); + } return OK; } @@ -2953,19 +2968,13 @@ static void do_autocmd_winclosed(win_T *win) recursive = false; } -/* - * Close window "win" in tab page "tp", which is not the current tab page. - * This may be the last window in that tab page and result in closing the tab, - * thus "tp" may become invalid! - * Caller must check if buffer is hidden and whether the tabline needs to be - * updated. - */ +// Close window "win" in tab page "tp", which is not the current tab page. +// This may be the last window in that tab page and result in closing the tab, +// thus "tp" may become invalid! +// Caller must check if buffer is hidden and whether the tabline needs to be +// updated. void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) { - int dir; - tabpage_T *ptp = NULL; - bool free_tp = false; - // Get here with win->w_buffer == NULL when win_close() detects the tab page // changed. if (win->w_closing @@ -2985,6 +2994,8 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false, true); } + tabpage_T *ptp = NULL; + // Careful: Autocommands may have closed the tab page or made it the // current tab page. for (ptp = first_tabpage; ptp != NULL && ptp != tp; ptp = ptp->tp_next) {} @@ -3013,6 +3024,8 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) } } + bool free_tp = false; + // When closing the last window in a tab page remove the tab page. if (tp->tp_firstwin == tp->tp_lastwin) { char prev_idx[NUMBUFLEN]; @@ -3020,6 +3033,8 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) vim_snprintf(prev_idx, NUMBUFLEN, "%i", tabpage_index(tp)); } + int h = tabline_height(); + if (tp == first_tabpage) { first_tabpage = tp->tp_next; } else { @@ -3034,6 +3049,10 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) ptp->tp_next = tp->tp_next; } free_tp = true; + redraw_tabline = true; + if (h != tabline_height()) { + win_new_screen_rows(); + } if (has_event(EVENT_TABCLOSED)) { apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); @@ -3041,6 +3060,7 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) } // Free the memory used for the window. + int dir; win_free_mem(win, &dir, tp); if (free_tp) { @@ -3056,32 +3076,36 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) /// @return a pointer to the window that got the freed up space. static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp) { - frame_T *frp; win_T *wp; + tabpage_T *win_tp = tp == NULL ? curtab : tp; if (!win->w_floating) { // Remove the window and its frame from the tree of frames. - frp = win->w_frame; + frame_T *frp = win->w_frame; wp = winframe_remove(win, dirp, tp); xfree(frp); } else { *dirp = 'h'; // Dummy value. - if (win_valid(prevwin) && prevwin != win) { - wp = prevwin; + if (tp == NULL) { + if (win_valid(prevwin) && prevwin != win) { + wp = prevwin; + } else { + wp = firstwin; + } } else { - wp = firstwin; + if (tabpage_win_valid(tp, tp->tp_prevwin) && tp->tp_prevwin != win) { + wp = tp->tp_prevwin; + } else { + wp = tp->tp_firstwin; + } } } win_free(win, tp); - // When deleting the current window of another tab page select a new - // current window. - if (tp != NULL && win == tp->tp_curwin) { - if (win_valid(tp->tp_prevwin) && tp->tp_prevwin != win) { - tp->tp_curwin = tp->tp_prevwin; - } else { - tp->tp_curwin = tp->tp_firstwin; - } + // When deleting the current window in the tab, select a new current + // window. + if (win == win_tp->tp_curwin) { + win_tp->tp_curwin = wp; } return wp; @@ -3090,30 +3114,37 @@ static win_T *win_free_mem(win_T *win, int *dirp, tabpage_T *tp) #if defined(EXITFREE) void win_free_all(void) { - int dummy; - // avoid an error for switching tabpage with the cmdline window open cmdwin_type = 0; while (first_tabpage->tp_next != NULL) { - tabpage_close(TRUE); + tabpage_close(true); } while (lastwin != NULL && lastwin->w_floating) { win_T *wp = lastwin; win_remove(lastwin, NULL); + int dummy; (void)win_free_mem(wp, &dummy, NULL); - if (wp == aucmd_win) { - aucmd_win = NULL; + for (int i = 0; i < AUCMD_WIN_COUNT; i++) { + if (aucmd_win[i].auc_win == wp) { + aucmd_win[i].auc_win = NULL; + } } } - if (aucmd_win != NULL) { - (void)win_free_mem(aucmd_win, &dummy, NULL); - aucmd_win = NULL; + for (int i = 0; i < AUCMD_WIN_COUNT; i++) { + if (aucmd_win[i].auc_win != NULL) { + int dummy; + (void)win_free_mem(aucmd_win[i].auc_win, &dummy, NULL); + aucmd_win[i].auc_win = NULL; + } } + kv_destroy(aucmd_win_vec); + while (firstwin != NULL) { + int dummy; (void)win_free_mem(firstwin, &dummy, NULL); } @@ -3132,22 +3163,16 @@ void win_free_all(void) /// @return a pointer to the window that got the freed up space. win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) { - frame_T *frp, *frp2, *frp3; - frame_T *frp_close = win->w_frame; - win_T *wp; - - /* - * If there is only one window there is nothing to remove. - */ + // If there is only one window there is nothing to remove. if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) { return NULL; } - /* - * Remove the window from its frame. - */ - frp2 = win_altframe(win, tp); - wp = frame2win(frp2); + frame_T *frp_close = win->w_frame; + + // Remove the window from its frame. + frame_T *frp2 = win_altframe(win, tp); + win_T *wp = frame2win(frp2); // Remove this frame from the list of frames. frame_remove(frp_close); @@ -3157,8 +3182,8 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) // (as close to the closed frame as possible) to distribute the height // to. if (frp2->fr_win != NULL && frp2->fr_win->w_p_wfh) { - frp = frp_close->fr_prev; - frp3 = frp_close->fr_next; + frame_T *frp = frp_close->fr_prev; + frame_T *frp3 = frp_close->fr_next; while (frp != NULL || frp3 != NULL) { if (frp != NULL) { if (!frame_fixed_height(frp)) { @@ -3186,8 +3211,8 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) // (as close to the closed frame as possible) to distribute the width // to. if (frp2->fr_win != NULL && frp2->fr_win->w_p_wfw) { - frp = frp_close->fr_prev; - frp3 = frp_close->fr_next; + frame_T *frp = frp_close->fr_prev; + frame_T *frp3 = frp_close->fr_next; while (frp != NULL || frp3 != NULL) { if (frp != NULL) { if (!frame_fixed_width(frp)) { @@ -3226,6 +3251,7 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) // and remove it. frp2->fr_parent->fr_layout = frp2->fr_layout; frp2->fr_parent->fr_child = frp2->fr_child; + frame_T *frp; FOR_ALL_FRAMES(frp, frp2->fr_child) { frp->fr_parent = frp2->fr_parent; } @@ -3251,6 +3277,7 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) if (frp->fr_prev != NULL) { frp->fr_prev->fr_next = frp->fr_child; } + frame_T *frp3; for (frp3 = frp->fr_child;; frp3 = frp3->fr_next) { frp3->fr_parent = frp2; if (frp3->fr_next == NULL) { @@ -3283,13 +3310,11 @@ win_T *winframe_remove(win_T *win, int *dirp, tabpage_T *tp) /// is left over after "win" is closed. static frame_T *win_altframe(win_T *win, tabpage_T *tp) { - frame_T *frp; - if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin) { return alt_tabpage()->tp_curwin->w_frame; } - frp = win->w_frame; + frame_T *frp = win->w_frame; if (frp->fr_prev == NULL) { return frp->fr_next; @@ -3332,26 +3357,21 @@ static frame_T *win_altframe(win_T *win, tabpage_T *tp) return target_fr; } -/* - * Return the tabpage that will be used if the current one is closed. - */ +// Return the tabpage that will be used if the current one is closed. static tabpage_T *alt_tabpage(void) { - tabpage_T *tp; - // Use the next tab page if possible. if (curtab->tp_next != NULL) { return curtab->tp_next; } // Find the last but one tab page. + tabpage_T *tp; for (tp = first_tabpage; tp->tp_next != curtab; tp = tp->tp_next) {} return tp; } -/* - * Find the left-upper window in frame "frp". - */ +// Find the left-upper window in frame "frp". win_T *frame2win(frame_T *frp) { while (frp->fr_win == NULL) { @@ -3384,8 +3404,7 @@ static bool frame_has_win(const frame_T *frp, const win_T *wp) static bool is_bottom_win(win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - frame_T *frp; - for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + for (frame_T *frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { if (frp->fr_parent->fr_layout == FR_COL && frp->fr_next != NULL) { return false; } @@ -3401,19 +3420,15 @@ static bool is_bottom_win(win_T *wp) void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) FUNC_ATTR_NONNULL_ALL { - frame_T *frp; - int extra_lines; - int h; - win_T *wp; - if (topfrp->fr_win != NULL) { // Simple case: just one window. - wp = topfrp->fr_win; + win_T *wp = topfrp->fr_win; if (is_bottom_win(wp)) { wp->w_hsep_height = 0; } win_new_height(wp, height - wp->w_hsep_height - wp->w_status_height); } else if (topfrp->fr_layout == FR_ROW) { + frame_T *frp; do { // All frames in this row get the same new height. FOR_ALL_FRAMES(frp, topfrp->fr_child) { @@ -3429,7 +3444,7 @@ void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) // Complicated case: Resize a column of frames. Resize the bottom // frame first, frames above that when needed. - frp = topfrp->fr_child; + frame_T *frp = topfrp->fr_child; if (wfh) { // Advance past frames with one window with 'wfh' set. while (frame_fixed_height(frp)) { @@ -3452,11 +3467,11 @@ void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) } } - extra_lines = height - topfrp->fr_height; + int extra_lines = height - topfrp->fr_height; if (extra_lines < 0) { // reduce height of contained frames, bottom or top frame first while (frp != NULL) { - h = frame_minheight(frp, NULL); + int h = frame_minheight(frp, NULL); if (frp->fr_height + extra_lines < h) { extra_lines += frp->fr_height - h; frame_new_height(frp, h, topfirst, wfh); @@ -3555,16 +3570,12 @@ static bool frame_fixed_width(frame_T *frp) return true; } -/* - * Add a status line to windows at the bottom of "frp". - * Note: Does not check if there is room! - */ +// Add a status line to windows at the bottom of "frp". +// Note: Does not check if there is room! static void frame_add_statusline(frame_T *frp) { - win_T *wp; - if (frp->fr_layout == FR_LEAF) { - wp = frp->fr_win; + win_T *wp = frp->fr_win; if (wp->w_status_height == 0) { if (wp->w_height - STATUS_HEIGHT >= 0) { // don't make it negative wp->w_height -= STATUS_HEIGHT; @@ -3592,15 +3603,11 @@ static void frame_add_statusline(frame_T *frp) /// may cause the width not to be set. static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw) { - frame_T *frp; - int extra_cols; - int w; - win_T *wp; - if (topfrp->fr_layout == FR_LEAF) { // Simple case: just one window. - wp = topfrp->fr_win; + win_T *wp = topfrp->fr_win; // Find out if there are any windows right of this one. + frame_T *frp; for (frp = topfrp; frp->fr_parent != NULL; frp = frp->fr_parent) { if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_next != NULL) { break; @@ -3611,6 +3618,7 @@ static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw } win_new_width(wp, width - wp->w_vsep_width); } else if (topfrp->fr_layout == FR_COL) { + frame_T *frp; do { // All frames in this column get the same new width. FOR_ALL_FRAMES(frp, topfrp->fr_child) { @@ -3626,7 +3634,7 @@ static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw // Complicated case: Resize a row of frames. Resize the rightmost // frame first, frames left of it when needed. - frp = topfrp->fr_child; + frame_T *frp = topfrp->fr_child; if (wfw) { // Advance past frames with one window with 'wfw' set. while (frame_fixed_width(frp)) { @@ -3649,11 +3657,11 @@ static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw } } - extra_cols = width - topfrp->fr_width; + int extra_cols = width - topfrp->fr_width; if (extra_cols < 0) { // reduce frame width, rightmost frame first while (frp != NULL) { - w = frame_minwidth(frp, NULL); + int w = frame_minwidth(frp, NULL); if (frp->fr_width + extra_cols < w) { extra_cols += frp->fr_width - w; frame_new_width(frp, w, leftfirst, wfw); @@ -3689,13 +3697,11 @@ static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw static void frame_add_vsep(const frame_T *frp) FUNC_ATTR_NONNULL_ARG(1) { - win_T *wp; - if (frp->fr_layout == FR_LEAF) { - wp = frp->fr_win; + win_T *wp = frp->fr_win; if (wp->w_vsep_width == 0) { if (wp->w_width > 0) { // don't make it negative - --wp->w_width; + wp->w_width--; } wp->w_vsep_width = 1; } @@ -3720,10 +3726,8 @@ static void frame_add_vsep(const frame_T *frp) static void frame_add_hsep(const frame_T *frp) FUNC_ATTR_NONNULL_ARG(1) { - win_T *wp; - if (frp->fr_layout == FR_LEAF) { - wp = frp->fr_win; + win_T *wp = frp->fr_win; if (wp->w_hsep_height == 0) { if (wp->w_height > 0) { // don't make it negative wp->w_height++; @@ -3746,17 +3750,13 @@ static void frame_add_hsep(const frame_T *frp) } } -/* - * Set frame width from the window it contains. - */ +// Set frame width from the window it contains. static void frame_fix_width(win_T *wp) { wp->w_frame->fr_width = wp->w_width + wp->w_vsep_width; } -/* - * Set frame height from the window it contains. - */ +// Set frame height from the window it contains. static void frame_fix_height(win_T *wp) FUNC_ATTR_NONNULL_ALL { @@ -3768,9 +3768,7 @@ static void frame_fix_height(win_T *wp) /// When "next_curwin" is NOWIN, don't use at least one line for the current window. static int frame_minheight(frame_T *topfrp, win_T *next_curwin) { - frame_T *frp; int m; - int n; if (topfrp->fr_win != NULL) { // Combined height of window bar and separator column or status line. @@ -3791,8 +3789,9 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin) } else if (topfrp->fr_layout == FR_ROW) { // get the minimal height from each frame in this row m = 0; + frame_T *frp; FOR_ALL_FRAMES(frp, topfrp->fr_child) { - n = frame_minheight(frp, next_curwin); + int n = frame_minheight(frp, next_curwin); if (n > m) { m = n; } @@ -3800,6 +3799,7 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin) } else { // Add up the minimal heights for all frames in this column. m = 0; + frame_T *frp; FOR_ALL_FRAMES(frp, topfrp->fr_child) { m += frame_minheight(frp, next_curwin); } @@ -3816,8 +3816,7 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin) /// @param next_curwin use p_wh and p_wiw for next_curwin static int frame_minwidth(frame_T *topfrp, win_T *next_curwin) { - frame_T *frp; - int m, n; + int m; if (topfrp->fr_win != NULL) { if (topfrp->fr_win == next_curwin) { @@ -3833,8 +3832,9 @@ static int frame_minwidth(frame_T *topfrp, win_T *next_curwin) } else if (topfrp->fr_layout == FR_COL) { // get the minimal width from each frame in this column m = 0; + frame_T *frp; FOR_ALL_FRAMES(frp, topfrp->fr_child) { - n = frame_minwidth(frp, next_curwin); + int n = frame_minwidth(frp, next_curwin); if (n > m) { m = n; } @@ -3842,6 +3842,7 @@ static int frame_minwidth(frame_T *topfrp, win_T *next_curwin) } else { // Add up the minimal widths for all frames in this row. m = 0; + frame_T *frp; FOR_ALL_FRAMES(frp, topfrp->fr_child) { m += frame_minwidth(frp, next_curwin); } @@ -3859,10 +3860,6 @@ static int frame_minwidth(frame_T *topfrp, win_T *next_curwin) /// @param forceit always hide all other windows void close_others(int message, int forceit) { - win_T *wp; - win_T *nextwp; - int r; - if (curwin->w_floating) { if (message && !autocmd_busy) { emsg(e_floatonly); @@ -3879,14 +3876,15 @@ void close_others(int message, int forceit) } // Be very careful here: autocommands may change the window layout. - for (wp = firstwin; win_valid(wp); wp = nextwp) { + win_T *nextwp; + for (win_T *wp = firstwin; win_valid(wp); wp = nextwp) { nextwp = wp->w_next; if (wp == curwin) { // don't close current window continue; } // Check if it's allowed to abandon this window - r = can_abandon(wp->w_buffer, forceit); + int r = can_abandon(wp->w_buffer, forceit); if (!win_valid(wp)) { // autocommands messed wp up nextwp = firstwin; continue; @@ -3911,48 +3909,59 @@ void close_others(int message, int forceit) } } -/* - * Allocate the first window and put an empty buffer in it. - * Called from main(). - * - * Return FAIL when something goes wrong. - */ -int win_alloc_first(void) +/// Store the relevant window pointers for tab page "tp". To be used before +/// use_tabpage(). +void unuse_tabpage(tabpage_T *tp) +{ + tp->tp_topframe = topframe; + tp->tp_firstwin = firstwin; + tp->tp_lastwin = lastwin; + tp->tp_curwin = curwin; +} + +/// Set the relevant pointers to use tab page "tp". May want to call +/// unuse_tabpage() first. +void use_tabpage(tabpage_T *tp) +{ + curtab = tp; + topframe = curtab->tp_topframe; + firstwin = curtab->tp_firstwin; + lastwin = curtab->tp_lastwin; + curwin = curtab->tp_curwin; +} + +// Allocate the first window and put an empty buffer in it. +// Only called from main(). +void win_alloc_first(void) { if (win_alloc_firstwin(NULL) == FAIL) { - return FAIL; + // allocating first buffer before any autocmds should not fail. + abort(); } first_tabpage = alloc_tabpage(); - first_tabpage->tp_topframe = topframe; curtab = first_tabpage; - curtab->tp_firstwin = firstwin; - curtab->tp_lastwin = lastwin; - curtab->tp_curwin = curwin; - - return OK; + unuse_tabpage(first_tabpage); } -// Init `aucmd_win`. This can only be done after the first window +// Init `aucmd_win[idx]`. This can only be done after the first window // is fully initialized, thus it can't be in win_alloc_first(). -void win_alloc_aucmd_win(void) +void win_alloc_aucmd_win(int idx) { Error err = ERROR_INIT; FloatConfig fconfig = FLOAT_CONFIG_INIT; fconfig.width = Columns; fconfig.height = 5; fconfig.focusable = false; - aucmd_win = win_new_float(NULL, true, fconfig, &err); - aucmd_win->w_buffer->b_nwindows--; - RESET_BINDING(aucmd_win); + aucmd_win[idx].auc_win = win_new_float(NULL, true, fconfig, &err); + aucmd_win[idx].auc_win->w_buffer->b_nwindows--; + RESET_BINDING(aucmd_win[idx].auc_win); } -/* - * Allocate the first window or the first window in a new tab page. - * When "oldwin" is NULL create an empty buffer for it. - * When "oldwin" is not NULL copy info from it to the new window. - * Return FAIL when something goes wrong (out of memory). - */ +// Allocate the first window or the first window in a new tab page. +// When "oldwin" is NULL create an empty buffer for it. +// When "oldwin" is not NULL copy info from it to the new window. +// Return FAIL when something goes wrong (out of memory). static int win_alloc_firstwin(win_T *oldwin) { curwin = win_alloc(NULL, false); @@ -3984,9 +3993,7 @@ static int win_alloc_firstwin(win_T *oldwin) return OK; } -/* - * Create a frame for window "wp". - */ +// Create a frame for window "wp". static void new_frame(win_T *wp) { frame_T *frp = xcalloc(1, sizeof(frame_T)); @@ -3996,12 +4003,11 @@ static void new_frame(win_T *wp) frp->fr_win = wp; } -/* - * Initialize the window and frame size to the maximum. - */ +// Initialize the window and frame size to the maximum. void win_init_size(void) { firstwin->w_height = (int)ROWS_AVAIL; + firstwin->w_prev_height = (int)ROWS_AVAIL; firstwin->w_height_inner = firstwin->w_height - firstwin->w_winbar_height; firstwin->w_height_outer = firstwin->w_height; firstwin->w_winrow_off = firstwin->w_winbar_height; @@ -4012,9 +4018,7 @@ void win_init_size(void) topframe->fr_width = Columns; } -/* - * Allocate a new tabpage_T and init the values. - */ +// Allocate a new tabpage_T and init the values. static tabpage_T *alloc_tabpage(void) { static int last_tp_handle = 0; @@ -4025,7 +4029,7 @@ static tabpage_T *alloc_tabpage(void) // Init t: variables. tp->tp_vars = tv_dict_alloc(); init_var_dict(tp->tp_vars, &tp->tp_winvar, VAR_SCOPE); - tp->tp_diff_invalid = TRUE; + tp->tp_diff_invalid = true; tp->tp_ch_used = p_ch; return tp; @@ -4033,11 +4037,9 @@ static tabpage_T *alloc_tabpage(void) void free_tabpage(tabpage_T *tp) { - int idx; - pmap_del(handle_T)(&tabpage_handles, tp->handle); diff_clear(tp); - for (idx = 0; idx < SNAP_COUNT; ++idx) { + for (int idx = 0; idx < SNAP_COUNT; idx++) { clear_snapshot(tp, idx); } vars_clear(&tp->tp_vars->dv_hashtab); // free all t: variables @@ -4061,18 +4063,16 @@ void free_tabpage(tabpage_T *tp) /// tabpage in case of 0. /// @param filename Will be passed to apply_autocmds(). /// @return Was the new tabpage created successfully? FAIL or OK. -int win_new_tabpage(int after, char_u *filename) +int win_new_tabpage(int after, char *filename) { tabpage_T *old_curtab = curtab; - tabpage_T *newtp; - int n; if (cmdwin_type != 0) { emsg(_(e_cmdwin)); return FAIL; } - newtp = alloc_tabpage(); + tabpage_T *newtp = alloc_tabpage(); // Remember the current windows in this Tab page. if (leave_tabpage(curbuf, true) == FAIL) { @@ -4097,7 +4097,7 @@ int win_new_tabpage(int after, char_u *filename) if (after > 0) { // Put new tab page before tab page "after". - n = 2; + int n = 2; for (tp = first_tabpage; tp->tp_next != NULL && n < after; tp = tp->tp_next) { n++; @@ -4110,12 +4110,13 @@ int win_new_tabpage(int after, char_u *filename) win_init_size(); firstwin->w_winrow = tabline_height(); + firstwin->w_prev_winrow = firstwin->w_winrow; win_comp_scroll(curwin); newtp->tp_topframe = topframe; last_status(false); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); tabpage_check_windows(old_curtab); @@ -4125,7 +4126,7 @@ int win_new_tabpage(int after, char_u *filename) apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); - apply_autocmds(EVENT_TABNEW, (char *)filename, (char *)filename, false, curbuf); + apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf); apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf); return OK; @@ -4136,11 +4137,9 @@ int win_new_tabpage(int after, char_u *filename) return FAIL; } -/* - * Open a new tab page if ":tab cmd" was used. It will edit the same buffer, - * like with ":split". - * Returns OK if a new tab page was created, FAIL otherwise. - */ +// Open a new tab page if ":tab cmd" was used. It will edit the same buffer, +// like with ":split". +// Returns OK if a new tab page was created, FAIL otherwise. int may_open_tabpage(void) { int n = (cmdmod.cmod_tab == 0) ? postponed_split_tab : cmdmod.cmod_tab; @@ -4153,27 +4152,23 @@ int may_open_tabpage(void) return FAIL; } -/* - * Create up to "maxcount" tabpages with empty windows. - * Returns the number of resulting tab pages. - */ +// Create up to "maxcount" tabpages with empty windows. +// Returns the number of resulting tab pages. int make_tabpages(int maxcount) { int count = maxcount; - int todo; // Limit to 'tabpagemax' tabs. if (count > p_tpm) { count = (int)p_tpm; } - /* - * Don't execute autocommands while creating the tab pages. Must do that - * when putting the buffers in the windows. - */ + // Don't execute autocommands while creating the tab pages. Must do that + // when putting the buffers in the windows. block_autocmds(); - for (todo = count - 1; todo > 0; --todo) { + int todo; + for (todo = count - 1; todo > 0; todo--) { if (win_new_tabpage(0, NULL) == FAIL) { break; } @@ -4237,9 +4232,7 @@ void close_tabpage(tabpage_T *tab) free_tabpage(tab); } -/* - * Find tab page "n" (first one is 1). Returns NULL when not found. - */ +// Find tab page "n" (first one is 1). Returns NULL when not found. tabpage_T *find_tabpage(int n) { tabpage_T *tp; @@ -4251,10 +4244,8 @@ tabpage_T *find_tabpage(int n) return tp; } -/* - * Get index of tab page "tp". First one has index 1. - * When not found returns number of tab pages plus one. - */ +// Get index of tab page "tp". First one has index 1. +// When not found returns number of tab pages plus one. int tabpage_index(tabpage_T *ftp) { int i = 1; @@ -4296,12 +4287,16 @@ static int leave_tabpage(buf_T *new_curbuf, bool trigger_leave_autocmds) return FAIL; } } + + reset_dragwin(); tp->tp_curwin = curwin; tp->tp_prevwin = prevwin; tp->tp_firstwin = firstwin; tp->tp_lastwin = lastwin; tp->tp_old_Rows_avail = ROWS_AVAIL; - tp->tp_old_Columns = Columns; + if (tp->tp_old_Columns != -1) { + tp->tp_old_Columns = Columns; + } firstwin = NULL; lastwin = NULL; return OK; @@ -4319,10 +4314,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a win_T *next_prevwin = tp->tp_prevwin; tabpage_T *old_curtab = curtab; - curtab = tp; - firstwin = tp->tp_firstwin; - lastwin = tp->tp_lastwin; - topframe = tp->tp_topframe; + use_tabpage(tp); if (old_curtab != curtab) { tabpage_check_windows(old_curtab); @@ -4343,23 +4335,37 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a // Use the stored value of p_ch, so that it can be different for each tab page. if (p_ch != curtab->tp_ch_used) { clear_cmdline = true; + if (msg_grid.chars && p_ch < curtab->tp_ch_used) { + // TODO(bfredl): a bit expensive, should be enough to invalidate the + // region between the old and the new p_ch. + grid_invalidate(&msg_grid); + } } p_ch = curtab->tp_ch_used; // When cmdheight is changed in a tab page with '<C-w>-', cmdline_row is // changed but p_ch and tp_ch_used are not changed. Thus we also need to // check cmdline_row. - if ((row < cmdline_row) && (cmdline_row <= Rows - p_ch)) { + if (row < cmdline_row && cmdline_row <= Rows - p_ch) { clear_cmdline = true; } + // If there was a click in a window, it won't be usable for a following + // drag. + reset_dragwin(); + // The tabpage line may have appeared or disappeared, may need to resize the frames for that. // When the Vim window was resized or ROWS_AVAIL changed need to update frame sizes too. if (curtab->tp_old_Rows_avail != ROWS_AVAIL || (old_off != firstwin->w_winrow)) { win_new_screen_rows(); } - if (curtab->tp_old_Columns != Columns && starting == 0) { - win_new_screen_cols(); // update window widths + if (curtab->tp_old_Columns != Columns) { + if (starting == 0) { + win_new_screen_cols(); // update window widths + curtab->tp_old_Columns = Columns; + } else { + curtab->tp_old_Columns = -1; // update window widths later + } } lastused_tabpage = old_curtab; @@ -4373,7 +4379,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a } } - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } /// tells external UI that windows and inline floats in old_curtab are invisible @@ -4405,16 +4411,10 @@ static void tabpage_check_windows(tabpage_T *old_curtab) } } -/* - * Go to tab page "n". For ":tab N" and "Ngt". - * When "n" is 9999 go to the last tab page. - */ +// Go to tab page "n". For ":tab N" and "Ngt". +// When "n" is 9999 go to the last tab page. void goto_tabpage(int n) { - tabpage_T *tp = NULL; // shut up compiler - tabpage_T *ttp; - int i; - if (text_locked()) { // Not allowed when editing the command line. text_locked_msg(); @@ -4429,6 +4429,8 @@ void goto_tabpage(int n) return; } + tabpage_T *tp = NULL; // shut up compiler + if (n == 0) { // No count, go to next tab page, wrap around end. if (curtab->tp_next == NULL) { @@ -4439,8 +4441,8 @@ void goto_tabpage(int n) } else if (n < 0) { // "gT": go to previous tab page, wrap around end. "N gT" repeats // this N times. - ttp = curtab; - for (i = n; i < 0; ++i) { + tabpage_T *ttp = curtab; + for (int i = n; i < 0; i++) { for (tp = first_tabpage; tp->tp_next != ttp && tp->tp_next != NULL; tp = tp->tp_next) {} ttp = tp; @@ -4474,6 +4476,7 @@ void goto_tabpage_tp(tabpage_T *tp, bool trigger_enter_autocmds, bool trigger_le // Don't repeat a message in another tab page. set_keep_msg(NULL, 0); + skip_win_fix_scroll = true; if (tp != curtab && leave_tabpage(tp->tp_curwin->w_buffer, trigger_leave_autocmds) == OK) { if (valid_tabpage(tp)) { @@ -4484,6 +4487,7 @@ void goto_tabpage_tp(tabpage_T *tp, bool trigger_enter_autocmds, bool trigger_le trigger_leave_autocmds); } } + skip_win_fix_scroll = false; } /// Go to the last accessed tab page, if there is one. @@ -4497,10 +4501,8 @@ bool goto_tabpage_lastused(void) return false; } -/* - * Enter window "wp" in tab page "tp". - * Also updates the GUI tab. - */ +// Enter window "wp" in tab page "tp". +// Also updates the GUI tab. void goto_tabpage_win(tabpage_T *tp, win_T *wp) { goto_tabpage_tp(tp, true, true); @@ -4512,16 +4514,15 @@ void goto_tabpage_win(tabpage_T *tp, win_T *wp) // Move the current tab page to after tab page "nr". void tabpage_move(int nr) { - int n = 1; - tabpage_T *tp; - tabpage_T *tp_dst; - assert(curtab != NULL); if (first_tabpage->tp_next == NULL) { return; } + int n = 1; + tabpage_T *tp; + for (tp = first_tabpage; tp->tp_next != NULL && n < nr; tp = tp->tp_next) { n++; } @@ -4531,7 +4532,7 @@ void tabpage_move(int nr) return; } - tp_dst = tp; + tabpage_T *tp_dst = tp; // Remove the current tab page from the list of tab pages. if (curtab == first_tabpage) { @@ -4563,13 +4564,11 @@ void tabpage_move(int nr) redraw_tabline = true; } -/* - * Go to another window. - * When jumping to another buffer, stop Visual mode. Do this before - * changing windows so we can yank the selection into the '*' register. - * When jumping to another window on the same buffer, adjust its cursor - * position to keep the same Visual area. - */ +// Go to another window. +// When jumping to another buffer, stop Visual mode. Do this before +// changing windows so we can yank the selection into the '*' register. +// When jumping to another window on the same buffer, adjust its cursor +// position to keep the same Visual area. void win_goto(win_T *wp) { win_T *owp = curwin; @@ -4596,9 +4595,7 @@ void win_goto(win_T *wp) } } -/* - * Find the tabpage for window "win". - */ +// Find the tabpage for window "win". tabpage_T *win_find_tabpage(win_T *win) { FOR_ALL_TAB_WINDOWS(tp, wp) { @@ -4620,22 +4617,17 @@ tabpage_T *win_find_tabpage(win_T *win) /// @return found window win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, long count) { - frame_T *fr; - frame_T *nfr; - frame_T *foundfr; - - foundfr = wp->w_frame; + frame_T *foundfr = wp->w_frame; if (wp->w_floating) { return win_valid(prevwin) && !prevwin->w_floating ? prevwin : firstwin; } while (count--) { - /* - * First go upwards in the tree of frames until we find an upwards or - * downwards neighbor. - */ - fr = foundfr; + frame_T *nfr; + // First go upwards in the tree of frames until we find an upwards or + // downwards neighbor. + frame_T *fr = foundfr; for (;;) { if (fr == tp->tp_topframe) { goto end; @@ -4651,9 +4643,7 @@ win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, long count) fr = fr->fr_parent; } - /* - * Now go downwards to find the bottom or top frame in it. - */ + // Now go downwards to find the bottom or top frame in it. for (;;) { if (nfr->fr_layout == FR_LEAF) { foundfr = nfr; @@ -4703,22 +4693,17 @@ static void win_goto_ver(bool up, long count) /// @return found window win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, long count) { - frame_T *fr; - frame_T *nfr; - frame_T *foundfr; - - foundfr = wp->w_frame; + frame_T *foundfr = wp->w_frame; if (wp->w_floating) { return win_valid(prevwin) && !prevwin->w_floating ? prevwin : firstwin; } while (count--) { - /* - * First go upwards in the tree of frames until we find a left or - * right neighbor. - */ - fr = foundfr; + frame_T *nfr; + // First go upwards in the tree of frames until we find a left or + // right neighbor. + frame_T *fr = foundfr; for (;;) { if (fr == tp->tp_topframe) { goto end; @@ -4734,9 +4719,7 @@ win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, long count) fr = fr->fr_parent; } - /* - * Now go downwards to find the leftmost or rightmost frame in it. - */ + // Now go downwards to find the leftmost or rightmost frame in it. for (;;) { if (nfr->fr_layout == FR_LEAF) { foundfr = nfr; @@ -4828,7 +4811,9 @@ static void win_enter_ext(win_T *const wp, const int flags) // Might need to scroll the old window before switching, e.g., when the // cursor was moved. - update_topline(curwin); + if (*p_spk == 'c') { + update_topline(curwin); + } // may have to copy the buffer options when 'cpo' contains 'S' if (wp->w_buffer != curbuf) { @@ -4836,7 +4821,7 @@ static void win_enter_ext(win_T *const wp, const int flags) } if (!curwin_invalid) { prevwin = curwin; // remember for CTRL-W p - curwin->w_redr_status = TRUE; + curwin->w_redr_status = true; } curwin = wp; curbuf = wp->w_buffer; @@ -4845,7 +4830,11 @@ static void win_enter_ext(win_T *const wp, const int flags) if (!virtual_active()) { curwin->w_cursor.coladd = 0; } - changed_line_abv_curs(); // assume cursor position needs updating + if (*p_spk == 'c') { + changed_line_abv_curs(); // assume cursor position needs updating + } else { + win_fix_cursor(true); + } fix_current_dir(); @@ -4867,7 +4856,7 @@ static void win_enter_ext(win_T *const wp, const int flags) curwin->w_redr_status = true; redraw_tabline = true; if (restart_edit) { - redraw_later(curwin, VALID); // causes status line redraw + redraw_later(curwin, UPD_VALID); // causes status line redraw } // change background color according to NormalNC, @@ -4875,11 +4864,11 @@ static void win_enter_ext(win_T *const wp, const int flags) if (curwin->w_hl_attr_normal != curwin->w_hl_attr_normalnc) { // TODO(bfredl): eventually we should be smart enough // to only recompose the window, not redraw it. - redraw_later(curwin, NOT_VALID); + redraw_later(curwin, UPD_NOT_VALID); } if (prevwin) { if (prevwin->w_hl_attr_normal != prevwin->w_hl_attr_normalnc) { - redraw_later(prevwin, NOT_VALID); + redraw_later(prevwin, UPD_NOT_VALID); } } @@ -4907,7 +4896,7 @@ void fix_current_dir(void) // New directory is either the local directory of the window, tab or NULL. char *new_dir = curwin->w_localdir ? curwin->w_localdir : curtab->tp_localdir; char cwd[MAXPATHL]; - if (os_dirname((char_u *)cwd, MAXPATHL) != OK) { + if (os_dirname(cwd, MAXPATHL) != OK) { cwd[0] = NUL; } @@ -4957,12 +4946,11 @@ win_T *buf_jump_open_win(buf_T *buf) if (curwin->w_buffer == buf) { win_enter(curwin, false); return curwin; - } else { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf) { - win_enter(wp, false); - return wp; - } + } + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf) { + win_enter(wp, false); + return wp; } } @@ -4970,7 +4958,7 @@ win_T *buf_jump_open_win(buf_T *buf) } /// Jump to the first open window in any tab page that contains buffer "buf", -/// if one exists. +/// if one exists. First search in the windows present in the current tab page. /// @return the found window, or NULL. win_T *buf_jump_open_tab(buf_T *buf) { @@ -5029,9 +5017,7 @@ static win_T *win_alloc(win_T *after, bool hidden) // initialized yet. gui_create_scrollbar() may trigger a FocusGained // event. block_autocmds(); - /* - * link the window in the window list - */ + // link the window in the window list if (!hidden) { win_append(after, new_wp); } @@ -5061,8 +5047,7 @@ static win_T *win_alloc(win_T *after, bool hidden) foldInitWin(new_wp); unblock_autocmds(); - new_wp->w_match_head = NULL; - new_wp->w_next_match_id = 4; + new_wp->w_next_match_id = 1000; // up to 1000 can be picked by the user return new_wp; } @@ -5081,9 +5066,6 @@ void free_wininfo(wininfo_T *wip, buf_T *bp) /// @param tp tab page "win" is in, NULL for current static void win_free(win_T *wp, tabpage_T *tp) { - int i; - wininfo_T *wip; - pmap_del(handle_T)(&window_handles, wp->handle); clearFolding(wp); @@ -5115,7 +5097,7 @@ static void win_free(win_T *wp, tabpage_T *tp) xfree(wp->w_lines); - for (i = 0; i < wp->w_tagstacklen; i++) { + for (int i = 0; i < wp->w_tagstacklen; i++) { xfree(wp->w_tagstack[i].tagname); xfree(wp->w_tagstack[i].user_data); } @@ -5123,16 +5105,19 @@ static void win_free(win_T *wp, tabpage_T *tp) xfree(wp->w_localdir); xfree(wp->w_prevdir); - stl_clear_click_defs(wp->w_status_click_defs, (long)wp->w_status_click_defs_size); + stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size); xfree(wp->w_status_click_defs); - stl_clear_click_defs(wp->w_winbar_click_defs, (long)wp->w_winbar_click_defs_size); + stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size); xfree(wp->w_winbar_click_defs); + stl_clear_click_defs(wp->w_statuscol_click_defs, wp->w_statuscol_click_defs_size); + xfree(wp->w_statuscol_click_defs); + // Remove the window from the b_wininfo lists, it may happen that the // freed memory is re-used for another window. FOR_ALL_BUFFERS(buf) { - for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { + for (wininfo_T *wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { if (wip->wi_win == wp) { wininfo_T *wip2; @@ -5160,6 +5145,9 @@ static void win_free(win_T *wp, tabpage_T *tp) } } + // free the border title text + clear_virttext(&wp->w_float_config.title_chunks); + clear_matches(wp); free_jumplist(wp); @@ -5170,7 +5158,7 @@ static void win_free(win_T *wp, tabpage_T *tp) win_free_grid(wp, false); - if (wp != aucmd_win) { + if (win_valid_any_tab(wp)) { win_remove(wp, tp); } if (autocmd_busy) { @@ -5195,18 +5183,11 @@ void win_free_grid(win_T *wp, bool reinit) } } -/* - * Append window "wp" in the window list after window "after". - */ +// Append window "wp" in the window list after window "after". void win_append(win_T *after, win_T *wp) { - win_T *before; - - if (after == NULL) { // after NULL is in front of the first - before = firstwin; - } else { - before = after->w_next; - } + // after NULL is in front of the first + win_T *before = after == NULL ? firstwin : after->w_next; wp->w_next = before; wp->w_prev = after; @@ -5243,9 +5224,7 @@ void win_remove(win_T *wp, tabpage_T *tp) } } -/* - * Append frame "frp" in a frame list after frame "after". - */ +// Append frame "frp" in a frame list after frame "after". static void frame_append(frame_T *after, frame_T *frp) { frp->fr_next = after->fr_next; @@ -5256,9 +5235,7 @@ static void frame_append(frame_T *after, frame_T *frp) frp->fr_prev = after; } -/* - * Insert frame "frp" in a frame list before frame "before". - */ +// Insert frame "frp" in a frame list before frame "before". static void frame_insert(frame_T *before, frame_T *frp) { frp->fr_next = before; @@ -5271,9 +5248,7 @@ static void frame_insert(frame_T *before, frame_T *frp) } } -/* - * Remove a frame from a frame list. - */ +// Remove a frame from a frame list. static void frame_remove(frame_T *frp) { if (frp->fr_prev != NULL) { @@ -5294,7 +5269,7 @@ void win_new_screensize(void) if (old_Rows != Rows) { // If 'window' uses the whole screen, keep it using that. // Don't change it when set with "-w size" on the command line. - if (p_window == old_Rows - 1 || (old_Rows == 0 && p_window == 0)) { + if (p_window == old_Rows - 1 || (old_Rows == 0 && !option_was_set("window"))) { p_window = Rows - 1; } old_Rows = Rows; @@ -5330,6 +5305,10 @@ void win_new_screen_rows(void) win_reconfig_floats(); // The size of floats might change compute_cmdrow(); curtab->tp_ch_used = p_ch; + + if (*p_spk != 'c' && !skip_win_fix_scroll) { + win_fix_scroll(true); + } } /// Called from win_new_screensize() after Columns changed. @@ -5350,40 +5329,290 @@ void win_new_screen_cols(void) win_reconfig_floats(); // The size of floats might change } -/// Trigger WinScrolled for "curwin" if needed. -void may_trigger_winscrolled(void) +/// Make a snapshot of all the window scroll positions and sizes of the current +/// tab page. +void snapshot_windows_scroll_size(void) { - static bool recursive = false; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + wp->w_last_topline = wp->w_topline; + wp->w_last_topfill = wp->w_topfill; + wp->w_last_leftcol = wp->w_leftcol; + wp->w_last_skipcol = wp->w_skipcol; + wp->w_last_width = wp->w_width; + wp->w_last_height = wp->w_height; + } +} - if (recursive || !has_event(EVENT_WINSCROLLED)) { - return; +static bool did_initial_scroll_size_snapshot = false; + +void may_make_initial_scroll_size_snapshot(void) +{ + if (!did_initial_scroll_size_snapshot) { + did_initial_scroll_size_snapshot = true; + snapshot_windows_scroll_size(); } +} - win_T *wp = curwin; - if (wp->w_last_topline != wp->w_topline - || wp->w_last_leftcol != wp->w_leftcol - || wp->w_last_width != wp->w_width - || wp->w_last_height != wp->w_height) { - char winid[NUMBUFLEN]; - vim_snprintf(winid, sizeof(winid), "%d", wp->handle); +/// Create a dictionary with information about size and scroll changes in a +/// window. +/// Returns the dictionary with refcount set to one. +/// Returns NULL on internal error. +static dict_T *make_win_info_dict(int width, int height, int topline, int topfill, int leftcol, + int skipcol) +{ + dict_T *const d = tv_dict_alloc(); + d->dv_refcount = 1; + + // not actually looping, for breaking out on error + while (1) { + typval_T tv = { + .v_lock = VAR_UNLOCKED, + .v_type = VAR_NUMBER, + }; + + tv.vval.v_number = width; + if (tv_dict_add_tv(d, S_LEN("width"), &tv) == FAIL) { + break; + } + tv.vval.v_number = height; + if (tv_dict_add_tv(d, S_LEN("height"), &tv) == FAIL) { + break; + } + tv.vval.v_number = topline; + if (tv_dict_add_tv(d, S_LEN("topline"), &tv) == FAIL) { + break; + } + tv.vval.v_number = topfill; + if (tv_dict_add_tv(d, S_LEN("topfill"), &tv) == FAIL) { + break; + } + tv.vval.v_number = leftcol; + if (tv_dict_add_tv(d, S_LEN("leftcol"), &tv) == FAIL) { + break; + } + tv.vval.v_number = skipcol; + if (tv_dict_add_tv(d, S_LEN("skipcol"), &tv) == FAIL) { + break; + } + return d; + } + tv_dict_unref(d); + return NULL; +} - recursive = true; - apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, wp->w_buffer); - recursive = false; +/// Return values of check_window_scroll_resize(): +enum { + CWSR_SCROLLED = 1, ///< at least one window scrolled + CWSR_RESIZED = 2, ///< at least one window size changed +}; + +/// This function is used for three purposes: +/// 1. Goes over all windows in the current tab page and returns: +/// 0 no scrolling and no size changes found +/// CWSR_SCROLLED at least one window scrolled +/// CWSR_RESIZED at least one window changed size +/// CWSR_SCROLLED + CWSR_RESIZED both +/// "size_count" is set to the nr of windows with size changes. +/// "first_scroll_win" is set to the first window with any relevant changes. +/// "first_size_win" is set to the first window with size changes. +/// +/// 2. When the first three arguments are NULL and "winlist" is not NULL, +/// "winlist" is set to the list of window IDs with size changes. +/// +/// 3. When the first three arguments are NULL and "v_event" is not NULL, +/// information about changed windows is added to "v_event". +static int check_window_scroll_resize(int *size_count, win_T **first_scroll_win, + win_T **first_size_win, list_T *winlist, dict_T *v_event) +{ + int result = 0; + // int listidx = 0; + int tot_width = 0; + int tot_height = 0; + int tot_topline = 0; + int tot_topfill = 0; + int tot_leftcol = 0; + int tot_skipcol = 0; - // an autocmd may close the window, "wp" may be invalid now - if (win_valid_any_tab(wp)) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + // Skip floating windows that do not have a snapshot (usually because they are newly-created), + // as unlike split windows, creating floating windows do not cause other windows to resize. + if (wp->w_floating && wp->w_last_topline == 0) { wp->w_last_topline = wp->w_topline; + wp->w_last_topfill = wp->w_topfill; wp->w_last_leftcol = wp->w_leftcol; + wp->w_last_skipcol = wp->w_skipcol; wp->w_last_width = wp->w_width; wp->w_last_height = wp->w_height; + continue; + } + + const bool size_changed = wp->w_last_width != wp->w_width + || wp->w_last_height != wp->w_height; + if (size_changed) { + result |= CWSR_RESIZED; + if (winlist != NULL) { + // Add this window to the list of changed windows. + typval_T tv = { + .v_lock = VAR_UNLOCKED, + .v_type = VAR_NUMBER, + .vval.v_number = wp->handle, + }; + // tv_list_set_item(winlist, listidx++, &tv); + tv_list_append_owned_tv(winlist, tv); + } else if (size_count != NULL) { + assert(first_size_win != NULL && first_scroll_win != NULL); + (*size_count)++; + if (*first_size_win == NULL) { + *first_size_win = wp; + } + // For WinScrolled the first window with a size change is used + // even when it didn't scroll. + if (*first_scroll_win == NULL) { + *first_scroll_win = wp; + } + } + } + + const bool scroll_changed = wp->w_last_topline != wp->w_topline + || wp->w_last_topfill != wp->w_topfill + || wp->w_last_leftcol != wp->w_leftcol + || wp->w_last_skipcol != wp->w_skipcol; + if (scroll_changed) { + result |= CWSR_SCROLLED; + if (first_scroll_win != NULL && *first_scroll_win == NULL) { + *first_scroll_win = wp; + } + } + + if ((size_changed || scroll_changed) && v_event != NULL) { + // Add info about this window to the v:event dictionary. + int width = wp->w_width - wp->w_last_width; + int height = wp->w_height - wp->w_last_height; + int topline = wp->w_topline - wp->w_last_topline; + int topfill = wp->w_topfill - wp->w_last_topfill; + int leftcol = wp->w_leftcol - wp->w_last_leftcol; + int skipcol = wp->w_skipcol - wp->w_last_skipcol; + dict_T *d = make_win_info_dict(width, height, topline, + topfill, leftcol, skipcol); + if (d == NULL) { + break; + } + char winid[NUMBUFLEN]; + int key_len = vim_snprintf(winid, sizeof(winid), "%d", wp->handle); + if (tv_dict_add_dict(v_event, winid, (size_t)key_len, d) == FAIL) { + tv_dict_unref(d); + break; + } + d->dv_refcount--; + + tot_width += abs(width); + tot_height += abs(height); + tot_topline += abs(topline); + tot_topfill += abs(topfill); + tot_leftcol += abs(leftcol); + tot_skipcol += abs(skipcol); + } + } + + if (v_event != NULL) { + dict_T *alldict = make_win_info_dict(tot_width, tot_height, tot_topline, + tot_topfill, tot_leftcol, tot_skipcol); + if (alldict != NULL) { + if (tv_dict_add_dict(v_event, S_LEN("all"), alldict) == FAIL) { + tv_dict_unref(alldict); + } else { + alldict->dv_refcount--; + } } } + + return result; } -/* - * Save the size of all windows in "gap". - */ +/// Trigger WinScrolled and/or WinResized if any window in the current tab page +/// scrolled or changed size. +void may_trigger_win_scrolled_resized(void) +{ + static bool recursive = false; + const bool do_resize = has_event(EVENT_WINRESIZED); + const bool do_scroll = has_event(EVENT_WINSCROLLED); + + if (recursive + || !(do_scroll || do_resize) + || !did_initial_scroll_size_snapshot) { + return; + } + + int size_count = 0; + win_T *first_scroll_win = NULL, *first_size_win = NULL; + int cwsr = check_window_scroll_resize(&size_count, + &first_scroll_win, &first_size_win, + NULL, NULL); + int trigger_resize = do_resize && size_count > 0; + int trigger_scroll = do_scroll && cwsr != 0; + if (!trigger_resize && !trigger_scroll) { + return; // no relevant changes + } + + list_T *windows_list = NULL; + if (trigger_resize) { + // Create the list for v:event.windows before making the snapshot. + // windows_list = tv_list_alloc_with_items(size_count); + windows_list = tv_list_alloc(size_count); + (void)check_window_scroll_resize(NULL, NULL, NULL, windows_list, NULL); + } + + dict_T *scroll_dict = NULL; + if (trigger_scroll) { + // Create the dict with entries for v:event before making the snapshot. + scroll_dict = tv_dict_alloc(); + scroll_dict->dv_refcount = 1; + (void)check_window_scroll_resize(NULL, NULL, NULL, NULL, scroll_dict); + } + + // WinScrolled/WinResized are triggered only once, even when multiple + // windows scrolled or changed size. Store the current values before + // triggering the event, if a scroll or resize happens as a side effect + // then WinScrolled/WinResized is triggered for that later. + snapshot_windows_scroll_size(); + + recursive = true; + + // If both are to be triggered do WinResized first. + if (trigger_resize) { + save_v_event_T save_v_event; + dict_T *v_event = get_v_event(&save_v_event); + + if (tv_dict_add_list(v_event, S_LEN("windows"), windows_list) == OK) { + tv_dict_set_keys_readonly(v_event); + + char winid[NUMBUFLEN]; + vim_snprintf(winid, sizeof(winid), "%d", first_size_win->handle); + apply_autocmds(EVENT_WINRESIZED, winid, winid, false, first_size_win->w_buffer); + } + restore_v_event(v_event, &save_v_event); + } + + if (trigger_scroll) { + save_v_event_T save_v_event; + dict_T *v_event = get_v_event(&save_v_event); + + // Move the entries from scroll_dict to v_event. + tv_dict_extend(v_event, scroll_dict, "move"); + tv_dict_set_keys_readonly(v_event); + tv_dict_unref(scroll_dict); + + char winid[NUMBUFLEN]; + vim_snprintf(winid, sizeof(winid), "%d", first_scroll_win->handle); + apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, first_scroll_win->w_buffer); + + restore_v_event(v_event, &save_v_event); + } + + recursive = false; +} + +// Save the size of all windows in "gap". void win_size_save(garray_T *gap) { ga_init(gap, (int)sizeof(int), 1); @@ -5450,27 +5679,20 @@ void win_reconfig_floats(void) } } -/* - * Update the position of the windows in frame "topfrp", using the width and - * height of the frames. - * "*row" and "*col" are the top-left position of the frame. They are updated - * to the bottom-right position plus one. - */ +// Update the position of the windows in frame "topfrp", using the width and +// height of the frames. +// "*row" and "*col" are the top-left position of the frame. They are updated +// to the bottom-right position plus one. static void frame_comp_pos(frame_T *topfrp, int *row, int *col) { - win_T *wp; - frame_T *frp; - int startcol; - int startrow; - - wp = topfrp->fr_win; + win_T *wp = topfrp->fr_win; if (wp != NULL) { if (wp->w_winrow != *row || wp->w_wincol != *col) { // position changed, redraw wp->w_winrow = *row; wp->w_wincol = *col; - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); wp->w_redr_status = true; wp->w_pos_changed = true; } @@ -5478,8 +5700,9 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col) *row += h > topfrp->fr_height ? topfrp->fr_height : h; *col += wp->w_width + wp->w_vsep_width; } else { - startrow = *row; - startcol = *col; + int startrow = *row; + int startcol = *col; + frame_T *frp; FOR_ALL_FRAMES(frp, topfrp->fr_child) { if (topfrp->fr_layout == FR_ROW) { *row = startrow; // all frames are at the same row @@ -5491,19 +5714,15 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col) } } -/* - * Set current window height and take care of repositioning other windows to - * fit around it. - */ +// Set current window height and take care of repositioning other windows to +// fit around it. void win_setheight(int height) { win_setheight_win(height, curwin); } -/* - * Set the window height of window "win" and take care of repositioning other - * windows to fit around it. - */ +// Set the window height of window "win" and take care of repositioning other +// windows to fit around it. void win_setheight_win(int height, win_T *win) { // Always keep current window at least one line high, even when 'winminheight' is zero. @@ -5513,7 +5732,7 @@ void win_setheight_win(int height, win_T *win) if (win->w_floating) { win->w_float_config.height = height; win_config_float(win, win->w_float_config); - redraw_later(win, NOT_VALID); + redraw_later(win, UPD_VALID); } else { frame_setheight(win->w_frame, height + win->w_hsep_height + win->w_status_height); @@ -5533,34 +5752,29 @@ void win_setheight_win(int height, win_T *win) curtab->tp_ch_used = p_ch; msg_row = row; msg_col = 0; - redraw_all_later(NOT_VALID); + + if (*p_spk != 'c') { + win_fix_scroll(true); + } + + redraw_all_later(UPD_NOT_VALID); redraw_cmdline = true; } } -/* - * Set the height of a frame to "height" and take care that all frames and - * windows inside it are resized. Also resize frames on the left and right if - * the are in the same FR_ROW frame. - * - * Strategy: - * If the frame is part of a FR_COL frame, try fitting the frame in that - * frame. If that doesn't work (the FR_COL frame is too small), recursively - * go to containing frames to resize them and make room. - * If the frame is part of a FR_ROW frame, all frames must be resized as well. - * Check for the minimal height of the FR_ROW frame. - * At the top level we can also use change the command line height. - */ +// Set the height of a frame to "height" and take care that all frames and +// windows inside it are resized. Also resize frames on the left and right if +// the are in the same FR_ROW frame. +// +// Strategy: +// If the frame is part of a FR_COL frame, try fitting the frame in that +// frame. If that doesn't work (the FR_COL frame is too small), recursively +// go to containing frames to resize them and make room. +// If the frame is part of a FR_ROW frame, all frames must be resized as well. +// Check for the minimal height of the FR_ROW frame. +// At the top level we can also use change the command line height. static void frame_setheight(frame_T *curfrp, int height) { - int room; // total number of lines available - int take; // number of lines taken from other windows - int room_cmdline; // lines available from cmdline - int run; - frame_T *frp; - int h; - int room_reserved; - // If the height already is the desired value, nothing to do. if (curfrp->fr_height == height) { return; @@ -5568,11 +5782,15 @@ static void frame_setheight(frame_T *curfrp, int height) if (curfrp->fr_parent == NULL) { // topframe: can only change the command line height + // Avoid doing so with external messages. + if (ui_has(kUIMessages)) { + return; + } if (height > ROWS_AVAIL) { // If height is greater than the available space, try to create space for // the frame by reducing 'cmdheight' if possible, while making sure - // `cmdheight` doesn't go below 1. - height = (int)MIN((p_ch > 0 ? ROWS_AVAIL + (p_ch - 1) : ROWS_AVAIL), height); + // `cmdheight` doesn't go below 1 if it wasn't set to 0 explicitly. + height = (int)MIN(ROWS_AVAIL + p_ch - !p_ch_was_zero, height); } if (height > 0) { frame_new_height(curfrp, height, false, false); @@ -5580,25 +5798,27 @@ static void frame_setheight(frame_T *curfrp, int height) } else if (curfrp->fr_parent->fr_layout == FR_ROW) { // Row of frames: Also need to resize frames left and right of this // one. First check for the minimal height of these. - h = frame_minheight(curfrp->fr_parent, NULL); + int h = frame_minheight(curfrp->fr_parent, NULL); if (height < h) { height = h; } frame_setheight(curfrp->fr_parent, height); } else { - /* - * Column of frames: try to change only frames in this column. - */ - /* - * Do this twice: - * 1: compute room available, if it's not enough try resizing the - * containing frame. - * 2: compute the room available and adjust the height to it. - * Try not to reduce the height of a window with 'winfixheight' set. - */ - for (run = 1; run <= 2; ++run) { + // Column of frames: try to change only frames in this column. + + int room; // total number of lines available + int room_cmdline; // lines available from cmdline + int room_reserved; + + // Do this twice: + // 1: compute room available, if it's not enough try resizing the + // containing frame. + // 2: compute the room available and adjust the height to it. + // Try not to reduce the height of a window with 'winfixheight' set. + for (int run = 1; run <= 2; run++) { room = 0; room_reserved = 0; + frame_T *frp; FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) { if (frp != curfrp && frp->fr_win != NULL @@ -5630,14 +5850,12 @@ static void frame_setheight(frame_T *curfrp, int height) } frame_setheight(curfrp->fr_parent, height + frame_minheight(curfrp->fr_parent, NOWIN) - (int)p_wmh - 1); - //NOTREACHED + // NOTREACHED } - /* - * Compute the number of lines we will take from others frames (can be - * negative!). - */ - take = height - curfrp->fr_height; + // Compute the number of lines we will take from others frames (can be + // negative!). + int take = height - curfrp->fr_height; // If there is not enough room, also reduce the height of a window // with 'winfixheight' set. @@ -5659,24 +5877,19 @@ static void frame_setheight(frame_T *curfrp, int height) topframe->fr_height += room_cmdline; } - /* - * set the current frame to the new height - */ + // set the current frame to the new height frame_new_height(curfrp, height, false, false); - /* - * First take lines from the frames after the current frame. If - * that is not enough, takes lines from frames above the current - * frame. - */ - for (run = 0; run < 2; ++run) { - if (run == 0) { - frp = curfrp->fr_next; // 1st run: start with next window - } else { - frp = curfrp->fr_prev; // 2nd run: start with prev window - } + // First take lines from the frames after the current frame. If + // that is not enough, takes lines from frames above the current + // frame. + for (int run = 0; run < 2; run++) { + // 1st run: start with next window + // 2nd run: start with prev window + frame_T *frp = run == 0 ? curfrp->fr_next : curfrp->fr_prev; + while (frp != NULL && take != 0) { - h = frame_minheight(frp, NULL); + int h = frame_minheight(frp, NULL); if (room_reserved > 0 && frp->fr_win != NULL && frp->fr_win->w_p_wfh) { @@ -5709,10 +5922,8 @@ static void frame_setheight(frame_T *curfrp, int height) } } -/* - * Set current window width and take care of repositioning other windows to - * fit around it. - */ +// Set current window width and take care of repositioning other windows to +// fit around it. void win_setwidth(int width) { win_setwidth_win(width, curwin); @@ -5735,32 +5946,23 @@ void win_setwidth_win(int width, win_T *wp) if (wp->w_floating) { wp->w_float_config.width = width; win_config_float(wp, wp->w_float_config); - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } else { frame_setwidth(wp->w_frame, width + wp->w_vsep_width); // recompute the window positions (void)win_comp_pos(); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } } -/* - * Set the width of a frame to "width" and take care that all frames and - * windows inside it are resized. Also resize frames above and below if the - * are in the same FR_ROW frame. - * - * Strategy is similar to frame_setheight(). - */ +// Set the width of a frame to "width" and take care that all frames and +// windows inside it are resized. Also resize frames above and below if the +// are in the same FR_ROW frame. +// +// Strategy is similar to frame_setheight(). static void frame_setwidth(frame_T *curfrp, int width) { - int room; // total number of lines available - int take; // number of lines taken from other windows - int run; - frame_T *frp; - int w; - int room_reserved; - // If the width already is the desired value, nothing to do. if (curfrp->fr_width == width) { return; @@ -5774,23 +5976,25 @@ static void frame_setwidth(frame_T *curfrp, int width) if (curfrp->fr_parent->fr_layout == FR_COL) { // Column of frames: Also need to resize frames above and below of // this one. First check for the minimal width of these. - w = frame_minwidth(curfrp->fr_parent, NULL); + int w = frame_minwidth(curfrp->fr_parent, NULL); if (width < w) { width = w; } frame_setwidth(curfrp->fr_parent, width); } else { - /* - * Row of frames: try to change only frames in this row. - * - * Do this twice: - * 1: compute room available, if it's not enough try resizing the - * containing frame. - * 2: compute the room available and adjust the width to it. - */ - for (run = 1; run <= 2; ++run) { + // Row of frames: try to change only frames in this row. + // + // Do this twice: + // 1: compute room available, if it's not enough try resizing the + // containing frame. + // 2: compute the room available and adjust the width to it. + + int room; // total number of lines available + int room_reserved; + for (int run = 1; run <= 2; run++) { room = 0; room_reserved = 0; + frame_T *frp; FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) { if (frp != curfrp && frp->fr_win != NULL @@ -5814,11 +6018,9 @@ static void frame_setwidth(frame_T *curfrp, int width) + frame_minwidth(curfrp->fr_parent, NOWIN) - (int)p_wmw - 1); } - /* - * Compute the number of lines we will take from others frames (can be - * negative!). - */ - take = width - curfrp->fr_width; + // Compute the number of lines we will take from others frames (can be + // negative!). + int take = width - curfrp->fr_width; // If there is not enough room, also reduce the width of a window // with 'winfixwidth' set. @@ -5831,24 +6033,19 @@ static void frame_setwidth(frame_T *curfrp, int width) room_reserved = 0; } - /* - * set the current frame to the new width - */ + // set the current frame to the new width frame_new_width(curfrp, width, false, false); - /* - * First take lines from the frames right of the current frame. If - * that is not enough, takes lines from frames left of the current - * frame. - */ - for (run = 0; run < 2; ++run) { - if (run == 0) { - frp = curfrp->fr_next; // 1st run: start with next window - } else { - frp = curfrp->fr_prev; // 2nd run: start with prev window - } + // First take lines from the frames right of the current frame. If + // that is not enough, takes lines from frames left of the current + // frame. + for (int run = 0; run < 2; run++) { + // 1st run: start with next window + // 2nd run: start with prev window + frame_T *frp = run == 0 ? curfrp->fr_next : curfrp->fr_prev; + while (frp != NULL && take != 0) { - w = frame_minwidth(frp, NULL); + int w = frame_minwidth(frp, NULL); if (room_reserved > 0 && frp->fr_win != NULL && frp->fr_win->w_p_wfw) { @@ -5924,22 +6121,14 @@ void win_setminwidth(void) /// Status line of dragwin is dragged "offset" lines down (negative is up). void win_drag_status_line(win_T *dragwin, int offset) { - frame_T *curfr; - frame_T *fr; - int room; - int row; - bool up; // if true, drag status line up, otherwise down - int n; - static bool p_ch_was_zero = false; + frame_T *fr = dragwin->w_frame; - // If the user explicitly set 'cmdheight' to zero, then allow for dragging - // the status line making it zero again. - if (p_ch == 0) { - p_ch_was_zero = true; + // Avoid changing command line height with external messages. + if (fr->fr_next == NULL && ui_has(kUIMessages)) { + return; } - fr = dragwin->w_frame; - curfr = fr; + frame_T *curfr = fr; if (fr != topframe) { // more than one window fr = fr->fr_parent; // When the parent frame is not a column of frames, its parent should @@ -5964,8 +6153,10 @@ void win_drag_status_line(win_T *dragwin, int offset) } } - if (offset < 0) { // drag up - up = true; + int room; + const bool up = offset < 0; // if true, drag status line up, otherwise down + + if (up) { // drag up offset = -offset; // sum up the room of the current frame and above it if (fr == curfr) { @@ -5982,7 +6173,6 @@ void win_drag_status_line(win_T *dragwin, int offset) } fr = curfr->fr_next; // put fr at frame that grows } else { // drag down - up = false; // Only dragging the last status line can reduce p_ch. room = Rows - cmdline_row; if (curfr->fr_next != NULL) { @@ -6007,10 +6197,8 @@ void win_drag_status_line(win_T *dragwin, int offset) return; } - /* - * Grow frame fr by "offset" lines. - * Doesn't happen when dragging the last status line up. - */ + // Grow frame fr by "offset" lines. + // Doesn't happen when dragging the last status line up. if (fr != NULL) { frame_new_height(fr, fr->fr_height + offset, up, false); } @@ -6020,11 +6208,9 @@ void win_drag_status_line(win_T *dragwin, int offset) } else { fr = curfr->fr_next; // next frame gets smaller } - /* - * Now make the other frames smaller. - */ + // Now make the other frames smaller. while (fr != NULL && offset > 0) { - n = frame_minheight(fr, NULL); + int n = frame_minheight(fr, NULL); if (fr->fr_height - offset <= n) { offset -= fr->fr_height - n; frame_new_height(fr, n, !up, false); @@ -6038,7 +6224,7 @@ void win_drag_status_line(win_T *dragwin, int offset) fr = fr->fr_next; } } - row = win_comp_pos(); + int row = win_comp_pos(); grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0); if (msg_grid.chars) { clear_cmdline = true; @@ -6046,26 +6232,23 @@ void win_drag_status_line(win_T *dragwin, int offset) cmdline_row = row; p_ch = MAX(Rows - cmdline_row, p_ch_was_zero ? 0 : 1); curtab->tp_ch_used = p_ch; - redraw_all_later(SOME_VALID); + + if (*p_spk != 'c') { + win_fix_scroll(true); + } + + redraw_all_later(UPD_SOME_VALID); showmode(); } -/* - * Separator line of dragwin is dragged "offset" lines right (negative is left). - */ +// Separator line of dragwin is dragged "offset" lines right (negative is left). void win_drag_vsep_line(win_T *dragwin, int offset) { - frame_T *curfr; - frame_T *fr; - int room; - bool left; // if true, drag separator line left, otherwise right - int n; - - fr = dragwin->w_frame; + frame_T *fr = dragwin->w_frame; if (fr == topframe) { // only one window (cannot happen?) return; } - curfr = fr; + frame_T *curfr = fr; fr = fr->fr_parent; // When the parent frame is not a row of frames, its parent should be. if (fr->fr_layout != FR_ROW) { @@ -6090,8 +6273,10 @@ void win_drag_vsep_line(win_T *dragwin, int offset) } } - if (offset < 0) { // drag left - left = true; + int room; + const bool left = offset < 0; // if true, drag separator line left, otherwise right + + if (left) { // drag left offset = -offset; // sum up the room of the current frame and left of it room = 0; @@ -6103,7 +6288,6 @@ void win_drag_vsep_line(win_T *dragwin, int offset) } fr = curfr->fr_next; // put fr at frame that grows } else { // drag right - left = false; // sum up the room of frames right of the current one room = 0; FOR_ALL_FRAMES(fr, curfr->fr_next) { @@ -6138,7 +6322,7 @@ void win_drag_vsep_line(win_T *dragwin, int offset) fr = curfr->fr_next; // next frame gets smaller } while (fr != NULL && offset > 0) { - n = frame_minwidth(fr, NULL); + int n = frame_minwidth(fr, NULL); if (fr->fr_width - offset <= n) { offset -= fr->fr_width - n; frame_new_width(fr, n, !left, false); @@ -6153,7 +6337,7 @@ void win_drag_vsep_line(win_T *dragwin, int offset) } } (void)win_comp_pos(); - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } #define FRACTION_MULT 16384L @@ -6170,6 +6354,98 @@ void set_fraction(win_T *wp) } } +/// Handle scroll position for 'splitkeep'. Replaces scroll_to_fraction() +/// call from win_set_inner_size(). Instead we iterate over all windows in a +/// tabpage and calculate the new scroll position. +/// TODO(luukvbaal): Ensure this also works with wrapped lines. +/// Requires topline to be able to be set to a bufferline with some +/// offset(row-wise scrolling/smoothscroll). +void win_fix_scroll(int resize) +{ + linenr_T lnum; + + skip_update_topline = true; + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + // Skip when window height has not changed or when floating. + if (!wp->w_floating && wp->w_height != wp->w_prev_height) { + // If window has moved update botline to keep the same screenlines. + if (*p_spk == 's' && wp->w_winrow != wp->w_prev_winrow + && wp->w_botline - 1 <= wp->w_buffer->b_ml.ml_line_count) { + lnum = wp->w_cursor.lnum; + int diff = (wp->w_winrow - wp->w_prev_winrow) + + (wp->w_height - wp->w_prev_height); + wp->w_cursor.lnum = wp->w_botline - 1; + // Add difference in height and row to botline. + if (diff > 0) { + cursor_down_inner(wp, diff); + } else { + cursor_up_inner(wp, -diff); + } + // Bring the new cursor position to the bottom of the screen. + wp->w_fraction = FRACTION_MULT; + scroll_to_fraction(wp, wp->w_prev_height); + wp->w_cursor.lnum = lnum; + } else if (wp == curwin) { + wp->w_valid &= ~VALID_CROW; + } + invalidate_botline_win(wp); + validate_botline(wp); + } + wp->w_prev_height = wp->w_height; + wp->w_prev_winrow = wp->w_winrow; + } + skip_update_topline = false; + // Ensure cursor is valid when not in normal mode or when resized. + if (!(get_real_state() & (MODE_NORMAL|MODE_CMDLINE|MODE_TERMINAL))) { + win_fix_cursor(false); + } else if (resize) { + win_fix_cursor(true); + } +} + +/// Make sure the cursor position is valid for 'splitkeep'. +/// If it is not, put the cursor position in the jumplist and move it. +/// If we are not in normal mode, scroll to make valid instead. +static void win_fix_cursor(int normal) +{ + win_T *wp = curwin; + long so = get_scrolloff_value(wp); + linenr_T nlnum = 0; + linenr_T lnum = wp->w_cursor.lnum; + + if (wp->w_buffer->b_ml.ml_line_count < wp->w_height + || skip_win_fix_cursor) { + return; + } + + // Determine valid cursor range. + so = MIN(wp->w_height_inner / 2, so); + wp->w_cursor.lnum = wp->w_topline; + linenr_T top = cursor_down_inner(wp, so); + wp->w_cursor.lnum = wp->w_botline - 1; + linenr_T bot = cursor_up_inner(wp, so); + wp->w_cursor.lnum = lnum; + // Check if cursor position is above or below valid cursor range. + if (lnum > bot && (wp->w_botline - wp->w_buffer->b_ml.ml_line_count) != 1) { + nlnum = bot; + } else if (lnum < top && wp->w_topline != 1) { + nlnum = (so == wp->w_height / 2) ? bot : top; + } + + if (nlnum) { // Cursor is invalid for current scroll position. + if (normal) { + // Save to jumplist and set cursor to avoid scrolling. + setmark('\''); + wp->w_cursor.lnum = nlnum; + } else { + // Scroll instead when not in normal mode. + wp->w_fraction = (nlnum == bot) ? FRACTION_MULT : 0; + scroll_to_fraction(wp, wp->w_prev_height); + validate_botline(curwin); + } + } +} + // Set the height of a window. // "height" excludes any window toolbar. // This takes care of the things inside the window, not what happens to the @@ -6192,8 +6468,6 @@ void win_new_height(win_T *wp, int height) void scroll_to_fraction(win_T *wp, int prev_height) { - linenr_T lnum; - int sline, line_size; int height = wp->w_height_inner; // Don't change w_topline in any of these cases: @@ -6205,17 +6479,15 @@ void scroll_to_fraction(win_T *wp, int prev_height) && (!wp->w_p_scb || wp == curwin) && (height < wp->w_buffer->b_ml.ml_line_count || wp->w_topline > 1)) { - /* - * Find a value for w_topline that shows the cursor at the same - * relative position in the window as before (more or less). - */ - lnum = wp->w_cursor.lnum; + // Find a value for w_topline that shows the cursor at the same + // relative position in the window as before (more or less). + linenr_T lnum = wp->w_cursor.lnum; if (lnum < 1) { // can happen when starting up lnum = 1; } wp->w_wrow = (int)((long)wp->w_fraction * (long)height - 1L) / FRACTION_MULT; - line_size = plines_win_col(wp, lnum, (long)(wp->w_cursor.col)) - 1; - sline = wp->w_wrow - line_size; + int line_size = plines_win_col(wp, lnum, (long)(wp->w_cursor.col)) - 1; + int sline = wp->w_wrow - line_size; if (sline >= 0) { // Make sure the whole cursor line is visible, if possible. @@ -6228,11 +6500,9 @@ void scroll_to_fraction(win_T *wp, int prev_height) } if (sline < 0) { - /* - * Cursor line would go off top of screen if w_wrow was this high. - * Make cursor line the first line in the window. If not enough - * room use w_skipcol; - */ + // Cursor line would go off top of screen if w_wrow was this high. + // Make cursor line the first line in the window. If not enough + // room use w_skipcol; wp->w_wrow = line_size; if (wp->w_wrow >= wp->w_height_inner && (wp->w_width_inner - win_col_off(wp)) > 0) { @@ -6264,10 +6534,8 @@ void scroll_to_fraction(win_T *wp, int prev_height) } if (sline < 0) { - /* - * Line we want at top would go off top of screen. Use next - * line instead. - */ + // Line we want at top would go off top of screen. Use next + // line instead. (void)hasFoldingWin(wp, lnum, NULL, &lnum, true, NULL); lnum++; wp->w_wrow -= line_size + sline; @@ -6290,9 +6558,7 @@ void scroll_to_fraction(win_T *wp, int prev_height) wp->w_prev_fraction_row = wp->w_wrow; } - win_comp_scroll(wp); - redraw_later(wp, SOME_VALID); - wp->w_redr_status = true; + redraw_later(wp, UPD_SOME_VALID); invalidate_botline_win(wp); } @@ -6311,7 +6577,7 @@ void win_set_inner_size(win_T *wp, bool valid_cursor) if (height != prev_height) { if (height > 0 && valid_cursor) { - if (wp == curwin) { + if (wp == curwin && *p_spk == 'c') { // w_wrow needs to be valid. When setting 'laststatus' this may // call win_new_height() recursively. validate_cursor(); @@ -6325,14 +6591,14 @@ void win_set_inner_size(win_T *wp, bool valid_cursor) } wp->w_skipcol = 0; wp->w_height_inner = height; + win_comp_scroll(wp); // There is no point in adjusting the scroll position when exiting. Some // values might be invalid. - // Skip scroll_to_fraction() when 'cmdheight' was set to one from zero. - if (!exiting && !made_cmdheight_nonzero && valid_cursor) { + if (valid_cursor && !exiting && *p_spk == 'c') { scroll_to_fraction(wp, prev_height); } - redraw_later(wp, NOT_VALID); // SOME_VALID?? + redraw_later(wp, UPD_SOME_VALID); } if (width != wp->w_width_inner) { @@ -6342,11 +6608,13 @@ void win_set_inner_size(win_T *wp, bool valid_cursor) changed_line_abv_curs_win(wp); invalidate_botline_win(wp); if (wp == curwin) { + skip_update_topline = (*p_spk != 'c'); update_topline(wp); curs_columns(wp, true); // validate w_wrow + skip_update_topline = false; } } - redraw_later(wp, NOT_VALID); + redraw_later(wp, UPD_NOT_VALID); } if (wp->w_buffer->terminal) { @@ -6357,6 +6625,7 @@ void win_set_inner_size(win_T *wp, bool valid_cursor) wp->w_width_outer = (wp->w_width_inner + win_border_width(wp)); wp->w_winrow_off = wp->w_border_adj[0] + wp->w_winbar_height; wp->w_wincol_off = wp->w_border_adj[3]; + wp->w_redr_status = true; } static int win_border_height(win_T *wp) @@ -6373,17 +6642,15 @@ static int win_border_width(win_T *wp) void win_new_width(win_T *wp, int width) { wp->w_width = width; - win_set_inner_size(wp, true); - - wp->w_redr_status = true; wp->w_pos_changed = true; + win_set_inner_size(wp, true); } void win_comp_scroll(win_T *wp) { const long old_w_p_scr = wp->w_p_scr; - wp->w_p_scr = wp->w_height / 2; + wp->w_p_scr = wp->w_height_inner / 2; if (wp->w_p_scr == 0) { wp->w_p_scr = 1; } @@ -6394,13 +6661,9 @@ void win_comp_scroll(win_T *wp) } } -/* - * command_height: called whenever p_ch has been changed - */ +/// command_height: called whenever p_ch has been changed. void command_height(void) { - int h; - frame_T *frp; int old_p_ch = (int)curtab->tp_ch_used; // Use the value of p_ch that we remembered. This is needed for when the @@ -6408,11 +6671,8 @@ void command_height(void) // p_ch was changed in another tab page. curtab->tp_ch_used = p_ch; - // If the space for the command line is already more than 'cmdheight' there - // is nothing to do (window size must have decreased). - if (p_ch > old_p_ch && cmdline_row <= Rows - p_ch) { - return; - } + // Update cmdline_row to what it should be: just below the last window. + cmdline_row = topframe->fr_height + tabline_height() + global_stl_height(); // If cmdline_row is smaller than what it is supposed to be for 'cmdheight' // then set old_p_ch to what it would be, so that the windows get resized @@ -6422,7 +6682,7 @@ void command_height(void) } // Find bottom frame with width of screen. - frp = lastwin_nofloating()->w_frame; + frame_T *frp = lastwin_nofloating()->w_frame; while (frp->fr_width != Columns && frp->fr_parent != NULL) { frp = frp->fr_parent; } @@ -6445,7 +6705,7 @@ void command_height(void) cmdline_row = Rows - (int)p_ch; break; } - h = frp->fr_height - frame_minheight(frp, NULL); + int h = frp->fr_height - frame_minheight(frp, NULL); if (h > p_ch - old_p_ch) { h = (int)p_ch - old_p_ch; } @@ -6479,10 +6739,8 @@ void command_height(void) } } -/* - * Resize frame "frp" to be "n" lines higher (negative for less high). - * Also resize the frames it is contained in. - */ +// Resize frame "frp" to be "n" lines higher (negative for less high). +// Also resize the frames it is contained in. static void frame_add_height(frame_T *frp, int n) { frame_new_height(frp, frp->fr_height + n, false, false); @@ -6495,12 +6753,10 @@ static void frame_add_height(frame_T *frp, int n) } } -/* - * Get the file name at the cursor. - * If Visual mode is active, use the selected text if it's in one line. - * Returns the name in allocated memory, NULL for failure. - */ -char_u *grab_file_name(long count, linenr_T *file_lnum) +// Get the file name at the cursor. +// If Visual mode is active, use the selected text if it's in one line. +// Returns the name in allocated memory, NULL for failure. +char *grab_file_name(long count, linenr_T *file_lnum) { int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC; if (VIsual_active) { @@ -6510,33 +6766,31 @@ char_u *grab_file_name(long count, linenr_T *file_lnum) return NULL; } // Only recognize ":123" here - if (file_lnum != NULL && ptr[len] == ':' && isdigit(ptr[len + 1])) { + if (file_lnum != NULL && ptr[len] == ':' && isdigit((uint8_t)ptr[len + 1])) { char *p = ptr + len + 1; *file_lnum = (linenr_T)getdigits_long(&p, false, 0); } - return find_file_name_in_path((char_u *)ptr, len, options, count, (char_u *)curbuf->b_ffname); + return find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname); } return file_name_at_cursor(options | FNAME_HYP, count, file_lnum); } -/* - * Return the file name under or after the cursor. - * - * The 'path' option is searched if the file name is not absolute. - * The string returned has been alloc'ed and should be freed by the caller. - * NULL is returned if the file name or file is not found. - * - * options: - * FNAME_MESS give error messages - * FNAME_EXP expand to path - * FNAME_HYP check for hypertext link - * FNAME_INCL apply "includeexpr" - */ -char_u *file_name_at_cursor(int options, long count, linenr_T *file_lnum) +// Return the file name under or after the cursor. +// +// The 'path' option is searched if the file name is not absolute. +// The string returned has been alloc'ed and should be freed by the caller. +// NULL is returned if the file name or file is not found. +// +// options: +// FNAME_MESS give error messages +// FNAME_EXP expand to path +// FNAME_HYP check for hypertext link +// FNAME_INCL apply "includeexpr" +char *file_name_at_cursor(int options, long count, linenr_T *file_lnum) { return file_name_in_line(get_cursor_line_ptr(), - curwin->w_cursor.col, options, count, (char_u *)curbuf->b_ffname, + curwin->w_cursor.col, options, count, curbuf->b_ffname, file_lnum); } @@ -6544,19 +6798,12 @@ char_u *file_name_at_cursor(int options, long count, linenr_T *file_lnum) /// @param file_lnum line number after the file name /// /// @return the name of the file under or after ptr[col]. Otherwise like file_name_at_cursor(). -char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u *rel_fname, - linenr_T *file_lnum) +char *file_name_in_line(char *line, int col, int options, long count, char *rel_fname, + linenr_T *file_lnum) { - char *ptr; - size_t len; - bool in_type = true; - bool is_url = false; - - /* - * search forward for what could be the start of a file name - */ - ptr = (char *)line + col; - while (*ptr != NUL && !vim_isfilec(*ptr)) { + // search forward for what could be the start of a file name + char *ptr = line + col; + while (*ptr != NUL && !vim_isfilec((uint8_t)(*ptr))) { MB_PTR_ADV(ptr); } if (*ptr == NUL) { // nothing found @@ -6566,28 +6813,29 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u return NULL; } - /* - * Search backward for first char of the file name. - * Go one char back to ":" before "//" even when ':' is not in 'isfname'. - */ - while ((char_u *)ptr > line) { - if ((len = (size_t)(utf_head_off(line, (char_u *)ptr - 1))) > 0) { + size_t len; + bool in_type = true; + bool is_url = false; + + // Search backward for first char of the file name. + // Go one char back to ":" before "//" even when ':' is not in 'isfname'. + while (ptr > line) { + if ((len = (size_t)(utf_head_off(line, ptr - 1))) > 0) { ptr -= len + 1; - } else if (vim_isfilec(ptr[-1]) || ((options & FNAME_HYP) && path_is_url(ptr - 1))) { + } else if (vim_isfilec((uint8_t)ptr[-1]) + || ((options & FNAME_HYP) && path_is_url(ptr - 1))) { ptr--; } else { break; } } - /* - * Search forward for the last char of the file name. - * Also allow "://" when ':' is not in 'isfname'. - */ + // Search forward for the last char of the file name. + // Also allow ":/" when ':' is not in 'isfname'. len = 0; - while (vim_isfilec(ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ') + while (vim_isfilec((uint8_t)ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ') || ((options & FNAME_HYP) && path_is_url(ptr + len)) - || (is_url && vim_strchr(":?&=", ptr[len]) != NULL)) { + || (is_url && vim_strchr(":?&=", (uint8_t)ptr[len]) != NULL)) { // After type:// we also include :, ?, & and = as valid characters, so that // http://google.com:8080?q=this&that=ok works. if ((ptr[len] >= 'A' && ptr[len] <= 'Z') @@ -6606,43 +6854,40 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u len += (size_t)(utfc_ptr2len(ptr + len)); } - /* - * If there is trailing punctuation, remove it. - * But don't remove "..", could be a directory name. - */ - if (len > 2 && vim_strchr(".,:;!", ptr[len - 1]) != NULL + // If there is trailing punctuation, remove it. + // But don't remove "..", could be a directory name. + if (len > 2 && vim_strchr(".,:;!", (uint8_t)ptr[len - 1]) != NULL && ptr[len - 2] != '.') { len--; } if (file_lnum != NULL) { - char *p; const char *line_english = " line "; const char *line_transl = _(line_msg); // Get the number after the file name and a separator character. // Also accept " line 999" with and without the same translation as // used in last_set_msg(). - p = ptr + len; - if (STRNCMP(p, line_english, STRLEN(line_english)) == 0) { - p += STRLEN(line_english); - } else if (STRNCMP(p, line_transl, STRLEN(line_transl)) == 0) { - p += STRLEN(line_transl); + char *p = ptr + len; + if (strncmp(p, line_english, strlen(line_english)) == 0) { + p += strlen(line_english); + } else if (strncmp(p, line_transl, strlen(line_transl)) == 0) { + p += strlen(line_transl); } else { p = skipwhite(p); } if (*p != NUL) { - if (!isdigit(*p)) { + if (!isdigit((uint8_t)(*p))) { p++; // skip the separator } p = skipwhite(p); - if (isdigit(*p)) { + if (isdigit((uint8_t)(*p))) { *file_lnum = (linenr_T)getdigits_long(&p, false, 0); } } } - return find_file_name_in_path((char_u *)ptr, len, options, count, rel_fname); + return find_file_name_in_path(ptr, len, options, count, rel_fname); } /// Add or remove a status line from window(s), according to the @@ -6667,7 +6912,7 @@ static void win_remove_status_line(win_T *wp, bool add_hsep) } comp_col(); - stl_clear_click_defs(wp->w_status_click_defs, (long)wp->w_status_click_defs_size); + stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size); xfree(wp->w_status_click_defs); wp->w_status_click_defs_size = 0; wp->w_status_click_defs = NULL; @@ -6726,23 +6971,19 @@ static bool resize_frame_for_winbar(frame_T *fr) if (fp == NULL || fp == fr) { emsg(_(e_noroom)); return false; - } else { - frame_new_height(fp, fp->fr_height - 1, false, false); - win_new_height(wp, wp->w_height + 1); - frame_fix_height(wp); - (void)win_comp_pos(); } + frame_new_height(fp, fp->fr_height - 1, false, false); + win_new_height(wp, wp->w_height + 1); + frame_fix_height(wp); + (void)win_comp_pos(); return true; } static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) { - frame_T *fp; - win_T *wp; - if (fr->fr_layout == FR_LEAF) { - wp = fr->fr_win; + win_T *wp = fr->fr_win; bool is_last = is_bottom_win(wp); if (is_last) { @@ -6756,6 +6997,10 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) } comp_col(); } + // Set prev_height when difference is due to 'laststatus'. + if (abs(wp->w_height - wp->w_prev_height) == 1) { + wp->w_prev_height = wp->w_height; + } } else if (wp->w_status_height != 0 && is_stl_global) { // If statusline is global and the window has a statusline, replace it with a horizontal // separator @@ -6766,9 +7011,9 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) wp->w_hsep_height = 0; comp_col(); } - redraw_all_later(SOME_VALID); } else { // For a column or row frame, recursively call this function for all child frames + frame_T *fp; FOR_ALL_FRAMES(fp, fr->fr_child) { last_status_rec(fp, statusline, is_stl_global); } @@ -6799,11 +7044,10 @@ int set_winbar_win(win_T *wp, bool make_room, bool valid_cursor) } wp->w_winbar_height = winbar_height; win_set_inner_size(wp, valid_cursor); - wp->w_redr_status = wp->w_redr_status || winbar_height; if (winbar_height == 0) { // When removing winbar, deallocate the w_winbar_click_defs array - stl_clear_click_defs(wp->w_winbar_click_defs, (long)wp->w_winbar_click_defs_size); + stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size); xfree(wp->w_winbar_click_defs); wp->w_winbar_click_defs_size = 0; wp->w_winbar_click_defs = NULL; @@ -6889,7 +7133,7 @@ bool only_one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_buffer != NULL && (!((bt_help(wp->w_buffer) && !bt_help(curbuf)) || wp->w_floating - || wp->w_p_pvw) || wp == curwin) && wp != aucmd_win) { + || wp->w_p_pvw) || wp == curwin) && !is_aucmd_win(wp)) { count++; } } @@ -6963,21 +7207,17 @@ void reset_lnums(void) } } -/* - * A snapshot of the window sizes, to restore them after closing the help - * window. - * Only these fields are used: - * fr_layout - * fr_width - * fr_height - * fr_next - * fr_child - * fr_win (only valid for the old curwin, NULL otherwise) - */ +// A snapshot of the window sizes, to restore them after closing the help +// window. +// Only these fields are used: +// fr_layout +// fr_width +// fr_height +// fr_next +// fr_child +// fr_win (only valid for the old curwin, NULL otherwise) -/* - * Create a snapshot of the current frame sizes. - */ +// Create a snapshot of the current frame sizes. void make_snapshot(int idx) { clear_snapshot(curtab, idx); @@ -7001,9 +7241,7 @@ static void make_snapshot_rec(frame_T *fr, frame_T **frp) } } -/* - * Remove any existing snapshot. - */ +// Remove any existing snapshot. static void clear_snapshot(tabpage_T *tp, int idx) { clear_snapshot_rec(tp->tp_snapshot[idx]); @@ -7055,18 +7293,16 @@ static win_T *get_snapshot_curwin(int idx) /// @param close_curwin closing current window void restore_snapshot(int idx, int close_curwin) { - win_T *wp; - if (curtab->tp_snapshot[idx] != NULL && curtab->tp_snapshot[idx]->fr_width == topframe->fr_width && curtab->tp_snapshot[idx]->fr_height == topframe->fr_height && check_snapshot_rec(curtab->tp_snapshot[idx], topframe) == OK) { - wp = restore_snapshot_rec(curtab->tp_snapshot[idx], topframe); + win_T *wp = restore_snapshot_rec(curtab->tp_snapshot[idx], topframe); (void)win_comp_pos(); if (wp != NULL && close_curwin) { win_goto(wp); } - redraw_all_later(NOT_VALID); + redraw_all_later(UPD_NOT_VALID); } clear_snapshot(curtab, idx); } @@ -7088,15 +7324,12 @@ static int check_snapshot_rec(frame_T *sn, frame_T *fr) return OK; } -/* - * Copy the size of snapshot frame "sn" to frame "fr". Do the same for all - * following frames and children. - * Returns a pointer to the old current window, or NULL. - */ +// Copy the size of snapshot frame "sn" to frame "fr". Do the same for all +// following frames and children. +// Returns a pointer to the old current window, or NULL. static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) { win_T *wp = NULL; - win_T *wp2; fr->fr_height = sn->fr_height; fr->fr_width = sn->fr_width; @@ -7106,13 +7339,13 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) wp = sn->fr_win; } if (sn->fr_next != NULL) { - wp2 = restore_snapshot_rec(sn->fr_next, fr->fr_next); + win_T *wp2 = restore_snapshot_rec(sn->fr_next, fr->fr_next); if (wp2 != NULL) { wp = wp2; } } if (sn->fr_child != NULL) { - wp2 = restore_snapshot_rec(sn->fr_child, fr->fr_child); + win_T *wp2 = restore_snapshot_rec(sn->fr_child, fr->fr_child); if (wp2 != NULL) { wp = wp2; } @@ -7120,114 +7353,6 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) return wp; } -/// Set "win" to be the curwin and "tp" to be the current tab page. -/// restore_win() MUST be called to undo, also when FAIL is returned. -/// No autocommands will be executed until restore_win() is called. -/// -/// @param no_display if true the display won't be affected, no redraw is -/// triggered, another tabpage access is limited. -/// -/// @return FAIL if switching to "win" failed. -int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) -{ - block_autocmds(); - return switch_win_noblock(switchwin, win, tp, no_display); -} - -// As switch_win() but without blocking autocommands. -int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) -{ - CLEAR_POINTER(switchwin); - switchwin->sw_curwin = curwin; - if (win == curwin) { - switchwin->sw_same_win = true; - } else { - // Disable Visual selection, because redrawing may fail. - switchwin->sw_visual_active = VIsual_active; - VIsual_active = false; - } - - if (tp != NULL) { - switchwin->sw_curtab = curtab; - if (no_display) { - curtab->tp_firstwin = firstwin; - curtab->tp_lastwin = lastwin; - curtab = tp; - firstwin = curtab->tp_firstwin; - lastwin = curtab->tp_lastwin; - } else { - goto_tabpage_tp(tp, false, false); - } - } - if (!win_valid(win)) { - return FAIL; - } - curwin = win; - curbuf = curwin->w_buffer; - return OK; -} - -// Restore current tabpage and window saved by switch_win(), if still valid. -// When "no_display" is true the display won't be affected, no redraw is -// triggered. -void restore_win(switchwin_T *switchwin, bool no_display) -{ - restore_win_noblock(switchwin, no_display); - unblock_autocmds(); -} - -// As restore_win() but without unblocking autocommands. -void restore_win_noblock(switchwin_T *switchwin, bool no_display) -{ - if (switchwin->sw_curtab != NULL && valid_tabpage(switchwin->sw_curtab)) { - if (no_display) { - curtab->tp_firstwin = firstwin; - curtab->tp_lastwin = lastwin; - curtab = switchwin->sw_curtab; - firstwin = curtab->tp_firstwin; - lastwin = curtab->tp_lastwin; - } else { - goto_tabpage_tp(switchwin->sw_curtab, false, false); - } - } - - if (!switchwin->sw_same_win) { - VIsual_active = switchwin->sw_visual_active; - } - - if (win_valid(switchwin->sw_curwin)) { - curwin = switchwin->sw_curwin; - curbuf = curwin->w_buffer; - } -} - -/// Make "buf" the current buffer. -/// -/// restore_buffer() MUST be called to undo. -/// No autocommands will be executed. Use aucmd_prepbuf() if there are any. -void switch_buffer(bufref_T *save_curbuf, buf_T *buf) -{ - block_autocmds(); - set_bufref(save_curbuf, curbuf); - curbuf->b_nwindows--; - curbuf = buf; - curwin->w_buffer = buf; - curbuf->b_nwindows++; -} - -/// Restore the current buffer after using switch_buffer(). -void restore_buffer(bufref_T *save_curbuf) -{ - unblock_autocmds(); - // Check for valid buffer, just in case. - if (bufref_valid(save_curbuf)) { - curbuf->b_nwindows--; - curwin->w_buffer = save_curbuf->br_buf; - curbuf = save_curbuf->br_buf; - curbuf->b_nwindows++; - } -} - /// Check that "topfrp" and its children are at the right height. /// /// @param topfrp top frame pointer @@ -7281,17 +7406,14 @@ static int int_cmp(const void *a, const void *b) /// @return error message, NULL if it's OK. char *check_colorcolumn(win_T *wp) { - char *s; - int col; - unsigned int count = 0; - int color_cols[256]; - int j = 0; - if (wp->w_buffer == NULL) { return NULL; // buffer was closed } - for (s = (char *)wp->w_p_cc; *s != NUL && count < 255;) { + unsigned int count = 0; + int color_cols[256]; + for (char *s = wp->w_p_cc; *s != NUL && count < 255;) { + int col; if (*s == '-' || *s == '+') { // -N and +N: add to 'textwidth' col = (*s == '-') ? -1 : 1; @@ -7340,6 +7462,7 @@ skip: // win_line() qsort(color_cols, count, sizeof(int), int_cmp); + int j = 0; for (unsigned int i = 0; i < count; i++) { // skip duplicates if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) { @@ -7352,56 +7475,6 @@ skip: return NULL; // no error } -int win_getid(typval_T *argvars) -{ - if (argvars[0].v_type == VAR_UNKNOWN) { - return curwin->handle; - } - int winnr = (int)tv_get_number(&argvars[0]); - win_T *wp; - if (winnr > 0) { - if (argvars[1].v_type == VAR_UNKNOWN) { - wp = firstwin; - } else { - tabpage_T *tp = NULL; - int tabnr = (int)tv_get_number(&argvars[1]); - FOR_ALL_TABS(tp2) { - if (--tabnr == 0) { - tp = tp2; - break; - } - } - if (tp == NULL) { - return -1; - } - if (tp == curtab) { - wp = firstwin; - } else { - wp = tp->tp_firstwin; - } - } - for (; wp != NULL; wp = wp->w_next) { - if (--winnr == 0) { - return wp->handle; - } - } - } - return 0; -} - -int win_gotoid(typval_T *argvars) -{ - int id = (int)tv_get_number(&argvars[0]); - - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->handle == id) { - goto_tabpage_win(tp, wp); - return 1; - } - } - return 0; -} - void win_get_tabwin(handle_T id, int *tabnr, int *winnr) { *tabnr = 0; @@ -7422,109 +7495,16 @@ void win_get_tabwin(handle_T id, int *tabnr, int *winnr) } } -void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) -{ - int winnr = 1; - int tabnr = 1; - handle_T id = (handle_T)tv_get_number(&argvars[0]); - - win_get_tabwin(id, &tabnr, &winnr); - - list_T *const list = tv_list_alloc_ret(rettv, 2); - tv_list_append_number(list, tabnr); - tv_list_append_number(list, winnr); -} - -win_T *win_id2wp(int id) -{ - return win_id2wp_tp(id, NULL); -} - -// Return the window and tab pointer of window "id". -win_T *win_id2wp_tp(int id, tabpage_T **tpp) -{ - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->handle == id) { - if (tpp != NULL) { - *tpp = tp; - } - return wp; - } - } - - return NULL; -} - -int win_id2win(typval_T *argvars) -{ - int nr = 1; - int id = (int)tv_get_number(&argvars[0]); - - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->handle == id) { - return nr; - } - nr++; - } - return 0; -} - -void win_findbuf(typval_T *argvars, list_T *list) -{ - int bufnr = (int)tv_get_number(&argvars[0]); - - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer->b_fnum == bufnr) { - tv_list_append_number(list, wp->handle); - } - } -} - -// Get the layout of the given tab page for winlayout(). -void get_framelayout(const frame_T *fr, list_T *l, bool outer) -{ - list_T *fr_list; - - if (fr == NULL) { - return; - } - - if (outer) { - // outermost call from f_winlayout() - fr_list = l; - } else { - fr_list = tv_list_alloc(2); - tv_list_append_list(l, fr_list); - } - - if (fr->fr_layout == FR_LEAF) { - if (fr->fr_win != NULL) { - tv_list_append_string(fr_list, "leaf", -1); - tv_list_append_number(fr_list, fr->fr_win->handle); - } - } else { - tv_list_append_string(fr_list, fr->fr_layout == FR_ROW ? "row" : "col", -1); - - list_T *const win_list = tv_list_alloc(kListLenUnknown); - tv_list_append_list(fr_list, win_list); - const frame_T *child = fr->fr_child; - while (child != NULL) { - get_framelayout(child, win_list, false); - child = child->fr_next; - } - } -} - -void win_ui_flush(void) +void win_ui_flush(bool validate) { FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_pos_changed && wp->w_grid_alloc.chars != NULL) { if (tp == curtab) { - ui_ext_win_position(wp); + ui_ext_win_position(wp, validate); } else { ui_call_win_hide(wp->w_grid_alloc.handle); + wp->w_pos_changed = false; } - wp->w_pos_changed = false; } if (tp == curtab) { ui_ext_win_viewport(wp); diff --git a/src/nvim/window.h b/src/nvim/window.h index b75b8abd9b..4ab2bea60a 100644 --- a/src/nvim/window.h +++ b/src/nvim/window.h @@ -2,10 +2,15 @@ #define NVIM_WINDOW_H #include <stdbool.h> +#include <stddef.h> +#include "nvim/buffer.h" #include "nvim/buffer_defs.h" +#include "nvim/macros.h" #include "nvim/mark.h" #include "nvim/os/os.h" +#include "nvim/os/os_defs.h" +#include "nvim/vim.h" // Values for file_name_in_line() #define FNAME_MESS 1 // give error message @@ -17,75 +22,22 @@ #define FNAME_UNESC 32 // remove backslashes used for escaping // arguments for win_split() -#define WSP_ROOM 1 // require enough room -#define WSP_VERT 2 // split vertically -#define WSP_TOP 4 // window at top-left of shell -#define WSP_BOT 8 // window at bottom-right of shell -#define WSP_HELP 16 // creating the help window -#define WSP_BELOW 32 // put new window below/right -#define WSP_ABOVE 64 // put new window above/left -#define WSP_NEWLOC 128 // don't copy location list +#define WSP_ROOM 0x01 // require enough room +#define WSP_VERT 0x02 // split/equalize vertically +#define WSP_HOR 0x04 // equalize horizontally +#define WSP_TOP 0x08 // window at top-left of shell +#define WSP_BOT 0x10 // window at bottom-right of shell +#define WSP_HELP 0x20 // creating the help window +#define WSP_BELOW 0x40 // put new window below/right +#define WSP_ABOVE 0x80 // put new window above/left +#define WSP_NEWLOC 0x100 // don't copy location list // Minimum screen size #define MIN_COLUMNS 12 // minimal columns for screen #define MIN_LINES 2 // minimal lines for screen -/// Structure used by switch_win() to pass values to restore_win() -typedef struct { - win_T *sw_curwin; - tabpage_T *sw_curtab; - bool sw_same_win; ///< VIsual_active was not reset - bool sw_visual_active; -} switchwin_T; - -/// Execute a block of code in the context of window `wp` in tabpage `tp`. -/// Ensures the status line is redrawn and cursor position is valid if it is moved. -#define WIN_EXECUTE(wp, tp, block) \ - do { \ - win_T *const wp_ = (wp); \ - const pos_T curpos_ = wp_->w_cursor; \ - char_u cwd_[MAXPATHL]; \ - char_u autocwd_[MAXPATHL]; \ - bool apply_acd_ = false; \ - int cwd_status_ = FAIL; \ - /* Getting and setting directory can be slow on some systems, only do */ \ - /* this when the current or target window/tab have a local directory or */ \ - /* 'acd' is set. */ \ - if (curwin != wp \ - && (curwin->w_localdir != NULL || wp->w_localdir != NULL \ - || (curtab != tp && (curtab->tp_localdir != NULL || tp->tp_localdir != NULL)) \ - || p_acd)) { \ - cwd_status_ = os_dirname(cwd_, MAXPATHL); \ - } \ - /* If 'acd' is set, check we are using that directory. If yes, then */ \ - /* apply 'acd' afterwards, otherwise restore the current directory. */ \ - if (cwd_status_ == OK && p_acd) { \ - do_autochdir(); \ - apply_acd_ = os_dirname(autocwd_, MAXPATHL) == OK && STRCMP(cwd_, autocwd_) == 0; \ - } \ - switchwin_T switchwin_; \ - if (switch_win_noblock(&switchwin_, wp_, (tp), true) == OK) { \ - check_cursor(); \ - block; \ - } \ - restore_win_noblock(&switchwin_, true); \ - if (apply_acd_) { \ - do_autochdir(); \ - } else if (cwd_status_ == OK) { \ - os_chdir((char *)cwd_); \ - } \ - /* Update the status line if the cursor moved. */ \ - if (win_valid(wp_) && !equalpos(curpos_, wp_->w_cursor)) { \ - wp_->w_redr_status = true; \ - } \ - /* In case the command moved the cursor or changed the Visual area, */ \ - /* check it is valid. */ \ - check_cursor(); \ - if (VIsual_active) { \ - check_pos(curbuf, &VIsual); \ - } \ - } while (false) - +// Set to true if 'cmdheight' was explicitly set to 0. +EXTERN bool p_ch_was_zero INIT(= false); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "window.h.generated.h" #endif diff --git a/src/uncrustify.cfg b/src/uncrustify.cfg index 2b9396e635..f9e6617d40 100644 --- a/src/uncrustify.cfg +++ b/src/uncrustify.cfg @@ -125,7 +125,7 @@ sp_before_assign = ignore # ignore/add/remove/force/not_defined # Add or remove space after assignment operator '=', '+=', etc. # # Overrides sp_assign. -sp_after_assign = ignore # ignore/add/remove/force/not_defined +sp_after_assign = force # ignore/add/remove/force/not_defined # Add or remove space in 'enum {'. # @@ -1642,7 +1642,7 @@ nl_end_of_file = force # ignore/add/remove/force/not_defined nl_end_of_file_min = 1 # unsigned number # Add or remove newline between '=' and '{'. -nl_assign_brace = ignore # ignore/add/remove/force/not_defined +nl_assign_brace = remove # ignore/add/remove/force/not_defined # (D) Add or remove newline between '=' and '['. nl_assign_square = ignore # ignore/add/remove/force/not_defined @@ -1714,7 +1714,7 @@ nl_try_brace = ignore # ignore/add/remove/force/not_defined nl_getset_brace = ignore # ignore/add/remove/force/not_defined # Add or remove newline between 'for' and '{'. -nl_for_brace = ignore # ignore/add/remove/force/not_defined +nl_for_brace = remove # ignore/add/remove/force/not_defined # Add or remove newline before the '{' of a 'catch' statement, as in # 'catch (decl) <here> {'. @@ -1738,7 +1738,7 @@ nl_brace_square = ignore # ignore/add/remove/force/not_defined nl_brace_fparen = remove # ignore/add/remove/force/not_defined # Add or remove newline between 'while' and '{'. -nl_while_brace = ignore # ignore/add/remove/force/not_defined +nl_while_brace = remove # ignore/add/remove/force/not_defined # (D) Add or remove newline between 'scope (x)' and '{'. nl_scope_brace = ignore # ignore/add/remove/force/not_defined @@ -1763,7 +1763,7 @@ nl_do_brace = remove # ignore/add/remove/force/not_defined nl_brace_while = remove # ignore/add/remove/force/not_defined # Add or remove newline between 'switch' and '{'. -nl_switch_brace = ignore # ignore/add/remove/force/not_defined +nl_switch_brace = remove # ignore/add/remove/force/not_defined # Add or remove newline between 'synchronized' and '{'. nl_synchronized_brace = ignore # ignore/add/remove/force/not_defined @@ -1777,7 +1777,7 @@ nl_multi_line_cond = false # true/false # Add a newline after '(' if an if/for/while/switch condition spans multiple # lines -nl_multi_line_sparen_open = ignore # ignore/add/remove/force/not_defined +nl_multi_line_sparen_open = remove # ignore/add/remove/force/not_defined # Add a newline before ')' if an if/for/while/switch condition spans multiple # lines. Overrides nl_before_if_closing_paren if both are specified. @@ -1908,7 +1908,7 @@ nl_func_paren_empty = ignore # ignore/add/remove/force/not_defined # Add or remove newline between a function name and the opening '(' in the # definition. -nl_func_def_paren = ignore # ignore/add/remove/force/not_defined +nl_func_def_paren = remove # ignore/add/remove/force/not_defined # Overrides nl_func_def_paren for functions with no parameters. nl_func_def_paren_empty = ignore # ignore/add/remove/force/not_defined @@ -1941,7 +1941,7 @@ nl_func_decl_start_multi_line = false # true/false nl_func_def_start_multi_line = false # true/false # Add or remove newline after each ',' in a function declaration. -nl_func_decl_args = ignore # ignore/add/remove/force/not_defined +nl_func_decl_args = remove # ignore/add/remove/force/not_defined # Add or remove newline after each ',' in a function definition. nl_func_def_args = remove # ignore/add/remove/force/not_defined @@ -1958,7 +1958,7 @@ nl_func_decl_args_multi_line = false # true/false nl_func_def_args_multi_line = false # true/false # Add or remove newline before the ')' in a function declaration. -nl_func_decl_end = ignore # ignore/add/remove/force/not_defined +nl_func_decl_end = remove # ignore/add/remove/force/not_defined # Add or remove newline before the ')' in a function definition. nl_func_def_end = remove # ignore/add/remove/force/not_defined @@ -1991,7 +1991,7 @@ nl_func_call_empty = ignore # ignore/add/remove/force/not_defined nl_func_call_start = remove # ignore/add/remove/force/not_defined # Whether to add a newline before ')' in a function call. -nl_func_call_end = ignore # ignore/add/remove/force/not_defined +nl_func_call_end = remove # ignore/add/remove/force/not_defined # Whether to add a newline after '(' in a function call if '(' and ')' are in # different lines. @@ -3515,5 +3515,5 @@ set QUESTION REAL_FATTR_CONST set QUESTION REAL_FATTR_NONNULL_ALL set QUESTION REAL_FATTR_PURE set QUESTION REAL_FATTR_WARN_UNUSED_RESULT -# option(s) with 'not default' value: 102 +# option(s) with 'not default' value: 112 # diff --git a/src/unicode/CaseFolding.txt b/src/unicode/CaseFolding.txt index 932ace29e6..65aa0fcd6b 100644 --- a/src/unicode/CaseFolding.txt +++ b/src/unicode/CaseFolding.txt @@ -1,11 +1,11 @@ -# CaseFolding-14.0.0.txt -# Date: 2021-03-08, 19:35:41 GMT -# © 2021 Unicode®, Inc. +# CaseFolding-15.0.0.txt +# Date: 2022-02-02, 23:35:35 GMT +# © 2022 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. -# For terms of use, see http://www.unicode.org/terms_of_use.html +# For terms of use, see https://www.unicode.org/terms_of_use.html # # Unicode Character Database -# For documentation, see http://www.unicode.org/reports/tr44/ +# For documentation, see https://www.unicode.org/reports/tr44/ # # Case Folding Properties # diff --git a/src/unicode/Copyright.txt b/src/unicode/Copyright.txt index bfae4154b6..9d281d674a 100644 --- a/src/unicode/Copyright.txt +++ b/src/unicode/Copyright.txt @@ -2,7 +2,7 @@ COPYRIGHT AND PERMISSION NOTICE Copyright © 1991-2015 Unicode, Inc. All rights reserved. Distributed under the Terms of Use in -http://www.unicode.org/copyright.html. +https://www.unicode.org/copyright.html. Permission is hereby granted, free of charge, to any person obtaining a copy of the Unicode data files and any associated documentation @@ -34,4 +34,4 @@ PERFORMANCE OF THE DATA FILES OR SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior -written authorization of the copyright holder.
\ No newline at end of file +written authorization of the copyright holder. diff --git a/src/unicode/EastAsianWidth.txt b/src/unicode/EastAsianWidth.txt index e04f705178..38b7076c02 100644 --- a/src/unicode/EastAsianWidth.txt +++ b/src/unicode/EastAsianWidth.txt @@ -1,6 +1,6 @@ -# EastAsianWidth-14.0.0.txt -# Date: 2021-07-06, 09:58:53 GMT [KW, LI] -# © 2021 Unicode®, Inc. +# EastAsianWidth-15.0.0.txt +# Date: 2022-05-24, 17:40:20 GMT [KW, LI] +# © 2022 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. # For terms of use, see https://www.unicode.org/terms_of_use.html # @@ -534,6 +534,7 @@ 0CE2..0CE3;N # Mn [2] KANNADA VOWEL SIGN VOCALIC L..KANNADA VOWEL SIGN VOCALIC LL 0CE6..0CEF;N # Nd [10] KANNADA DIGIT ZERO..KANNADA DIGIT NINE 0CF1..0CF2;N # Lo [2] KANNADA SIGN JIHVAMULIYA..KANNADA SIGN UPADHMANIYA +0CF3;N # Mc KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT 0D00..0D01;N # Mn [2] MALAYALAM SIGN COMBINING ANUSVARA ABOVE..MALAYALAM SIGN CANDRABINDU 0D02..0D03;N # Mc [2] MALAYALAM SIGN ANUSVARA..MALAYALAM SIGN VISARGA 0D04..0D0C;N # Lo [9] MALAYALAM LETTER VEDIC ANUSVARA..MALAYALAM LETTER VOCALIC L @@ -595,7 +596,7 @@ 0EBD;N # Lo LAO SEMIVOWEL SIGN NYO 0EC0..0EC4;N # Lo [5] LAO VOWEL SIGN E..LAO VOWEL SIGN AI 0EC6;N # Lm LAO KO LA -0EC8..0ECD;N # Mn [6] LAO TONE MAI EK..LAO NIGGAHITA +0EC8..0ECE;N # Mn [7] LAO TONE MAI EK..LAO YAMAKKAN 0ED0..0ED9;N # Nd [10] LAO DIGIT ZERO..LAO DIGIT NINE 0EDC..0EDF;N # Lo [4] LAO HO NO..LAO LETTER KHMU NYO 0F00;N # Lo TIBETAN SYLLABLE OM @@ -1946,6 +1947,7 @@ FFFD;A # So REPLACEMENT CHARACTER 10EAB..10EAC;N # Mn [2] YEZIDI COMBINING HAMZA MARK..YEZIDI COMBINING MADDA MARK 10EAD;N # Pd YEZIDI HYPHENATION MARK 10EB0..10EB1;N # Lo [2] YEZIDI LETTER LAM WITH DOT ABOVE..YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE +10EFD..10EFF;N # Mn [3] ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA 10F00..10F1C;N # Lo [29] OLD SOGDIAN LETTER ALEPH..OLD SOGDIAN LETTER FINAL TAW WITH VERTICAL TAIL 10F1D..10F26;N # No [10] OLD SOGDIAN NUMBER ONE..OLD SOGDIAN FRACTION ONE HALF 10F27;N # Lo OLD SOGDIAN LIGATURE AYIN-DALETH @@ -2028,6 +2030,8 @@ FFFD;A # So REPLACEMENT CHARACTER 11236..11237;N # Mn [2] KHOJKI SIGN NUKTA..KHOJKI SIGN SHADDA 11238..1123D;N # Po [6] KHOJKI DANDA..KHOJKI ABBREVIATION SIGN 1123E;N # Mn KHOJKI SIGN SUKUN +1123F..11240;N # Lo [2] KHOJKI LETTER QA..KHOJKI LETTER SHORT I +11241;N # Mn KHOJKI VOWEL SIGN VOCALIC R 11280..11286;N # Lo [7] MULTANI LETTER A..MULTANI LETTER GA 11288;N # Lo MULTANI LETTER GHA 1128A..1128D;N # Lo [4] MULTANI LETTER CA..MULTANI LETTER JJA @@ -2190,6 +2194,7 @@ FFFD;A # So REPLACEMENT CHARACTER 11A9E..11AA2;N # Po [5] SOYOMBO HEAD MARK WITH MOON AND SUN AND TRIPLE FLAME..SOYOMBO TERMINAL MARK-2 11AB0..11ABF;N # Lo [16] CANADIAN SYLLABICS NATTILIK HI..CANADIAN SYLLABICS SPA 11AC0..11AF8;N # Lo [57] PAU CIN HAU LETTER PA..PAU CIN HAU GLOTTAL STOP FINAL +11B00..11B09;N # Po [10] DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU 11C00..11C08;N # Lo [9] BHAIKSUKI LETTER A..BHAIKSUKI LETTER VOCALIC L 11C0A..11C2E;N # Lo [37] BHAIKSUKI LETTER E..BHAIKSUKI LETTER HA 11C2F;N # Mc BHAIKSUKI VOWEL SIGN AA @@ -2235,6 +2240,19 @@ FFFD;A # So REPLACEMENT CHARACTER 11EF3..11EF4;N # Mn [2] MAKASAR VOWEL SIGN I..MAKASAR VOWEL SIGN U 11EF5..11EF6;N # Mc [2] MAKASAR VOWEL SIGN E..MAKASAR VOWEL SIGN O 11EF7..11EF8;N # Po [2] MAKASAR PASSIMBANG..MAKASAR END OF SECTION +11F00..11F01;N # Mn [2] KAWI SIGN CANDRABINDU..KAWI SIGN ANUSVARA +11F02;N # Lo KAWI SIGN REPHA +11F03;N # Mc KAWI SIGN VISARGA +11F04..11F10;N # Lo [13] KAWI LETTER A..KAWI LETTER O +11F12..11F33;N # Lo [34] KAWI LETTER KA..KAWI LETTER JNYA +11F34..11F35;N # Mc [2] KAWI VOWEL SIGN AA..KAWI VOWEL SIGN ALTERNATE AA +11F36..11F3A;N # Mn [5] KAWI VOWEL SIGN I..KAWI VOWEL SIGN VOCALIC R +11F3E..11F3F;N # Mc [2] KAWI VOWEL SIGN E..KAWI VOWEL SIGN AI +11F40;N # Mn KAWI VOWEL SIGN EU +11F41;N # Mc KAWI SIGN KILLER +11F42;N # Mn KAWI CONJOINER +11F43..11F4F;N # Po [13] KAWI DANDA..KAWI PUNCTUATION CLOSING SPIRAL +11F50..11F59;N # Nd [10] KAWI DIGIT ZERO..KAWI DIGIT NINE 11FB0;N # Lo LISU LETTER YHA 11FC0..11FD4;N # No [21] TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH..TAMIL FRACTION DOWNSCALING FACTOR KIIZH 11FD5..11FDC;N # So [8] TAMIL SIGN NEL..TAMIL SIGN MUKKURUNI @@ -2247,8 +2265,11 @@ FFFD;A # So REPLACEMENT CHARACTER 12480..12543;N # Lo [196] CUNEIFORM SIGN AB TIMES NUN TENU..CUNEIFORM SIGN ZU5 TIMES THREE DISH TENU 12F90..12FF0;N # Lo [97] CYPRO-MINOAN SIGN CM001..CYPRO-MINOAN SIGN CM114 12FF1..12FF2;N # Po [2] CYPRO-MINOAN SIGN CM301..CYPRO-MINOAN SIGN CM302 -13000..1342E;N # Lo [1071] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH AA032 -13430..13438;N # Cf [9] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END SEGMENT +13000..1342F;N # Lo [1072] EGYPTIAN HIEROGLYPH A001..EGYPTIAN HIEROGLYPH V011D +13430..1343F;N # Cf [16] EGYPTIAN HIEROGLYPH VERTICAL JOINER..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE +13440;N # Mn EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY +13441..13446;N # Lo [6] EGYPTIAN HIEROGLYPH FULL BLANK..EGYPTIAN HIEROGLYPH WIDE LOST SIGN +13447..13455;N # Mn [15] EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED 14400..14646;N # Lo [583] ANATOLIAN HIEROGLYPH A001..ANATOLIAN HIEROGLYPH A530 16800..16A38;N # Lo [569] BAMUM LETTER PHASE-A NGKUE MFON..BAMUM LETTER PHASE-F VUEQ 16A40..16A5E;N # Lo [31] MRO LETTER TA..MRO LETTER TEK @@ -2293,7 +2314,9 @@ FFFD;A # So REPLACEMENT CHARACTER 1AFFD..1AFFE;W # Lm [2] KATAKANA LETTER MINNAN NASALIZED TONE-7..KATAKANA LETTER MINNAN NASALIZED TONE-8 1B000..1B0FF;W # Lo [256] KATAKANA LETTER ARCHAIC E..HENTAIGANA LETTER RE-2 1B100..1B122;W # Lo [35] HENTAIGANA LETTER RE-3..KATAKANA LETTER ARCHAIC WU +1B132;W # Lo HIRAGANA LETTER SMALL KO 1B150..1B152;W # Lo [3] HIRAGANA LETTER SMALL WI..HIRAGANA LETTER SMALL WO +1B155;W # Lo KATAKANA LETTER SMALL KO 1B164..1B167;W # Lo [4] KATAKANA LETTER SMALL WI..KATAKANA LETTER SMALL N 1B170..1B2FB;W # Lo [396] NUSHU CHARACTER-1B170..NUSHU CHARACTER-1B2FB 1BC00..1BC6A;N # Lo [107] DUPLOYAN LETTER H..DUPLOYAN LETTER VOCALIC M @@ -2324,6 +2347,7 @@ FFFD;A # So REPLACEMENT CHARACTER 1D200..1D241;N # So [66] GREEK VOCAL NOTATION SYMBOL-1..GREEK INSTRUMENTAL NOTATION SYMBOL-54 1D242..1D244;N # Mn [3] COMBINING GREEK MUSICAL TRISEME..COMBINING GREEK MUSICAL PENTASEME 1D245;N # So GREEK MUSICAL LEIMMA +1D2C0..1D2D3;N # No [20] KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN 1D2E0..1D2F3;N # No [20] MAYAN NUMERAL ZERO..MAYAN NUMERAL NINETEEN 1D300..1D356;N # So [87] MONOGRAM FOR EARTH..TETRAGRAM FOR FOSTERING 1D360..1D378;N # No [25] COUNTING ROD UNIT DIGIT ONE..TALLY MARK FIVE @@ -2383,11 +2407,14 @@ FFFD;A # So REPLACEMENT CHARACTER 1DF00..1DF09;N # Ll [10] LATIN SMALL LETTER FENG DIGRAPH WITH TRILL..LATIN SMALL LETTER T WITH HOOK AND RETROFLEX HOOK 1DF0A;N # Lo LATIN LETTER RETROFLEX CLICK WITH RETROFLEX HOOK 1DF0B..1DF1E;N # Ll [20] LATIN SMALL LETTER ESH WITH DOUBLE BAR..LATIN SMALL LETTER S WITH CURL +1DF25..1DF2A;N # Ll [6] LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK 1E000..1E006;N # Mn [7] COMBINING GLAGOLITIC LETTER AZU..COMBINING GLAGOLITIC LETTER ZHIVETE 1E008..1E018;N # Mn [17] COMBINING GLAGOLITIC LETTER ZEMLJA..COMBINING GLAGOLITIC LETTER HERU 1E01B..1E021;N # Mn [7] COMBINING GLAGOLITIC LETTER SHTA..COMBINING GLAGOLITIC LETTER YATI 1E023..1E024;N # Mn [2] COMBINING GLAGOLITIC LETTER YU..COMBINING GLAGOLITIC LETTER SMALL YUS 1E026..1E02A;N # Mn [5] COMBINING GLAGOLITIC LETTER YO..COMBINING GLAGOLITIC LETTER FITA +1E030..1E06D;N # Lm [62] MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE +1E08F;N # Mn COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I 1E100..1E12C;N # Lo [45] NYIAKENG PUACHUE HMONG LETTER MA..NYIAKENG PUACHUE HMONG LETTER W 1E130..1E136;N # Mn [7] NYIAKENG PUACHUE HMONG TONE-B..NYIAKENG PUACHUE HMONG TONE-D 1E137..1E13D;N # Lm [7] NYIAKENG PUACHUE HMONG SIGN FOR PERSON..NYIAKENG PUACHUE HMONG SYLLABLE LENGTHENER @@ -2400,6 +2427,10 @@ FFFD;A # So REPLACEMENT CHARACTER 1E2EC..1E2EF;N # Mn [4] WANCHO TONE TUP..WANCHO TONE KOINI 1E2F0..1E2F9;N # Nd [10] WANCHO DIGIT ZERO..WANCHO DIGIT NINE 1E2FF;N # Sc WANCHO NGUN SIGN +1E4D0..1E4EA;N # Lo [27] NAG MUNDARI LETTER O..NAG MUNDARI LETTER ELL +1E4EB;N # Lm NAG MUNDARI SIGN OJOD +1E4EC..1E4EF;N # Mn [4] NAG MUNDARI SIGN MUHOR..NAG MUNDARI SIGN SUTUH +1E4F0..1E4F9;N # Nd [10] NAG MUNDARI DIGIT ZERO..NAG MUNDARI DIGIT NINE 1E7E0..1E7E6;N # Lo [7] ETHIOPIC SYLLABLE HHYA..ETHIOPIC SYLLABLE HHYO 1E7E8..1E7EB;N # Lo [4] ETHIOPIC SYLLABLE GURAGE HHWA..ETHIOPIC SYLLABLE HHWE 1E7ED..1E7EE;N # Lo [2] ETHIOPIC SYLLABLE GURAGE MWI..ETHIOPIC SYLLABLE GURAGE MWEE @@ -2528,13 +2559,14 @@ FFFD;A # So REPLACEMENT CHARACTER 1F6D0..1F6D2;W # So [3] PLACE OF WORSHIP..SHOPPING TROLLEY 1F6D3..1F6D4;N # So [2] STUPA..PAGODA 1F6D5..1F6D7;W # So [3] HINDU TEMPLE..ELEVATOR -1F6DD..1F6DF;W # So [3] PLAYGROUND SLIDE..RING BUOY +1F6DC..1F6DF;W # So [4] WIRELESS..RING BUOY 1F6E0..1F6EA;N # So [11] HAMMER AND WRENCH..NORTHEAST-POINTING AIRPLANE 1F6EB..1F6EC;W # So [2] AIRPLANE DEPARTURE..AIRPLANE ARRIVING 1F6F0..1F6F3;N # So [4] SATELLITE..PASSENGER SHIP 1F6F4..1F6FC;W # So [9] SCOOTER..ROLLER SKATE -1F700..1F773;N # So [116] ALCHEMICAL SYMBOL FOR QUINTESSENCE..ALCHEMICAL SYMBOL FOR HALF OUNCE -1F780..1F7D8;N # So [89] BLACK LEFT-POINTING ISOSCELES RIGHT TRIANGLE..NEGATIVE CIRCLED SQUARE +1F700..1F776;N # So [119] ALCHEMICAL SYMBOL FOR QUINTESSENCE..LUNAR ECLIPSE +1F77B..1F77F;N # So [5] HAUMEA..ORCUS +1F780..1F7D9;N # So [90] BLACK LEFT-POINTING ISOSCELES RIGHT TRIANGLE..NINE POINTED WHITE STAR 1F7E0..1F7EB;W # So [12] LARGE ORANGE CIRCLE..LARGE BROWN SQUARE 1F7F0;W # So HEAVY EQUALS SIGN 1F800..1F80B;N # So [12] LEFTWARDS ARROW WITH SMALL TRIANGLE ARROWHEAD..DOWNWARDS ARROW WITH LARGE TRIANGLE ARROWHEAD @@ -2551,22 +2583,20 @@ FFFD;A # So REPLACEMENT CHARACTER 1F947..1F9FF;W # So [185] FIRST PLACE MEDAL..NAZAR AMULET 1FA00..1FA53;N # So [84] NEUTRAL CHESS KING..BLACK CHESS KNIGHT-BISHOP 1FA60..1FA6D;N # So [14] XIANGQI RED GENERAL..XIANGQI BLACK SOLDIER -1FA70..1FA74;W # So [5] BALLET SHOES..THONG SANDAL -1FA78..1FA7C;W # So [5] DROP OF BLOOD..CRUTCH -1FA80..1FA86;W # So [7] YO-YO..NESTING DOLLS -1FA90..1FAAC;W # So [29] RINGED PLANET..HAMSA -1FAB0..1FABA;W # So [11] FLY..NEST WITH EGGS -1FAC0..1FAC5;W # So [6] ANATOMICAL HEART..PERSON WITH CROWN -1FAD0..1FAD9;W # So [10] BLUEBERRIES..JAR -1FAE0..1FAE7;W # So [8] MELTING FACE..BUBBLES -1FAF0..1FAF6;W # So [7] HAND WITH INDEX FINGER AND THUMB CROSSED..HEART HANDS +1FA70..1FA7C;W # So [13] BALLET SHOES..CRUTCH +1FA80..1FA88;W # So [9] YO-YO..FLUTE +1FA90..1FABD;W # So [46] RINGED PLANET..WING +1FABF..1FAC5;W # So [7] GOOSE..PERSON WITH CROWN +1FACE..1FADB;W # So [14] MOOSE..PEA POD +1FAE0..1FAE8;W # So [9] MELTING FACE..SHAKING FACE +1FAF0..1FAF8;W # So [9] HAND WITH INDEX FINGER AND THUMB CROSSED..RIGHTWARDS PUSHING HAND 1FB00..1FB92;N # So [147] BLOCK SEXTANT-1..UPPER HALF INVERSE MEDIUM SHADE AND LOWER HALF BLOCK 1FB94..1FBCA;N # So [55] LEFT HALF INVERSE MEDIUM SHADE AND RIGHT HALF BLOCK..WHITE UP-POINTING CHEVRON 1FBF0..1FBF9;N # Nd [10] SEGMENTED DIGIT ZERO..SEGMENTED DIGIT NINE 20000..2A6DF;W # Lo [42720] CJK UNIFIED IDEOGRAPH-20000..CJK UNIFIED IDEOGRAPH-2A6DF 2A6E0..2A6FF;W # Cn [32] <reserved-2A6E0>..<reserved-2A6FF> -2A700..2B738;W # Lo [4153] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B738 -2B739..2B73F;W # Cn [7] <reserved-2B739>..<reserved-2B73F> +2A700..2B739;W # Lo [4154] CJK UNIFIED IDEOGRAPH-2A700..CJK UNIFIED IDEOGRAPH-2B739 +2B73A..2B73F;W # Cn [6] <reserved-2B73A>..<reserved-2B73F> 2B740..2B81D;W # Lo [222] CJK UNIFIED IDEOGRAPH-2B740..CJK UNIFIED IDEOGRAPH-2B81D 2B81E..2B81F;W # Cn [2] <reserved-2B81E>..<reserved-2B81F> 2B820..2CEA1;W # Lo [5762] CJK UNIFIED IDEOGRAPH-2B820..CJK UNIFIED IDEOGRAPH-2CEA1 @@ -2577,7 +2607,9 @@ FFFD;A # So REPLACEMENT CHARACTER 2FA1E..2FA1F;W # Cn [2] <reserved-2FA1E>..<reserved-2FA1F> 2FA20..2FFFD;W # Cn [1502] <reserved-2FA20>..<reserved-2FFFD> 30000..3134A;W # Lo [4939] CJK UNIFIED IDEOGRAPH-30000..CJK UNIFIED IDEOGRAPH-3134A -3134B..3FFFD;W # Cn [60595] <reserved-3134B>..<reserved-3FFFD> +3134B..3134F;W # Cn [5] <reserved-3134B>..<reserved-3134F> +31350..323AF;W # Lo [4192] CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF +323B0..3FFFD;W # Cn [56398] <reserved-323B0>..<reserved-3FFFD> E0001;N # Cf LANGUAGE TAG E0020..E007F;N # Cf [96] TAG SPACE..CANCEL TAG E0100..E01EF;A # Mn [240] VARIATION SELECTOR-17..VARIATION SELECTOR-256 diff --git a/src/unicode/UnicodeData.txt b/src/unicode/UnicodeData.txt index b5abef7ed4..ea963a7162 100644 --- a/src/unicode/UnicodeData.txt +++ b/src/unicode/UnicodeData.txt @@ -2975,6 +2975,7 @@ 0CEF;KANNADA DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 0CF1;KANNADA SIGN JIHVAMULIYA;Lo;0;L;;;;;N;;;;; 0CF2;KANNADA SIGN UPADHMANIYA;Lo;0;L;;;;;N;;;;; +0CF3;KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT;Mc;0;L;;;;;N;;;;; 0D00;MALAYALAM SIGN COMBINING ANUSVARA ABOVE;Mn;0;NSM;;;;;N;;;;; 0D01;MALAYALAM SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; 0D02;MALAYALAM SIGN ANUSVARA;Mc;0;L;;;;;N;;;;; @@ -3339,6 +3340,7 @@ 0ECB;LAO TONE MAI CATAWA;Mn;122;NSM;;;;;N;;;;; 0ECC;LAO CANCELLATION MARK;Mn;0;NSM;;;;;N;;;;; 0ECD;LAO NIGGAHITA;Mn;0;NSM;;;;;N;;;;; +0ECE;LAO YAMAKKAN;Mn;0;NSM;;;;;N;;;;; 0ED0;LAO DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; 0ED1;LAO DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; 0ED2;LAO DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; @@ -19393,6 +19395,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 10EAD;YEZIDI HYPHENATION MARK;Pd;0;R;;;;;N;;;;; 10EB0;YEZIDI LETTER LAM WITH DOT ABOVE;Lo;0;R;;;;;N;;;;; 10EB1;YEZIDI LETTER YOT WITH CIRCUMFLEX ABOVE;Lo;0;R;;;;;N;;;;; +10EFD;ARABIC SMALL LOW WORD SAKTA;Mn;220;NSM;;;;;N;;;;; +10EFE;ARABIC SMALL LOW WORD QASR;Mn;220;NSM;;;;;N;;;;; +10EFF;ARABIC SMALL LOW WORD MADDA;Mn;220;NSM;;;;;N;;;;; 10F00;OLD SOGDIAN LETTER ALEPH;Lo;0;R;;;;;N;;;;; 10F01;OLD SOGDIAN LETTER FINAL ALEPH;Lo;0;R;;;;;N;;;;; 10F02;OLD SOGDIAN LETTER BETH;Lo;0;R;;;;;N;;;;; @@ -20058,6 +20063,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1123C;KHOJKI DOUBLE SECTION MARK;Po;0;L;;;;;N;;;;; 1123D;KHOJKI ABBREVIATION SIGN;Po;0;L;;;;;N;;;;; 1123E;KHOJKI SIGN SUKUN;Mn;0;NSM;;;;;N;;;;; +1123F;KHOJKI LETTER QA;Lo;0;L;;;;;N;;;;; +11240;KHOJKI LETTER SHORT I;Lo;0;L;;;;;N;;;;; +11241;KHOJKI VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; 11280;MULTANI LETTER A;Lo;0;L;;;;;N;;;;; 11281;MULTANI LETTER I;Lo;0;L;;;;;N;;;;; 11282;MULTANI LETTER U;Lo;0;L;;;;;N;;;;; @@ -21256,6 +21264,16 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11AF6;PAU CIN HAU LOW-FALLING TONE LONG FINAL;Lo;0;L;;;;;N;;;;; 11AF7;PAU CIN HAU LOW-FALLING TONE FINAL;Lo;0;L;;;;;N;;;;; 11AF8;PAU CIN HAU GLOTTAL STOP FINAL;Lo;0;L;;;;;N;;;;; +11B00;DEVANAGARI HEAD MARK;Po;0;L;;;;;N;;;;; +11B01;DEVANAGARI HEAD MARK WITH HEADSTROKE;Po;0;L;;;;;N;;;;; +11B02;DEVANAGARI SIGN BHALE;Po;0;L;;;;;N;;;;; +11B03;DEVANAGARI SIGN BHALE WITH HOOK;Po;0;L;;;;;N;;;;; +11B04;DEVANAGARI SIGN EXTENDED BHALE;Po;0;L;;;;;N;;;;; +11B05;DEVANAGARI SIGN EXTENDED BHALE WITH HOOK;Po;0;L;;;;;N;;;;; +11B06;DEVANAGARI SIGN WESTERN FIVE-LIKE BHALE;Po;0;L;;;;;N;;;;; +11B07;DEVANAGARI SIGN WESTERN NINE-LIKE BHALE;Po;0;L;;;;;N;;;;; +11B08;DEVANAGARI SIGN REVERSED NINE-LIKE BHALE;Po;0;L;;;;;N;;;;; +11B09;DEVANAGARI SIGN MINDU;Po;0;L;;;;;N;;;;; 11C00;BHAIKSUKI LETTER A;Lo;0;L;;;;;N;;;;; 11C01;BHAIKSUKI LETTER AA;Lo;0;L;;;;;N;;;;; 11C02;BHAIKSUKI LETTER I;Lo;0;L;;;;;N;;;;; @@ -21584,6 +21602,92 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 11EF6;MAKASAR VOWEL SIGN O;Mc;0;L;;;;;N;;;;; 11EF7;MAKASAR PASSIMBANG;Po;0;L;;;;;N;;;;; 11EF8;MAKASAR END OF SECTION;Po;0;L;;;;;N;;;;; +11F00;KAWI SIGN CANDRABINDU;Mn;0;NSM;;;;;N;;;;; +11F01;KAWI SIGN ANUSVARA;Mn;0;NSM;;;;;N;;;;; +11F02;KAWI SIGN REPHA;Lo;0;L;;;;;N;;;;; +11F03;KAWI SIGN VISARGA;Mc;0;L;;;;;N;;;;; +11F04;KAWI LETTER A;Lo;0;L;;;;;N;;;;; +11F05;KAWI LETTER AA;Lo;0;L;;;;;N;;;;; +11F06;KAWI LETTER I;Lo;0;L;;;;;N;;;;; +11F07;KAWI LETTER II;Lo;0;L;;;;;N;;;;; +11F08;KAWI LETTER U;Lo;0;L;;;;;N;;;;; +11F09;KAWI LETTER UU;Lo;0;L;;;;;N;;;;; +11F0A;KAWI LETTER VOCALIC R;Lo;0;L;;;;;N;;;;; +11F0B;KAWI LETTER VOCALIC RR;Lo;0;L;;;;;N;;;;; +11F0C;KAWI LETTER VOCALIC L;Lo;0;L;;;;;N;;;;; +11F0D;KAWI LETTER VOCALIC LL;Lo;0;L;;;;;N;;;;; +11F0E;KAWI LETTER E;Lo;0;L;;;;;N;;;;; +11F0F;KAWI LETTER AI;Lo;0;L;;;;;N;;;;; +11F10;KAWI LETTER O;Lo;0;L;;;;;N;;;;; +11F12;KAWI LETTER KA;Lo;0;L;;;;;N;;;;; +11F13;KAWI LETTER KHA;Lo;0;L;;;;;N;;;;; +11F14;KAWI LETTER GA;Lo;0;L;;;;;N;;;;; +11F15;KAWI LETTER GHA;Lo;0;L;;;;;N;;;;; +11F16;KAWI LETTER NGA;Lo;0;L;;;;;N;;;;; +11F17;KAWI LETTER CA;Lo;0;L;;;;;N;;;;; +11F18;KAWI LETTER CHA;Lo;0;L;;;;;N;;;;; +11F19;KAWI LETTER JA;Lo;0;L;;;;;N;;;;; +11F1A;KAWI LETTER JHA;Lo;0;L;;;;;N;;;;; +11F1B;KAWI LETTER NYA;Lo;0;L;;;;;N;;;;; +11F1C;KAWI LETTER TTA;Lo;0;L;;;;;N;;;;; +11F1D;KAWI LETTER TTHA;Lo;0;L;;;;;N;;;;; +11F1E;KAWI LETTER DDA;Lo;0;L;;;;;N;;;;; +11F1F;KAWI LETTER DDHA;Lo;0;L;;;;;N;;;;; +11F20;KAWI LETTER NNA;Lo;0;L;;;;;N;;;;; +11F21;KAWI LETTER TA;Lo;0;L;;;;;N;;;;; +11F22;KAWI LETTER THA;Lo;0;L;;;;;N;;;;; +11F23;KAWI LETTER DA;Lo;0;L;;;;;N;;;;; +11F24;KAWI LETTER DHA;Lo;0;L;;;;;N;;;;; +11F25;KAWI LETTER NA;Lo;0;L;;;;;N;;;;; +11F26;KAWI LETTER PA;Lo;0;L;;;;;N;;;;; +11F27;KAWI LETTER PHA;Lo;0;L;;;;;N;;;;; +11F28;KAWI LETTER BA;Lo;0;L;;;;;N;;;;; +11F29;KAWI LETTER BHA;Lo;0;L;;;;;N;;;;; +11F2A;KAWI LETTER MA;Lo;0;L;;;;;N;;;;; +11F2B;KAWI LETTER YA;Lo;0;L;;;;;N;;;;; +11F2C;KAWI LETTER RA;Lo;0;L;;;;;N;;;;; +11F2D;KAWI LETTER LA;Lo;0;L;;;;;N;;;;; +11F2E;KAWI LETTER WA;Lo;0;L;;;;;N;;;;; +11F2F;KAWI LETTER SHA;Lo;0;L;;;;;N;;;;; +11F30;KAWI LETTER SSA;Lo;0;L;;;;;N;;;;; +11F31;KAWI LETTER SA;Lo;0;L;;;;;N;;;;; +11F32;KAWI LETTER HA;Lo;0;L;;;;;N;;;;; +11F33;KAWI LETTER JNYA;Lo;0;L;;;;;N;;;;; +11F34;KAWI VOWEL SIGN AA;Mc;0;L;;;;;N;;;;; +11F35;KAWI VOWEL SIGN ALTERNATE AA;Mc;0;L;;;;;N;;;;; +11F36;KAWI VOWEL SIGN I;Mn;0;NSM;;;;;N;;;;; +11F37;KAWI VOWEL SIGN II;Mn;0;NSM;;;;;N;;;;; +11F38;KAWI VOWEL SIGN U;Mn;0;NSM;;;;;N;;;;; +11F39;KAWI VOWEL SIGN UU;Mn;0;NSM;;;;;N;;;;; +11F3A;KAWI VOWEL SIGN VOCALIC R;Mn;0;NSM;;;;;N;;;;; +11F3E;KAWI VOWEL SIGN E;Mc;0;L;;;;;N;;;;; +11F3F;KAWI VOWEL SIGN AI;Mc;0;L;;;;;N;;;;; +11F40;KAWI VOWEL SIGN EU;Mn;0;NSM;;;;;N;;;;; +11F41;KAWI SIGN KILLER;Mc;9;L;;;;;N;;;;; +11F42;KAWI CONJOINER;Mn;9;NSM;;;;;N;;;;; +11F43;KAWI DANDA;Po;0;L;;;;;N;;;;; +11F44;KAWI DOUBLE DANDA;Po;0;L;;;;;N;;;;; +11F45;KAWI PUNCTUATION SECTION MARKER;Po;0;L;;;;;N;;;;; +11F46;KAWI PUNCTUATION ALTERNATE SECTION MARKER;Po;0;L;;;;;N;;;;; +11F47;KAWI PUNCTUATION FLOWER;Po;0;L;;;;;N;;;;; +11F48;KAWI PUNCTUATION SPACE FILLER;Po;0;L;;;;;N;;;;; +11F49;KAWI PUNCTUATION DOT;Po;0;L;;;;;N;;;;; +11F4A;KAWI PUNCTUATION DOUBLE DOT;Po;0;L;;;;;N;;;;; +11F4B;KAWI PUNCTUATION TRIPLE DOT;Po;0;L;;;;;N;;;;; +11F4C;KAWI PUNCTUATION CIRCLE;Po;0;L;;;;;N;;;;; +11F4D;KAWI PUNCTUATION FILLED CIRCLE;Po;0;L;;;;;N;;;;; +11F4E;KAWI PUNCTUATION SPIRAL;Po;0;L;;;;;N;;;;; +11F4F;KAWI PUNCTUATION CLOSING SPIRAL;Po;0;L;;;;;N;;;;; +11F50;KAWI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; +11F51;KAWI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; +11F52;KAWI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; +11F53;KAWI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; +11F54;KAWI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; +11F55;KAWI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; +11F56;KAWI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; +11F57;KAWI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; +11F58;KAWI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; +11F59;KAWI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 11FB0;LISU LETTER YHA;Lo;0;L;;;;;N;;;;; 11FC0;TAMIL FRACTION ONE THREE-HUNDRED-AND-TWENTIETH;No;0;L;;;;1/320;N;;;;; 11FC1;TAMIL FRACTION ONE ONE-HUNDRED-AND-SIXTIETH;No;0;L;;;;1/160;N;;;;; @@ -24040,6 +24144,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1342C;EGYPTIAN HIEROGLYPH AA030;Lo;0;L;;;;;N;;;;; 1342D;EGYPTIAN HIEROGLYPH AA031;Lo;0;L;;;;;N;;;;; 1342E;EGYPTIAN HIEROGLYPH AA032;Lo;0;L;;;;;N;;;;; +1342F;EGYPTIAN HIEROGLYPH V011D;Lo;0;L;;;;;N;;;;; 13430;EGYPTIAN HIEROGLYPH VERTICAL JOINER;Cf;0;L;;;;;N;;;;; 13431;EGYPTIAN HIEROGLYPH HORIZONTAL JOINER;Cf;0;L;;;;;N;;;;; 13432;EGYPTIAN HIEROGLYPH INSERT AT TOP START;Cf;0;L;;;;;N;;;;; @@ -24049,6 +24154,35 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 13436;EGYPTIAN HIEROGLYPH OVERLAY MIDDLE;Cf;0;L;;;;;N;;;;; 13437;EGYPTIAN HIEROGLYPH BEGIN SEGMENT;Cf;0;L;;;;;N;;;;; 13438;EGYPTIAN HIEROGLYPH END SEGMENT;Cf;0;L;;;;;N;;;;; +13439;EGYPTIAN HIEROGLYPH INSERT AT MIDDLE;Cf;0;L;;;;;N;;;;; +1343A;EGYPTIAN HIEROGLYPH INSERT AT TOP;Cf;0;L;;;;;N;;;;; +1343B;EGYPTIAN HIEROGLYPH INSERT AT BOTTOM;Cf;0;L;;;;;N;;;;; +1343C;EGYPTIAN HIEROGLYPH BEGIN ENCLOSURE;Cf;0;L;;;;;N;;;;; +1343D;EGYPTIAN HIEROGLYPH END ENCLOSURE;Cf;0;L;;;;;N;;;;; +1343E;EGYPTIAN HIEROGLYPH BEGIN WALLED ENCLOSURE;Cf;0;L;;;;;N;;;;; +1343F;EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE;Cf;0;L;;;;;N;;;;; +13440;EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY;Mn;0;NSM;;;;;N;;;;; +13441;EGYPTIAN HIEROGLYPH FULL BLANK;Lo;0;L;;;;;N;;;;; +13442;EGYPTIAN HIEROGLYPH HALF BLANK;Lo;0;L;;;;;N;;;;; +13443;EGYPTIAN HIEROGLYPH LOST SIGN;Lo;0;L;;;;;N;;;;; +13444;EGYPTIAN HIEROGLYPH HALF LOST SIGN;Lo;0;L;;;;;N;;;;; +13445;EGYPTIAN HIEROGLYPH TALL LOST SIGN;Lo;0;L;;;;;N;;;;; +13446;EGYPTIAN HIEROGLYPH WIDE LOST SIGN;Lo;0;L;;;;;N;;;;; +13447;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START;Mn;0;NSM;;;;;N;;;;; +13448;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT BOTTOM START;Mn;0;NSM;;;;;N;;;;; +13449;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT START;Mn;0;NSM;;;;;N;;;;; +1344A;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP END;Mn;0;NSM;;;;;N;;;;; +1344B;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP;Mn;0;NSM;;;;;N;;;;; +1344C;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT BOTTOM START AND TOP END;Mn;0;NSM;;;;;N;;;;; +1344D;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT START AND TOP;Mn;0;NSM;;;;;N;;;;; +1344E;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT BOTTOM END;Mn;0;NSM;;;;;N;;;;; +1344F;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP START AND BOTTOM END;Mn;0;NSM;;;;;N;;;;; +13450;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT BOTTOM;Mn;0;NSM;;;;;N;;;;; +13451;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT START AND BOTTOM;Mn;0;NSM;;;;;N;;;;; +13452;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT END;Mn;0;NSM;;;;;N;;;;; +13453;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT TOP AND END;Mn;0;NSM;;;;;N;;;;; +13454;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED AT BOTTOM AND END;Mn;0;NSM;;;;;N;;;;; +13455;EGYPTIAN HIEROGLYPH MODIFIER DAMAGED;Mn;0;NSM;;;;;N;;;;; 14400;ANATOLIAN HIEROGLYPH A001;Lo;0;L;;;;;N;;;;; 14401;ANATOLIAN HIEROGLYPH A002;Lo;0;L;;;;;N;;;;; 14402;ANATOLIAN HIEROGLYPH A003;Lo;0;L;;;;;N;;;;; @@ -27289,9 +27423,11 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1B120;KATAKANA LETTER ARCHAIC YI;Lo;0;L;;;;;N;;;;; 1B121;KATAKANA LETTER ARCHAIC YE;Lo;0;L;;;;;N;;;;; 1B122;KATAKANA LETTER ARCHAIC WU;Lo;0;L;;;;;N;;;;; +1B132;HIRAGANA LETTER SMALL KO;Lo;0;L;;;;;N;;;;; 1B150;HIRAGANA LETTER SMALL WI;Lo;0;L;;;;;N;;;;; 1B151;HIRAGANA LETTER SMALL WE;Lo;0;L;;;;;N;;;;; 1B152;HIRAGANA LETTER SMALL WO;Lo;0;L;;;;;N;;;;; +1B155;KATAKANA LETTER SMALL KO;Lo;0;L;;;;;N;;;;; 1B164;KATAKANA LETTER SMALL WI;Lo;0;L;;;;;N;;;;; 1B165;KATAKANA LETTER SMALL WE;Lo;0;L;;;;;N;;;;; 1B166;KATAKANA LETTER SMALL WO;Lo;0;L;;;;;N;;;;; @@ -28573,6 +28709,26 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1D243;COMBINING GREEK MUSICAL TETRASEME;Mn;230;NSM;;;;;N;;;;; 1D244;COMBINING GREEK MUSICAL PENTASEME;Mn;230;NSM;;;;;N;;;;; 1D245;GREEK MUSICAL LEIMMA;So;0;ON;;;;;N;;;;; +1D2C0;KAKTOVIK NUMERAL ZERO;No;0;L;;;;0;N;;;;; +1D2C1;KAKTOVIK NUMERAL ONE;No;0;L;;;;1;N;;;;; +1D2C2;KAKTOVIK NUMERAL TWO;No;0;L;;;;2;N;;;;; +1D2C3;KAKTOVIK NUMERAL THREE;No;0;L;;;;3;N;;;;; +1D2C4;KAKTOVIK NUMERAL FOUR;No;0;L;;;;4;N;;;;; +1D2C5;KAKTOVIK NUMERAL FIVE;No;0;L;;;;5;N;;;;; +1D2C6;KAKTOVIK NUMERAL SIX;No;0;L;;;;6;N;;;;; +1D2C7;KAKTOVIK NUMERAL SEVEN;No;0;L;;;;7;N;;;;; +1D2C8;KAKTOVIK NUMERAL EIGHT;No;0;L;;;;8;N;;;;; +1D2C9;KAKTOVIK NUMERAL NINE;No;0;L;;;;9;N;;;;; +1D2CA;KAKTOVIK NUMERAL TEN;No;0;L;;;;10;N;;;;; +1D2CB;KAKTOVIK NUMERAL ELEVEN;No;0;L;;;;11;N;;;;; +1D2CC;KAKTOVIK NUMERAL TWELVE;No;0;L;;;;12;N;;;;; +1D2CD;KAKTOVIK NUMERAL THIRTEEN;No;0;L;;;;13;N;;;;; +1D2CE;KAKTOVIK NUMERAL FOURTEEN;No;0;L;;;;14;N;;;;; +1D2CF;KAKTOVIK NUMERAL FIFTEEN;No;0;L;;;;15;N;;;;; +1D2D0;KAKTOVIK NUMERAL SIXTEEN;No;0;L;;;;16;N;;;;; +1D2D1;KAKTOVIK NUMERAL SEVENTEEN;No;0;L;;;;17;N;;;;; +1D2D2;KAKTOVIK NUMERAL EIGHTEEN;No;0;L;;;;18;N;;;;; +1D2D3;KAKTOVIK NUMERAL NINETEEN;No;0;L;;;;19;N;;;;; 1D2E0;MAYAN NUMERAL ZERO;No;0;L;;;;0;N;;;;; 1D2E1;MAYAN NUMERAL ONE;No;0;L;;;;1;N;;;;; 1D2E2;MAYAN NUMERAL TWO;No;0;L;;;;2;N;;;;; @@ -30404,6 +30560,12 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1DF1C;LATIN SMALL LETTER TESH DIGRAPH WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1DF1D;LATIN SMALL LETTER C WITH RETROFLEX HOOK;Ll;0;L;;;;;N;;;;; 1DF1E;LATIN SMALL LETTER S WITH CURL;Ll;0;L;;;;;N;;;;; +1DF25;LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK;Ll;0;L;;;;;N;;;;; +1DF26;LATIN SMALL LETTER L WITH MID-HEIGHT LEFT HOOK;Ll;0;L;;;;;N;;;;; +1DF27;LATIN SMALL LETTER N WITH MID-HEIGHT LEFT HOOK;Ll;0;L;;;;;N;;;;; +1DF28;LATIN SMALL LETTER R WITH MID-HEIGHT LEFT HOOK;Ll;0;L;;;;;N;;;;; +1DF29;LATIN SMALL LETTER S WITH MID-HEIGHT LEFT HOOK;Ll;0;L;;;;;N;;;;; +1DF2A;LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK;Ll;0;L;;;;;N;;;;; 1E000;COMBINING GLAGOLITIC LETTER AZU;Mn;230;NSM;;;;;N;;;;; 1E001;COMBINING GLAGOLITIC LETTER BUKY;Mn;230;NSM;;;;;N;;;;; 1E002;COMBINING GLAGOLITIC LETTER VEDE;Mn;230;NSM;;;;;N;;;;; @@ -30442,6 +30604,69 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1E028;COMBINING GLAGOLITIC LETTER BIG YUS;Mn;230;NSM;;;;;N;;;;; 1E029;COMBINING GLAGOLITIC LETTER IOTATED BIG YUS;Mn;230;NSM;;;;;N;;;;; 1E02A;COMBINING GLAGOLITIC LETTER FITA;Mn;230;NSM;;;;;N;;;;; +1E030;MODIFIER LETTER CYRILLIC SMALL A;Lm;0;L;<super> 0430;;;;N;;;;; +1E031;MODIFIER LETTER CYRILLIC SMALL BE;Lm;0;L;<super> 0431;;;;N;;;;; +1E032;MODIFIER LETTER CYRILLIC SMALL VE;Lm;0;L;<super> 0432;;;;N;;;;; +1E033;MODIFIER LETTER CYRILLIC SMALL GHE;Lm;0;L;<super> 0433;;;;N;;;;; +1E034;MODIFIER LETTER CYRILLIC SMALL DE;Lm;0;L;<super> 0434;;;;N;;;;; +1E035;MODIFIER LETTER CYRILLIC SMALL IE;Lm;0;L;<super> 0435;;;;N;;;;; +1E036;MODIFIER LETTER CYRILLIC SMALL ZHE;Lm;0;L;<super> 0436;;;;N;;;;; +1E037;MODIFIER LETTER CYRILLIC SMALL ZE;Lm;0;L;<super> 0437;;;;N;;;;; +1E038;MODIFIER LETTER CYRILLIC SMALL I;Lm;0;L;<super> 0438;;;;N;;;;; +1E039;MODIFIER LETTER CYRILLIC SMALL KA;Lm;0;L;<super> 043A;;;;N;;;;; +1E03A;MODIFIER LETTER CYRILLIC SMALL EL;Lm;0;L;<super> 043B;;;;N;;;;; +1E03B;MODIFIER LETTER CYRILLIC SMALL EM;Lm;0;L;<super> 043C;;;;N;;;;; +1E03C;MODIFIER LETTER CYRILLIC SMALL O;Lm;0;L;<super> 043E;;;;N;;;;; +1E03D;MODIFIER LETTER CYRILLIC SMALL PE;Lm;0;L;<super> 043F;;;;N;;;;; +1E03E;MODIFIER LETTER CYRILLIC SMALL ER;Lm;0;L;<super> 0440;;;;N;;;;; +1E03F;MODIFIER LETTER CYRILLIC SMALL ES;Lm;0;L;<super> 0441;;;;N;;;;; +1E040;MODIFIER LETTER CYRILLIC SMALL TE;Lm;0;L;<super> 0442;;;;N;;;;; +1E041;MODIFIER LETTER CYRILLIC SMALL U;Lm;0;L;<super> 0443;;;;N;;;;; +1E042;MODIFIER LETTER CYRILLIC SMALL EF;Lm;0;L;<super> 0444;;;;N;;;;; +1E043;MODIFIER LETTER CYRILLIC SMALL HA;Lm;0;L;<super> 0445;;;;N;;;;; +1E044;MODIFIER LETTER CYRILLIC SMALL TSE;Lm;0;L;<super> 0446;;;;N;;;;; +1E045;MODIFIER LETTER CYRILLIC SMALL CHE;Lm;0;L;<super> 0447;;;;N;;;;; +1E046;MODIFIER LETTER CYRILLIC SMALL SHA;Lm;0;L;<super> 0448;;;;N;;;;; +1E047;MODIFIER LETTER CYRILLIC SMALL YERU;Lm;0;L;<super> 044B;;;;N;;;;; +1E048;MODIFIER LETTER CYRILLIC SMALL E;Lm;0;L;<super> 044D;;;;N;;;;; +1E049;MODIFIER LETTER CYRILLIC SMALL YU;Lm;0;L;<super> 044E;;;;N;;;;; +1E04A;MODIFIER LETTER CYRILLIC SMALL DZZE;Lm;0;L;<super> A689;;;;N;;;;; +1E04B;MODIFIER LETTER CYRILLIC SMALL SCHWA;Lm;0;L;<super> 04D9;;;;N;;;;; +1E04C;MODIFIER LETTER CYRILLIC SMALL BYELORUSSIAN-UKRAINIAN I;Lm;0;L;<super> 0456;;;;N;;;;; +1E04D;MODIFIER LETTER CYRILLIC SMALL JE;Lm;0;L;<super> 0458;;;;N;;;;; +1E04E;MODIFIER LETTER CYRILLIC SMALL BARRED O;Lm;0;L;<super> 04E9;;;;N;;;;; +1E04F;MODIFIER LETTER CYRILLIC SMALL STRAIGHT U;Lm;0;L;<super> 04AF;;;;N;;;;; +1E050;MODIFIER LETTER CYRILLIC SMALL PALOCHKA;Lm;0;L;<super> 04CF;;;;N;;;;; +1E051;CYRILLIC SUBSCRIPT SMALL LETTER A;Lm;0;L;<sub> 0430;;;;N;;;;; +1E052;CYRILLIC SUBSCRIPT SMALL LETTER BE;Lm;0;L;<sub> 0431;;;;N;;;;; +1E053;CYRILLIC SUBSCRIPT SMALL LETTER VE;Lm;0;L;<sub> 0432;;;;N;;;;; +1E054;CYRILLIC SUBSCRIPT SMALL LETTER GHE;Lm;0;L;<sub> 0433;;;;N;;;;; +1E055;CYRILLIC SUBSCRIPT SMALL LETTER DE;Lm;0;L;<sub> 0434;;;;N;;;;; +1E056;CYRILLIC SUBSCRIPT SMALL LETTER IE;Lm;0;L;<sub> 0435;;;;N;;;;; +1E057;CYRILLIC SUBSCRIPT SMALL LETTER ZHE;Lm;0;L;<sub> 0436;;;;N;;;;; +1E058;CYRILLIC SUBSCRIPT SMALL LETTER ZE;Lm;0;L;<sub> 0437;;;;N;;;;; +1E059;CYRILLIC SUBSCRIPT SMALL LETTER I;Lm;0;L;<sub> 0438;;;;N;;;;; +1E05A;CYRILLIC SUBSCRIPT SMALL LETTER KA;Lm;0;L;<sub> 043A;;;;N;;;;; +1E05B;CYRILLIC SUBSCRIPT SMALL LETTER EL;Lm;0;L;<sub> 043B;;;;N;;;;; +1E05C;CYRILLIC SUBSCRIPT SMALL LETTER O;Lm;0;L;<sub> 043E;;;;N;;;;; +1E05D;CYRILLIC SUBSCRIPT SMALL LETTER PE;Lm;0;L;<sub> 043F;;;;N;;;;; +1E05E;CYRILLIC SUBSCRIPT SMALL LETTER ES;Lm;0;L;<sub> 0441;;;;N;;;;; +1E05F;CYRILLIC SUBSCRIPT SMALL LETTER U;Lm;0;L;<sub> 0443;;;;N;;;;; +1E060;CYRILLIC SUBSCRIPT SMALL LETTER EF;Lm;0;L;<sub> 0444;;;;N;;;;; +1E061;CYRILLIC SUBSCRIPT SMALL LETTER HA;Lm;0;L;<sub> 0445;;;;N;;;;; +1E062;CYRILLIC SUBSCRIPT SMALL LETTER TSE;Lm;0;L;<sub> 0446;;;;N;;;;; +1E063;CYRILLIC SUBSCRIPT SMALL LETTER CHE;Lm;0;L;<sub> 0447;;;;N;;;;; +1E064;CYRILLIC SUBSCRIPT SMALL LETTER SHA;Lm;0;L;<sub> 0448;;;;N;;;;; +1E065;CYRILLIC SUBSCRIPT SMALL LETTER HARD SIGN;Lm;0;L;<sub> 044A;;;;N;;;;; +1E066;CYRILLIC SUBSCRIPT SMALL LETTER YERU;Lm;0;L;<sub> 044B;;;;N;;;;; +1E067;CYRILLIC SUBSCRIPT SMALL LETTER GHE WITH UPTURN;Lm;0;L;<sub> 0491;;;;N;;;;; +1E068;CYRILLIC SUBSCRIPT SMALL LETTER BYELORUSSIAN-UKRAINIAN I;Lm;0;L;<sub> 0456;;;;N;;;;; +1E069;CYRILLIC SUBSCRIPT SMALL LETTER DZE;Lm;0;L;<sub> 0455;;;;N;;;;; +1E06A;CYRILLIC SUBSCRIPT SMALL LETTER DZHE;Lm;0;L;<sub> 045F;;;;N;;;;; +1E06B;MODIFIER LETTER CYRILLIC SMALL ES WITH DESCENDER;Lm;0;L;<super> 04AB;;;;N;;;;; +1E06C;MODIFIER LETTER CYRILLIC SMALL YERU WITH BACK YER;Lm;0;L;<super> A651;;;;N;;;;; +1E06D;MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE;Lm;0;L;<super> 04B1;;;;N;;;;; +1E08F;COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I;Mn;230;NSM;;;;;N;;;;; 1E100;NYIAKENG PUACHUE HMONG LETTER MA;Lo;0;L;;;;;N;;;;; 1E101;NYIAKENG PUACHUE HMONG LETTER TSA;Lo;0;L;;;;;N;;;;; 1E102;NYIAKENG PUACHUE HMONG LETTER NTA;Lo;0;L;;;;;N;;;;; @@ -30603,6 +30828,48 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1E2F8;WANCHO DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; 1E2F9;WANCHO DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1E2FF;WANCHO NGUN SIGN;Sc;0;ET;;;;;N;;;;; +1E4D0;NAG MUNDARI LETTER O;Lo;0;L;;;;;N;;;;; +1E4D1;NAG MUNDARI LETTER OP;Lo;0;L;;;;;N;;;;; +1E4D2;NAG MUNDARI LETTER OL;Lo;0;L;;;;;N;;;;; +1E4D3;NAG MUNDARI LETTER OY;Lo;0;L;;;;;N;;;;; +1E4D4;NAG MUNDARI LETTER ONG;Lo;0;L;;;;;N;;;;; +1E4D5;NAG MUNDARI LETTER A;Lo;0;L;;;;;N;;;;; +1E4D6;NAG MUNDARI LETTER AJ;Lo;0;L;;;;;N;;;;; +1E4D7;NAG MUNDARI LETTER AB;Lo;0;L;;;;;N;;;;; +1E4D8;NAG MUNDARI LETTER ANY;Lo;0;L;;;;;N;;;;; +1E4D9;NAG MUNDARI LETTER AH;Lo;0;L;;;;;N;;;;; +1E4DA;NAG MUNDARI LETTER I;Lo;0;L;;;;;N;;;;; +1E4DB;NAG MUNDARI LETTER IS;Lo;0;L;;;;;N;;;;; +1E4DC;NAG MUNDARI LETTER IDD;Lo;0;L;;;;;N;;;;; +1E4DD;NAG MUNDARI LETTER IT;Lo;0;L;;;;;N;;;;; +1E4DE;NAG MUNDARI LETTER IH;Lo;0;L;;;;;N;;;;; +1E4DF;NAG MUNDARI LETTER U;Lo;0;L;;;;;N;;;;; +1E4E0;NAG MUNDARI LETTER UC;Lo;0;L;;;;;N;;;;; +1E4E1;NAG MUNDARI LETTER UD;Lo;0;L;;;;;N;;;;; +1E4E2;NAG MUNDARI LETTER UK;Lo;0;L;;;;;N;;;;; +1E4E3;NAG MUNDARI LETTER UR;Lo;0;L;;;;;N;;;;; +1E4E4;NAG MUNDARI LETTER E;Lo;0;L;;;;;N;;;;; +1E4E5;NAG MUNDARI LETTER ENN;Lo;0;L;;;;;N;;;;; +1E4E6;NAG MUNDARI LETTER EG;Lo;0;L;;;;;N;;;;; +1E4E7;NAG MUNDARI LETTER EM;Lo;0;L;;;;;N;;;;; +1E4E8;NAG MUNDARI LETTER EN;Lo;0;L;;;;;N;;;;; +1E4E9;NAG MUNDARI LETTER ETT;Lo;0;L;;;;;N;;;;; +1E4EA;NAG MUNDARI LETTER ELL;Lo;0;L;;;;;N;;;;; +1E4EB;NAG MUNDARI SIGN OJOD;Lm;0;L;;;;;N;;;;; +1E4EC;NAG MUNDARI SIGN MUHOR;Mn;232;NSM;;;;;N;;;;; +1E4ED;NAG MUNDARI SIGN TOYOR;Mn;232;NSM;;;;;N;;;;; +1E4EE;NAG MUNDARI SIGN IKIR;Mn;220;NSM;;;;;N;;;;; +1E4EF;NAG MUNDARI SIGN SUTUH;Mn;230;NSM;;;;;N;;;;; +1E4F0;NAG MUNDARI DIGIT ZERO;Nd;0;L;;0;0;0;N;;;;; +1E4F1;NAG MUNDARI DIGIT ONE;Nd;0;L;;1;1;1;N;;;;; +1E4F2;NAG MUNDARI DIGIT TWO;Nd;0;L;;2;2;2;N;;;;; +1E4F3;NAG MUNDARI DIGIT THREE;Nd;0;L;;3;3;3;N;;;;; +1E4F4;NAG MUNDARI DIGIT FOUR;Nd;0;L;;4;4;4;N;;;;; +1E4F5;NAG MUNDARI DIGIT FIVE;Nd;0;L;;5;5;5;N;;;;; +1E4F6;NAG MUNDARI DIGIT SIX;Nd;0;L;;6;6;6;N;;;;; +1E4F7;NAG MUNDARI DIGIT SEVEN;Nd;0;L;;7;7;7;N;;;;; +1E4F8;NAG MUNDARI DIGIT EIGHT;Nd;0;L;;8;8;8;N;;;;; +1E4F9;NAG MUNDARI DIGIT NINE;Nd;0;L;;9;9;9;N;;;;; 1E7E0;ETHIOPIC SYLLABLE HHYA;Lo;0;L;;;;;N;;;;; 1E7E1;ETHIOPIC SYLLABLE HHYU;Lo;0;L;;;;;N;;;;; 1E7E2;ETHIOPIC SYLLABLE HHYI;Lo;0;L;;;;;N;;;;; @@ -32678,6 +32945,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F6D5;HINDU TEMPLE;So;0;ON;;;;;N;;;;; 1F6D6;HUT;So;0;ON;;;;;N;;;;; 1F6D7;ELEVATOR;So;0;ON;;;;;N;;;;; +1F6DC;WIRELESS;So;0;ON;;;;;N;;;;; 1F6DD;PLAYGROUND SLIDE;So;0;ON;;;;;N;;;;; 1F6DE;WHEEL;So;0;ON;;;;;N;;;;; 1F6DF;RING BUOY;So;0;ON;;;;;N;;;;; @@ -32823,6 +33091,14 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F771;ALCHEMICAL SYMBOL FOR MONTH;So;0;ON;;;;;N;;;;; 1F772;ALCHEMICAL SYMBOL FOR HALF DRAM;So;0;ON;;;;;N;;;;; 1F773;ALCHEMICAL SYMBOL FOR HALF OUNCE;So;0;ON;;;;;N;;;;; +1F774;LOT OF FORTUNE;So;0;ON;;;;;N;;;;; +1F775;OCCULTATION;So;0;ON;;;;;N;;;;; +1F776;LUNAR ECLIPSE;So;0;ON;;;;;N;;;;; +1F77B;HAUMEA;So;0;ON;;;;;N;;;;; +1F77C;MAKEMAKE;So;0;ON;;;;;N;;;;; +1F77D;GONGGONG;So;0;ON;;;;;N;;;;; +1F77E;QUAOAR;So;0;ON;;;;;N;;;;; +1F77F;ORCUS;So;0;ON;;;;;N;;;;; 1F780;BLACK LEFT-POINTING ISOSCELES RIGHT TRIANGLE;So;0;ON;;;;;N;;;;; 1F781;BLACK UP-POINTING ISOSCELES RIGHT TRIANGLE;So;0;ON;;;;;N;;;;; 1F782;BLACK RIGHT-POINTING ISOSCELES RIGHT TRIANGLE;So;0;ON;;;;;N;;;;; @@ -32912,6 +33188,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1F7D6;NEGATIVE CIRCLED TRIANGLE;So;0;ON;;;;;N;;;;; 1F7D7;CIRCLED SQUARE;So;0;ON;;;;;N;;;;; 1F7D8;NEGATIVE CIRCLED SQUARE;So;0;ON;;;;;N;;;;; +1F7D9;NINE POINTED WHITE STAR;So;0;ON;;;;;N;;;;; 1F7E0;LARGE ORANGE CIRCLE;So;0;ON;;;;;N;;;;; 1F7E1;LARGE YELLOW CIRCLE;So;0;ON;;;;;N;;;;; 1F7E2;LARGE GREEN CIRCLE;So;0;ON;;;;;N;;;;; @@ -33434,6 +33711,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA72;BRIEFS;So;0;ON;;;;;N;;;;; 1FA73;SHORTS;So;0;ON;;;;;N;;;;; 1FA74;THONG SANDAL;So;0;ON;;;;;N;;;;; +1FA75;LIGHT BLUE HEART;So;0;ON;;;;;N;;;;; +1FA76;GREY HEART;So;0;ON;;;;;N;;;;; +1FA77;PINK HEART;So;0;ON;;;;;N;;;;; 1FA78;DROP OF BLOOD;So;0;ON;;;;;N;;;;; 1FA79;ADHESIVE BANDAGE;So;0;ON;;;;;N;;;;; 1FA7A;STETHOSCOPE;So;0;ON;;;;;N;;;;; @@ -33446,6 +33726,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FA84;MAGIC WAND;So;0;ON;;;;;N;;;;; 1FA85;PINATA;So;0;ON;;;;;N;;;;; 1FA86;NESTING DOLLS;So;0;ON;;;;;N;;;;; +1FA87;MARACAS;So;0;ON;;;;;N;;;;; +1FA88;FLUTE;So;0;ON;;;;;N;;;;; 1FA90;RINGED PLANET;So;0;ON;;;;;N;;;;; 1FA91;CHAIR;So;0;ON;;;;;N;;;;; 1FA92;RAZOR;So;0;ON;;;;;N;;;;; @@ -33475,6 +33757,9 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAAA;IDENTIFICATION CARD;So;0;ON;;;;;N;;;;; 1FAAB;LOW BATTERY;So;0;ON;;;;;N;;;;; 1FAAC;HAMSA;So;0;ON;;;;;N;;;;; +1FAAD;FOLDING HAND FAN;So;0;ON;;;;;N;;;;; +1FAAE;HAIR PICK;So;0;ON;;;;;N;;;;; +1FAAF;KHANDA;So;0;ON;;;;;N;;;;; 1FAB0;FLY;So;0;ON;;;;;N;;;;; 1FAB1;WORM;So;0;ON;;;;;N;;;;; 1FAB2;BEETLE;So;0;ON;;;;;N;;;;; @@ -33486,12 +33771,18 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAB8;CORAL;So;0;ON;;;;;N;;;;; 1FAB9;EMPTY NEST;So;0;ON;;;;;N;;;;; 1FABA;NEST WITH EGGS;So;0;ON;;;;;N;;;;; +1FABB;HYACINTH;So;0;ON;;;;;N;;;;; +1FABC;JELLYFISH;So;0;ON;;;;;N;;;;; +1FABD;WING;So;0;ON;;;;;N;;;;; +1FABF;GOOSE;So;0;ON;;;;;N;;;;; 1FAC0;ANATOMICAL HEART;So;0;ON;;;;;N;;;;; 1FAC1;LUNGS;So;0;ON;;;;;N;;;;; 1FAC2;PEOPLE HUGGING;So;0;ON;;;;;N;;;;; 1FAC3;PREGNANT MAN;So;0;ON;;;;;N;;;;; 1FAC4;PREGNANT PERSON;So;0;ON;;;;;N;;;;; 1FAC5;PERSON WITH CROWN;So;0;ON;;;;;N;;;;; +1FACE;MOOSE;So;0;ON;;;;;N;;;;; +1FACF;DONKEY;So;0;ON;;;;;N;;;;; 1FAD0;BLUEBERRIES;So;0;ON;;;;;N;;;;; 1FAD1;BELL PEPPER;So;0;ON;;;;;N;;;;; 1FAD2;OLIVE;So;0;ON;;;;;N;;;;; @@ -33502,6 +33793,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAD7;POURING LIQUID;So;0;ON;;;;;N;;;;; 1FAD8;BEANS;So;0;ON;;;;;N;;;;; 1FAD9;JAR;So;0;ON;;;;;N;;;;; +1FADA;GINGER ROOT;So;0;ON;;;;;N;;;;; +1FADB;PEA POD;So;0;ON;;;;;N;;;;; 1FAE0;MELTING FACE;So;0;ON;;;;;N;;;;; 1FAE1;SALUTING FACE;So;0;ON;;;;;N;;;;; 1FAE2;FACE WITH OPEN EYES AND HAND OVER MOUTH;So;0;ON;;;;;N;;;;; @@ -33510,6 +33803,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAE5;DOTTED LINE FACE;So;0;ON;;;;;N;;;;; 1FAE6;BITING LIP;So;0;ON;;;;;N;;;;; 1FAE7;BUBBLES;So;0;ON;;;;;N;;;;; +1FAE8;SHAKING FACE;So;0;ON;;;;;N;;;;; 1FAF0;HAND WITH INDEX FINGER AND THUMB CROSSED;So;0;ON;;;;;N;;;;; 1FAF1;RIGHTWARDS HAND;So;0;ON;;;;;N;;;;; 1FAF2;LEFTWARDS HAND;So;0;ON;;;;;N;;;;; @@ -33517,6 +33811,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 1FAF4;PALM UP HAND;So;0;ON;;;;;N;;;;; 1FAF5;INDEX POINTING AT THE VIEWER;So;0;ON;;;;;N;;;;; 1FAF6;HEART HANDS;So;0;ON;;;;;N;;;;; +1FAF7;LEFTWARDS PUSHING HAND;So;0;ON;;;;;N;;;;; +1FAF8;RIGHTWARDS PUSHING HAND;So;0;ON;;;;;N;;;;; 1FB00;BLOCK SEXTANT-1;So;0;ON;;;;;N;;;;; 1FB01;BLOCK SEXTANT-2;So;0;ON;;;;;N;;;;; 1FB02;BLOCK SEXTANT-12;So;0;ON;;;;;N;;;;; @@ -33732,7 +34028,7 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 20000;<CJK Ideograph Extension B, First>;Lo;0;L;;;;;N;;;;; 2A6DF;<CJK Ideograph Extension B, Last>;Lo;0;L;;;;;N;;;;; 2A700;<CJK Ideograph Extension C, First>;Lo;0;L;;;;;N;;;;; -2B738;<CJK Ideograph Extension C, Last>;Lo;0;L;;;;;N;;;;; +2B739;<CJK Ideograph Extension C, Last>;Lo;0;L;;;;;N;;;;; 2B740;<CJK Ideograph Extension D, First>;Lo;0;L;;;;;N;;;;; 2B81D;<CJK Ideograph Extension D, Last>;Lo;0;L;;;;;N;;;;; 2B820;<CJK Ideograph Extension E, First>;Lo;0;L;;;;;N;;;;; @@ -34283,6 +34579,8 @@ FFFD;REPLACEMENT CHARACTER;So;0;ON;;;;;N;;;;; 2FA1D;CJK COMPATIBILITY IDEOGRAPH-2FA1D;Lo;0;L;2A600;;;;N;;;;; 30000;<CJK Ideograph Extension G, First>;Lo;0;L;;;;;N;;;;; 3134A;<CJK Ideograph Extension G, Last>;Lo;0;L;;;;;N;;;;; +31350;<CJK Ideograph Extension H, First>;Lo;0;L;;;;;N;;;;; +323AF;<CJK Ideograph Extension H, Last>;Lo;0;L;;;;;N;;;;; E0001;LANGUAGE TAG;Cf;0;BN;;;;;N;;;;; E0020;TAG SPACE;Cf;0;BN;;;;;N;;;;; E0021;TAG EXCLAMATION MARK;Cf;0;BN;;;;;N;;;;; diff --git a/src/unicode/emoji-data.txt b/src/unicode/emoji-data.txt index 7806c7ab53..999a436779 100644 --- a/src/unicode/emoji-data.txt +++ b/src/unicode/emoji-data.txt @@ -1,13 +1,13 @@ -# emoji-data-14.0.0.txt -# Date: 2021-08-26, 17:22:22 GMT -# © 2021 Unicode®, Inc. +# emoji-data.txt +# Date: 2022-08-02, 00:26:10 GMT +# © 2022 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. -# For terms of use, see http://www.unicode.org/terms_of_use.html +# For terms of use, see https://www.unicode.org/terms_of_use.html # # Emoji Data for UTS #51 -# Used with Emoji Version 14.0 and subsequent minor revisions (if any) +# Used with Emoji Version 15.0 and subsequent minor revisions (if any) # -# For documentation and usage, see http://www.unicode.org/reports/tr51 +# For documentation and usage, see https://www.unicode.org/reports/tr51 # # Format: # <codepoint(s)> ; <property> # <comments> @@ -19,8 +19,7 @@ # ================================================ -# All omitted code points have Emoji=No -# @missing: 0000..10FFFF ; Emoji ; No +# All omitted code points have Emoji=No 0023 ; Emoji # E0.0 [1] (#️) hash sign 002A ; Emoji # E0.0 [1] (*️) asterisk @@ -341,6 +340,7 @@ 1F6D1..1F6D2 ; Emoji # E3.0 [2] (🛑..🛒) stop sign..shopping cart 1F6D5 ; Emoji # E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Emoji # E13.0 [2] (🛖..🛗) hut..elevator +1F6DC ; Emoji # E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Emoji # E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6E0..1F6E5 ; Emoji # E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat 1F6E9 ; Emoji # E0.7 [1] (🛩️) small airplane @@ -401,28 +401,36 @@ 1F9E7..1F9FF ; Emoji # E11.0 [25] (🧧..🧿) red envelope..nazar amulet 1FA70..1FA73 ; Emoji # E12.0 [4] (🩰..🩳) ballet shoes..shorts 1FA74 ; Emoji # E13.0 [1] (🩴) thong sandal +1FA75..1FA77 ; Emoji # E15.0 [3] (🩵..🩷) light blue heart..pink heart 1FA78..1FA7A ; Emoji # E12.0 [3] (🩸..🩺) drop of blood..stethoscope 1FA7B..1FA7C ; Emoji # E14.0 [2] (🩻..🩼) x-ray..crutch 1FA80..1FA82 ; Emoji # E12.0 [3] (🪀..🪂) yo-yo..parachute 1FA83..1FA86 ; Emoji # E13.0 [4] (🪃..🪆) boomerang..nesting dolls +1FA87..1FA88 ; Emoji # E15.0 [2] (🪇..🪈) maracas..flute 1FA90..1FA95 ; Emoji # E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Emoji # E13.0 [19] (🪖..🪨) military helmet..rock 1FAA9..1FAAC ; Emoji # E14.0 [4] (🪩..🪬) mirror ball..hamsa +1FAAD..1FAAF ; Emoji # E15.0 [3] (🪭..🪯) folding hand fan..khanda 1FAB0..1FAB6 ; Emoji # E13.0 [7] (🪰..🪶) fly..feather 1FAB7..1FABA ; Emoji # E14.0 [4] (🪷..🪺) lotus..nest with eggs +1FABB..1FABD ; Emoji # E15.0 [3] (🪻..🪽) hyacinth..wing +1FABF ; Emoji # E15.0 [1] (🪿) goose 1FAC0..1FAC2 ; Emoji # E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Emoji # E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FACE..1FACF ; Emoji # E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Emoji # E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Emoji # E14.0 [3] (🫗..🫙) pouring liquid..jar +1FADA..1FADB ; Emoji # E15.0 [2] (🫚..🫛) ginger root..pea pod 1FAE0..1FAE7 ; Emoji # E14.0 [8] (🫠..🫧) melting face..bubbles +1FAE8 ; Emoji # E15.0 [1] (🫨) shaking face 1FAF0..1FAF6 ; Emoji # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Emoji # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand -# Total elements: 1404 +# Total elements: 1424 # ================================================ -# All omitted code points have Emoji_Presentation=No -# @missing: 0000..10FFFF ; Emoji_Presentation ; No +# All omitted code points have Emoji_Presentation=No 231A..231B ; Emoji_Presentation # E0.6 [2] (⌚..⌛) watch..hourglass done 23E9..23EC ; Emoji_Presentation # E0.6 [4] (⏩..⏬) fast-forward button..fast down button @@ -625,6 +633,7 @@ 1F6D1..1F6D2 ; Emoji_Presentation # E3.0 [2] (🛑..🛒) stop sign..shopping cart 1F6D5 ; Emoji_Presentation # E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Emoji_Presentation # E13.0 [2] (🛖..🛗) hut..elevator +1F6DC ; Emoji_Presentation # E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Emoji_Presentation # E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6EB..1F6EC ; Emoji_Presentation # E1.0 [2] (🛫..🛬) airplane departure..airplane arrival 1F6F4..1F6F6 ; Emoji_Presentation # E3.0 [3] (🛴..🛶) kick scooter..canoe @@ -681,28 +690,36 @@ 1F9E7..1F9FF ; Emoji_Presentation # E11.0 [25] (🧧..🧿) red envelope..nazar amulet 1FA70..1FA73 ; Emoji_Presentation # E12.0 [4] (🩰..🩳) ballet shoes..shorts 1FA74 ; Emoji_Presentation # E13.0 [1] (🩴) thong sandal +1FA75..1FA77 ; Emoji_Presentation # E15.0 [3] (🩵..🩷) light blue heart..pink heart 1FA78..1FA7A ; Emoji_Presentation # E12.0 [3] (🩸..🩺) drop of blood..stethoscope 1FA7B..1FA7C ; Emoji_Presentation # E14.0 [2] (🩻..🩼) x-ray..crutch 1FA80..1FA82 ; Emoji_Presentation # E12.0 [3] (🪀..🪂) yo-yo..parachute 1FA83..1FA86 ; Emoji_Presentation # E13.0 [4] (🪃..🪆) boomerang..nesting dolls +1FA87..1FA88 ; Emoji_Presentation # E15.0 [2] (🪇..🪈) maracas..flute 1FA90..1FA95 ; Emoji_Presentation # E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Emoji_Presentation # E13.0 [19] (🪖..🪨) military helmet..rock 1FAA9..1FAAC ; Emoji_Presentation # E14.0 [4] (🪩..🪬) mirror ball..hamsa +1FAAD..1FAAF ; Emoji_Presentation # E15.0 [3] (🪭..🪯) folding hand fan..khanda 1FAB0..1FAB6 ; Emoji_Presentation # E13.0 [7] (🪰..🪶) fly..feather 1FAB7..1FABA ; Emoji_Presentation # E14.0 [4] (🪷..🪺) lotus..nest with eggs +1FABB..1FABD ; Emoji_Presentation # E15.0 [3] (🪻..🪽) hyacinth..wing +1FABF ; Emoji_Presentation # E15.0 [1] (🪿) goose 1FAC0..1FAC2 ; Emoji_Presentation # E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Emoji_Presentation # E14.0 [3] (🫃..🫅) pregnant man..person with crown +1FACE..1FACF ; Emoji_Presentation # E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Emoji_Presentation # E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Emoji_Presentation # E14.0 [3] (🫗..🫙) pouring liquid..jar +1FADA..1FADB ; Emoji_Presentation # E15.0 [2] (🫚..🫛) ginger root..pea pod 1FAE0..1FAE7 ; Emoji_Presentation # E14.0 [8] (🫠..🫧) melting face..bubbles +1FAE8 ; Emoji_Presentation # E15.0 [1] (🫨) shaking face 1FAF0..1FAF6 ; Emoji_Presentation # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Emoji_Presentation # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand -# Total elements: 1185 +# Total elements: 1205 # ================================================ -# All omitted code points have Emoji_Modifier=No -# @missing: 0000..10FFFF ; Emoji_Modifier ; No +# All omitted code points have Emoji_Modifier=No 1F3FB..1F3FF ; Emoji_Modifier # E1.0 [5] (🏻..🏿) light skin tone..dark skin tone @@ -710,8 +727,7 @@ # ================================================ -# All omitted code points have Emoji_Modifier_Base=No -# @missing: 0000..10FFFF ; Emoji_Modifier_Base ; No +# All omitted code points have Emoji_Modifier_Base=No 261D ; Emoji_Modifier_Base # E0.6 [1] (☝️) index pointing up 26F9 ; Emoji_Modifier_Base # E0.7 [1] (⛹️) person bouncing ball @@ -762,13 +778,13 @@ 1F9D1..1F9DD ; Emoji_Modifier_Base # E5.0 [13] (🧑..🧝) person..elf 1FAC3..1FAC5 ; Emoji_Modifier_Base # E14.0 [3] (🫃..🫅) pregnant man..person with crown 1FAF0..1FAF6 ; Emoji_Modifier_Base # E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands +1FAF7..1FAF8 ; Emoji_Modifier_Base # E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand -# Total elements: 132 +# Total elements: 134 # ================================================ -# All omitted code points have Emoji_Component=No -# @missing: 0000..10FFFF ; Emoji_Component ; No +# All omitted code points have Emoji_Component=No 0023 ; Emoji_Component # E0.0 [1] (#️) hash sign 002A ; Emoji_Component # E0.0 [1] (*️) asterisk @@ -785,8 +801,7 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (..) tag space..c # ================================================ -# All omitted code points have Extended_Pictographic=No -# @missing: 0000..10FFFF ; Extended_Pictographic ; No +# All omitted code points have Extended_Pictographic=No 00A9 ; Extended_Pictographic# E0.6 [1] (©️) copyright 00AE ; Extended_Pictographic# E0.6 [1] (®️) registered @@ -1190,7 +1205,8 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (..) tag space..c 1F6D3..1F6D4 ; Extended_Pictographic# E0.0 [2] (🛓..🛔) STUPA..PAGODA 1F6D5 ; Extended_Pictographic# E12.0 [1] (🛕) hindu temple 1F6D6..1F6D7 ; Extended_Pictographic# E13.0 [2] (🛖..🛗) hut..elevator -1F6D8..1F6DC ; Extended_Pictographic# E0.0 [5] (..🛜) <reserved-1F6D8>..<reserved-1F6DC> +1F6D8..1F6DB ; Extended_Pictographic# E0.0 [4] (..) <reserved-1F6D8>..<reserved-1F6DB> +1F6DC ; Extended_Pictographic# E15.0 [1] (🛜) wireless 1F6DD..1F6DF ; Extended_Pictographic# E14.0 [3] (🛝..🛟) playground slide..ring buoy 1F6E0..1F6E5 ; Extended_Pictographic# E0.7 [6] (🛠️..🛥️) hammer and wrench..motor boat 1F6E6..1F6E8 ; Extended_Pictographic# E0.0 [3] (🛦..🛨) UP-POINTING MILITARY AIRPLANE..UP-POINTING SMALL AIRPLANE @@ -1207,7 +1223,7 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (..) tag space..c 1F6FA ; Extended_Pictographic# E12.0 [1] (🛺) auto rickshaw 1F6FB..1F6FC ; Extended_Pictographic# E13.0 [2] (🛻..🛼) pickup truck..roller skate 1F6FD..1F6FF ; Extended_Pictographic# E0.0 [3] (..) <reserved-1F6FD>..<reserved-1F6FF> -1F774..1F77F ; Extended_Pictographic# E0.0 [12] (🝴..🝿) <reserved-1F774>..<reserved-1F77F> +1F774..1F77F ; Extended_Pictographic# E0.0 [12] (🝴..🝿) LOT OF FORTUNE..ORCUS 1F7D5..1F7DF ; Extended_Pictographic# E0.0 [11] (🟕..) CIRCLED TRIANGLE..<reserved-1F7DF> 1F7E0..1F7EB ; Extended_Pictographic# E12.0 [12] (🟠..🟫) orange circle..brown square 1F7EC..1F7EF ; Extended_Pictographic# E0.0 [4] (..) <reserved-1F7EC>..<reserved-1F7EF> @@ -1266,30 +1282,37 @@ E0020..E007F ; Emoji_Component # E0.0 [96] (..) tag space..c 1FA00..1FA6F ; Extended_Pictographic# E0.0 [112] (🨀..) NEUTRAL CHESS KING..<reserved-1FA6F> 1FA70..1FA73 ; Extended_Pictographic# E12.0 [4] (🩰..🩳) ballet shoes..shorts 1FA74 ; Extended_Pictographic# E13.0 [1] (🩴) thong sandal -1FA75..1FA77 ; Extended_Pictographic# E0.0 [3] (🩵..🩷) <reserved-1FA75>..<reserved-1FA77> +1FA75..1FA77 ; Extended_Pictographic# E15.0 [3] (🩵..🩷) light blue heart..pink heart 1FA78..1FA7A ; Extended_Pictographic# E12.0 [3] (🩸..🩺) drop of blood..stethoscope 1FA7B..1FA7C ; Extended_Pictographic# E14.0 [2] (🩻..🩼) x-ray..crutch 1FA7D..1FA7F ; Extended_Pictographic# E0.0 [3] (..) <reserved-1FA7D>..<reserved-1FA7F> 1FA80..1FA82 ; Extended_Pictographic# E12.0 [3] (🪀..🪂) yo-yo..parachute 1FA83..1FA86 ; Extended_Pictographic# E13.0 [4] (🪃..🪆) boomerang..nesting dolls -1FA87..1FA8F ; Extended_Pictographic# E0.0 [9] (🪇..) <reserved-1FA87>..<reserved-1FA8F> +1FA87..1FA88 ; Extended_Pictographic# E15.0 [2] (🪇..🪈) maracas..flute +1FA89..1FA8F ; Extended_Pictographic# E0.0 [7] (..) <reserved-1FA89>..<reserved-1FA8F> 1FA90..1FA95 ; Extended_Pictographic# E12.0 [6] (🪐..🪕) ringed planet..banjo 1FA96..1FAA8 ; Extended_Pictographic# E13.0 [19] (🪖..🪨) military helmet..rock 1FAA9..1FAAC ; Extended_Pictographic# E14.0 [4] (🪩..🪬) mirror ball..hamsa -1FAAD..1FAAF ; Extended_Pictographic# E0.0 [3] (🪭..🪯) <reserved-1FAAD>..<reserved-1FAAF> +1FAAD..1FAAF ; Extended_Pictographic# E15.0 [3] (🪭..🪯) folding hand fan..khanda 1FAB0..1FAB6 ; Extended_Pictographic# E13.0 [7] (🪰..🪶) fly..feather 1FAB7..1FABA ; Extended_Pictographic# E14.0 [4] (🪷..🪺) lotus..nest with eggs -1FABB..1FABF ; Extended_Pictographic# E0.0 [5] (🪻..🪿) <reserved-1FABB>..<reserved-1FABF> +1FABB..1FABD ; Extended_Pictographic# E15.0 [3] (🪻..🪽) hyacinth..wing +1FABE ; Extended_Pictographic# E0.0 [1] () <reserved-1FABE> +1FABF ; Extended_Pictographic# E15.0 [1] (🪿) goose 1FAC0..1FAC2 ; Extended_Pictographic# E13.0 [3] (🫀..🫂) anatomical heart..people hugging 1FAC3..1FAC5 ; Extended_Pictographic# E14.0 [3] (🫃..🫅) pregnant man..person with crown -1FAC6..1FACF ; Extended_Pictographic# E0.0 [10] (..🫏) <reserved-1FAC6>..<reserved-1FACF> +1FAC6..1FACD ; Extended_Pictographic# E0.0 [8] (..) <reserved-1FAC6>..<reserved-1FACD> +1FACE..1FACF ; Extended_Pictographic# E15.0 [2] (🫎..🫏) moose..donkey 1FAD0..1FAD6 ; Extended_Pictographic# E13.0 [7] (🫐..🫖) blueberries..teapot 1FAD7..1FAD9 ; Extended_Pictographic# E14.0 [3] (🫗..🫙) pouring liquid..jar -1FADA..1FADF ; Extended_Pictographic# E0.0 [6] (🫚..) <reserved-1FADA>..<reserved-1FADF> +1FADA..1FADB ; Extended_Pictographic# E15.0 [2] (🫚..🫛) ginger root..pea pod +1FADC..1FADF ; Extended_Pictographic# E0.0 [4] (..) <reserved-1FADC>..<reserved-1FADF> 1FAE0..1FAE7 ; Extended_Pictographic# E14.0 [8] (🫠..🫧) melting face..bubbles -1FAE8..1FAEF ; Extended_Pictographic# E0.0 [8] (🫨..) <reserved-1FAE8>..<reserved-1FAEF> +1FAE8 ; Extended_Pictographic# E15.0 [1] (🫨) shaking face +1FAE9..1FAEF ; Extended_Pictographic# E0.0 [7] (..) <reserved-1FAE9>..<reserved-1FAEF> 1FAF0..1FAF6 ; Extended_Pictographic# E14.0 [7] (🫰..🫶) hand with index finger and thumb crossed..heart hands -1FAF7..1FAFF ; Extended_Pictographic# E0.0 [9] (🫷..) <reserved-1FAF7>..<reserved-1FAFF> +1FAF7..1FAF8 ; Extended_Pictographic# E15.0 [2] (🫷..🫸) leftwards pushing hand..rightwards pushing hand +1FAF9..1FAFF ; Extended_Pictographic# E0.0 [7] (..) <reserved-1FAF9>..<reserved-1FAFF> 1FC00..1FFFD ; Extended_Pictographic# E0.0[1022] (..) <reserved-1FC00>..<reserved-1FFFD> # Total elements: 3537 |